摘要:另外一個嚴重的打擊是當你需要排序測試的時候,在單元測試中這會是一個不小的麻煩。為什么因為每個單元測試都應該依賴于另外一個。對于所有子類來說,在類中修改也是相當危險的。
前言
原文地址:https://github.com/jupeter/cl...
譯文地址:https://github.com/nineyang/c...,歡迎star。
使用更有意義和更加直白的命名方式
不友好的:
$ymdstr = $moment->format("y-m-d");友好的:
$currentDate = $moment->format("y-m-d");
對于同一實體使用相同變量名
不友好的:
getUserInfo(); getUserData(); getUserRecord(); getUserProfile();友好的:
getUser();
使用可以查找到的變量
我們讀的代碼量遠比我們寫過的多。因此,寫出可閱讀和便于搜索的代碼是及其重要的。在我們的程序中寫出一些難以理解的變量名
到最后甚至會讓自己非常傷腦筋。
因此,讓你的名字便于搜索吧。不友好的:
// 這里的448代表什么意思呢? $result = $serializer->serialize($data, 448);友好的:
$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | >JSON_UNESCAPED_UNICODE);
使用解釋型變量
不友好的
$address = "One Infinite Loop, Cupertino 95014"; $cityZipCodeRegex = "/^[^,]+,s*(.+?)s*(d{5})$/"; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches[1], $matches[2]);不至于那么糟糕的:
稍微好一些,但是這取決于我們對正則的熟練程度。$address = "One Infinite Loop, Cupertino 95014"; $cityZipCodeRegex = "/^[^,]+,s*(.+?)s*(d{5})$/"; preg_match($cityZipCodeRegex, $address, $matches); [, $city, $zipCode] = $matches; saveCityZipCode($city, $zipCode);友好的:
通過對子模式的重命名減少了我們對正則的熟悉和依賴程度。$address = "One Infinite Loop, Cupertino 95014"; $cityZipCodeRegex = "/^[^,]+,s*(?.+?)s*(? d{5})$/"; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches["city"], $matches["zipCode"]);
嵌套無邊,回頭是岸
太多的if else嵌套會讓你的代碼難以閱讀和維護。更加直白的代碼會好很多。
demo1
不友好的:
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; } }友好的:
function isShopOpen(string $day): bool { if (empty($day)) { return false; } $openingDays = [ "friday", "saturday", "sunday" ]; return in_array(strtolower($day), $openingDays, true); }demo2
不友好的:
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"; } }友好的:
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); }
避免使用不合理的變量名
別讓其他人去猜你的變量名的意思。
更加直白的代碼會好很多。不友好的:
$l = ["Austin", "New York", "San Francisco"]; for ($i = 0; $i < count($l); $i++) { $li = $l[$i]; doStuff(); doSomeOtherStuff(); // ... // ... // ... // 等等,這個$li是什么意思? dispatch($li); }友好的:
$locations = ["Austin", "New York", "San Francisco"]; foreach ($locations as $location) { doStuff(); doSomeOtherStuff(); // ... // ... // ... dispatch($location); }
別添加沒必要的上下文
如果你的類或對象的名字已經傳達了一些信息,那么請別在變量名中重復。
不友好的
class Car { public $carMake; public $carModel; public $carColor; //... }友好的
class Car { public $make; public $model; public $color; //... }
用參數默認值代替短路運算或條件運算
不友好的
這里不太合理,因為變量$breweryName有可能是NULL。
function createMicrobrewery($breweryName = "Hipster Brew Co."): void { // ... }不至于那么糟糕的
這種寫法要比上一版稍微好理解一些,但是如果能控制變量值獲取會更好。
function createMicrobrewery($name = null): void { $breweryName = $name ?: "Hipster Brew Co."; // ... }友好的
如果你僅支持 PHP 7+,那么你可以使用類型約束并且保證$http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration變量不會為NULL。
function createMicrobrewery(string $breweryName = "Hipster Brew Co."): void { // ... }
函數參數應該控制在兩個以下
限制函數的參數對于在對函數做測試來說相當重要。有超過三個可選的參數會給你的測試工作量帶來倍速增長。
最理想的情況是沒有參數。1-2個參數也還湊合,但是三個參數就應該避免了。參數越多,我們需要維護的就越多。通常,如果你的函>數有超過2個的參數,那么你的這個函數需要處理的事情就太多了。如果的確需要這么多參數,那么在大多數情況下, 用一個對象來處理可能會更合適。
不友好的:
function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void { // ... }友好的:
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 { // ... }
一個函數只做一件事情
在軟件工程行業,這是最重要的準則。當函數所處理的事情超過一件,他就會變得難以實現,測試和理解。當你能讓一個函數僅僅負責一個事情,他們就會變得容易重構并且理解起來越清晰。光是執行這樣一條原則就能讓你成為開發者中的佼佼者了。
不友好的:
function emailClients(array $clients): void { foreach ($clients as $client) { $clientRecord = $db->find($client); if ($clientRecord->isActive()) { email($client); } } }友好的:
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(); }
函數名應該說到做到
不友好的:
class Email { //... public function handle(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); // 這是什么?這個`handle`方法是什么?我們現在應該寫入到一個文件嗎? $message->handle();友好的:
class Email { //... public function send(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); // 清晰并且顯而易見 $message->send();
函數應該只有一層抽象
當你的函數有超過一層的抽象時便意味著這個函數做了太多事情。解耦這個函數致使其變得可重用和更易測試。
不友好的:
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... } }同樣不太友好:
我們已經從函數中拆分除了一些東西出來,但是parseBetterJSAlternative()這個函數還是太復雜以至于難以測試。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... } }友好的
最優解就是把parseBetterJSAlternative()函數依賴的東西分離出來。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... } } }
不要在函數中帶入flag相關的參數
當你使用flag時便意味著你的函數做了超過一件事情。前面我們也提到了,函數應該只做一件事情。如果你的代碼取決于一個boolean,那么還是把這些內容拆分出來吧。
不友好的
function createFile(string $name, bool $temp = false): void { if ($temp) { touch("./temp/".$name); } else { touch($name); } }友好的
function createFile(string $name): void { touch($name); } function createTempFile(string $name): void { touch("./temp/".$name); }
避免函數帶來的副作用
當函數有數據的輸入和輸出時可能會產生副作用。這個副作用可能被寫入一個文件,修改一些全局變量,或者意外的把你的錢轉給一個陌生人。
此刻,你可能有時候會需要這些副作用。正如前面所說,你可能需要寫入到一個文件中。你需要注意的是把這些你所做的東西在你的掌控之下。別讓某些個別函數或者類寫入了一個特別的文件。對于所有的都應該一視同仁。有且僅有一個結果。
重要的是要避免那些譬如共享無結構的對象,使用可以寫入任何類型的可變數據,不對副作用進行集中處理等常見的陷阱。如果你可以做到,你將會比大多數程序猿更加輕松。
不友好的
// 全局變量被下面的函數引用了。 // 如果我們在另外一個函數中使用了這個`$name`變量,那么可能會變成一個數組或者程序被打斷。 $name = "Ryan McDermott"; function splitIntoFirstAndLastName(): void { global $name; $name = explode(" ", $name); } splitIntoFirstAndLastName(); var_dump($name); // ["Ryan", "McDermott"];友好的
function splitIntoFirstAndLastName(string $name): array { return explode(" ", $name); } $name = "Ryan McDermott"; $newName = splitIntoFirstAndLastName($name); var_dump($name); // "Ryan McDermott"; var_dump($newName); // ["Ryan", "McDermott"];
避免寫全局方法
在大多數語言中,全局變量被污染都是一個不太好的實踐,因為當你引入另外的包時會起沖突并且使用你的API的人知道拋出了一個異常才明白。我們假設一個簡單的例子:如果你想要一個配置數組。你可能會寫一個類似于config()的全局的函數,但是在引入其他包并在其他地方嘗試做同樣的事情時會起沖突。
不友好的
function config(): array { return [ "foo" => "bar", ] }不友好的
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; } }通過創建Configuration類的實例來引入配置$configuration = new Configuration([ "foo" => "bar", ]);至此,你就可以是在你的項目中使用這個配置了。
避免使用單例模式
單例模式是一種反模式。為什么不建議使用:
他們通常使用一個全局實例,為什么這么糟糕?因為你隱藏了依賴關系在你的項目的代碼中,而不是通過接口暴露出來。你應該有意識的去避免那些全局的東西。
他們違背了單一職責原則:他們會自己控制自己的生命周期。
這種模式會自然而然的使代碼耦合在一起。這會讓他們在測試中,很多情況下都理所當然的不一致。
他們持續在整個項目的生命周期中。另外一個嚴重的打擊是當你需要排序測試的時候,在單元測試中這會是一個不小的麻煩。為什么?因為每個單元測試都應該依賴于另外一個。
不友好的
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();友好的
class DBConnection { public function __construct(string $dsn) { // ... } // ... }使用DSN配置來創建一個DBConnection類的單例。$connection = new DBConnection($dsn);此時,在你的項目中必須使用DBConnection的單例。
對條件判斷進行包裝
不友好的
if ($article->state === "published") { // ... }友好的
if ($article->isPublished()) { // ... }
避免對條件取反
不友好的
function isDOMNodeNotPresent(DOMNode $node): bool { // ... } if (!isDOMNodeNotPresent($node)) { // ... }友好的
function isDOMNodePresent(DOMNode $node): bool { // ... } if (isDOMNodePresent($node)) { // ... }
避免太多的條件嵌套
這似乎是一個不可能的任務。很多人的腦海中可能會在第一時間縈繞“如果沒有if條件我還能做什么呢?”。答案就是,在大多數情況下,你可以使用多態去處理這個難題。此外,可能有人又會說了,“即使多態可以做到,但是我們為什么要這么做呢?”,對此我們的解釋是,一個函數應該只做一件事情,這也正是我們在前面所提到的讓代碼更加整潔的原則。當你的函數中使用了太多的if條件時,便意味著你的函數做了超過一件事情。牢記:要專一。
不友好的:
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(); } } }友好的:
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(); } }
避免類型檢測 (part 1)
PHP是一門弱類型語言,這意味著你的函數可以使用任何類型的參數。他在給予你無限的自由的同時又讓你困擾,因為有有時候你需要做類型檢測。這里有很多方式去避免這種事情,第一種方式就是統一API。
不友好的:
function travelToTexas($vehicle): void { if ($vehicle instanceof Bicycle) { $vehicle->pedalTo(new Location("texas")); } elseif ($vehicle instanceof Car) { $vehicle->driveTo(new Location("texas")); } }友好的:
function travelToTexas(Traveler $vehicle): void { $vehicle->travelTo(new Location("texas")); }
避免類型檢測 (part 2)
如果你正使用諸如字符串、整型和數組等基本類型,且要求版本是PHP 7+,不能使用多態,需要類型檢測,那你應當考慮類型聲明或者嚴格模式。它提供了基于標準PHP語法的靜態類型。手動檢查類型的問題是做好了需要好多的廢話,好像為了安全就可以不顧損失可讀性。保持你的PHP代碼整潔,寫好測試,保持良好的回顧代碼的習慣。否則的話,那就還是用PHP嚴格類型聲明和嚴格模式來確保安全吧。
不友好的:
function combine($val1, $val2): int { if (!is_numeric($val1) || !is_numeric($val2)) { throw new Exception("Must be of type Number"); } return $val1 + $val2; }友好的:
function combine(int $val1, int $val2): int { return $val1 + $val2; }
移除那些沒有使用的代碼
沒有再使用的代碼就好比重復代碼一樣糟糕。在你的代碼庫中完全沒有必要保留。如果確定不再使用,那就把它刪掉吧!如果有一天你要使用,你也可以在你的版本記錄中找到它。
不友好的:
function oldRequestModule(string $url): void { // ... } function newRequestModule(string $url): void { // ... } $request = newRequestModule($requestUrl); inventoryTracker("apples", $request, "www.inventory-awesome.io");友好的:
function requestModule(string $url): void { // ... } $request = requestModule($requestUrl); inventoryTracker("apples", $request, "www.inventory-awesome.io");
使用對象封裝
在PHP中你可以設置public,protected,和private關鍵詞來修飾你的方法。當你使用它們,你就可以在一個對象中控制這些屬性的修改權限了。
當你想要對對象的屬性進行除了“獲取”之外的操作時,你不必再去瀏覽并在代碼庫中修改權限。
當你要做一些修改屬性的操作時,你更易于在代碼中做邏輯驗證。
封裝內部表示。
當你在做獲取和設置屬性的操作時,更易于添加log或error的操作。
當其他class繼承了這個基類,你可以重寫默認的方法。
你可以為一個服務延遲的去獲取這個對象的屬性值。
不太友好的:
class BankAccount { public $balance = 1000; } $bankAccount = new BankAccount(); // Buy shoes... $bankAccount->balance -= 100;友好的:
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(); // Buy shoes... $bankAccount->withdraw($shoesPrice); // Get balance $balance = $bankAccount->getBalance();
在對象的屬性上可以使用private/protected限定
public修飾的方法和屬性同上來說被修改是比較危險的,因為一些外部的代碼可以輕易的依賴于他們并且你沒辦法控制哪些代碼依賴于他們。對于所有用戶的類來說,在類中可以修改是相當危險的。
protected修飾器和public同樣危險,因為他們在繼承鏈中同樣可以操作。二者的區別僅限于權限機制,并且封裝保持不變。對于所有子類來說,在類中修改也是相當危險的。
private修飾符保證了代碼只有在自己類的內部修改才是危險的。
因此,當你在需要對外部的類設置權限時使用private修飾符去取代public/protected吧。
如果需要了解更多信息你可以讀Fabien Potencier寫的這篇文章
不太友好的:
class Employee { public $name; public function __construct(string $name) { $this->name = $name; } } $employee = new Employee("John Doe"); echo "Employee name: ".$employee->name; // Employee name: John Doe友好的:
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(); // Employee name: John Doe
組合優于繼承
正如the Gang of Four在著名的Design Patterns中所說,你應該盡可能的使用組合而不是繼承。不管是使用組合還是繼承都有很多的優點。最重要的一個準則在于當你本能的想要使用繼承時,不妨思考一下組合是否能讓你的問題解決的更加優雅。在某些時候確實如此。
你可能會這么問了,“那到底什么時候我應該使用繼承呢?”這完全取決你你手頭上的問題,下面正好有一些繼承優于組合的例子:
你的繼承表達了“是一個”而不是“有一個”的關系(Human->Animal vs. User->UserDetails)。
你可能會重復的使用基類的代碼(Humans can move like all animals)。
你渴望在修改代碼的時候通過基類來統一調度(Change the caloric expenditure of all animals when they move)。
不友好的:
class Employee { private $name; private $email; public function __construct(string $name, string $email) { $this->name = $name; $this->email = $email; } // ... } // 這里不太合理的原因在于并非所有的職員都有`tax`這個特征。 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; } // ... }友好的:
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); } // ... }
避免鏈式調用(連貫接口)
在使用一些鏈式方法時,這種連貫接口可以不斷地指向當前對象讓我們的代碼顯得更加清晰可讀。
通常情況下,我們在構建對象時都可以利用他的上下文這一特征,因為這種模式可以減少代碼的冗余,不過在PHPUnit Mock Builder或者Doctrine Query Builder所提及的,有時候這種方式會帶來一些麻煩:
破壞封裝
破壞設計
難以測試
可能會難以閱讀
如果需要了解更多信息你可以讀Marco Pivetta寫的這篇文章
友好的:
class Car { private $make = "Honda"; private $model = "Accord"; private $color = "white"; public function setMake(string $make): self { $this->make = $make; // NOTE: Returning this for chaining return $this; } public function setModel(string $model): self { $this->model = $model; // NOTE: Returning this for chaining return $this; } public function setColor(string $color): self { $this->color = $color; // NOTE: Returning this for chaining 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();不友好的:
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();
SOLID最開始是由Robert Martin提出的五個準則,并最后由Michael Feathers命名的簡寫,這五個是在面對對象設計中的五個基本原則。
S: 職責單一原則 (SRP)
O: 開閉原則 (OCP)
L: 里氏替換原則 (LSP)
I: 接口隔離原則 (ISP)
D: 依賴反轉原則 (DIP)
職責單一原則 (SRP)
正如Clean Code所述,“修改類應該只有一個理由”。我們總是喜歡在類中寫入太多的方法,就像你在飛機上塞滿你的行李箱。在這種情況下你的類沒有高內聚的概念并且留下了很多可以修改的理由。盡可能的減少你需要去修改類的時間是非常重要的。如果在你的單個類中有太多的方法并且你經常修改的話,那么如果其他代碼庫中有引入這樣的模塊的話會非常難以理解。
不友好的:
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 { // ... } }友好的:
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()) { // ... } } }
開閉原則 (OCP)
正如Bertrand Meyer所說,“軟件開發應該對擴展開發,對修改關閉。”這是什么意思呢?這個原則的意思大概就是說你應該允許其他人在不修改已經存在的功能的情況下去增加新功能。
不友好的
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 } }友好的:
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); } }
里氏替換原則 (LSP)
這本身是一個非常簡單的原則卻起了一個不太容易理解的名字。這個原則通常的定義是“如果S是T的一個子類,那么對象T可以在沒有任何警告的情況下被他的子類替換(例如:對象S可能代替對象T)一些更合適的屬性。”好像更難理解了。
最好的解釋就是說如果你有一個父類和子類,那么你的父類和子類可以在原來的基礎上任意交換。這個可能還是難以理解,我們舉一個正方形-長方形的例子吧。在數學中,一個矩形屬于長方形,但是如果在你的模型中通過繼承使用了“is-a”的關系就不對了。
不友好的:
class Rectangle { protected $width = 0; protected $height = 0; public function render(int $area): void { // ... } 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 renderLargeRectangles(array $rectangles): void { foreach ($rectangles as $rectangle) { $rectangle->setWidth(4); $rectangle->setHeight(5); $area = $rectangle->getArea(); // BAD: Will return 25 for Square. Should be 20. $rectangle->render($area); } } $rectangles = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles($rectangles);友好的:
abstract class Shape { protected $width = 0; protected $height = 0; abstract public function getArea(): int; public function render(int $area): void { // ... } } class Rectangle extends Shape { 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 Shape { private $length = 0; public function setLength(int $length): void { $this->length = $length; } public function getArea(): int { return pow($this->length, 2); } } function renderLargeRectangles(array $rectangles): void { foreach ($rectangles as $rectangle) { if ($rectangle instanceof Square) { $rectangle->setLength(5); } elseif ($rectangle instanceof Rectangle) { $rectangle->setWidth(4); $rectangle->setHeight(5); } $area = $rectangle->getArea(); $rectangle->render($area); } } $shapes = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles($shapes);
接口隔離原則 (ISP)
ISP的意思就是說“使用者不應該強制使用它不需要的接口”。
當一個類需要大量的設置是一個不錯的例子去解釋這個原則。為了方便去調用這個接口需要做大量的設置,但是大多數情況下是不需要的。強制讓他們使用這些設置會讓整個接口顯得臃腫。
不友好的:
interface Employee { public function work(): void; public function eat(): void; } class Human implements Employee { public function work(): void { // ....working } public function eat(): void { // ...... eating in lunch break } } class Robot implements Employee { public function work(): void { //.... working much more } public function eat(): void { //.... robot can"t eat, but it must implement this method } }友好的:
并非每一個工人都是職員,但是每一個職員都是工人。interface Workable { public function work(): void; } interface Feedable { public function eat(): void; } interface Employee extends Feedable, Workable { } class Human implements Employee { public function work(): void { // ....working } public function eat(): void { //.... eating in lunch break } } // robot can only work class Robot implements Workable { public function work(): void { // ....working } }
依賴反轉原則 (DIP)
別重復你的代碼 (DRY)這個原則有兩個需要注意的地方:
高階模塊不能依賴于低階模塊。他們都應該依賴于抽象。
抽象不應該依賴于實現,實現應該依賴于抽象。
第一點可能有點難以理解,但是如果你有使用過像Symfony的PHP框架,你應該有見到過依賴注入這樣的原則的實現。盡管他們是不一樣的概念,DIP讓高階模塊從我們所知道的低階模塊中分離出去。可以通過DI這種方式實現。一個巨大的好處在于它解耦了不同的模塊。耦合是一個非常不好的開發模式,因為它會讓你的代碼難以重構。
不友好的:
class Employee { public function work(): void { // ....working } } class Robot extends Employee { public function work(): void { //.... working much more } } class Manager { private $employee; public function __construct(Employee $employee) { $this->employee = $employee; } public function manage(): void { $this->employee->work(); } }友好的
interface Employee { public function work(): void; } class Human implements Employee { public function work(): void { // ....working } } class Robot implements Employee { public function work(): void { //.... working much more } } class Manager { private $employee; public function __construct(Employee $employee) { $this->employee = $employee; } public function manage(): void { $this->employee->work(); } }
原文地址嘗試去研究DRY原則。
盡可能別去復制代碼。復制代碼非常不好,因為這意味著將來有需要修改的業務邏輯時你需要修改不止一處。
想象一下你在經營一個餐館并且你需要經常整理你的存貨清單:你所有的土豆,洋蔥,大蒜,辣椒等。如果你有多個列表來管理進銷記錄,當你用其中一些土豆做菜時你需要更新所有的列表。如果你只有一個列表的話只有一個地方需要更新!
大多數情況下你有重復的代碼是因為你有超過兩處細微的差別,他們大部分都是相同的,但是他們的不同之處又不得不讓你去分成不同的方法去處理相同的事情。移除這些重復的代碼意味著你需要創建一個可以用一個方法/模塊/類來處理的抽象。
使用一個抽象是關鍵的,這也是為什么在類中你要遵循SOLID原則的原因。一個不優雅的抽象往往比重復的代碼更糟糕,所以要謹慎使用!說了這么多,如果你已經可以構造一個優雅的抽象,那就趕緊去做吧!別重復你的代碼,否則當你需要修改時你會發現你要修改許多地方。
不友好的:
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); } }友好的:
function showList(array $employees): void { foreach ($employees as $employee) { $expectedSalary = $employee->calculateExpectedSalary(); $experience = $employee->getExperience(); $githubLink = $employee->getGithubLink(); $data = [ $expectedSalary, $experience, $githubLink ]; render($data); } }非常優雅的:
如果能更簡潔那就更好了。function showList(array $employees): void { foreach ($employees as $employee) { render([ $employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink() ]); } }
clean-code-php
文章首發地址:我的博客
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/25975.html
摘要:將代碼寫的簡潔并且易讀易懂是每一位優秀的所應該具備的基本功。前幾天在上看到這個項目,感覺很有收獲,于是在這里記錄一下。 將代碼寫的簡潔并且易讀易懂是每一位優秀的coder所應該具備的基本功。 前幾天在github上看到clean-code-php這個項目,感覺很有收獲,于是在這里記錄一下。 使用有意義并且可讀的變量名稱 Bad: $ymdstr = $moment->format(y-...
摘要:統一的編碼規范編碼規范往簡單說其實就是三個方面換行空格變量命名放在里面,還有一些附加的地方,比如關鍵字大小寫,語法糖的使用與等的問題。這些都是規范代碼的重要手段。推廣給你的隊友團隊項目中,隊友的配合對整個代碼的規范起著決定性的作用。 1. 統一的編碼規范 編碼規范往簡單說其實就是三個方面: 換行 空格 變量命名 放在 PHP 里面,還有一些附加的地方,比如關鍵字大小寫,語法糖的使用...
摘要:使用和在中,通過為屬性或方法設置和關鍵字可以實現對屬性或方法的可見性控制。你的繼承表達了一個對等比如人類是動物的關系,不是包含的關系比如用戶具有用戶詳情你能從基類中復用代碼你想通過修改全局類來對所有派生類進行修改。 使用getter和setter 在 PHP 中,通過為屬性或方法設置 public, protected 和 private 關鍵字可以實現對屬性或方法的可見性控制。不過,...
摘要:超過三個參數會導致參數之間的組合過多,你必須對每個單獨的參數測試大量不同的情況。拆分這些函數,可以讓代碼可重用性更高且更易測試。 函數參數不要超過兩個 限制函數的參數數量是非常重要的,因為它使你的函數更容易測試。超過三個參數會導致參數之間的組合過多,你必須對每個單獨的參數測試大量不同的情況。 沒有參數是最理想的情況,一個或兩個參數是可以接受的,三個以上則是應該避免的。這很重要的。如果你...
摘要:是推薦的便于記憶的首字母簡寫,它代表了命名的最重要的五個面對對象編碼設計原則單一職責原則開閉原則里氏替換原則接口隔離原則依賴反轉原則單一職責原則修改一個類應該只為一個理由。別寫重復代碼這條原則大家應該都是比較熟悉了。 SOLID 是Michael Feathers推薦的便于記憶的首字母簡寫,它代表了Robert Martin命名的最重要的五個面對對象編碼設計原則 S: 單一職責原則 ...
閱讀 2227·2021-11-15 11:39
閱讀 982·2021-09-26 09:55
閱讀 925·2021-09-04 16:48
閱讀 2831·2021-08-12 13:23
閱讀 919·2021-07-30 15:30
閱讀 2455·2019-08-29 14:16
閱讀 885·2019-08-26 10:15
閱讀 525·2019-08-23 18:40