- 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
{
constACCESS_READ = 1;
constACCESS_CREATE = 2;
constACCESS_UPDATE = 4;
constACCESS_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ü:
functionisShopOpen($day): bool
{
if ($day) {
if (is_string($day)) {
$day = strtolower($day);
if ($day === 'friday') {
returntrue;
} elseif ($day === 'saturday') {
returntrue;
} elseif ($day === 'sunday') {
returntrue;
} else {
returnfalse;
}
} else {
returnfalse;
}
} else {
returnfalse;
}
}İyi:
functionisShopOpen(string$day): bool
{
if (empty($day)) {
returnfalse;
}
$openingDays = [
'friday', 'saturday', 'sunday'
];
returnin_array(strtolower($day), $openingDays, true);
}Kötü:
functionfibonacci(int$n)
{
if ($n < 50) {
if ($n !== 0) {
if ($n !== 1) {
returnfibonacci($n - 1) + fibonacci($n - 2);
} else {
return1;
}
} else {
return0;
}
} else {
return'Not supported';
}
}İyi:
functionfibonacci(int$n): int
{
if ($n === 0 || $n === 1) {
return$n;
}
if ($n > 50) {
thrownew \Exception('Not supported');
}
returnfibonacci($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 ($locationsas$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.
functioncreateMicrobrewery($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.
functioncreateMicrobrewery($name = null): void
{
$breweryName = $name ?: 'Hipster Brew Co.';
// ...
}İyi:
tür ipucu kullanabilirsiniz ve $breweryName öğesinin NULL olmadığından emin olun.
functioncreateMicrobrewery(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ü:
functioncreateMenu(string$title, string$body, string$buttonText, bool$cancellable): void
{
// ...
}İyi:
class MenuConfig
{
public$title;
public$body;
public$buttonText;
public$cancellable = false;
}
$config = newMenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;
functioncreateMenu(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ü:
functionemailClients(array$clients): void
{
foreach ($clientsas$client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}İyi:
functionemailClients(array$clients): void
{
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}
functionactiveClients(array$clients): array
{
returnarray_filter($clients, 'isClientActive');
}
functionisClientActive(int$client): bool
{
$clientRecord = $db->find($client);
return$clientRecord->isActive();
}Kötü:
class Email
{
//...publicfunctionhandle(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = newEmail(...);
// Bu nedir? Mesaj için `handle` ? Şimdi bir dosyaya mı yazıyoruz ?$message->handle();İyi:
class Email {
//...publicfunctionsend(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = newEmail(...);
// 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ü:
functionparseBetterJSAlternative(string$code): void
{
$regexes = [
// ...
];
$statements = explode('', $code);
$tokens = [];
foreach ($regexesas$regex) {
foreach ($statementsas$statement) {
// ...
}
}
$ast = [];
foreach ($tokensas$token) {
// lex...
}
foreach ($astas$node) {
// parse...
}
}Çok Kötü:
Bazı fonksiyonellikleri gerçekleştirdik ama parseBetterJSAlternative() fonksiyonu hala çok karmaşık ve test edilebilir değil.
functiontokenize(string$code): array
{
$regexes = [
// ...
];
$statements = explode('', $code);
$tokens = [];
foreach ($regexesas$regex) {
foreach ($statementsas$statement) {
$tokens[] = /* ... */;
}
}
return$tokens;
}
functionlexer(array$tokens): array
{
$ast = [];
foreach ($tokensas$token) {
$ast[] = /* ... */;
}
return$ast;
}
functionparseBetterJSAlternative(string$code): void
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($astas$node) {
// parse...
}
}İyi:
En iyi çözüm parseBetterJSAlternative() fonksiyonundaki bağımlılıkları ortadan kaldırmaktır.
class Tokenizer
{
publicfunctiontokenize(string$code): array
{
$regexes = [
// ...
];
$statements = explode('', $code);
$tokens = [];
foreach ($regexesas$regex) {
foreach ($statementsas$statement) {
$tokens[] = /* ... */;
}
}
return$tokens;
}
}
class Lexer
{
publicfunctionlexify(array$tokens): array
{
$ast = [];
foreach ($tokensas$token) {
$ast[] = /* ... */;
}
return$ast;
}
}
class BetterJSAlternative
{
private$tokenizer;
private$lexer;
publicfunction__construct(Tokenizer$tokenizer, Lexer$lexer)
{
$this->tokenizer = $tokenizer;
$this->lexer = $lexer;
}
publicfunctionparse(string$code): void
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($astas$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ü:
functioncreateFile(string$name, bool$temp = false): void
{
if ($temp) {
touch('./temp/'.$name);
} else {
touch($name);
}
}İyi:
functioncreateFile(string$name): void
{
touch($name);
}
functioncreateTempFile(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';
functionsplitIntoFirstAndLastName(): void
{
global$name;
$name = explode('', $name);
}
splitIntoFirstAndLastName();
var_dump($name); // ['Ryan', 'McDermott'];İyi:
functionsplitIntoFirstAndLastName(string$name): array
{
returnexplode('', $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ü:
functionconfig(): array
{
return [
'foo' => 'bar',
]
}İyi:
class Configuration
{
private$configuration = [];
publicfunction__construct(array$configuration)
{
$this->configuration = $configuration;
}
publicfunctionget(string$key): ?string
{
returnisset($this->configuration[$key]) ? $this->configuration[$key] : null;
}
}Yapılandırmayı yükleyin ve Configuration sınıfının örneğini oluşturun.
$configuration = newConfiguration([
'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
{
privatestatic$instance;
privatefunction__construct(string$dsn)
{
// ...
}
publicstaticfunctiongetInstance(): DBConnection
{
if (self::$instance === null) {
self::$instance = newself();
}
returnself::$instance;
}
// ...
}
$singleton = DBConnection::getInstance();İyi:
class DBConnection
{
publicfunction__construct(string$dsn)
{
// ...
}
// ...
}DBConnection sınıfının örneğini oluşturun ve DSN ile yapılandırın.
$connection = newDBConnection($dsn);Ve şimdi uygulamanızda DBConnection örneğini kullanmalısınız.
Kötü:
if ($article->state === 'published') {
// ...
}İyi:
if ($article->isPublished()) {
// ...
}Kötü:
functionisDOMNodeNotPresent(\DOMNode$node): bool
{
// ...
}
if (!isDOMNodeNotPresent($node))
{
// ...
}İyi:
functionisDOMNodePresent(\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
{
// ...publicfunctiongetCruisingAltitude(): 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
{
// ...publicfunctiongetCruisingAltitude(): int;
}
class Boeing777 implements Airplane
{
// ...publicfunctiongetCruisingAltitude(): int
{
return$this->getMaxAltitude() - $this->getPassengerCount();
}
}
class AirForceOne implements Airplane
{
// ...publicfunctiongetCruisingAltitude(): int
{
return$this->getMaxAltitude();
}
}
class Cessna implements Airplane
{
// ...publicfunctiongetCruisingAltitude(): 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ü:
functiontravelToTexas($vehicle): void
{
if ($vehicleinstanceof Bicycle) {
$vehicle->pedalTo(newLocation('texas'));
} elseif ($vehicleinstanceof Car) {
$vehicle->driveTo(newLocation('texas'));
}
}İyi:
functiontravelToTexas(Traveler$vehicle): void
{
$vehicle->travelTo(newLocation('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ü:
functioncombine($val1, $val2): int
{
if (!is_numeric($val1) || !is_numeric($val2)) {
thrownew \Exception('Must be of type Number');
}
return$val1 + $val2;
}İyi:
functioncombine(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ü:
functionoldRequestModule(string$url): void
{
// ...
}
functionnewRequestModule(string$url): void
{
// ...
}
$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');İyi:
functionrequestModule(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
setyaparken 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 = newBankAccount();
// Ayakkabı al...$bankAccount->balance -= 100;İyi:
class BankAccount
{
private$balance;
publicfunction__construct(int$balance = 1000)
{
$this->balance = $balance;
}
publicfunctionwithdraw(int$amount): void
{
if ($amount > $this->balance) {
thrownew \Exception('Amount greater than available balance.');
}
$this->balance -= $amount;
}
publicfunctiondeposit(int$amount): void
{
$this->balance += $amount;
}
publicfunctiongetBalance(): int
{
return$this->balance;
}
}
$bankAccount = newBankAccount();
// Ayakkabı al...$bankAccount->withdraw($shoesPrice);
// Bakiyeyi al$balance = $bankAccount->getBalance();publicmetodları 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.protecteddeğiştiricileri,publickadar 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.privatedeğ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;
publicfunction__construct(string$name)
{
$this->name = $name;
}
}
$employee = newEmployee('John Doe');
echo'Employee name: '.$employee->name; // Çalışan Adı: John Doeİyi:
class Employee
{
private$name;
publicfunction__construct(string$name)
{
$this->name = $name;
}
publicfunctiongetName(): string
{
return$this->name;
}
}
$employee = newEmployee('John Doe');
echo'Employee name: '.$employee->getName(); // Çalışan Adı: John DoeGang 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;
publicfunction__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;
publicfunction__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;
publicfunction__construct(string$ssn, string$salary)
{
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
class Employee {
private$name;
private$email;
private$taxData;
publicfunction__construct(string$name, string$email)
{
$this->name = $name;
$this->email = $email;
}
publicfunctionsetTaxData(string$ssn, string$salary)
{
$this->taxData = newEmployeeTaxData($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';
publicfunctionsetMake(string$make): self
{
$this->make = $make;
// NOT: Bunu zincirleme için döndüyor.return$this;
}
publicfunctionsetModel(string$model): self
{
$this->model = $model;
// NOT: Bunu zincirleme için döndüyor.return$this;
}
publicfunctionsetColor(string$color): self
{
$this->color = $color;
// NOT: Bunu zincirleme için döndüyor.return$this;
}
publicfunctiondump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = (newCar())
->setColor('pink')
->setMake('Ford')
->setModel('F-150')
->dump();İyi:
class Car
{
private$make = 'Honda';
private$model = 'Accord';
private$color = 'white';
publicfunctionsetMake(string$make): void
{
$this->make = $make;
}
publicfunctionsetModel(string$model): void
{
$this->model = $model;
}
publicfunctionsetColor(string$color): void
{
$this->color = $color;
}
publicfunctiondump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = newCar();
$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ü:
finalclass Car
{
private$color;
publicfunction__construct($color)
{
$this->color = $color;
}
/** * @return string The color of the vehicle */publicfunctiongetColor() {
return$this->color;
}
}İyi:
interface Vehicle
{
/** * @return string The color of the vehicle */publicfunctiongetColor();
}
finalclass Car implements Vehicle
{
private$color;
publicfunction__construct($color)
{
$this->color = $color;
}
/** * {@inheritdoc} */publicfunctiongetColor() {
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;
publicfunction__construct(User$user)
{
$this->user = $user;
}
publicfunctionchangeSettings(array$settings): void
{
if ($this->verifyCredentials()) {
// ...
}
}
privatefunctionverifyCredentials(): bool
{
// ...
}
}İyi:
class UserAuth {
private$user;
publicfunction__construct(User$user)
{
$this->user = $user;
}
publicfunctionverifyCredentials(): bool
{
// ...
}
}
class UserSettings {
private$user;
private$auth;
publicfunction__construct(User$user) {
$this->user = $user;
$this->auth = newUserAuth($user);
}
publicfunctionchangeSettings(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ü:
abstractclass Adapter
{
protected$name;
publicfunctiongetName(): string
{
return$this->name;
}
}
class AjaxAdapter extends Adapter
{
publicfunction__construct()
{
parent::__construct();
$this->name = 'ajaxAdapter';
}
}
class NodeAdapter extends Adapter
{
publicfunction__construct()
{
parent::__construct();
$this->name = 'nodeAdapter';
}
}
class HttpRequester
{
private$adapter;
publicfunction__construct(Adapter$adapter)
{
$this->adapter = $adapter;
}
publicfunctionfetch(string$url): Promise
{
$adapterName = $this->adapter->getName();
if ($adapterName === 'ajaxAdapter') {
return$this->makeAjaxCall($url);
} elseif ($adapterName === 'httpNodeAdapter') {
return$this->makeHttpCall($url);
}
}
privatefunctionmakeAjaxCall(string$url): Promise
{
// request and return promise
}
privatefunctionmakeHttpCall(string$url): Promise
{
// request and return promise
}
}İyi:
interface Adapter
{
publicfunctionrequest(string$url): Promise;
}
class AjaxAdapter implements Adapter
{
publicfunctionrequest(string$url): Promise
{
// request and return promise
}
}
class NodeAdapter implements Adapter
{
publicfunctionrequest(string$url): Promise
{
// request and return promise
}
}
class HttpRequester
{
private$adapter;
publicfunction__construct(Adapter$adapter)
{
$this->adapter = $adapter;
}
publicfunctionfetch(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;
publicfunctionsetWidth(int$width): void
{
$this->width = $width;
}
publicfunctionsetHeight(int$height): void
{
$this->height = $height;
}
publicfunctiongetArea(): int
{
return$this->width * $this->height;
}
}
class Square extends Rectangle
{
publicfunctionsetWidth(int$width): void
{
$this->width = $this->height = $width;
}
publicfunctionsetHeight(int$height): void
{
$this->width = $this->height = $height;
}
}
functionprintArea(Rectangle$rectangle): void
{
$rectangle->setWidth(4);
$rectangle->setHeight(5);
// BAD: Will return 25 for Square. Should be 20.echosprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()).PHP_EOL;
}
$rectangles = [newRectangle(), newSquare()];
foreach ($rectanglesas$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
{
publicfunctiongetArea(): int;
}
class Rectangle implements Shape
{
private$width = 0;
private$height = 0;
publicfunction__construct(int$width, int$height)
{
$this->width = $width;
$this->height = $height;
}
publicfunctiongetArea(): int
{
return$this->width * $this->height;
}
}
class Square implements Shape
{
private$length = 0;
publicfunction__construct(int$length)
{
$this->length = $length;
}
publicfunctiongetArea(): int
{
return$this->length ** 2;
}
}
functionprintArea(Shape$shape): void
{
echosprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}
$shapes = [newRectangle(4, 5), newSquare(5)];
foreach ($shapesas$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
{
publicfunctionwork(): void;
publicfunctioneat(): void;
}
class HumanEmployee implements Employee
{
publicfunctionwork(): void
{
// ....çalışıyor
}
publicfunctioneat(): void
{
// ...... öğle arasında yemek yiyor
}
}
class RobotEmployee implements Employee
{
publicfunctionwork(): void
{
//.... çok daha fazla çalışıyor
}
publicfunctioneat(): 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
{
publicfunctionwork(): void;
}
interface Feedable
{
publicfunctioneat(): void;
}
interface Employee extends Feedable, Workable
{
}
class HumanEmployee implements Employee
{
publicfunctionwork(): void
{
// ....çalışıyor
}
publicfunctioneat(): void
{
//.... öğle arasında yemek yiyor
}
}
// robot sadece çalışabilirclass RobotEmployee implements Workable
{
publicfunctionwork(): 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
{
publicfunctionwork(): void
{
// ....çalışıyor
}
}
class Robot extends Employee
{
publicfunctionwork(): void
{
//.... çok daha fazla çalışıyor
}
}
class Manager
{
private$employee;
publicfunction__construct(Employee$employee)
{
$this->employee = $employee;
}
publicfunctionmanage(): void
{
$this->employee->work();
}
}İyi:
interface Employee
{
publicfunctionwork(): void;
}
class Human implements Employee
{
publicfunctionwork(): void
{
// ....çalışıyor
}
}
class Robot implements Employee
{
publicfunctionwork(): void
{
//.... çok daha fazla çalışıyor
}
}
class Manager
{
private$employee;
publicfunction__construct(Employee$employee)
{
$this->employee = $employee;
}
publicfunctionmanage(): 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ü:
functionshowDeveloperList(array$developers): void
{
foreach ($developersas$developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
functionshowManagerList(array$managers): void
{
foreach ($managersas$manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}İyi:
functionshowList(array$employees): void
{
foreach ($employeesas$employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}Çok İyi:
Kodun kompakt bir versiyonunu kullanmak iyidir.
functionshowList(array$employees): void
{
foreach ($employeesas$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: