- Giriş
- Değişkenler
- Anlamlı ve telaffuz edilebilir değişken isimleri kullanın
- Aynı türden değişkenler için aynı kelimeleri kullanın
- Aranabilir isimler kullanın (bölüm 1)
- Aranabilir isimler kullanın (bölüm 2)
- Açıklayıcı değişkenler kullanın
- Çok fazla iç içe kullanımdan ve geri döndürmeden kaçının (bölüm 1)
- Çok fazla iç içe kullanımdan ve geri döndürmeden kaçının (bolum 2)
- Zihin Haritasından Kaçının
- Gereksiz bağlam eklemeyin
- Kısaltmalar veya şartlılar yerine varsayılan argümanları kullanın
- Karşılaştırma
- Fonksiyonlar
- Fonksiyon Parametreleri (2 veya daha az ideal)
- Fonksiyonlar bir şey yapmalı
- Fonksiyon isimleri ne yaptıklarını söylemeli
- Fonksiyonlar sadece bir seviye soyutlama olmalıdır
- Bayrakları, fonksiyon parametreleri olarak kullanmayın
- Yan Etkilerden Kaçının
- Global Fonksiyonlar yazmayın
- Singleton desenini kullanmayın
- Koşulları kapsülleyin
- Olumsuz koşullardan kaçının
- Koşullardan kaçının
- Tür kontrolünden kaçının (bölüm 1)
- Tür kontrolünden kaçının (bölüm 2)
- Ölü Kodu kaldırın
- Nesneler ve Veri Yapıları
- Sınıflar
- SOLID
- Kendinizi Tekrar Etmeyin (DRY)
- Çeviriler
Yazılım mühendisliği prensipleri,Robert C. Martin'in Temiz Kod kitabından, PHP için uyarlanmıştır. Bu bir stil kılavuzu değildir. PHP’de okunabilir, yeniden kullanılabilir ve düzenlenebilir yazılımlar üretmek için bir kılavuzdur.
Buradaki her prensibe tamamen uyulmamalıdır, ve evrensel olarak daha az kabul edilecek. Bunlar ilke ve başka birşey değildir, ama bunlar Temiz Kod yazarlarının uzun yıllara dayanan kolektif deneyimlerine göre kodlanmıştır.
temiz-kod-javascript den ilham alınmıştır.
Çoğu geliştirici hala PHP 5 kullanıyor olsa da, bu makaledeki örneklerin çoğu sadece PHP 7.1+ ile çalışmaktadır.
Kötü:
$ymdstr = $moment->format('y-m-d');
İyi:
$currentDate = $moment->format('y-m-d');
Kötü:
getUserInfo();
getUserData();
getUserRecord();
getUserProfile();
İyi:
getUser();
Yazacağımızdan daha fazla kod okuyacağız. Önemli olan okunabilir ve aranabilir kod yazmamızdır. Değişkenleri anlamlı isimlendirmezsek programımızı anlamaya çalışan okuyucularımıza zarar verebiliriz. İsimleri aranabilir yapın.
Kötü:
// 448 ne için ?
$result = $serializer->serialize($data, 448);
İyi:
$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Kötü:
// 4 ne için?
if ($user->access & 4) {
// ...
}
İyi:
class User
{
const ACCESS_READ = 1;
const ACCESS_CREATE = 2;
const ACCESS_UPDATE = 4;
const ACCESS_DELETE = 8;
}
if ($user->access & User::ACCESS_UPDATE) {
// düzenle ...
}
Kötü:
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);
Fena Değil:
Daha iyi ama hala regex'e son derece bağlıyız.
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
[, $city, $zipCode] = $matches;
saveCityZipCode($city, $zipCode);
İyi:
Alt şablonlara ad vererek regex bağımlılığını azaltın.
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches['city'], $matches['zipCode']);
Çok fazla if-else ifadeleri kodun takip edilmesini zorlaştırır. Direkt olmak, dolaylı olmaktan iyidir.
Kötü:
function isShopOpen($day): bool
{
if ($day) {
if (is_string($day)) {
$day = strtolower($day);
if ($day === 'friday') {
return true;
} elseif ($day === 'saturday') {
return true;
} elseif ($day === 'sunday') {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
İyi:
function isShopOpen(string $day): bool
{
if (empty($day)) {
return false;
}
$openingDays = [
'friday', 'saturday', 'sunday'
];
return in_array(strtolower($day), $openingDays, true);
}
Kötü:
function fibonacci(int $n)
{
if ($n < 50) {
if ($n !== 0) {
if ($n !== 1) {
return fibonacci($n - 1) + fibonacci($n - 2);
} else {
return 1;
}
} else {
return 0;
}
} else {
return 'Not supported';
}
}
İyi:
function fibonacci(int $n): int
{
if ($n === 0 || $n === 1) {
return $n;
}
if ($n > 50) {
throw new \Exception('Not supported');
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
Okuyucuyu, kodunuzdaki değişkenin ne demek olduğunu tercüme etmek için zorlamayın. Direkt olmak, dolaylı olmaktan iyidir.
Kötü:
$l = ['Austin', 'New York', 'San Francisco'];
for ($i = 0; $i < count($l); $i++) {
$li = $l[$i];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Bekle, yeniden `$li` ne için?
dispatch($li);
}
İyi:
$locations = ['Austin', 'New York', 'San Francisco'];
foreach ($locations as $location) {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch($location);
}
Sınıf/Nesne isimleriniz bir şey anlatıyorsa, bunu değişken adınızda tekrarlamayın.
Kötü:
class Car
{
public $carMake;
public $carModel;
public $carColor;
//...
}
İyi:
class Car
{
public $make;
public $model;
public $color;
//...
}
İyi Değil:
Bu iyi değil çünkü $breweryName
, NULL
olabilir.
function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
// ...
}
Fena Değil:
Bu düşünce bir önceki versiyondan daha anlaşılır ama değişkenin değerini kontrol etse daha iyi olur.
function createMicrobrewery($name = null): void
{
$breweryName = $name ?: 'Hipster Brew Co.';
// ...
}
İyi:
tür ipucu kullanabilirsiniz ve $breweryName
öğesinin NULL
olmadığından emin olun.
function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
// ...
}
Aynı karşılaştırmayı kullanın.
İyi Değil:
Basit karşılaştırma harf dizinleri bir ifadeyi tam sayıya döndürür.
$a = '42';
$b = 42;
if ($a != $b) {
// İfade her zaman geçer.
}
$a != $b
karşılaştırması FALSE
değerini döndürüyor ama aslında TRUE
!
Harf dizini 42
, tam sayı olan 42
den farklıdır.
İyi:
Aynı karşılaştırma türü ve değeri karşılaştırır.
$a = '42';
$b = 42;
if ($a !== $b) {
// İfade doğrulandı.
}
$a !== $b
karşılaştırması TRUE
değerini döndürür.
Fonksiyon parametrelerinin miktarını sınırlamak inanılmaz derecede önemlidir çünkü fonksiyonunuzu test etmeyi kolaylaştırır. Üç leads ten fazlasının olması, her bir ayrı argümana sahip tonlarca farklı durumu test etmeniz gereken bir kombinatoryal patlamaya yol açar.
Sıfır argümanlar ideal durumdur. Bir veya iki argüman iyidir fakat üç argümandan kaçınılmalıdır. Bundan daha fazlası birleştirilmelidir. Genellikle iki argümandan fazlası varsa fonksiyonunuz çok fazlasını yapmaya çalışır. Olmadığı durumlarda çoğu zaman yüksek seviye bir nesne bir argüman olarak yeterli olacaktır.
Kötü:
function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
// ...
}
İyi:
class MenuConfig
{
public $title;
public $body;
public $buttonText;
public $cancellable = false;
}
$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;
function createMenu(MenuConfig $config): void
{
// ...
}
Bu, yazılım mühendisliğinde açık ara en önemli kuraldır. Fonksiyonlar bir şeyden fazlasını yaptığında bunları oluşturmak, test etmek ve akıl yürütmek daha zordur. Bir fonksiyonu sadece bir eyleme ayırdığında, kolayca düzenlenebilir ve kodunuz daha temiz okunacaktır. Eğer bu kılavuzdan bunda başka bir şey almazsanız bile, birçok geliştiriciden önde olacaksınız.
Kötü:
function emailClients(array $clients): void
{
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}
İyi:
function emailClients(array $clients): void
{
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}
function activeClients(array $clients): array
{
return array_filter($clients, 'isClientActive');
}
function isClientActive(int $client): bool
{
$clientRecord = $db->find($client);
return $clientRecord->isActive();
}
Kötü:
class Email
{
//...
public function handle(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// Bu nedir? Mesaj için `handle` ? Şimdi bir dosyaya mı yazıyoruz ?
$message->handle();
İyi:
class Email
{
//...
public function send(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// Temiz ve açık
$message->send();
Birden fazla soyutlama seviyeniz olduğu zaman fonksiyonunuz genellikle çok fazla şey yapıyordur. Fonksiyonları ayırmak tekrar kullanılabilirlik ve daha kolay test edilebilirlik sağlar.
Kötü:
function parseBetterJSAlternative(string $code): void
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
// ...
}
}
$ast = [];
foreach ($tokens as $token) {
// lex...
}
foreach ($ast as $node) {
// parse...
}
}
Çok Kötü:
Bazı fonksiyonellikleri gerçekleştirdik ama parseBetterJSAlternative()
fonksiyonu hala çok karmaşık ve test edilebilir değil.
function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
function lexer(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
function parseBetterJSAlternative(string $code): void
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
// parse...
}
}
İyi:
En iyi çözüm parseBetterJSAlternative()
fonksiyonundaki bağımlılıkları ortadan kaldırmaktır.
class Tokenizer
{
public function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}
class Lexer
{
public function lexify(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
}
class BetterJSAlternative
{
private $tokenizer;
private $lexer;
public function __construct(Tokenizer $tokenizer, Lexer $lexer)
{
$this->tokenizer = $tokenizer;
$this->lexer = $lexer;
}
public function parse(string $code): void
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// parse...
}
}
}
Bayraklar, kullanıcılarınıza bu fonksiyonun birden fazla şey yapacağını söyler.
Fonksiyonlar bir şey yapmalıdır. Eğer bir boolean
tabanlı farklı kod yollarını takip ediyorsanız, fonksiyonlarınızı bölün.
Kötü:
function createFile(string $name, bool $temp = false): void
{
if ($temp) {
touch('./temp/'.$name);
} else {
touch($name);
}
}
İyi:
function createFile(string $name): void
{
touch($name);
}
function createTempFile(string $name): void
{
touch('./temp/'.$name);
}
Bir fonksiyon, bir değeri almak ve başka değer veya değerleri geri döndürmek dışında bir şey yaparsa bir yan etki üretir. Bir yan etki bir dosyaya yazılabilir, global değişkenleri değiştirebilir veya bir yabancıya bütün paranızı yanlışlıkla bağlayabilir.
Şimdi, bir sebeple programında yan etkilere ihtiyacınız var. Önceki örnekteki gibi, belki bir dosyaya yazmanız gerekebilir. Yapmak istediğiniz şey, yaptığınızı merkezileştirmektir. Belirli bir dosyaya birkaç fonksiyon ve sınıflar yazılmasına gerek yoktur. Bunu yapan bir servis var. Bir ve sadece bir tane.
Ana nokta, herhangi bir yapıya sahip olmayan nesneler arasındaki paylaşım durumu gibi ortak tuzaklardan kaçınmaktır. Herhangi bir şeye göre yazılabilen ve yan etkilerin meydana geldiği yeri merkezileştirmeyen değişebilen veri türleri kullanılmalıdır. Eğer bunu yapabiliyorsanız diğer programcıların büyük çoğunluğundan daha mutlu olacaksınız.
Kötü:
// Global değişkenler, aşağıdaki fonksiyonları referans alır.
// Bu ismi kullanan başka fonksiyonumuz olsaydı, şimdi bir dizi olurdu ve bozulurdu.
$name = 'Ryan McDermott';
function splitIntoFirstAndLastName(): void
{
global $name;
$name = explode(' ', $name);
}
splitIntoFirstAndLastName();
var_dump($name); // ['Ryan', 'McDermott'];
İyi:
function splitIntoFirstAndLastName(string $name): array
{
return explode(' ', $name);
}
$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);
var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];
Global kirletme birçok dilde kötü bir uygulama yöntemidir. Çünkü başka bir kütüphane ile çakışabilir ve API'nızın kullanıcısı ürününüzden istisna elde edene kadar bilemezdiniz. Hadi bir örnek düşünelim: Eğer konfigürasyon dizisine sahip olmak isteseniz ne olurdu?
config()
gibi global fonksiyon yazabilirsiniz ama aynı şeyi yapmaya çalışan başka bir kütüphaneyle çakışabilir.
Kötü:
function config(): array
{
return [
'foo' => 'bar',
]
}
İyi:
class Configuration
{
private $configuration = [];
public function __construct(array $configuration)
{
$this->configuration = $configuration;
}
public function get(string $key): ?string
{
return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
}
}
Yapılandırmayı yükleyin ve Configuration
sınıfının örneğini oluşturun.
$configuration = new Configuration([
'foo' => 'bar',
]);
Ve şimdi uygulamanızda Configuration
örneğini kullanmalısınız.
Singeton bir anti-pattern'dir. Brian Button'un yorumuyla:
- Genellikle global örnek kullanırlar, neden bu kadar kötü ki? Çünkü bunları arayüzlerde göstermek yerine uygulamanızın kodlarında bağımlılıkları saklıyorsunuz. Global bir şeyler yapmak, kod kokusu etrafında dolaşmamaktır.
- Tek Sorumluluk Prensibi (SRP)'ni ihlal ediyorlar : kendi kreasyonlarını ve yaşam döngülerini kontrol ettikleri gerçeğiyle.
- Bunlar doğal olarak kodun sıkı sıkıya bağlanmış olmasına neden olurlar. Birçok durumda testi zorlaştırmak için onları kandırır.
- Uygulamanın yaşam süresi kadar durum taşıyorlar. Başka bir test yaptıktan sonra birim testleri için büyük bir hiç olan testlerin sıralı olması gereken bir durumla karşılaşabilirsiniz. Neden? Çünkü her birim testi diğerlerinden bağımsız olmalıdır.
Problemin kökü hakkında Misko Hevery 'in çok güzel düşünceleri var.
Kötü:
class DBConnection
{
private static $instance;
private function __construct(string $dsn)
{
// ...
}
public static function getInstance(): DBConnection
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// ...
}
$singleton = DBConnection::getInstance();
İyi:
class DBConnection
{
public function __construct(string $dsn)
{
// ...
}
// ...
}
DBConnection
sınıfının örneğini oluşturun ve DSN ile yapılandırın.
$connection = new DBConnection($dsn);
Ve şimdi uygulamanızda DBConnection
örneğini kullanmalısınız.
Kötü:
if ($article->state === 'published') {
// ...
}
İyi:
if ($article->isPublished()) {
// ...
}
Kötü:
function isDOMNodeNotPresent(\DOMNode $node): bool
{
// ...
}
if (!isDOMNodeNotPresent($node))
{
// ...
}
İyi:
function isDOMNodePresent(\DOMNode $node): bool
{
// ...
}
if (isDOMNodePresent($node)) {
// ...
}
Bu imkansız bir iş gibi görünüyor. Bunu ilk duyduğunda, birçok insan şöyle der;
"Bir if
koşulu olmadan nasıl bir şey yapayım ki?" Bunun cevabı ise çoğu durumda aynı işi gerçekleştirmek için polimorfizm(polymorphism
) kullanabilmenizdir. Genellikle ikinci soru ise; "tamam bu harika ama neden bunu yapmak isterdim?" Bunun cevabı öğrendiğimiz bir önceki temiz kod kavramıdır: bir fonksiyon sadece bir şey yapmalı. if
koşuluna sahip sınıflar ve fonksiyonlarınız olduğu zaman, kullanıcılarınıza fonksiyonunuzun birden fazla şey yaptığınız söylersiniz. Unutmayın, sadece bir şey yapın.
Kötü:
class Airplane
{
// ...
public function getCruisingAltitude(): int
{
switch ($this->type) {
case '777':
return $this->getMaxAltitude() - $this->getPassengerCount();
case 'Air Force One':
return $this->getMaxAltitude();
case 'Cessna':
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
}
İyi:
interface Airplane
{
// ...
public function getCruisingAltitude(): int;
}
class Boeing777 implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getPassengerCount();
}
}
class AirForceOne implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude();
}
}
class Cessna implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
PHP türlendirilmemiş, bu da fonksiyonlarınızın herhangi bir türde argümanı alabileceğini anlamına geliyor. Bu özgürlük bazen sizi zora sokabilir ve fonksiyonlarınızda tür kontrolü yapmak cazip hale gelir. Bunu yapmaktan kaçınmanın bir çok yolu vardır. Dikkate alınacak ilk şey tutarlı API'lardır.
Kötü:
function travelToTexas($vehicle): void
{
if ($vehicle instanceof Bicycle) {
$vehicle->pedalTo(new Location('texas'));
} elseif ($vehicle instanceof Car) {
$vehicle->driveTo(new Location('texas'));
}
}
İyi:
function travelToTexas(Traveler $vehicle): void
{
$vehicle->travelTo(new Location('texas'));
}
Dizeler, tamsayılar ve diziler gibi basit ilkel değerler ile çalışıyorsanız ve PHP 7+ kullanıyorsanız ve polimorfizmi kullanamıyorsanız ama yine de tür kontrolü yapmanız gerektiğini düşünüyorsanız, tür beyanı veya strict(katı) modunu dikkate almalısınız. Standart PHP sözdiziminin üstünde size statik yazım sağlar. Manuel tür kontrolü ile ilgili problem, aldığınız sahte "tür-güvenliği" nin kaybolan okunabilirliği telafi etmemesi için ekstra gereksiz kelime gerektirmesidir. PHP'nizi temiz tutun, iyi testler yazın ve iyi bir kod incelemesine sahip olun. Aksi taktirde, tüm bunları katı tür beyanı yada katı(strict) mod ile yapın.
Kötü:
function combine($val1, $val2): int
{
if (!is_numeric($val1) || !is_numeric($val2)) {
throw new \Exception('Must be of type Number');
}
return $val1 + $val2;
}
İyi:
function combine(int $val1, int $val2): int
{
return $val1 + $val2;
}
Ölü kod, tekrarlanan kod kadar kötüdür. Kod tabanınızda tutmak için bir sebep yok. Eğer çağrılmıyorsa ondan kurtulun! Hala ihtiyacınız varsa versiyon geçmişinizde hala güvende olacak.
Kötü:
function oldRequestModule(string $url): void
{
// ...
}
function newRequestModule(string $url): void
{
// ...
}
$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
İyi:
function requestModule(string $url): void
{
// ...
}
$request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
PHP'de, metodlar için public
, protected
ve private
anahtar kelimeler ayarlayabilirsiniz. Bunu kullanarak bir nesne üzerinde özellik değişikliklerini kontrol edebilirsiniz.
- Nesne özelliği elde etmenin ötesinde daha fazlasını yapmak istediğinizde, kod tabanınızdaki her erişimciyi aramanıza ve değiştirmenize gerek yoktur.
- Bir
set
yaparken doğrulama ekleyerek basitleştirir. - İçsel temsili dahil eder.
- Alma ve ayarlama yapılırken kaydetme(loglama) ve hata işlemeyi eklemek kolaydır.
- Bu sınıfı miras olarak alırken, varsayılan fonksiyonelliği geçersiz kılabilirsiniz.
- Nesnenin özelliklerini ağır yükletebilirsiniz, bir sunucudan almayı söyleyelim. (Lazy Load)
Ek olarak, bu Açık/Kapalı prensibinin bir parçasıdır.
Kötü:
class BankAccount
{
public $balance = 1000;
}
$bankAccount = new BankAccount();
// Ayakkabı al...
$bankAccount->balance -= 100;
İyi:
class BankAccount
{
private $balance;
public function __construct(int $balance = 1000)
{
$this->balance = $balance;
}
public function withdraw(int $amount): void
{
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance.');
}
$this->balance -= $amount;
}
public function deposit(int $amount): void
{
$this->balance += $amount;
}
public function getBalance(): int
{
return $this->balance;
}
}
$bankAccount = new BankAccount();
// Ayakkabı al...
$bankAccount->withdraw($shoesPrice);
// Bakiyeyi al
$balance = $bankAccount->getBalance();
public
metodları ve özellikleri değişiklikler için çok tehlikelidir çünkü bazı dış kodlar bunlara kolaylıkla güvenebilir ve hangi kodun onlara bağlı olduğunu kontrol edemezsiniz. Sınıftaki değişiklikler, sınıfın tüm kullanıcıları için tehlikelidir.protected
değiştiricileri,public
kadar tehlikelidir çünkü herhangi bir çocuk sınıfı kapsamındadırlar. Bu public ve protected arasındaki farkın yalnızca erişim mekanizmasında olduğunu ama kapsülleme garantisinin aynı kaldığı anlamına gelir. Sınıftaki değişiklikler tüm alt sınıflar için tehlikelidir.private
değiştiricileri kodun sadece tek bir sınıfın sınırları içinde değiştirilmesinin tehlikeli olduğunu garanti eder (değişiklikler için güvendesiniz ve Jenga etkisinde olmayacaksın).
Bu sebeple, varsayılan olarak private
kullanın ve harici sınıflara erişim sağlamanız gerektiğinde public/protected
kullanın.
Daha fazla bilgi için, bu konuda Fabien Potencier'ın yazdığı blog yazısını okuyabilirsiniz.
Kötü:
class Employee
{
public $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->name; // Çalışan Adı: John Doe
İyi:
class Employee
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->getName(); // Çalışan Adı: John Doe
Gang of Four(Dörtlü Çete)'un ünlü Tasarım Desenleri'nde belirtildiği gibi, yapabileceğiniz yerlerde kompozisyonu kalıtıma tercih etmelisiniz. Kalıtımın kullanılması için pek çok iyi sebep ve kompozisyon kullanmak için birçok iyi sebep vardır. Bu ilkenin ana noktası, eğer aklınız içgüdüsel olarak kalıtıma giderse, kompozisyonun problemini daha iyi modellemeyi düşünmeye çalışın. Bazı durumlarda yapabilir.
O zaman merak ediyor olabilirsiniz, "kalıtımımı ne zaman kullanmalıyım?" Bu senin probleminize bağlı ama kalıtımın kompozisyondan daha anlamlı olduğu zamanın iyi bir listesidir:
- Kalıtımınız "bir" ilişkiyi temsil eder ve ilişkinin "bir" ilişkisi yoktur (Human->Animal vs. User->UserDetails).
- Temel sınıflardan kodu tekrar kullanabilirsiniz (İnsanlar tüm hayvanlar gibi hareket edebilir).
- Bir temel sınıfı değiştirerek türetilmiş sınıflarda global değişiklikler yapmak istersiniz (Hareket ettikleri zaman tüm hayvanların kalori giderlerini değiştirin).
Kötü:
class Employee
{
private $name;
private $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
// ...
}
// Kötü çünkü Employees vergi verileri "var".
// EmployeeTaxData bir Employee türü değil.
class EmployeeTaxData extends Employee
{
private $ssn;
private $salary;
public function __construct(string $name, string $email, string $ssn, string $salary)
{
parent::__construct($name, $email);
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
İyi:
class EmployeeTaxData
{
private $ssn;
private $salary;
public function __construct(string $ssn, string $salary)
{
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
class Employee
{
private $name;
private $email;
private $taxData;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
public function setTaxData(string $ssn, string $salary)
{
$this->taxData = new EmployeeTaxData($ssn, $salary);
}
// ...
}
Akıcı arayüz, Metod Zincirleme kullanarak kaynak kodun okunabilirliği arttırmaya çalışan nesne tabanlı bir API'dir.
Bazı bağlamlar olsa da kodun (Örneğin PHPUnit Mock Builder veya Doctrine Query Builder) ayrıntılarını azalttığı sıklıkla yapıcı madde olsa da, sıklıkla bir bedeli vardır:
- Kapsüllemeyi bozar.
- Dekoratörleri bozar.
- Testte sahte nesne kullanmak daha zordur.
- Okuması zor olan farklı işlemler yapar.
Daha fazla bilgi için, bu konuda Marco Pivetta 'nın yazdığı blog yazısını okuyabilirsiniz.
Kötü:
class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake(string $make): self
{
$this->make = $make;
// NOT: Bunu zincirleme için döndüyor.
return $this;
}
public function setModel(string $model): self
{
$this->model = $model;
// NOT: Bunu zincirleme için döndüyor.
return $this;
}
public function setColor(string $color): self
{
$this->color = $color;
// NOT: Bunu zincirleme için döndüyor.
return $this;
}
public function dump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = (new Car())
->setColor('pink')
->setMake('Ford')
->setModel('F-150')
->dump();
İyi:
class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake(string $make): void
{
$this->make = $make;
}
public function setModel(string $model): void
{
$this->model = $model;
}
public function setColor(string $color): void
{
$this->color = $color;
}
public function dump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();
final
mümkün olduğunda kullanılmalıdır:
- Kontrolsüz kalıtım zincirini önler.
- Kompozisyonu teşvik eder.
- Tek Sorumluluk Desenini teşvik der.
- Geliştiricilerin, protected metodlara erişmek için sınıfı geliştirmesi yerine public metodlarınızı kullanmasını teşvik eder.
- Sınıfını kullanmayan uygulamalarda bozulma olmadan kodunu değiştirmene izin verir.
Tek şart sınıfınızın arayüz kullanmasıdır ve başka hiçbir metod tanımlanmamasıdır.
Daha fazla bilgi için, bu konuda Marco Pivetta (Ocramius) 'nın yazdığı blog yazısını okuyabilirsiniz.
Kötü:
final class Car
{
private $color;
public function __construct($color)
{
$this->color = $color;
}
/**
* @return string The color of the vehicle
*/
public function getColor()
{
return $this->color;
}
}
İyi:
interface Vehicle
{
/**
* @return string The color of the vehicle
*/
public function getColor();
}
final class Car implements Vehicle
{
private $color;
public function __construct($color)
{
$this->color = $color;
}
/**
* {@inheritdoc}
*/
public function getColor()
{
return $this->color;
}
}
SOLID, Robert Martin'in bahsettiği ilk beş prensip için Michael Feathers tarafından ortaya çıkarılmış bir mnemonik kısaltmadır.
- S: Tek Sorumluluk Prensibi (SRP)
- O: Açık/Kapalı Prensibi (OCP)
- L: Liskov'un Yerine Geçme Prensibi (LSP)
- I: Arayüz Ayırma Prensibi (ISP)
- D: Bağlılığı Tersine Çevirme Prensibi (DIP)
Temiz Kodda belirtildiği gibi, "Bir sınıfın değişmesi için asla birden fazla sebep olmamalıdır". Bir sınıfı, uçuşunuzda sadece bir bavul alabildiğiniz gibi tek fonksiyonellikle sıkıştırmak daha caziptir. Bununla ilgili bir sorun, sınıfınızın kavramsal olarak birleşemeyeceği ve değişmesi için birçok sebep vereceğidir. Bir sınıfı değiştirmek için gereken süreyi en aza indirmek önemlidir. Bu önemlidir çünkü bir sınıfta çok fazla fonksiyonellik varsa ve bir parçasını değiştirirsen, kod tabanınızda bulunan diğer bağımlı modülleri nasıl etkileyeceğini kestirmek zordur.
Kötü:
class UserSettings
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function changeSettings(array $settings): void
{
if ($this->verifyCredentials()) {
// ...
}
}
private function verifyCredentials(): bool
{
// ...
}
}
İyi:
class UserAuth
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function verifyCredentials(): bool
{
// ...
}
}
class UserSettings
{
private $user;
private $auth;
public function __construct(User $user)
{
$this->user = $user;
$this->auth = new UserAuth($user);
}
public function changeSettings(array $settings): void
{
if ($this->auth->verifyCredentials()) {
// ...
}
}
}
Bertrand Meyer'in belirttiği gibi, "yazılım varlıkları (sınıflar, modüller, fonksiyonlar vb.) ilaveye açık olmalı ama değişiklik için kapatılmalıdır." Bu ne anlama geliyor? Bu prensip temel olarak kullanıcıların mevcut kodu değiştirmeden yeni fonksiyonlar eklemelerine izin vermeniz gerektiğini belirtir.
Kötü:
abstract class Adapter
{
protected $name;
public function getName(): string
{
return $this->name;
}
}
class AjaxAdapter extends Adapter
{
public function __construct()
{
parent::__construct();
$this->name = 'ajaxAdapter';
}
}
class NodeAdapter extends Adapter
{
public function __construct()
{
parent::__construct();
$this->name = 'nodeAdapter';
}
}
class HttpRequester
{
private $adapter;
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}
public function fetch(string $url): Promise
{
$adapterName = $this->adapter->getName();
if ($adapterName === 'ajaxAdapter') {
return $this->makeAjaxCall($url);
} elseif ($adapterName === 'httpNodeAdapter') {
return $this->makeHttpCall($url);
}
}
private function makeAjaxCall(string $url): Promise
{
// request and return promise
}
private function makeHttpCall(string $url): Promise
{
// request and return promise
}
}
İyi:
interface Adapter
{
public function request(string $url): Promise;
}
class AjaxAdapter implements Adapter
{
public function request(string $url): Promise
{
// request and return promise
}
}
class NodeAdapter implements Adapter
{
public function request(string $url): Promise
{
// request and return promise
}
}
class HttpRequester
{
private $adapter;
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}
public function fetch(string $url): Promise
{
return $this->adapter->request($url);
}
}
Bu çok basit bir kavram için korkutucu bir terimdir. "Eğer S, T'nin bir alt türüyse, o zaman T tipi nesneler bu programın istenen özelliklerinden herhangi birini değiştirmeden S tipi (Örneğin, S türündeki nesneler T türündeki nesnelerin yerine geçebilir) nesnelerle değiştirilebilir (uygunluk, yapılan görev, vb.)." Bu daha da korkunç bir açıklamadır.
Bunun için en iyi açıklama, bir ebeveyn ve bir çocuk sınıfınız varsa yanlış sonuçlar almadan, çocuk sınıfı ve temel sınıfı dönüşümlü olarak değiştirilebilir. Bu hala kafa karıştırıcı olabilir. Bu yüzden klasik, Kare-Dikdörtgen örneğine bir bakalım. Matematiksel olarak kare bir dikdörtgendir ama kalıtım aracığıyla ama bunu ilişkiyi kullanarak modelliyorsanız, kısa sürede başınız ağrıyabilir.
Kötü:
class Rectangle
{
protected $width = 0;
protected $height = 0;
public function setWidth(int $width): void
{
$this->width = $width;
}
public function setHeight(int $height): void
{
$this->height = $height;
}
public function getArea(): int
{
return $this->width * $this->height;
}
}
class Square extends Rectangle
{
public function setWidth(int $width): void
{
$this->width = $this->height = $width;
}
public function setHeight(int $height): void
{
$this->width = $this->height = $height;
}
}
function printArea(Rectangle $rectangle): void
{
$rectangle->setWidth(4);
$rectangle->setHeight(5);
// BAD: Will return 25 for Square. Should be 20.
echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()).PHP_EOL;
}
$rectangles = [new Rectangle(), new Square()];
foreach ($rectangles as $rectangle) {
printArea($rectangle);
}
İyi:
En iyi yöntem dörtgenlerini ayırmak ve iki şekil için de daha genel bir alt türü ayrıştırmaktır.
Kare ve dikdörtgen görüntüde benzer olsa da aslında farklılardır. Bir kare, eşkenar dikdörtgene daha benzerdir, bir dikdörtgen de paralelkenara daha benzerdir ama alt tür değillerdir. Bir kare, bir dikdörtgen bir eşkenar dikdörtgen ve bir paralelkenar kendi özellikleri olan farklı şekillerdir. Fakat benzerlerdir.
interface Shape
{
public function getArea(): int;
}
class Rectangle implements Shape
{
private $width = 0;
private $height = 0;
public function __construct(int $width, int $height)
{
$this->width = $width;
$this->height = $height;
}
public function getArea(): int
{
return $this->width * $this->height;
}
}
class Square implements Shape
{
private $length = 0;
public function __construct(int $length)
{
$this->length = $length;
}
public function getArea(): int
{
return $this->length ** 2;
}
}
function printArea(Shape $shape): void
{
echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}
$shapes = [new Rectangle(4, 5), new Square(5)];
foreach ($shapes as $shape) {
printArea($shape);
}
ISP, "Müşteriler kullanmadıkları arayüzlere bağlı olmaya zorlanmamalıdır." der.
Bu prensibi gösteren iyi bir örneğe bakarsak, büyük ayarlama nesnelerine ihtiyaç duyan sınıflar sınıflar vardır. Müşterinin, çok fazla sayıda seçenek kurmaya ihtiyacı olmaması yararlıdır. Çünkü genelde o ayarlamaların hepsine ihtiyaçları olmayacaktır. Onları opsiyonel yapmak "şişman arayüz" olmasını engeller.
Kötü:
interface Employee
{
public function work(): void;
public function eat(): void;
}
class HumanEmployee implements Employee
{
public function work(): void
{
// ....çalışıyor
}
public function eat(): void
{
// ...... öğle arasında yemek yiyor
}
}
class RobotEmployee implements Employee
{
public function work(): void
{
//.... çok daha fazla çalışıyor
}
public function eat(): void
{
//.... robot yemek yemez ama bu metodu uygulamak zorundadır
}
}
İyi:
Her işçi bir çalışan değil ama her çalışan bir işçidir.
interface Workable
{
public function work(): void;
}
interface Feedable
{
public function eat(): void;
}
interface Employee extends Feedable, Workable
{
}
class HumanEmployee implements Employee
{
public function work(): void
{
// ....çalışıyor
}
public function eat(): void
{
//.... öğle arasında yemek yiyor
}
}
// robot sadece çalışabilir
class RobotEmployee implements Workable
{
public function work(): void
{
// ....çalışıyor
}
}
Bu prensip iki temel öğeyi barındırır:
- Yüksek seviyeli modüller, düşük seviyeli modüllere bağlı olmamalıdır. İkisi de soyutlamaya bağlı olmalıdır.
- Soyutlamalar, detaylara bağlı olmamalıdır. Detaylar, soyutlamalara bağlı olmalıdır.
Bunu başta anlamak zordur ama PHP frameworkleri(Symfony gibi) ile çalıştıysanız, Dependency Injection (DI) formunda bu prensibin uygulandığını görmüşsünüzdür. Aynı konseptler olmadıklarında DIP düşük seviyeli modüllerinin detaylarını bilerek yüksek seviyeli modülleri saklar ve ayarlar. Bunu DI üzerinden tamamlayabilir. Bunun en büyük yararlarından biri de modüller arasındaki eşleşmeyi azaltır. Eşleşme çok kötü bir geliştirme desenidir çünkü kodunuzu yeniden düzenlemeyi zorlaştırır.
Kötü:
class Employee
{
public function work(): void
{
// ....çalışıyor
}
}
class Robot extends Employee
{
public function work(): void
{
//.... çok daha fazla çalışıyor
}
}
class Manager
{
private $employee;
public function __construct(Employee $employee)
{
$this->employee = $employee;
}
public function manage(): void
{
$this->employee->work();
}
}
İyi:
interface Employee
{
public function work(): void;
}
class Human implements Employee
{
public function work(): void
{
// ....çalışıyor
}
}
class Robot implements Employee
{
public function work(): void
{
//.... çok daha fazla çalışıyor
}
}
class Manager
{
private $employee;
public function __construct(Employee $employee)
{
$this->employee = $employee;
}
public function manage(): void
{
$this->employee->work();
}
}
DRY prensibini gözlemlemeye çalışın.
Tekrarlanan kodlardan kaçınmak için mutlaka en iyisini yapın. Tekrarlanan kod kötüdür çünkü bir mantığı değiştirmek gerekirse bir şeyi değiştirmek için birden fazla yer olduğu anlamına geliyor.
Bir restoran işlettiğinizi düşünün ve malzeme takibini yapıyorsunuz: tüm domates, soğan, sarımsak, baharat, ve benzerlerinin. Bu işte birden çok liste kullanırsanız içinde domates olan bir yemek servis ettiğinizde tüm listeleri güncellemeniz gerekir. Sadece bir listeniz olursa, güncelleyecek sadece bir yer olur.
Genelde tekrarlanan kod vardır çünkü iki veya daha fazla çok az farklı şey vardır. Birçok ortak yönleri vardır ama farklılıkları, oldukça benzer şeyleri yapan iki veya daha fazla ayrı fonksiyonunuzun olmasına sizi zorlar. Tekrarlayan kodları kaldırmak, bir dizi farklı şeyleri sadece bir fonksiyon/modül/sınıf ile halledebilen soyutlamalar oluşturmak demektir.
Soyutlamayı doğru yapmak çok önemlidir. Bu yüzden Sınıflar bölümündeki SOLID prensiplerini izlemelisiniz. Kötü soyutlamalar, tekrarlanan kodlardan daha kötü olabilir, o yüzden dikkat edin! Söylediğim gibi, iyi bir soyutlama yapabiliyorsanız, yapın! Kendinizi tekrar etmeyin, yoksa bir şeyi değiştirmek istediğiniz zaman kendinizi birçok yeri güncellerken bulursunuz.
Kötü:
function showDeveloperList(array $developers): void
{
foreach ($developers as $developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
function showManagerList(array $managers): void
{
foreach ($managers as $manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
İyi:
function showList(array $employees): void
{
foreach ($employees as $employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
Çok İyi:
Kodun kompakt bir versiyonunu kullanmak iyidir.
function showList(array $employees): void
{
foreach ($employees as $employee) {
render([
$employee->calculateExpectedSalary(),
$employee->getExperience(),
$employee->getGithubLink()
]);
}
}
Başka diller de mevcuttur:
- 🇨🇳 Çince:
- 🇷🇺 Rusça:
- 🇪🇸 İspanyolca:
- 🇧🇷 Portekizce:
- 🇹🇭 Tayca:
- 🇫🇷 Fransızca:
- 🇻🇳 Vietnamca
- 🇰🇷 Korece:
- 🇹🇷 Türkçe: