From ac66b68c6a5dd32c2b5f51b30ec13db6f5b1e912 Mon Sep 17 00:00:00 2001 From: zouyi6 Date: Fri, 1 Sep 2017 09:36:05 +0800 Subject: [PATCH 01/21] sync offical new contents --- README.md | 1609 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 1264 insertions(+), 345 deletions(-) diff --git a/README.md b/README.md index c8fdf4f9..d5cde3f8 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,82 @@ # clean-code-php -## Table of Contents +## 目录 1. [介绍](#介绍) 2. [变量](#变量) 3. [函数](#函数) + 4. [对象和数据结构 Objects and Data Structures](#objects-and-data-structures) + 5. [类的SOLID原则 Classes](#classes) + 1. [S: 单一功能 Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) + 2. [O: 开闭原则 Open/Closed Principle (OCP)](#openclosed-principle-ocp) + 3. [L: 里氏替换 Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) + 4. [I: 接口隔离 Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) + 5. [D: 依赖反转 Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) ## 介绍 -本文由 yangweijie 翻译自[clen php code](https://github.com/jupeter/clean-code-php),团建用,欢迎大家指正。 +本文由 php-cpm 基于 yangweijie 的[clen php code](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容,欢迎大家指正。 -摘录自 Robert C. Martin的[*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) 书中的软件工程师的原则 +本文参考自 Robert C. Martin的[*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) 书中的软件工程师的原则 ,适用于PHP。 这不是风格指南。 这是一个关于开发可读、可复用并且可重构的PHP软件指南。 并不是这里所有的原则都得遵循,甚至很少的能被普遍接受。 这些虽然只是指导,但是都是*Clean Code*作者多年总结出来的。 -Inspired from [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) +本文受到 [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) 的启发 ## **变量** ### 使用有意义且可拼写的变量名 -**Bad:** +**坏:** ```php $ymdstr = $moment->format('y-m-d'); ``` -**Good**: -```javascript +**好:** +```php $currentDate = $moment->format('y-m-d'); ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 同种类型的变量使用相同词汇 -**Bad:** +**坏:** ```php getUserInfo(); getClientData(); getCustomerRecord(); ``` -**Good**: +**好:** ```php getUser(); ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** -### 使用易检索的名称 -我们会读比写要多的代码。通过是命名易搜索,让我们写出可读性和易搜索代码很重要。 +### 使用便于搜索的名称 +我们会阅读比我们写的更多的代码。所以写出高可读性和便于搜索的代码很重要。 +命名变量时如果*不*是有意义和易于理解的,那就是在伤害读者。 +请让你的代码便于搜索。 -**Bad:** +**坏:** ```php // What the heck is 86400 for? addExpireAt(86400); ``` -**Good**: +**好:** ```php // Declare them as capitalized `const` globals. interface DateGlobal { - const SECONDS_IN_A_DAY = 86400; + const SECONDS_IN_A_DAY = 86400; } addExpireAt(DateGlobal::SECONDS_IN_A_DAY); ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 使用解释型变量 -**Bad:** +**坏:** ```php $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; @@ -75,7 +84,10 @@ preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches[1], $matches[2]); ``` -**Good**: +**不错:** + +It's better, but we are still heavily dependent on regex. + ```php $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; @@ -83,91 +95,102 @@ preg_match($cityZipCodeRegex, $address, $matches); list(, $city, $zipCode) = $matchers; saveCityZipCode($city, $zipCode); ``` -**[⬆ 返回顶部](#table-of-contents)** + +**好:** + +Decrease dependence on regex by naming subpatterns. +```php +$address = 'One Infinite Loop, Cupertino 95014'; +$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(?.+?)\s*(?\d{5})?$/'; +preg_match($cityZipCodeRegex, $address, $matches); + +saveCityZipCode($matches['city'], $matches['zipCode']); +``` + +**[⬆ 返回顶部](#目录)** ### 避免心理映射 明确比隐性好。 -**Bad:** +**坏:** ```php $l = ['Austin', 'New York', 'San Francisco']; -foreach($i=0; $i 'Honda', - 'carModel' => 'Accord', - 'carColor' => 'Blue', -]; +class Car +{ + public $carMake; + public $carModel; + public $carColor; -function paintCar(&$car) { - $car['carColor'] = 'Red'; + //... } ``` -**Good**: +**好:** + ```php -$car = [ - 'make' => 'Honda', - 'model' => 'Accord', - 'color' => 'Blue', -]; +class Car +{ + public $make; + public $model; + public $color; -function paintCar(&$car) { - $car['color'] = 'Red'; + //... } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ###使用参数默认值代替短路或条件语句。 -**Bad:** +**坏:** ```php function createMicrobrewery($name = null) { - $breweryName = $name ?: 'Hipster Brew Co.'; - // ... + $breweryName = $name ?: 'Hipster Brew Co.'; + // ... } ``` -**Good**: +**好:** ```php function createMicrobrewery($breweryName = 'Hipster Brew Co.') { - // ... + // ... } ``` - -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ## **函数** ### 函数参数最好少于2个 @@ -175,20 +198,21 @@ function createMicrobrewery($breweryName = 'Hipster Brew Co.') { 无参数是理想情况。1个或2个都可以,最好避免3个。再多旧需要加固了。通常如果你的函数有超过两个参数,说明他多做了一些事。 在参数少的情况里,大多数时候一个高级别对象(数组)作为参数就足够应付。 -**Bad:** +**坏:** ```php function createMenu($title, $body, $buttonText, $cancellable) { - // ... + // ... } ``` -**Good**: +**好:** ```php -class menuConfig() { - public $title; - public $body; - public $buttonText; - public $cancellable = false; +class MenuConfig +{ + public $title; + public $body; + public $buttonText; + public $cancellable = false; } $config = new MenuConfig(); @@ -202,131 +226,218 @@ function createMenu(MenuConfig $config) { } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 函数应该只做一件事 这是迄今为止软件工程里最重要的一个规则。当函数做超过一件事的时候,他们就难于实现、测试和理解。当你隔离函数只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。 -**Bad:** +**坏:** ```php function emailClients($clients) { - foreach ($clients as $client) { - $clientRecord = $db->find($client); - if($clientRecord->isActive()) { - email($client); + foreach ($clients as $client) { + $clientRecord = $db->find($client); + if ($clientRecord->isActive()) { + email($client); + } } - } } ``` -**Good**: +**好:** ```php function emailClients($clients) { - $activeClients = activeClients($clients); - array_walk($activeClients, 'email'); + $activeClients = activeClients($clients); + array_walk($activeClients, 'email'); } function activeClients($clients) { - return array_filter($clients, 'isClientActive'); + return array_filter($clients, 'isClientActive'); } function isClientActive($client) { - $clientRecord = $db->find($client); - return $clientRecord->isActive(); + $clientRecord = $db->find($client); + return $clientRecord->isActive(); } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 函数名应当描述他们所做的事 -**Bad:** +**坏:** + ```php -function addToDate($date, $month) { - // ... -} +class Email +{ + //... -$date = new \DateTime(); + public function handle() + { + mail($this->to, $this->subject, $this->body); + } +} -// It's hard to to tell from the function name what is added -addToDate($date, 1); +$message = new Email(...); +// What is this? A handle for the message? Are we writing to a file now? +$message->handle(); ``` -**Good**: +**好:** + ```php -function addMonthToDate($month, $date) { - // ... +class Email +{ + //... + + public function send() + { + mail($this->to, $this->subject, $this->body); + } } -$date = new \DateTime(); -addMonthToDate(1, $date); +$message = new Email(...); +// Clear and obvious +$message->send(); ``` -**[⬆ 返回顶部](#table-of-contents)** + +**[⬆ 返回顶部](#目录)** ### 函数应当只为一层抽象,当你超过一层抽象时,函数正在做多件事。拆分功能易达到可重用性和易用性。. -**Bad:** -```php -function parseBetterJSAlternative($code) { - $regexes = [ - // ... - ]; +**坏:** - $statements = split(' ', $code); - $tokens = []; - foreach($regexes as $regex) { - foreach($statements as $statement) { - // ... +```php +function parseBetterJSAlternative($code) +{ + $regexes = [ + // ... + ]; + + $statements = split(' ', $code); + $tokens = []; + foreach ($regexes as $regex) { + foreach ($statements as $statement) { + // ... + } + } + + $ast = []; + foreach ($tokens as $token) { + // lex... + } + + foreach ($ast as $node) { + // parse... } - } - - $ast = []; - foreach($tokens as $token) { - // lex... - } - - foreach($ast as $node) { - // parse... - } } ``` -**Good**: +**坏:** + +We have carried out some of the functionality, but the `parseBetterJSAlternative()` function is still very complex and not testable. + ```php -function tokenize($code) { - $regexes = [ - // ... - ]; +function tokenize($code) +{ + $regexes = [ + // ... + ]; + + $statements = split(' ', $code); + $tokens = []; + foreach ($regexes as $regex) { + foreach ($statements as $statement) { + $tokens[] = /* ... */; + } + } + + return $tokens; +} - $statements = split(' ', $code); - $tokens = []; - foreach($regexes as $regex) { - foreach($statements as $statement) { - $tokens[] = /* ... */; - }); - }); +function lexer($tokens) +{ + $ast = []; + foreach ($tokens as $token) { + $ast[] = /* ... */; + } + + return $ast; +} - return tokens; +function parseBetterJSAlternative($code) +{ + $tokens = tokenize($code); + $ast = lexer($tokens); + foreach ($ast as $node) { + // parse... + } } +``` + +**好:** -function lexer($tokens) { - $ast = []; - foreach($tokens as $token) { - $ast[] = /* ... */; - }); +The best solution is move out the dependencies of `parseBetterJSAlternative()` function. - return ast; +```php +class Tokenizer +{ + public function tokenize($code) + { + $regexes = [ + // ... + ]; + + $statements = split(' ', $code); + $tokens = []; + foreach ($regexes as $regex) { + foreach ($statements as $statement) { + $tokens[] = /* ... */; + } + } + + return $tokens; + } } +``` + +```php +class Lexer +{ + public function lexify($tokens) + { + $ast = []; + foreach ($tokens as $token) { + $ast[] = /* ... */; + } + + return $ast; + } +} +``` -function parseBetterJSAlternative($code) { - $tokens = tokenize($code); - $ast = lexer($tokens); - foreach($ast as $node) { - // parse... - }); +```php +class BetterJSAlternative +{ + private $tokenizer; + private $lexer; + + public function __construct(Tokenizer $tokenizer, Lexer $lexer) + { + $this->tokenizer = $tokenizer; + $this->lexer = $lexer; + } + + public function parse($code) + { + $tokens = $this->tokenizer->tokenize($code); + $ast = $this->lexer->lexify($tokens); + foreach ($ast as $node) { + // parse... + } + } } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 删除重复的代码 尽你最大的努力来避免重复的代码。重复代码不好,因为它意味着如果你修改一些逻辑,那就有不止一处地方要同步修改了。 @@ -337,129 +448,106 @@ function parseBetterJSAlternative($code) { 抽象正确非常重要,这也是为什么你应当遵循SOLID原则(奠定*Class*基础的原则)。坏的抽象可能比重复代码还要糟,因为要小心。在这个前提下,如果你可以抽象好,那就开始做把!不要重复你自己,否则任何你想改变一件事的时候你都发现在即在更新维护多处。 -**Bad:** -```php -function showDeveloperList($developers) { - foreach($developers as $developer) { - $expectedSalary = $developer->calculateExpectedSalary(); - $experience = $developer->getExperience(); - $githubLink = $developer->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; +**坏:** - render($data); - } +```php +function showDeveloperList($developers) +{ +    foreach ($developers as $developer) { + $expectedSalary = $developer->calculateExpectedSalary(); + $experience = $developer->getExperience(); + $githubLink = $developer->getGithubLink(); + $data = [ + $expectedSalary, + $experience, + $githubLink + ]; + + render($data); + } } -function showManagerList($managers) { - foreach($managers as $manager) { - $expectedSalary = $manager->calculateExpectedSalary(); - $experience = $manager->getExperience(); - $githubLink = $manager->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; - - render($data); - } +function showManagerList($managers) +{ +    foreach ($managers as $manager) { + $expectedSalary = $manager->calculateExpectedSalary(); + $experience = $manager->getExperience(); + $githubLink = $manager->getGithubLink(); + $data = [ + $expectedSalary, + $experience, + $githubLink + ]; + + render($data); + } } ``` -**Good**: -```php -function showList($employees) { - foreach($employees as $employe) { - $expectedSalary = $employe->calculateExpectedSalary(); - $experience = $employe->getExperience(); - $githubLink = $employe->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; +**好:** - render($data); - } +```php +function showList($employees) +{ +    foreach ($employees as $employe) { +        $expectedSalary = $employe->calculateExpectedSalary(); +        $experience = $employe->getExperience(); +        $githubLink = $employe->getGithubLink(); + $data = [ + $expectedSalary, + $experience, + $githubLink + ]; + + render($data); + } } ``` -**[⬆ 返回顶部](#table-of-contents)** -### 通过对象赋值设置默认值 -**Bad:** -```php -$menuConfig = [ - 'title' => null, - 'body' => 'Bar', - 'buttonText' => null, - 'cancellable' => true, -]; +**非常好:** -function createMenu(&$config) { - $config['title'] = $config['title'] ?: 'Foo'; - $config['body'] = $config['body'] ?: 'Bar'; - $config['buttonText'] = $config['buttonText'] ?: 'Baz'; - $config['cancellable'] = $config['cancellable'] ?: true; -} - -createMenu($menuConfig); -``` +It is better to use a compact version of the code. -**Good**: ```php -$menuConfig = [ - 'title' => 'Order', - // User did not include 'body' key - 'buttonText' => 'Send', - 'cancellable' => true, -]; - -function createMenu(&$config) { - $config = array_merge([ - 'title' => 'Foo', - 'body' => 'Bar', - 'buttonText' => 'Baz', - 'cancellable' => true, - ], $config); - - // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} - // ... +function showList($employees) +{ +    foreach ($employees as $employe) { + render([ + $employe->calculateExpectedSalary(), + $employe->getExperience(), + $employe->getGithubLink() + ]); + } } - -createMenu($menuConfig); ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 不要用标志作为函数的参数,标志告诉你的用户函数做很多事了。函数应当只做一件事。 根据布尔值区别的路径来拆分你的复杂函数。 -**Bad:** +**坏:** ```php -function createFile(name, temp = false) { - if (temp) { - touch('./temp/'.$name); - } else { - touch($name); - } +function createFile($name, $temp = false) { + if ($temp) { + touch('./temp/'.$name); + } else { + touch($name); + } } ``` -**Good**: +**好:** ```php function createFile($name) { - touch(name); + touch($name); } function createTempFile($name) { - touch('./temp/'.$name); + touch('./temp/'.$name); } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 避免副作用 一个函数做了比获取一个值然后返回另外一个值或值们会产生副作用如果。副作用可能是写入一个文件,修改某些全局变量或者偶然的把你全部的钱给了陌生人。 @@ -468,14 +556,14 @@ function createTempFile($name) { 重点是避免常见陷阱比如对象间共享无结构的数据,使用可以写入任何的可变数据类型,不集中处理副作用发生的地方。如果你做了这些你就会比大多数程序员快乐。 -**Bad:** +**坏:** ```php // Global variable referenced by following function. // If we had another function that used this name, now it'd be an array and it could break it. $name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { - $name = preg_split('/ /', $name); + $name = preg_split('/ /', $name); } splitIntoFirstAndLastName(); @@ -483,228 +571,1059 @@ splitIntoFirstAndLastName(); var_dump($name); // ['Ryan', 'McDermott']; ``` -**Good**: +**好:** ```php $name = 'Ryan McDermott'; function splitIntoFirstAndLastName($name) { - return preg_split('/ /', $name); + return preg_split('/ /', $name); } $name = 'Ryan McDermott'; -$newName = splitIntoFirstAndLastName(name); +$newName = splitIntoFirstAndLastName($name); -var_export($name); // 'Ryan McDermott'; -var_export($newName); // ['Ryan', 'McDermott']; +var_dump($name); // 'Ryan McDermott'; +var_dump($newName); // ['Ryan', 'McDermott']; ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 不要写全局函数 在大多数语言中污染全局变量是一个坏的实践,因为你可能和其他类库冲突并且你api的用户不明白为什么直到他们获得产品的一个异常。让我们看一个例子:如果你想配置一个数组,你可能会写一个全局函数像`config()`,但是可能和试着做同样事的其他类库冲突。这就是为什么单例设计模式和简单配置会更好的原因。 -**Bad:** +**坏:** + +```php +function config() +{ + return [ + 'foo' => 'bar', + ] +} +``` + +**好:** + +Create PHP configuration file or something else + ```php -function config() { - return [ - 'foo': 'bar', - ] -}; +// config.php +return [ + 'foo' => 'bar', +]; ``` -**Good:** ```php -class Configuration { - private static $instance; - private function __construct($configuration) {/* */} - public static function getInstance() { - if(self::$instance === null) { - self::$instance = new Configuration(); - } - return self::$instance; - } - public function get($key) {/* */} - public function getAll() {/* */} +class Configuration +{ + private $configuration = []; + + public function __construct(array $configuration) + { + $this->configuration = $configuration; + } + + public function get($key) + { + return isset($this->configuration[$key]) ? $this->configuration[$key] : null; + } } +``` + +Load configuration from file and create instance of `Configuration` class -$singleton = Configuration::getInstance(); +```php +$configuration = new Configuration($configuration); ``` -**[⬆ 返回顶部](#table-of-contents)** -### 封装条件语句 +And now you must use instance of `Configuration` in your application. + + +**[⬆ 返回顶部](#目录)** + +### Don't use a Singleton pattern +Singleton is a [anti-pattern](https://en.wikipedia.org/wiki/Singleton_pattern). + +**坏:** -**Bad:** ```php -if ($fsm->state === 'fetching' && is_empty($listNode)) { - // ... +class DBConnection +{ + private static $instance; + + private function __construct($dsn) + { + // ... + } + + public static function getInstance() + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + // ... +} + +$singleton = DBConnection::getInstance(); +``` + +**好:** + +```php +class DBConnection +{ + public function __construct(array $dsn) + { + // ... + } + + // ... } ``` -**Good**: +Create instance of `DBConnection` class and configure it with [DSN](http://php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters). + ```php -function shouldShowSpinner($fsm, $listNode) { - return $fsm->state === 'fetching' && is_empty(listNode); +$connection = new DBConnection($dsn); +``` + +And now you must use instance of `DBConnection` in your application. + +**[⬆ 返回顶部](#目录)** + +### 封装条件语句 + +**坏:** + +```php +if ($article->state === 'published') { + // ... } +``` -if (shouldShowSpinner($fsmInstance, $listNodeInstance)) { - // ... +**好:** + +```php +if ($article->isPublished()) { + // ... } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 避免消极条件 -**Bad:** +**坏:** ```php function isDOMNodeNotPresent($node) { - // ... + // ... } if (!isDOMNodeNotPresent($node)) { - // ... + // ... } ``` -**Good**: +**好:** ```php function isDOMNodePresent($node) { - // ... + // ... } if (isDOMNodePresent($node)) { - // ... + // ... } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 避免条件声明 这看起来像一个不可能任务。当人们第一次听到这句话是都会这么说。 "没有一个`if声明`" 答案是你可以使用多态来达到许多case语句里的任务。第二个问题很常见, “那么为什么我要那么做?” 答案是前面我们学过的一个整洁代码原则:一个函数应当只做一件事。当你有类和函数有很多`if`声明,你自己知道你的函数做了不止一件事。记住,只做一件事。 -**Bad:** +**坏:** ```php class Airplane { - // ... - public function getCruisingAltitude() { - switch (this.type) { - case '777': - return $this->getMaxAltitude() - $this->getPassengerCount(); - case 'Air Force One': - return $this->getMaxAltitude(); - case 'Cessna': - return $this->getMaxAltitude() - $this->getFuelExpenditure(); + // ... + public function getCruisingAltitude() { + switch ($this->type) { + case '777': + return $this->getMaxAltitude() - $this->getPassengerCount(); + case 'Air Force One': + return $this->getMaxAltitude(); + case 'Cessna': + return $this->getMaxAltitude() - $this->getFuelExpenditure(); + } } - } } ``` -**Good**: +**好:** ```php class Airplane { - // ... + // ... } class Boeing777 extends Airplane { - // ... - public function getCruisingAltitude() { - return $this->getMaxAltitude() - $this->getPassengerCount(); - } + // ... + public function getCruisingAltitude() { + return $this->getMaxAltitude() - $this->getPassengerCount(); + } } class AirForceOne extends Airplane { - // ... - public function getCruisingAltitude() { - return $this->getMaxAltitude(); - } + // ... + public function getCruisingAltitude() { + return $this->getMaxAltitude(); + } } class Cessna extends Airplane { - // ... - public function getCruisingAltitude() { - return $this->getMaxAltitude() - $this->getFuelExpenditure(); - } + // ... + public function getCruisingAltitude() { + return $this->getMaxAltitude() - $this->getFuelExpenditure(); + } } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### Avoid 避免类型检查 (part 1) PHP是弱类型的,这意味着你的函数可以接收任何类型的参数。 有时候你为这自由所痛苦并且在你的函数渐渐尝试类型检查。有很多方法去避免这么做。第一种是考虑API的一致性。 -**Bad:** +**坏:** + ```php -function travelToTexas($vehicle) { - if ($vehicle instanceof Bicycle) { - $vehicle->peddle($this->currentLocation, new Location('texas')); - } else if ($vehicle instanceof Car) { - $vehicle->drive($this->currentLocation, new Location('texas')); - } +function travelToTexas($vehicle) +{ + if ($vehicle instanceof Bicycle) { + $vehicle->peddleTo(new Location('texas')); + } elseif ($vehicle instanceof Car) { + $vehicle->driveTo(new Location('texas')); + } } ``` -**Good**: +**好:** + ```php -function travelToTexas($vehicle) { - $vehicle->move($this->currentLocation, new Location('texas')); +function travelToTexas(Traveler $vehicle) +{ + $vehicle->travelTo(new Location('texas')); } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 避免类型检查 (part 2) 如果你正使用基本原始值比如字符串、整形和数组,你不能用多态,你仍然感觉需要类型检测,你应当考虑类型声明或者严格模式。 这给你了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。保持你的PHP 整洁,写好测试,做好代码回顾。做不到就用PHP严格类型声明和严格模式来确保安全。 -**Bad:** +**坏:** + ```php -function combine($val1, $val2) { - if (is_numeric($val1) && is_numeric(val2)) { - return val1 + val2; - } +function combine($val1, $val2) +{ + if (!is_numeric($val1) || !is_numeric($val2)) { + throw new \Exception('Must be of type Number'); + } - throw new \Exception('Must be of type Number'); + return $val1 + $val2; } ``` -**Good**: +**好:** + ```php -function combine(int $val1, int $val2) { - return $val1 + $val2; +function combine(int $val1, int $val2) +{ + return $val1 + $val2; } ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 移除僵尸代码 僵尸代码和重复代码一样坏。没有理由保留在你的代码库中。如果从来被调用过,见鬼去!在你的版本库里是如果你仍然需要他的话,因此这么做很安全。 -**Bad:** +**坏:** ```php function oldRequestModule($url) { - // ... + // ... } function newRequestModule($url) { - // ... + // ... } -$req = new newRequestModule(); +$req = new newRequestModule($requestUrl); inventoryTracker('apples', $req, 'www.inventory-awesome.io'); ``` -**Good**: +**好:** ```php -function newRequestModule($url) { - // ... +function requestModule($url) { + // ... } -$req = new newRequestModule(); +$req = new requestModule($requestUrl); inventoryTracker('apples', $req, 'www.inventory-awesome.io'); ``` -**[⬆ 返回顶部](#table-of-contents)** +**[⬆ 返回顶部](#目录)** + + +## **Objects and Data Structures** +### Use getters and setters +In PHP you can set `public`, `protected` and `private` keywords for methods. +Using it, you can control properties modification on an object. + +* When you want to do more beyond getting an object property, you don't have +to look up and change every accessor in your codebase. +* Makes adding validation simple when doing a `set`. +* Encapsulates the internal representation. +* Easy to add logging and error handling when getting and setting. +* Inheriting this class, you can override default functionality. +* You can lazy load your object's properties, let's say getting it from a +server. + +Additionally, this is part of Open/Closed principle, from object-oriented +design principles. + +**坏:** +```php +class BankAccount { + public $balance = 1000; +} +$bankAccount = new BankAccount(); + +// Buy shoes... +$bankAccount->balance -= 100; +``` + +**好:** +```php +class BankAccount { + private $balance; + + public function __construct($balance = 1000) { + $this->balance = $balance; + } + + public function withdrawBalance($amount) { + if ($amount > $this->balance) { + throw new \Exception('Amount greater than available balance.'); + } + $this->balance -= $amount; + } + + public function depositBalance($amount) { + $this->balance += $amount; + } + + public function getBalance() { + return $this->balance; + } +} + +$bankAccount = new BankAccount(); + +// Buy shoes... +$bankAccount->withdrawBalance($shoesPrice); + +// Get balance +$balance = $bankAccount->getBalance(); + +``` +**[⬆ 返回顶部](#目录)** -##有问题反馈 -在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流 -* 邮件(yangweijiest#gmail.com, 把#换成@) -* QQ: 917647288 -* weibo: [@黑白世界4648](http://weibo.com/1342658313) -* 人人: [@杨维杰](http://www.renren.com/247050624) +### Make objects have private/protected members + +**坏:** +```php +class Employee { + public $name; + + public function __construct($name) { + $this->name = $name; + } +} + +$employee = new Employee('John Doe'); +echo 'Employee name: '.$employee->name; // Employee name: John Doe +``` + +**好:** +```php +class Employee { + protected $name; + + public function __construct($name) { + $this->name = $name; + } + + public function getName() { + return $this->name; + } +} + +$employee = new Employee('John Doe'); +echo 'Employee name: '.$employee->getName(); // Employee name: John Doe +``` +**[⬆ 返回顶部](#目录)** + + +## **Classes** + +### Single Responsibility Principle (SRP) +As stated in Clean Code, "There should never be more than one reason for a class +to change". It's tempting to jam-pack a class with a lot of functionality, like +when you can only take one suitcase on your flight. The issue with this is +that your class won't be conceptually cohesive and it will give it many reasons +to change. Minimizing the amount of times you need to change a class is important. +It's important because if too much functionality is in one class and you modify a piece of it, +it can be difficult to understand how that will affect other dependent modules in +your codebase. + +**坏:** +```php +class UserSettings { + private $user; + public function __construct($user) { + $this->user = user; + } + + public function changeSettings($settings) { + if ($this->verifyCredentials()) { + // ... + } + } + + private function verifyCredentials() { + // ... + } +} +``` + +**好:** +```php +class UserAuth { + private $user; + public function __construct($user) { + $this->user = user; + } + + public function verifyCredentials() { + // ... + } +} + + +class UserSettings { + private $user; + public function __construct($user) { + $this->user = $user; + $this->auth = new UserAuth($user); + } + + public function changeSettings($settings) { + if ($this->auth->verifyCredentials()) { + // ... + } + } +} +``` +**[⬆ 返回顶部](#目录)** + +### Open/Closed Principle (OCP) + +As stated by Bertrand Meyer, "software entities (classes, modules, functions, +etc.) should be open for extension, but closed for modification." What does that +mean though? This principle basically states that you should allow users to +add new functionalities without changing existing code. + +**坏:** + +```php +abstract class Adapter +{ + protected $name; + + public function getName() + { + 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) + { + $this->adapter = $adapter; + } + + public function fetch($url) + { + $adapterName = $this->adapter->getName(); + + if ($adapterName === 'ajaxAdapter') { + return $this->makeAjaxCall($url); + } elseif ($adapterName === 'httpNodeAdapter') { + return $this->makeHttpCall($url); + } + } + + protected function makeAjaxCall($url) + { + // request and return promise + } + + protected function makeHttpCall($url) + { + // request and return promise + } +} +``` + +**好:** + +```php +interface Adapter +{ + public function request($url); +} + +class AjaxAdapter implements Adapter +{ + public function request($url) + { + // request and return promise + } +} + +class NodeAdapter implements Adapter +{ + public function request($url) + { + // request and return promise + } +} + +class HttpRequester +{ + private $adapter; + + public function __construct(Adapter $adapter) + { + $this->adapter = $adapter; + } + + public function fetch($url) + { + return $this->adapter->request($url); + } +} +``` + +**[⬆ 返回顶部](#目录)** + +### Liskov Substitution Principle (LSP) + +This is a scary term for a very simple concept. It's formally defined as "If S +is a subtype of T, then objects of type T may be replaced with objects of type S +(i.e., objects of type S may substitute objects of type T) without altering any +of the desirable properties of that program (correctness, task performed, +etc.)." That's an even scarier definition. + +The best explanation for this is if you have a parent class and a child class, +then the base class and child class can be used interchangeably without getting +incorrect results. This might still be confusing, so let's take a look at the +classic Square-Rectangle example. Mathematically, a square is a rectangle, but +if you model it using the "is-a" relationship via inheritance, you quickly +get into trouble. + +**坏:** +```php +class Rectangle { + private $width, $height; + + public function __construct() { + $this->width = 0; + $this->height = 0; + } + + public function setColor($color) { + // ... + } + + public function render($area) { + // ... + } + + public function setWidth($width) { + $this->width = $width; + } + + public function setHeight($height) { + $this->height = $height; + } + + public function getArea() { + return $this->width * $this->height; + } +} + +class Square extends Rectangle { + public function setWidth($width) { + $this->width = $this->height = $width; + } + + public function setHeight(height) { + $this->width = $this->height = $height; + } +} + +function renderLargeRectangles($rectangles) { + foreach($rectangle in $rectangles) { + $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); +``` + +**好:** +```php +abstract class Shape { + private $width, $height; + + abstract public function getArea(); + + public function setColor($color) { + // ... + } + + public function render($area) { + // ... + } +} + +class Rectangle extends Shape { + public function __construct { + parent::__construct(); + $this->width = 0; + $this->height = 0; + } + + public function setWidth($width) { + $this->width = $width; + } + + public function setHeight($height) { + $this->height = $height; + } + + public function getArea() { + return $this->width * $this->height; + } +} + +class Square extends Shape { + public function __construct { + parent::__construct(); + $this->length = 0; + } + + public function setLength($length) { + $this->length = $length; + } + + public function getArea() { + return pow($this->length, 2); + } +} + +function renderLargeRectangles($rectangles) { + foreach($rectangle in $rectangles) { + if ($rectangle instanceof Square) { + $rectangle->setLength(5); + } else if ($rectangle instanceof Rectangle) { + $rectangle->setWidth(4); + $rectangle->setHeight(5); + } + + $area = $rectangle->getArea(); + $rectangle->render($area); + }); +} + +$shapes = [new Rectangle(), new Rectangle(), new Square()]; +renderLargeRectangles($shapes); +``` +**[⬆ 返回顶部](#目录)** + +### Interface Segregation Principle (ISP) +ISP states that "Clients should not be forced to depend upon interfaces that +they do not use." + +A good example to look at that demonstrates this principle is for +classes that require large settings objects. Not requiring clients to setup +huge amounts of options is beneficial, because most of the time they won't need +all of the settings. Making them optional helps prevent having a "fat interface". + +**坏:** +```php +interface WorkerInterface { + public function work(); + public function eat(); +} + +class Worker implements WorkerInterface { + public function work() { + // ....working + } + public function eat() { + // ...... eating in launch break + } +} + +class SuperWorker implements WorkerInterface { + public function work() { + //.... working much more + } + + public function eat() { + //.... eating in launch break + } +} + +class Manager { + /** @var WorkerInterface $worker **/ + private $worker; + + public function setWorker(WorkerInterface $worker) { + $this->worker = $worker; + } + + public function manage() { + $this->worker->work(); + } +} +``` + +**好:** +```php +interface WorkerInterface extends FeedableInterface, WorkableInterface { +} + +interface WorkableInterface { + public function work(); +} + +interface FeedableInterface { + public function eat(); +} + +class Worker implements WorkableInterface, FeedableInterface { + public function work() { + // ....working + } + + public function eat() { + //.... eating in launch break + } +} + +class Robot implements WorkableInterface { + public function work() { + // ....working + } +} + +class SuperWorker implements WorkerInterface { + public function work() { + //.... working much more + } + + public function eat() { + //.... eating in launch break + } +} + +class Manager { + /** @var $worker WorkableInterface **/ + private $worker; + + public function setWorker(WorkableInterface $w) { + $this->worker = $w; + } + + public function manage() { + $this->worker->work(); + } +} +``` +**[⬆ 返回顶部](#目录)** + +### Dependency Inversion Principle (DIP) +This principle states two essential things: +1. High-level modules should not depend on low-level modules. Both should +depend on abstractions. +2. Abstractions should not depend upon details. Details should depend on +abstractions. + +This can be hard to understand at first, but if you've worked with PHP frameworks (like Symfony), you've seen an implementation of this principle in the form of Dependency +Injection (DI). While they are not identical concepts, DIP keeps high-level +modules from knowing the details of its low-level modules and setting them up. +It can accomplish this through DI. A huge benefit of this is that it reduces +the coupling between modules. Coupling is a very bad development pattern because +it makes your code hard to refactor. + +**坏:** +```php +class Worker { + public function work() { + // ....working + } +} + +class Manager { + /** @var Worker $worker **/ + private $worker; + + public function __construct(Worker $worker) { + $this->worker = $worker; + } + + public function manage() { + $this->worker->work(); + } +} + +class SuperWorker extends Worker { + public function work() { + //.... working much more + } +} +``` + +**好:** +```php +interface WorkerInterface { + public function work(); +} + +class Worker implements WorkerInterface { + public function work() { + // ....working + } +} + +class SuperWorker implements WorkerInterface { + public function work() { + //.... working much more + } +} + +class Manager { + /** @var Worker $worker **/ + private $worker; + + public function __construct(WorkerInterface $worker) { + $this->worker = $worker; + } + + public function manage() { + $this->worker->work(); + } +} + +``` +**[⬆ 返回顶部](#目录)** + +### Use method chaining +This pattern is very useful and commonly used it many libraries such +as PHPUnit and Doctrine. It allows your code to be expressive, and less verbose. +For that reason, I say, use method chaining and take a look at how clean your code +will be. In your class functions, simply return `this` at the end of every function, +and you can chain further class methods onto it. + +**坏:** +```php +class Car { + private $make, $model, $color; + + public function __construct() { + $this->make = 'Honda'; + $this->model = 'Accord'; + $this->color = 'white'; + } + + public function setMake($make) { + $this->make = $make; + } + + public function setModel($model) { + $this->model = $model; + } + + public function setColor($color) { + $this->color = $color; + } + + public function dump() { + var_dump($this->make, $this->model, $this->color); + } +} + +$car = new Car(); +$car->setColor('pink'); +$car->setMake('Ford'); +$car->setModel('F-150'); +$car->dump(); +``` + +**好:** +```php +class Car { + private $make, $model, $color; + + public function __construct() { + $this->make = 'Honda'; + $this->model = 'Accord'; + $this->color = 'white'; + } + + public function setMake($make) { + $this->make = $make; + + // NOTE: Returning this for chaining + return $this; + } + + public function setModel($model) { + $this->model = $model; + + // NOTE: Returning this for chaining + return $this; + } + + public function setColor($color) { + $this->color = $color; + + // NOTE: Returning this for chaining + return $this; + } + + public function dump() { + var_dump($this->make, $this->model, $this->color); + } +} + +$car = (new Car()) + ->setColor('pink') + ->setMake('Ford') + ->setModel('F-150') + ->dump(); +``` +**[⬆ 返回顶部](#目录)** + +### Prefer composition over inheritance +As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four, +you should prefer composition over inheritance where you can. There are lots of +good reasons to use inheritance and lots of good reasons to use composition. +The main point for this maxim is that if your mind instinctively goes for +inheritance, try to think if composition could model your problem better. In some +cases it can. + +You might be wondering then, "when should I use inheritance?" It +depends on your problem at hand, but this is a decent list of when inheritance +makes more sense than composition: + +1. Your inheritance represents an "is-a" relationship and not a "has-a" +relationship (Human->Animal vs. User->UserDetails). +2. You can reuse code from the base classes (Humans can move like all animals). +3. You want to make global changes to derived classes by changing a base class. +(Change the caloric expenditure of all animals when they move). + +**坏:** +```php +class Employee { + private $name, $email; + + public function __construct($name, $email) { + $this->name = $name; + $this->email = $email; + } + + // ... +} + +// Bad because Employees "have" tax data. +// EmployeeTaxData is not a type of Employee + +class EmployeeTaxData extends Employee { + private $ssn, $salary; + + public function __construct($ssn, $salary) { + parent::__construct(); + $this->ssn = $ssn; + $this->salary = $salary; + } + + // ... +} +``` + +**好:** +```php +class EmployeeTaxData { + private $ssn, $salary; + + public function __construct($ssn, $salary) { + $this->ssn = $ssn; + $this->salary = $salary; + } + + // ... +} + +class Employee { + private $name, $email, $taxData; + + public function __construct($name, $email) { + $this->name = $name; + $this->email = $email; + } + + public function setTaxData($ssn, $salary) { + $this->taxData = new EmployeeTaxData($ssn, $salary); + } + // ... +} +``` +**[⬆ 返回顶部](#目录)** From 345c543173a3c8c4a22e4c637cc830b3f716da6e Mon Sep 17 00:00:00 2001 From: zouyi6 Date: Tue, 5 Sep 2017 17:15:36 +0800 Subject: [PATCH 02/21] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=9C=80=E6=96=B0?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 390 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 220 insertions(+), 170 deletions(-) diff --git a/README.md b/README.md index d5cde3f8..2ed97960 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ 本文受到 [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) 的启发 ## **变量** -### 使用有意义且可拼写的变量名 +### 使用见字知意的变量名 **坏:** ```php @@ -36,7 +36,7 @@ $currentDate = $moment->format('y-m-d'); ``` **[⬆ 返回顶部](#目录)** -### 同种类型的变量使用相同词汇 +### 同一个实体要用相同的变量名 **坏:** ```php @@ -47,13 +47,15 @@ getCustomerRecord(); **好:** ```php -getUser(); +getUserInfo(); +getUserData(); +getUserRecord(); ``` **[⬆ 返回顶部](#目录)** ### 使用便于搜索的名称 -我们会阅读比我们写的更多的代码。所以写出高可读性和便于搜索的代码很重要。 -命名变量时如果*不*是有意义和易于理解的,那就是在伤害读者。 +写代码是用来读的。所以写出可读性高、便于搜索的代码至关重要。 +命名变量时如果没有有意义、不好理解,那就是在伤害读者。 请让你的代码便于搜索。 **坏:** @@ -92,7 +94,8 @@ It's better, but we are still heavily dependent on regex. $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; preg_match($cityZipCodeRegex, $address, $matches); -list(, $city, $zipCode) = $matchers; + +list(, $city, $zipCode) = $matches; saveCityZipCode($city, $zipCode); ``` @@ -109,8 +112,9 @@ saveCityZipCode($matches['city'], $matches['zipCode']); **[⬆ 返回顶部](#目录)** -### 避免心理映射 -明确比隐性好。 +### 少用无意义的变量名 +别让读你的代码的人猜你写的变量是什么意思。 +写清楚好过模糊不清。 **坏:** ```php @@ -144,7 +148,7 @@ foreach ($locations as $location) { ### 不要添加不必要上下文 -如果你的class/object 名能告诉你什么,不要把它重复在你变量名里。 +如果从你的类名、对象名已经可以得知一些信息,就别再在变量名里重复。 **坏:** @@ -173,7 +177,7 @@ class Car ``` **[⬆ 返回顶部](#目录)** -###使用参数默认值代替短路或条件语句。 +### 合理使用参数默认值,没必要在方法里再做默认值检测 **坏:** ```php function createMicrobrewery($name = null) { @@ -193,10 +197,10 @@ function createMicrobrewery($breweryName = 'Hipster Brew Co.') { **[⬆ 返回顶部](#目录)** ## **函数** -### 函数参数最好少于2个 -限制函数参数个数极其重要因为它是你函数测试容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。 +### 函数参数(最好少于2个) +限制函数参数个数极其重要,这样测试你的函数容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。 -无参数是理想情况。1个或2个都可以,最好避免3个。再多旧需要加固了。通常如果你的函数有超过两个参数,说明他多做了一些事。 在参数少的情况里,大多数时候一个高级别对象(数组)作为参数就足够应付。 +无参数是理想情况。1个或2个都可以,最好避免3个。再多就需要加固了。通常如果你的函数有超过两个参数,说明他要处理的事太多了。 如果必须要传入很多数据,建议封装一个高级别对象作为参数。 **坏:** ```php @@ -230,7 +234,7 @@ function createMenu(MenuConfig $config) { ### 函数应该只做一件事 -这是迄今为止软件工程里最重要的一个规则。当函数做超过一件事的时候,他们就难于实现、测试和理解。当你隔离函数只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。 +这是迄今为止软件工程里最重要的一个规则。当一个函数做超过一件事的时候,他们就难于实现、测试和理解。当你把一个函数拆分到只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。 **坏:** ```php @@ -262,7 +266,7 @@ function isClientActive($client) { ``` **[⬆ 返回顶部](#目录)** -### 函数名应当描述他们所做的事 +### 函数名应该是有意义的动词(或表明具体做了什么事) **坏:** @@ -278,7 +282,7 @@ class Email } $message = new Email(...); -// What is this? A handle for the message? Are we writing to a file now? +// 啥?handle处理一个消息干嘛了? $message->handle(); ``` @@ -296,13 +300,16 @@ class Email } $message = new Email(...); -// Clear and obvious +// 简单明了 $message->send(); ``` **[⬆ 返回顶部](#目录)** -### 函数应当只为一层抽象,当你超过一层抽象时,函数正在做多件事。拆分功能易达到可重用性和易用性。. +### 函数应当只有一层抽象abstraction + +当你抽象层次过多时时,函数处理的事情太多了。需要拆分功能来提高可重用性和易用性,以便简化测试。 +(译者注:这里从示例代码看应该是指嵌套过多) **坏:** @@ -334,7 +341,7 @@ function parseBetterJSAlternative($code) **坏:** -We have carried out some of the functionality, but the `parseBetterJSAlternative()` function is still very complex and not testable. +我们把一些方法从循环中提取出来,但是`parseBetterJSAlternative()`方法还是很复杂,而且不利于测试。 ```php function tokenize($code) @@ -376,7 +383,7 @@ function parseBetterJSAlternative($code) **好:** -The best solution is move out the dependencies of `parseBetterJSAlternative()` function. +最好的解决方案是把 `parseBetterJSAlternative()`方法的依赖移除。 ```php class Tokenizer @@ -437,94 +444,14 @@ class BetterJSAlternative } } ``` -**[⬆ 返回顶部](#目录)** - -### 删除重复的代码 -尽你最大的努力来避免重复的代码。重复代码不好,因为它意味着如果你修改一些逻辑,那就有不止一处地方要同步修改了。 - -想象一下如果你经营着一家餐厅并跟踪它的库存: 你全部的西红柿、洋葱、大蒜、香料等。如果你保留有多个列表,当你服务一个有着西红柿的菜,那么所有记录都得更新。如果你只有一个列表,那么只需要修改一个地方! - -经常你容忍重复代码,因为你有两个或更多有共同部分但是少许差异的东西强制你用两个或更多独立的函数来做相同的事。移除重复代码意味着创造一个处理这组不同事物的一个抽象,只需要一个函数/模块/类。 - -抽象正确非常重要,这也是为什么你应当遵循SOLID原则(奠定*Class*基础的原则)。坏的抽象可能比重复代码还要糟,因为要小心。在这个前提下,如果你可以抽象好,那就开始做把!不要重复你自己,否则任何你想改变一件事的时候你都发现在即在更新维护多处。 - -**坏:** - -```php -function showDeveloperList($developers) -{ -    foreach ($developers as $developer) { - $expectedSalary = $developer->calculateExpectedSalary(); - $experience = $developer->getExperience(); - $githubLink = $developer->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; - - render($data); - } -} - -function showManagerList($managers) -{ -    foreach ($managers as $manager) { - $expectedSalary = $manager->calculateExpectedSalary(); - $experience = $manager->getExperience(); - $githubLink = $manager->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; - - render($data); - } -} -``` -**好:** +这样我们可以对依赖做mock,并测试`BetterJSAlternative::parse()`运行是否符合预期。 -```php -function showList($employees) -{ -    foreach ($employees as $employe) { -        $expectedSalary = $employe->calculateExpectedSalary(); -        $experience = $employe->getExperience(); -        $githubLink = $employe->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; - - render($data); - } -} -``` - - -**非常好:** - -It is better to use a compact version of the code. - -```php -function showList($employees) -{ -    foreach ($employees as $employe) { - render([ - $employe->calculateExpectedSalary(), - $employe->getExperience(), - $employe->getGithubLink() - ]); - } -} -``` **[⬆ 返回顶部](#目录)** -### 不要用标志作为函数的参数,标志告诉你的用户函数做很多事了。函数应当只做一件事。 根据布尔值区别的路径来拆分你的复杂函数。 +### 不要用flag作为函数的参数 +flag就是在告诉大家,这个方法里处理很多事。前面刚说过,一个函数应当只做一件事。 把不同flag的代码拆分到多个函数里。 **坏:** ```php @@ -563,6 +490,8 @@ function createTempFile($name) { $name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { + global $name; + $name = preg_split('/ /', $name); } @@ -573,8 +502,6 @@ var_dump($name); // ['Ryan', 'McDermott']; **好:** ```php -$name = 'Ryan McDermott'; - function splitIntoFirstAndLastName($name) { return preg_split('/ /', $name); } @@ -713,7 +640,7 @@ if ($article->isPublished()) { ``` **[⬆ 返回顶部](#目录)** -### 避免消极条件 +### 避免用反义条件判断 **坏:** ```php @@ -788,9 +715,10 @@ class Cessna extends Airplane { ``` **[⬆ 返回顶部](#目录)** -### Avoid 避免类型检查 (part 1) +### 避免类型检查 (part 1) PHP是弱类型的,这意味着你的函数可以接收任何类型的参数。 -有时候你为这自由所痛苦并且在你的函数渐渐尝试类型检查。有很多方法去避免这么做。第一种是考虑API的一致性。 +有时候你为这自由所痛苦并且在你的函数渐渐尝试类型检查。 +有很多方法去避免这么做。第一种是统一API。 **坏:** @@ -816,7 +744,10 @@ function travelToTexas(Traveler $vehicle) **[⬆ 返回顶部](#目录)** ### 避免类型检查 (part 2) -如果你正使用基本原始值比如字符串、整形和数组,你不能用多态,你仍然感觉需要类型检测,你应当考虑类型声明或者严格模式。 这给你了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。保持你的PHP 整洁,写好测试,做好代码回顾。做不到就用PHP严格类型声明和严格模式来确保安全。 +如果你正使用基本原始值比如字符串、整形和数组,要求版本是PHP 7+,不用多态,需要类型检测, +那你应当考虑[类型声明](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration)或者严格模式。 +提供了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。 +保持你的PHP 整洁,写好测试,做好代码回顾。做不到就用PHP严格类型声明和严格模式来确保安全。 **坏:** @@ -842,7 +773,8 @@ function combine(int $val1, int $val2) **[⬆ 返回顶部](#目录)** ### 移除僵尸代码 -僵尸代码和重复代码一样坏。没有理由保留在你的代码库中。如果从来被调用过,见鬼去!在你的版本库里是如果你仍然需要他的话,因此这么做很安全。 +僵尸代码和重复代码一样坏。没有理由保留在你的代码库中。如果从来没被调用过,就删掉! +因为还在代码版本库里,因此很安全。 **坏:** ```php @@ -854,8 +786,8 @@ function newRequestModule($url) { // ... } -$req = new newRequestModule($requestUrl); -inventoryTracker('apples', $req, 'www.inventory-awesome.io'); +$request = newRequestModule($requestUrl); +inventoryTracker('apples', $request, 'www.inventory-awesome.io'); ``` @@ -865,8 +797,8 @@ function requestModule($url) { // ... } -$req = new requestModule($requestUrl); -inventoryTracker('apples', $req, 'www.inventory-awesome.io'); +$request = requestModule($requestUrl); +inventoryTracker('apples', $request, 'www.inventory-awesome.io'); ``` **[⬆ 返回顶部](#目录)** @@ -989,8 +921,9 @@ your codebase. ```php class UserSettings { private $user; + public function __construct($user) { - $this->user = user; + $this->user = $user; } public function changeSettings($settings) { @@ -1009,8 +942,9 @@ class UserSettings { ```php class UserAuth { private $user; + public function __construct($user) { - $this->user = user; + $this->user = $user; } public function verifyCredentials() { @@ -1021,6 +955,7 @@ class UserAuth { class UserSettings { private $user; + public function __construct($user) { $this->user = $user; $this->auth = new UserAuth($user); @@ -1164,52 +1099,59 @@ get into trouble. **坏:** ```php -class Rectangle { - private $width, $height; - - public function __construct() { +class Rectangle +{ + protected $width; + protected $height; + + public function __construct() + { $this->width = 0; $this->height = 0; } - - public function setColor($color) { - // ... - } - - public function render($area) { + + public function render($area) + { // ... } - - public function setWidth($width) { + + public function setWidth($width) + { $this->width = $width; } - - public function setHeight($height) { + + public function setHeight($height) + { $this->height = $height; } - - public function getArea() { + + public function getArea() + { return $this->width * $this->height; } } -class Square extends Rectangle { - public function setWidth($width) { +class Square extends Rectangle +{ + public function setWidth($width) + { $this->width = $this->height = $width; } - - public function setHeight(height) { + + public function setHeight(height) + { $this->width = $this->height = $height; } } -function renderLargeRectangles($rectangles) { - foreach($rectangle in $rectangles) { +function renderLargeRectangles($rectangles) +{ + 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()]; @@ -1218,67 +1160,76 @@ renderLargeRectangles($rectangles); **好:** ```php -abstract class Shape { - private $width, $height; - +abstract class Shape +{ + protected $width; + protected $height; + abstract public function getArea(); - - public function setColor($color) { - // ... - } - - public function render($area) { + + public function render($area) + { // ... } } -class Rectangle extends Shape { - public function __construct { - parent::__construct(); +class Rectangle extends Shape +{ + public function __construct() + { + parent::__construct(); $this->width = 0; $this->height = 0; } - - public function setWidth($width) { + + public function setWidth($width) + { $this->width = $width; } - - public function setHeight($height) { + + public function setHeight($height) + { $this->height = $height; } - - public function getArea() { + + public function getArea() + { return $this->width * $this->height; } } -class Square extends Shape { - public function __construct { +class Square extends Shape +{ + public function __construct() + { parent::__construct(); $this->length = 0; } - - public function setLength($length) { + + public function setLength($length) + { $this->length = $length; } - - public function getArea() { + + public function getArea() + { return pow($this->length, 2); } } -function renderLargeRectangles($rectangles) { - foreach($rectangle in $rectangles) { +function renderLargeRectangles($rectangles) +{ + foreach ($rectangles as $rectangle) { if ($rectangle instanceof Square) { $rectangle->setLength(5); - } else if ($rectangle instanceof Rectangle) { + } elseif ($rectangle instanceof Rectangle) { $rectangle->setWidth(4); $rectangle->setHeight(5); } $area = $rectangle->getArea(); $rectangle->render($area); - }); + } } $shapes = [new Rectangle(), new Rectangle(), new Square()]; @@ -1450,7 +1401,7 @@ class SuperWorker implements WorkerInterface { } class Manager { - /** @var Worker $worker **/ + /** @var WorkerInterface $worker **/ private $worker; public function __construct(WorkerInterface $worker) { @@ -1589,8 +1540,8 @@ class Employee { class EmployeeTaxData extends Employee { private $ssn, $salary; - public function __construct($ssn, $salary) { - parent::__construct(); + public function __construct($name, $email, $ssn, $salary) { + parent::__construct($name, $email); $this->ssn = $ssn; $this->salary = $salary; } @@ -1627,3 +1578,102 @@ class Employee { } ``` **[⬆ 返回顶部](#目录)** + +## 别写重复代码 (DRY) + +Try to observe the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle. + +Do your absolute best to avoid duplicate code. Duplicate code is bad because +it means that there's more than one place to alter something if you need to +change some logic. + +Imagine if you run a restaurant and you keep track of your inventory: all your +tomatoes, onions, garlic, spices, etc. If you have multiple lists that +you keep this on, then all have to be updated when you serve a dish with +tomatoes in them. If you only have one list, there's only one place to update! + +Oftentimes you have duplicate code because you have two or more slightly +different things, that share a lot in common, but their differences force you +to have two or more separate functions that do much of the same things. Removing +duplicate code means creating an abstraction that can handle this set of different +things with just one function/module/class. + +Getting the abstraction right is critical, that's why you should follow the +SOLID principles laid out in the [Classes](#classes) section. Bad abstractions can be +worse than duplicate code, so be careful! Having said this, if you can make +a good abstraction, do it! Don't repeat yourself, otherwise you'll find yourself +updating multiple places anytime you want to change one thing. + +**Bad:** + +```php +function showDeveloperList($developers) +{ +    foreach ($developers as $developer) { + $expectedSalary = $developer->calculateExpectedSalary(); + $experience = $developer->getExperience(); + $githubLink = $developer->getGithubLink(); + $data = [ + $expectedSalary, + $experience, + $githubLink + ]; + + render($data); + } +} + +function showManagerList($managers) +{ +    foreach ($managers as $manager) { + $expectedSalary = $manager->calculateExpectedSalary(); + $experience = $manager->getExperience(); + $githubLink = $manager->getGithubLink(); + $data = [ + $expectedSalary, + $experience, + $githubLink + ]; + + render($data); + } +} +``` + +**Good:** + +```php +function showList($employees) +{ +    foreach ($employees as $employee) { +        $expectedSalary = $employee->calculateExpectedSalary(); +        $experience = $employee->getExperience(); +        $githubLink = $employee->getGithubLink(); + $data = [ + $expectedSalary, + $experience, + $githubLink + ]; + + render($data); + } +} +``` + +**Very good:** + +It is better to use a compact version of the code. + +```php +function showList($employees) +{ +    foreach ($employees as $employee) { + render([ + $employee->calculateExpectedSalary(), + $employee->getExperience(), + $employee->getGithubLink() + ]); + } +} +``` +**[⬆ 返回顶部](#目录)** \ No newline at end of file From 899b4c8ad4d584a507b32f4a5a49728994813656 Mon Sep 17 00:00:00 2001 From: zouyi6 Date: Wed, 13 Sep 2017 10:09:17 +0800 Subject: [PATCH 03/21] sync --- README.md | 1144 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 715 insertions(+), 429 deletions(-) diff --git a/README.md b/README.md index 2ed97960..9af5b3b6 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,49 @@ -# clean-code-php +# Clean Code PHP ## 目录 + 1. [介绍](#介绍) 2. [变量](#变量) + * [使用见字知意的变量名](#使用见字知意的变量名) + * [同一个实体要用相同的变量名](#同一个实体要用相同的变量名) + * [使用便于搜索的名称 (part 1)](#使用便于搜索的名称part-1) + * [使用便于搜索的名称 (part 2)](#使用便于搜索的名称part-2) + * [使用自解释型变量](#使用自解释型变量) + * [少用无意义的变量名](#少用无意义的变量名) + * [Don't add unneeded context](#dont-add-unneeded-context) + * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值,没必要在方法里再做默认值检测) 3. [函数](#函数) + * [Function arguments (2 or fewer ideally)](#function-arguments-2-or-fewer-ideally) + * [Functions should do one thing](#functions-should-do-one-thing) + * [Function names should say what they do](#function-names-should-say-what-they-do) + * [Functions should only be one level of abstraction](#functions-should-only-be-one-level-of-abstraction) + * [Don't use flags as function parameters](#dont-use-flags-as-function-parameters) + * [Avoid Side Effects](#avoid-side-effects) + * [Don't write to global functions](#dont-write-to-global-functions) + * [Don't use a Singleton pattern](#dont-use-a-singleton-pattern) + * [Encapsulate conditionals](#encapsulate-conditionals) + * [Avoid negative conditionals](#avoid-negative-conditionals) + * [Avoid conditionals](#avoid-conditionals) + * [Avoid type-checking (part 1)](#avoid-type-checking-part-1) + * [Avoid type-checking (part 2)](#avoid-type-checking-part-2) + * [Remove dead code](#remove-dead-code) 4. [对象和数据结构 Objects and Data Structures](#objects-and-data-structures) - 5. [类的SOLID原则 Classes](#classes) - 1. [S: 单一功能 Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) - 2. [O: 开闭原则 Open/Closed Principle (OCP)](#openclosed-principle-ocp) - 3. [L: 里氏替换 Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) - 4. [I: 接口隔离 Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) - 5. [D: 依赖反转 Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) + * [Use getters and setters](#use-getters-and-setters) + * [Make objects have private/protected members](#make-objects-have-privateprotected-members) + 5. [Classes](#classes) + * [Use method chaining](#use-method-chaining) + * [Prefer composition over inheritance](#prefer-composition-over-inheritance) + 6. [类的SOLID原则 SOLID](#solid) + * [S: 单一功能 Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) + * [O: 开闭原则 Open/Closed Principle (OCP)](#openclosed-principle-ocp) + * [L: 里氏替换 Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) + * [I: 接口隔离 Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) + * [D: 依赖反转 Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) + 7. [Don’t repeat yourself (DRY)](#dont-repeat-yourself-dry) + 8. [Translations](#translations) ## 介绍 + 本文由 php-cpm 基于 yangweijie 的[clen php code](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容,欢迎大家指正。 本文参考自 Robert C. Martin的[*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) 书中的软件工程师的原则 @@ -26,69 +57,100 @@ ### 使用见字知意的变量名 **坏:** + ```php $ymdstr = $moment->format('y-m-d'); ``` **好:** + ```php $currentDate = $moment->format('y-m-d'); ``` + **[⬆ 返回顶部](#目录)** ### 同一个实体要用相同的变量名 **坏:** + ```php getUserInfo(); -getClientData(); -getCustomerRecord(); +getUserData(); +getUserRecord(); +getUserProfile(); ``` **好:** + ```php -getUserInfo(); -getUserData(); -getUserRecord(); +getUser(); ``` **[⬆ 返回顶部](#目录)** -### 使用便于搜索的名称 +### 使用便于搜索的名称 (part 1) 写代码是用来读的。所以写出可读性高、便于搜索的代码至关重要。 命名变量时如果没有有意义、不好理解,那就是在伤害读者。 请让你的代码便于搜索。 **坏:** ```php -// What the heck is 86400 for? -addExpireAt(86400); +// What the heck is 448 for? +$result = $serializer->serialize($data, 448); +``` + +**好:** + +```php +$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); +``` + +**[⬆ 返回顶部](#目录)** +### 使用便于搜索的名称 (part 2) + +**坏:** + +```php +// What the heck is 4 for? +if ($user->access & 4) { + // ... +} ``` **好:** + ```php -// Declare them as capitalized `const` globals. -interface DateGlobal { - const SECONDS_IN_A_DAY = 86400; +class User +{ + const ACCESS_READ = 1; + const ACCESS_CREATE = 2; +    const ACCESS_UPDATE = 4; + const ACCESS_DELETE = 8; } -addExpireAt(DateGlobal::SECONDS_IN_A_DAY); +if ($user->access & User::ACCESS_UPDATE) { + // do edit ... +} ``` + **[⬆ 返回顶部](#目录)** -### 使用解释型变量 +### 使用自解释型变量 + **坏:** ```php $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; preg_match($cityZipCodeRegex, $address, $matches); + saveCityZipCode($matches[1], $matches[2]); ``` **不错:** -It's better, but we are still heavily dependent on regex. +好一些,但强依赖于正则表达式的熟悉程度 ```php $address = 'One Infinite Loop, Cupertino 95014'; @@ -101,7 +163,8 @@ saveCityZipCode($city, $zipCode); **好:** -Decrease dependence on regex by naming subpatterns. +使用带名字的子规则,不用懂正则也能看的懂 + ```php $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(?.+?)\s*(?\d{5})?$/'; @@ -112,13 +175,108 @@ saveCityZipCode($matches['city'], $matches['zipCode']); **[⬆ 返回顶部](#目录)** +### Avoid nesting too deeply and return early + +Too many if else statemetns can make your code hard to follow. Explicit is better +than implicit. + +**Bad:** + +```php +function isShopOpen($day) +{ + 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; + } +} +``` + +**Good:** + +```php +function isShopOpen($day) +{ + if (empty($day) && ! is_string($day)) { + return false; + } + + $openingDays = [ + 'friday', 'saturday', 'sunday' + ]; + + return in_array(strtolower($day), $openingDays); +} +``` + +**Bad:** + +```php +function fibonacci($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'; + } +} +``` + +**Good:** + +```php +function fibonacci($n) +{ + if ($n === 0) { + return 0; + } + + if ($n === 1) { + return 1; + } + + if ($n > 50) { + return 'Not supported'; + } + + return fibonacci($n - 1) + fibonacci($n - 2); +} +``` + +**[⬆ back to top](#table-of-contents)** + ### 少用无意义的变量名 + 别让读你的代码的人猜你写的变量是什么意思。 写清楚好过模糊不清。 **坏:** + ```php $l = ['Austin', 'New York', 'San Francisco']; + for ($i = 0; $i < count($l); $i++) { $li = $l[$i]; doStuff(); @@ -127,11 +285,12 @@ for ($i = 0; $i < count($l); $i++) { // ... // ... // 等等, `$li` 又代表什么? - dispatch($li); + dispatch($li); } ``` **好:** + ```php $locations = ['Austin', 'New York', 'San Francisco']; @@ -142,12 +301,13 @@ foreach ($locations as $location) { // ... // ... dispatch($location); -}); +} ``` -**[⬆ 返回顶部](#目录)** +**[⬆ 返回顶部](#目录)** ### 不要添加不必要上下文 + 如果从你的类名、对象名已经可以得知一些信息,就别再在变量名里重复。 **坏:** @@ -175,25 +335,45 @@ class Car //... } ``` + **[⬆ 返回顶部](#目录)** ### 合理使用参数默认值,没必要在方法里再做默认值检测 -**坏:** + +**Not good:** + +This is not good because `$breweryName` can be `NULL`. + ```php -function createMicrobrewery($name = null) { - $breweryName = $name ?: 'Hipster Brew Co.'; - // ... +function createMicrobrewery($breweryName = 'Hipster Brew Co.') +{ +    // ... } - ``` -**好:** +**Not bad:** + +This opinion is more understandable than the previous version, but it better controls the value of the variable. + ```php -function createMicrobrewery($breweryName = 'Hipster Brew Co.') { +function createMicrobrewery($name = null) +{ +    $breweryName = $name ?: 'Hipster Brew Co.'; // ... } +``` + +**好:** + +If you support only PHP 7+, then you can use [type hinting](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) and be sure that the `$breweryName` will not be `NULL`. +```php +function createMicrobrewery(string $breweryName = 'Hipster Brew Co.') +{ +    // ... +} ``` + **[⬆ 返回顶部](#目录)** ## **函数** @@ -203,8 +383,10 @@ function createMicrobrewery($breweryName = 'Hipster Brew Co.') { 无参数是理想情况。1个或2个都可以,最好避免3个。再多就需要加固了。通常如果你的函数有超过两个参数,说明他要处理的事太多了。 如果必须要传入很多数据,建议封装一个高级别对象作为参数。 **坏:** + ```php -function createMenu($title, $body, $buttonText, $cancellable) { +function createMenu($title, $body, $buttonText, $cancellable) +{ // ... } ``` @@ -225,11 +407,12 @@ $config->body = 'Bar'; $config->buttonText = 'Baz'; $config->cancellable = true; -function createMenu(MenuConfig $config) { - // ... +function createMenu(MenuConfig $config) +{ + // ... } - ``` + **[⬆ 返回顶部](#目录)** @@ -238,7 +421,8 @@ function createMenu(MenuConfig $config) { **坏:** ```php -function emailClients($clients) { +function emailClients($clients) +{ foreach ($clients as $client) { $clientRecord = $db->find($client); if ($clientRecord->isActive()) { @@ -249,18 +433,23 @@ function emailClients($clients) { ``` **好:** + ```php -function emailClients($clients) { +function emailClients($clients) +{ $activeClients = activeClients($clients); array_walk($activeClients, 'email'); } -function activeClients($clients) { +function activeClients($clients) +{ return array_filter($clients, 'isClientActive'); } -function isClientActive($client) { +function isClientActive($client) +{ $clientRecord = $db->find($client); + return $clientRecord->isActive(); } ``` @@ -319,20 +508,20 @@ function parseBetterJSAlternative($code) $regexes = [ // ... ]; - - $statements = split(' ', $code); + + $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { // ... } } - + $ast = []; foreach ($tokens as $token) { // lex... } - + foreach ($ast as $node) { // parse... } @@ -349,15 +538,15 @@ function tokenize($code) $regexes = [ // ... ]; - - $statements = split(' ', $code); + + $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { $tokens[] = /* ... */; } } - + return $tokens; } @@ -367,7 +556,7 @@ function lexer($tokens) foreach ($tokens as $token) { $ast[] = /* ... */; } - + return $ast; } @@ -394,7 +583,7 @@ class Tokenizer // ... ]; - $statements = split(' ', $code); + $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { @@ -405,9 +594,7 @@ class Tokenizer return $tokens; } } -``` -```php class Lexer { public function lexify($tokens) @@ -420,9 +607,7 @@ class Lexer return $ast; } } -``` -```php class BetterJSAlternative { private $tokenizer; @@ -455,7 +640,8 @@ flag就是在告诉大家,这个方法里处理很多事。前面刚说过, **坏:** ```php -function createFile($name, $temp = false) { +function createFile($name, $temp = false) +{ if ($temp) { touch('./temp/'.$name); } else { @@ -465,12 +651,15 @@ function createFile($name, $temp = false) { ``` **好:** + ```php -function createFile($name) { +function createFile($name) +{ touch($name); } -function createTempFile($name) { +function createTempFile($name) +{ touch('./temp/'.$name); } ``` @@ -484,15 +673,17 @@ function createTempFile($name) { 重点是避免常见陷阱比如对象间共享无结构的数据,使用可以写入任何的可变数据类型,不集中处理副作用发生的地方。如果你做了这些你就会比大多数程序员快乐。 **坏:** + ```php // Global variable referenced by following function. // If we had another function that used this name, now it'd be an array and it could break it. $name = 'Ryan McDermott'; -function splitIntoFirstAndLastName() { +function splitIntoFirstAndLastName() +{ global $name; - $name = preg_split('/ /', $name); + $name = explode(' ', $name); } splitIntoFirstAndLastName(); @@ -502,8 +693,9 @@ var_dump($name); // ['Ryan', 'McDermott']; **好:** ```php -function splitIntoFirstAndLastName($name) { - return preg_split('/ /', $name); +function splitIntoFirstAndLastName($name) +{ + return explode(' ', $name); } $name = 'Ryan McDermott'; @@ -512,6 +704,7 @@ $newName = splitIntoFirstAndLastName($name); var_dump($name); // 'Ryan McDermott'; var_dump($newName); // ['Ryan', 'McDermott']; ``` + **[⬆ 返回顶部](#目录)** ### 不要写全局函数 @@ -530,15 +723,6 @@ function config() **好:** -Create PHP configuration file or something else - -```php -// config.php -return [ - 'foo' => 'bar', -]; -``` - ```php class Configuration { @@ -556,10 +740,12 @@ class Configuration } ``` -Load configuration from file and create instance of `Configuration` class +Load configuration and create instance of `Configuration` class ```php -$configuration = new Configuration($configuration); +$configuration = new Configuration([ + 'foo' => 'bar', +]); ``` And now you must use instance of `Configuration` in your application. @@ -568,6 +754,7 @@ And now you must use instance of `Configuration` in your application. **[⬆ 返回顶部](#目录)** ### Don't use a Singleton pattern + Singleton is a [anti-pattern](https://en.wikipedia.org/wiki/Singleton_pattern). **坏:** @@ -643,19 +830,24 @@ if ($article->isPublished()) { ### 避免用反义条件判断 **坏:** + ```php -function isDOMNodeNotPresent($node) { +function isDOMNodeNotPresent($node) +{ // ... } -if (!isDOMNodeNotPresent($node)) { +if (!isDOMNodeNotPresent($node)) +{ // ... } ``` **好:** + ```php -function isDOMNodePresent($node) { +function isDOMNodePresent($node) +{ // ... } @@ -663,17 +855,23 @@ if (isDOMNodePresent($node)) { // ... } ``` + **[⬆ 返回顶部](#目录)** ### 避免条件声明 + 这看起来像一个不可能任务。当人们第一次听到这句话是都会这么说。 "没有一个`if声明`" 答案是你可以使用多态来达到许多case语句里的任务。第二个问题很常见, “那么为什么我要那么做?” 答案是前面我们学过的一个整洁代码原则:一个函数应当只做一件事。当你有类和函数有很多`if`声明,你自己知道你的函数做了不止一件事。记住,只做一件事。 **坏:** + ```php -class Airplane { +class Airplane +{ // ... - public function getCruisingAltitude() { + + public function getCruisingAltitude() + { switch ($this->type) { case '777': return $this->getMaxAltitude() - $this->getPassengerCount(); @@ -687,32 +885,46 @@ class Airplane { ``` **好:** + ```php -class Airplane { +interface Airplane +{ // ... + + public function getCruisingAltitude(); } -class Boeing777 extends Airplane { +class Boeing777 implements Airplane +{ // ... - public function getCruisingAltitude() { + + public function getCruisingAltitude() + { return $this->getMaxAltitude() - $this->getPassengerCount(); } } -class AirForceOne extends Airplane { +class AirForceOne implements Airplane +{ // ... - public function getCruisingAltitude() { + + public function getCruisingAltitude() + { return $this->getMaxAltitude(); } } -class Cessna extends Airplane { +class Cessna implements Airplane +{ // ... - public function getCruisingAltitude() { + + public function getCruisingAltitude() + { return $this->getMaxAltitude() - $this->getFuelExpenditure(); } } ``` + **[⬆ 返回顶部](#目录)** ### 避免类型检查 (part 1) @@ -778,33 +990,39 @@ function combine(int $val1, int $val2) **坏:** ```php -function oldRequestModule($url) { +function oldRequestModule($url) +{ // ... } -function newRequestModule($url) { +function newRequestModule($url) +{ // ... } $request = newRequestModule($requestUrl); inventoryTracker('apples', $request, 'www.inventory-awesome.io'); - ``` **好:** + ```php -function requestModule($url) { +function requestModule($url) +{ // ... } $request = requestModule($requestUrl); inventoryTracker('apples', $request, 'www.inventory-awesome.io'); ``` + **[⬆ 返回顶部](#目录)** -## **Objects and Data Structures** +## Objects and Data Structures + ### Use getters and setters + In PHP you can set `public`, `protected` and `private` keywords for methods. Using it, you can control properties modification on an object. @@ -821,8 +1039,10 @@ Additionally, this is part of Open/Closed principle, from object-oriented design principles. **坏:** + ```php -class BankAccount { +class BankAccount +{ public $balance = 1000; } @@ -833,26 +1053,33 @@ $bankAccount->balance -= 100; ``` **好:** + ```php -class BankAccount { +class BankAccount +{ private $balance; - - public function __construct($balance = 1000) { + + public function __construct($balance = 1000) + { $this->balance = $balance; } - - public function withdrawBalance($amount) { + + public function withdrawBalance($amount) + { if ($amount > $this->balance) { throw new \Exception('Amount greater than available balance.'); } + $this->balance -= $amount; } - - public function depositBalance($amount) { + + public function depositBalance($amount) + { $this->balance += $amount; } - - public function getBalance() { + + public function getBalance() + { return $this->balance; } } @@ -864,19 +1091,21 @@ $bankAccount->withdrawBalance($shoesPrice); // Get balance $balance = $bankAccount->getBalance(); - ``` -**[⬆ 返回顶部](#目录)** +**[⬆ 返回顶部](#目录)** ### Make objects have private/protected members **坏:** + ```php -class Employee { +class Employee +{ public $name; - - public function __construct($name) { + + public function __construct($name) + { $this->name = $name; } } @@ -886,15 +1115,19 @@ echo 'Employee name: '.$employee->name; // Employee name: John Doe ``` **好:** + ```php -class Employee { - protected $name; - - public function __construct($name) { +class Employee +{ + private $name; + + public function __construct($name) + { $this->name = $name; } - - public function getName() { + + public function getName() + { return $this->name; } } @@ -902,72 +1135,285 @@ class Employee { $employee = new Employee('John Doe'); echo 'Employee name: '.$employee->getName(); // Employee name: John Doe ``` + **[⬆ 返回顶部](#目录)** +## Classes -## **Classes** +### Use method chaining -### Single Responsibility Principle (SRP) -As stated in Clean Code, "There should never be more than one reason for a class -to change". It's tempting to jam-pack a class with a lot of functionality, like -when you can only take one suitcase on your flight. The issue with this is -that your class won't be conceptually cohesive and it will give it many reasons -to change. Minimizing the amount of times you need to change a class is important. -It's important because if too much functionality is in one class and you modify a piece of it, -it can be difficult to understand how that will affect other dependent modules in -your codebase. +This pattern is very useful and commonly used in many libraries such +as PHPUnit and Doctrine. It allows your code to be expressive, and less verbose. +For that reason, use method chaining and take a look at how clean your code +will be. In your class functions, simply use `return $this` at the end of every `set` function, +and you can chain further class methods onto it. + +**Bad:** -**坏:** ```php -class UserSettings { - private $user; +class Car +{ + private $make = 'Honda'; + private $model = 'Accord'; + private $color = 'white'; - public function __construct($user) { - $this->user = $user; + public function setMake($make) + { + $this->make = $make; } - - public function changeSettings($settings) { - if ($this->verifyCredentials()) { - // ... - } + + public function setModel($model) + { + $this->model = $model; } - - private function verifyCredentials() { - // ... + + public function setColor($color) + { + $this->color = $color; } -} -``` -**好:** + public function dump() + { + var_dump($this->make, $this->model, $this->color); + } +} + +$car = new Car(); +$car->setColor('pink'); +$car->setMake('Ford'); +$car->setModel('F-150'); +$car->dump(); +``` + +**Good:** + ```php -class UserAuth { +class Car +{ + private $make = 'Honda'; + private $model = 'Accord'; + private $color = 'white'; + + public function setMake($make) + { + $this->make = $make; + + // NOTE: Returning this for chaining + return $this; + } + + public function setModel($model) + { + $this->model = $model; + + // NOTE: Returning this for chaining + return $this; + } + + public function setColor($color) + { + $this->color = $color; + + // NOTE: Returning this for chaining + return $this; + } + + public function dump() + { + var_dump($this->make, $this->model, $this->color); + } +} + +$car = (new Car()) + ->setColor('pink') + ->setMake('Ford') + ->setModel('F-150') + ->dump(); +``` + +**[⬆ back to top](#table-of-contents)** + +### Prefer composition over inheritance + +As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four, +you should prefer composition over inheritance where you can. There are lots of +good reasons to use inheritance and lots of good reasons to use composition. +The main point for this maxim is that if your mind instinctively goes for +inheritance, try to think if composition could model your problem better. In some +cases it can. + +You might be wondering then, "when should I use inheritance?" It +depends on your problem at hand, but this is a decent list of when inheritance +makes more sense than composition: + +1. Your inheritance represents an "is-a" relationship and not a "has-a" +relationship (Human->Animal vs. User->UserDetails). +2. You can reuse code from the base classes (Humans can move like all animals). +3. You want to make global changes to derived classes by changing a base class. +(Change the caloric expenditure of all animals when they move). + +**Bad:** + +```php +class Employee +{ + private $name; + private $email; + + public function __construct($name, $email) + { + $this->name = $name; + $this->email = $email; + } + + // ... +} + +// Bad because Employees "have" tax data. +// EmployeeTaxData is not a type of Employee + +class EmployeeTaxData extends Employee +{ + private $ssn; + private $salary; + + public function __construct($name, $email, $ssn, $salary) + { + parent::__construct($name, $email); + + $this->ssn = $ssn; + $this->salary = $salary; + } + + // ... +} +``` + +**Good:** + +```php +class EmployeeTaxData +{ + private $ssn; + private $salary; + + public function __construct($ssn, $salary) + { + $this->ssn = $ssn; + $this->salary = $salary; + } + + // ... +} + +class Employee +{ + private $name; + private $email; + private $taxData; + + public function __construct($name, $email) + { + $this->name = $name; + $this->email = $email; + } + + public function setTaxData($ssn, $salary) + { + $this->taxData = new EmployeeTaxData($ssn, $salary); + } + + // ... +} +``` + +**[⬆ back to top](#table-of-contents)** + +## SOLID + +**SOLID** is the mnemonic acronym introduced by Michael Feathers for the first five principles named by Robert Martin, which meant five basic principles of object-oriented programming and design. + + * [S: Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) + * [O: Open/Closed Principle (OCP)](#openclosed-principle-ocp) + * [L: Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) + * [I: Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) + * [D: Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) + +### Single Responsibility Principle (SRP) + +As stated in Clean Code, "There should never be more than one reason for a class +to change". It's tempting to jam-pack a class with a lot of functionality, like +when you can only take one suitcase on your flight. The issue with this is +that your class won't be conceptually cohesive and it will give it many reasons +to change. Minimizing the amount of times you need to change a class is important. +It's important because if too much functionality is in one class and you modify a piece of it, +it can be difficult to understand how that will affect other dependent modules in +your codebase. + +**坏:** + +```php +class UserSettings +{ private $user; - public function __construct($user) { + public function __construct($user) + { $this->user = $user; } - - public function verifyCredentials() { + + public function changeSettings($settings) + { + if ($this->verifyCredentials()) { + // ... + } + } + + private function verifyCredentials() + { // ... } } +``` +**好:** -class UserSettings { +```php +class UserAuth +{ private $user; - public function __construct($user) { + public function __construct($user) + { $this->user = $user; - $this->auth = new UserAuth($user); } - public function changeSettings($settings) { + public function verifyCredentials() + { + // ... + } +} + +class UserSettings +{ + private $user; + private $auth; + + public function __construct($user) + { + $this->user = $user; + $this->auth = new UserAuth($user); + } + + public function changeSettings($settings) + { if ($this->auth->verifyCredentials()) { // ... } } } ``` + **[⬆ 返回顶部](#目录)** ### Open/Closed Principle (OCP) @@ -995,6 +1441,7 @@ class AjaxAdapter extends Adapter public function __construct() { parent::__construct(); + $this->name = 'ajaxAdapter'; } } @@ -1004,6 +1451,7 @@ class NodeAdapter extends Adapter public function __construct() { parent::__construct(); + $this->name = 'nodeAdapter'; } } @@ -1016,7 +1464,7 @@ class HttpRequester { $this->adapter = $adapter; } - + public function fetch($url) { $adapterName = $this->adapter->getName(); @@ -1027,13 +1475,13 @@ class HttpRequester return $this->makeHttpCall($url); } } - - protected function makeAjaxCall($url) + + private function makeAjaxCall($url) { // request and return promise } - - protected function makeHttpCall($url) + + private function makeHttpCall($url) { // request and return promise } @@ -1072,7 +1520,7 @@ class HttpRequester { $this->adapter = $adapter; } - + public function fetch($url) { return $this->adapter->request($url); @@ -1098,17 +1546,12 @@ if you model it using the "is-a" relationship via inheritance, you quickly get into trouble. **坏:** + ```php class Rectangle { - protected $width; - protected $height; - - public function __construct() - { - $this->width = 0; - $this->height = 0; - } + protected $width = 0; + protected $height = 0; public function render($area) { @@ -1159,11 +1602,12 @@ renderLargeRectangles($rectangles); ``` **好:** + ```php abstract class Shape { - protected $width; - protected $height; + protected $width = 0; + protected $height = 0; abstract public function getArea(); @@ -1175,13 +1619,6 @@ abstract class Shape class Rectangle extends Shape { - public function __construct() - { - parent::__construct(); - $this->width = 0; - $this->height = 0; - } - public function setWidth($width) { $this->width = $width; @@ -1200,11 +1637,7 @@ class Rectangle extends Shape class Square extends Shape { - public function __construct() - { - parent::__construct(); - $this->length = 0; - } + private $length = 0; public function setLength($length) { @@ -1226,7 +1659,7 @@ function renderLargeRectangles($rectangles) $rectangle->setWidth(4); $rectangle->setHeight(5); } - + $area = $rectangle->getArea(); $rectangle->render($area); } @@ -1235,9 +1668,11 @@ function renderLargeRectangles($rectangles) $shapes = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles($shapes); ``` + **[⬆ 返回顶部](#目录)** ### Interface Segregation Principle (ISP) + ISP states that "Clients should not be forced to depend upon interfaces that they do not use." @@ -1247,100 +1682,88 @@ huge amounts of options is beneficial, because most of the time they won't need all of the settings. Making them optional helps prevent having a "fat interface". **坏:** + ```php -interface WorkerInterface { +interface Employee +{ public function work(); + public function eat(); } -class Worker implements WorkerInterface { - public function work() { +class Human implements Employee +{ + public function work() + { // ....working } - public function eat() { - // ...... eating in launch break - } -} -class SuperWorker implements WorkerInterface { - public function work() { - //.... working much more - } - - public function eat() { - //.... eating in launch break + public function eat() + { + // ...... eating in lunch break } } -class Manager { - /** @var WorkerInterface $worker **/ - private $worker; - - public function setWorker(WorkerInterface $worker) { - $this->worker = $worker; +class Robot implements Employee +{ + public function work() + { + //.... working much more } - public function manage() { - $this->worker->work(); + public function eat() + { + //.... robot can't eat, but it must implement this method } } ``` **好:** -```php -interface WorkerInterface extends FeedableInterface, WorkableInterface { -} -interface WorkableInterface { +Not every worker is an employee, but every employee is an worker. + +```php +interface Workable +{ public function work(); } -interface FeedableInterface { +interface Feedable +{ public function eat(); } -class Worker implements WorkableInterface, FeedableInterface { - public function work() { - // ....working - } - - public function eat() { - //.... eating in launch break - } +interface Employee extends Feedable, Workable +{ } -class Robot implements WorkableInterface { - public function work() { +class Human implements Employee +{ + public function work() + { // ....working } -} -class SuperWorker implements WorkerInterface { - public function work() { - //.... working much more - } - - public function eat() { - //.... eating in launch break + public function eat() + { + //.... eating in lunch break } } -class Manager { - /** @var $worker WorkableInterface **/ - private $worker; - - public function setWorker(WorkableInterface $w) { - $this->worker = $w; - } - - public function manage() { - $this->worker->work(); +// robot can only work +class Robot implements Workable +{ + public function work() + { + // ....working } } ``` + **[⬆ 返回顶部](#目录)** ### Dependency Inversion Principle (DIP) + This principle states two essential things: 1. High-level modules should not depend on low-level modules. Both should depend on abstractions. @@ -1355,228 +1778,80 @@ the coupling between modules. Coupling is a very bad development pattern because it makes your code hard to refactor. **坏:** -```php -class Worker { - public function work() { - // ....working - } -} -class Manager { - /** @var Worker $worker **/ - private $worker; - - public function __construct(Worker $worker) { - $this->worker = $worker; - } - - public function manage() { - $this->worker->work(); - } -} - -class SuperWorker extends Worker { - public function work() { - //.... working much more - } -} -``` - -**好:** ```php -interface WorkerInterface { - public function work(); -} - -class Worker implements WorkerInterface { - public function work() { +class Employee +{ + public function work() + { // ....working } } -class SuperWorker implements WorkerInterface { - public function work() { +class Robot extends Employee +{ + public function work() + { //.... working much more } } -class Manager { - /** @var WorkerInterface $worker **/ - private $worker; - - public function __construct(WorkerInterface $worker) { - $this->worker = $worker; - } - - public function manage() { - $this->worker->work(); - } -} - -``` -**[⬆ 返回顶部](#目录)** - -### Use method chaining -This pattern is very useful and commonly used it many libraries such -as PHPUnit and Doctrine. It allows your code to be expressive, and less verbose. -For that reason, I say, use method chaining and take a look at how clean your code -will be. In your class functions, simply return `this` at the end of every function, -and you can chain further class methods onto it. +class Manager +{ + private $employee; -**坏:** -```php -class Car { - private $make, $model, $color; - - public function __construct() { - $this->make = 'Honda'; - $this->model = 'Accord'; - $this->color = 'white'; - } - - public function setMake($make) { - $this->make = $make; - } - - public function setModel($model) { - $this->model = $model; - } - - public function setColor($color) { - $this->color = $color; - } - - public function dump() { - var_dump($this->make, $this->model, $this->color); + public function __construct(Employee $employee) + { + $this->employee = $employee; } -} - -$car = new Car(); -$car->setColor('pink'); -$car->setMake('Ford'); -$car->setModel('F-150'); -$car->dump(); -``` -**好:** -```php -class Car { - private $make, $model, $color; - - public function __construct() { - $this->make = 'Honda'; - $this->model = 'Accord'; - $this->color = 'white'; - } - - public function setMake($make) { - $this->make = $make; - - // NOTE: Returning this for chaining - return $this; - } - - public function setModel($model) { - $this->model = $model; - - // NOTE: Returning this for chaining - return $this; - } - - public function setColor($color) { - $this->color = $color; - - // NOTE: Returning this for chaining - return $this; - } - - public function dump() { - var_dump($this->make, $this->model, $this->color); + public function manage() + { + $this->employee->work(); } } - -$car = (new Car()) - ->setColor('pink') - ->setMake('Ford') - ->setModel('F-150') - ->dump(); ``` -**[⬆ 返回顶部](#目录)** -### Prefer composition over inheritance -As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four, -you should prefer composition over inheritance where you can. There are lots of -good reasons to use inheritance and lots of good reasons to use composition. -The main point for this maxim is that if your mind instinctively goes for -inheritance, try to think if composition could model your problem better. In some -cases it can. - -You might be wondering then, "when should I use inheritance?" It -depends on your problem at hand, but this is a decent list of when inheritance -makes more sense than composition: - -1. Your inheritance represents an "is-a" relationship and not a "has-a" -relationship (Human->Animal vs. User->UserDetails). -2. You can reuse code from the base classes (Humans can move like all animals). -3. You want to make global changes to derived classes by changing a base class. -(Change the caloric expenditure of all animals when they move). +**好:** -**坏:** ```php -class Employee { - private $name, $email; - - public function __construct($name, $email) { - $this->name = $name; - $this->email = $email; - } - - // ... +interface Employee +{ + public function work(); } -// Bad because Employees "have" tax data. -// EmployeeTaxData is not a type of Employee - -class EmployeeTaxData extends Employee { - private $ssn, $salary; - - public function __construct($name, $email, $ssn, $salary) { - parent::__construct($name, $email); - $this->ssn = $ssn; - $this->salary = $salary; +class Human implements Employee +{ + public function work() + { + // ....working } - - // ... } -``` -**好:** -```php -class EmployeeTaxData { - private $ssn, $salary; - - public function __construct($ssn, $salary) { - $this->ssn = $ssn; - $this->salary = $salary; +class Robot implements Employee +{ + public function work() + { + //.... working much more } - - // ... } -class Employee { - private $name, $email, $taxData; - - public function __construct($name, $email) { - $this->name = $name; - $this->email = $email; +class Manager +{ + private $employee; + + public function __construct(Employee $employee) + { + $this->employee = $employee; } - - public function setTaxData($ssn, $salary) { - $this->taxData = new EmployeeTaxData($ssn, $salary); + + public function manage() + { + $this->employee->work(); } - // ... } ``` + **[⬆ 返回顶部](#目录)** ## 别写重复代码 (DRY) @@ -1609,7 +1884,7 @@ updating multiple places anytime you want to change one thing. ```php function showDeveloperList($developers) { -    foreach ($developers as $developer) { + foreach ($developers as $developer) { $expectedSalary = $developer->calculateExpectedSalary(); $experience = $developer->getExperience(); $githubLink = $developer->getGithubLink(); @@ -1618,14 +1893,14 @@ function showDeveloperList($developers) $experience, $githubLink ]; - + render($data); } } function showManagerList($managers) { -    foreach ($managers as $manager) { + foreach ($managers as $manager) { $expectedSalary = $manager->calculateExpectedSalary(); $experience = $manager->getExperience(); $githubLink = $manager->getGithubLink(); @@ -1634,7 +1909,7 @@ function showManagerList($managers) $experience, $githubLink ]; - + render($data); } } @@ -1645,16 +1920,16 @@ function showManagerList($managers) ```php function showList($employees) { -    foreach ($employees as $employee) { -        $expectedSalary = $employee->calculateExpectedSalary(); -        $experience = $employee->getExperience(); -        $githubLink = $employee->getGithubLink(); + foreach ($employees as $employee) { + $expectedSalary = $employee->calculateExpectedSalary(); + $experience = $employee->getExperience(); + $githubLink = $employee->getGithubLink(); $data = [ $expectedSalary, $experience, $githubLink ]; - + render($data); } } @@ -1667,7 +1942,7 @@ It is better to use a compact version of the code. ```php function showList($employees) { -    foreach ($employees as $employee) { + foreach ($employees as $employee) { render([ $employee->calculateExpectedSalary(), $employee->getExperience(), @@ -1676,4 +1951,15 @@ function showList($employees) } } ``` -**[⬆ 返回顶部](#目录)** \ No newline at end of file + +**[⬆ 返回顶部](#目录)** + +## Translations + +This is also available in other languages: + + * ![cn](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/China.png) **Chinese:** + * [yangweijie/clean-code-php](https://github.com/yangweijie/clean-code-php) + * [php-cpm/clean-code-php](https://github.com/php-cpm/clean-code-php) + +**[⬆ back to top](#table-of-contents)** From 024130f428bd519bf70189febe13da786ededb8d Mon Sep 17 00:00:00 2001 From: gongbao Date: Fri, 15 Sep 2017 00:23:27 +0800 Subject: [PATCH 04/21] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=88=B05=E4=B8=AA?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=8E=9F=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 103 ++++++++++++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 9af5b3b6..5beda35b 100644 --- a/README.md +++ b/README.md @@ -175,12 +175,11 @@ saveCityZipCode($matches['city'], $matches['zipCode']); **[⬆ 返回顶部](#目录)** -### Avoid nesting too deeply and return early +### 避免深层潜逃,尽早返回 -Too many if else statemetns can make your code hard to follow. Explicit is better -than implicit. +太多的if else语句通常会是你的代码难以阅读,直白优于隐晦 -**Bad:** +**糟糕:** ```php function isShopOpen($day) @@ -206,7 +205,7 @@ function isShopOpen($day) } ``` -**Good:** +**好的:** ```php function isShopOpen($day) @@ -223,7 +222,7 @@ function isShopOpen($day) } ``` -**Bad:** +**糟糕的:** ```php function fibonacci($n) @@ -244,7 +243,7 @@ function fibonacci($n) } ``` -**Good:** +**好的:** ```php function fibonacci($n) @@ -753,9 +752,9 @@ And now you must use instance of `Configuration` in your application. **[⬆ 返回顶部](#目录)** -### Don't use a Singleton pattern +### 不要使用单例模式 -Singleton is a [anti-pattern](https://en.wikipedia.org/wiki/Singleton_pattern). +单例是一种 [anti-pattern](https://en.wikipedia.org/wiki/Singleton_pattern). **坏:** @@ -1019,26 +1018,29 @@ inventoryTracker('apples', $request, 'www.inventory-awesome.io'); **[⬆ 返回顶部](#目录)** -## Objects and Data Structures +## 对象和数据结构 -### Use getters and setters +### 使用 getters 和 setters +在PHP中你可以对方法使用`public`, `protected`, `private` 来控制对象属性的变更 +* 当你想对对象属性做获取之外的操作时,你不需要在代码中去寻找并修改每一个该属性访问方法 -In PHP you can set `public`, `protected` and `private` keywords for methods. -Using it, you can control properties modification on an object. - -* When you want to do more beyond getting an object property, you don't have -to look up and change every accessor in your codebase. * Makes adding validation simple when doing a `set`. +* 当有`set`对应的属性方法时,易于增加参数的验证 * Encapsulates the internal representation. +* 封装内部的表示 * Easy to add logging and error handling when getting and setting. +* 使用set*和get*时,易于增加日志和错误控制 * Inheriting this class, you can override default functionality. +* 继承当前类时,可以复写默认的方法功能 * You can lazy load your object's properties, let's say getting it from a server. +* 当对象属性是从其他服务获取时,get*,set*易于使用延迟加载 Additionally, this is part of Open/Closed principle, from object-oriented design principles. +此外,这样的方式也符合OOP开发中的开闭原则 -**坏:** +**糟糕:** ```php class BankAccount @@ -1096,8 +1098,9 @@ $balance = $bankAccount->getBalance(); **[⬆ 返回顶部](#目录)** ### Make objects have private/protected members +对象属性多使用private/protected 限定 -**坏:** +**糟糕:** ```php class Employee @@ -1138,17 +1141,15 @@ echo 'Employee name: '.$employee->getName(); // Employee name: John Doe **[⬆ 返回顶部](#目录)** -## Classes +## 类 -### Use method chaining +### 使用方法链 -This pattern is very useful and commonly used in many libraries such -as PHPUnit and Doctrine. It allows your code to be expressive, and less verbose. -For that reason, use method chaining and take a look at how clean your code -will be. In your class functions, simply use `return $this` at the end of every `set` function, -and you can chain further class methods onto it. +这是一种非常有用的,并且在其他类库中(PHPUnit 和 Doctrine)常用的模式 +它使你的代码更有表达力,减少冗余 +因为这个原因,来看看如何使用方法链来使你的代码变得清爽:在你的类的每一个`set`方法的最后简单的使用 `return $this`,然后进一步将类方法链起来 -**Bad:** +**糟糕的:** ```php class Car @@ -1185,7 +1186,7 @@ $car->setModel('F-150'); $car->dump(); ``` -**Good:** +**好的:** ```php class Car @@ -1197,7 +1198,7 @@ class Car public function setMake($make) { $this->make = $make; - + // NOTE: Returning this for chaining return $this; } @@ -1233,26 +1234,19 @@ $car = (new Car()) **[⬆ back to top](#table-of-contents)** -### Prefer composition over inheritance +### 组合优于基础 -As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four, -you should prefer composition over inheritance where you can. There are lots of -good reasons to use inheritance and lots of good reasons to use composition. -The main point for this maxim is that if your mind instinctively goes for -inheritance, try to think if composition could model your problem better. In some -cases it can. +正如之前所说[*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) the Gang of Four 所著, +我们应该尽量优先选择组合而不是继承的方式。使用继承和组合都有很多好处。 +这个准则的主要意义在于当你本能的使用继承时,试着思考一下`组合`是否能更好对你的需求建模。 +在一些情况下,是这样的。 -You might be wondering then, "when should I use inheritance?" It -depends on your problem at hand, but this is a decent list of when inheritance -makes more sense than composition: +接下来你或许会想,“那我应该在什么时候使用继承?” 答案依赖于你的问题,当然下面有一些何时继承比组合更好的说明: +1. 你的继承表达了“是一个”而不是“有一个”的关系(人类-》动物,用户-》用户详情) +2. 你可以复用基类的代码(人类可以像动物一样移动) +3. 你想通过修改基类对所有派生类做全局的修改(当动物移动时,修改她们的能量消耗) -1. Your inheritance represents an "is-a" relationship and not a "has-a" -relationship (Human->Animal vs. User->UserDetails). -2. You can reuse code from the base classes (Humans can move like all animals). -3. You want to make global changes to derived classes by changing a base class. -(Change the caloric expenditure of all animals when they move). - -**Bad:** +**糟糕的:** ```php class Employee @@ -1269,10 +1263,11 @@ class Employee // ... } -// Bad because Employees "have" tax data. -// EmployeeTaxData is not a type of Employee -class EmployeeTaxData extends Employee +// Employees "有" taxdata,EmployeeTaxData不是一种Employee,使用集成很糟糕 + + +class EmployeeTaxData extends Employee { private $ssn; private $salary; @@ -1331,13 +1326,13 @@ class Employee ## SOLID -**SOLID** is the mnemonic acronym introduced by Michael Feathers for the first five principles named by Robert Martin, which meant five basic principles of object-oriented programming and design. +**SOLID** 是Michael Feathers推荐的便于记忆的首字母简写,它代表了Robert Martin命名的最重要的五个面对对象编码设计原则 + * [S: 指责单一原则 (SRP)](#single-responsibility-principle-srp) + * [O: 开闭原则原则 (OCP)](#openclosed-principle-ocp) + * [L: 里氏替换原则 (LSP)](#liskov-substitution-principle-lsp) + * [I: 接口隔离原则 (ISP)](#interface-segregation-principle-isp) + * [D: 依赖反转原则 (DIP)](#dependency-inversion-principle-dip) - * [S: Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) - * [O: Open/Closed Principle (OCP)](#openclosed-principle-ocp) - * [L: Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) - * [I: Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) - * [D: Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) ### Single Responsibility Principle (SRP) From c72fd1cc356d822015c20db68658114966ef8a3b Mon Sep 17 00:00:00 2001 From: Dacheng Gao Date: Fri, 15 Sep 2017 17:16:06 +0800 Subject: [PATCH 05/21] =?UTF-8?q?fixed:=20=E6=96=87=E6=9C=AB=E7=9A=84?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E9=A1=B6=E9=83=A8=E9=93=BE=E6=8E=A5=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9af5b3b6..f4f06e7c 100644 --- a/README.md +++ b/README.md @@ -1962,4 +1962,4 @@ This is also available in other languages: * [yangweijie/clean-code-php](https://github.com/yangweijie/clean-code-php) * [php-cpm/clean-code-php](https://github.com/php-cpm/clean-code-php) -**[⬆ back to top](#table-of-contents)** +**[⬆ back to top](#目录)** From 62917f175501c45dc8063a90e652b55c6e48fe8c Mon Sep 17 00:00:00 2001 From: gongbao Date: Sat, 16 Sep 2017 04:49:56 +0800 Subject: [PATCH 06/21] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 98 ++++++++++++++++--------------------------------------- 1 file changed, 28 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 5beda35b..14c6cd74 100644 --- a/README.md +++ b/README.md @@ -1336,14 +1336,8 @@ class Employee ### Single Responsibility Principle (SRP) -As stated in Clean Code, "There should never be more than one reason for a class -to change". It's tempting to jam-pack a class with a lot of functionality, like -when you can only take one suitcase on your flight. The issue with this is -that your class won't be conceptually cohesive and it will give it many reasons -to change. Minimizing the amount of times you need to change a class is important. -It's important because if too much functionality is in one class and you modify a piece of it, -it can be difficult to understand how that will affect other dependent modules in -your codebase. +正如在Clean Code所述,"应该只为一个理由去修改类"。人们总是易于用一堆方法塞满一个类,如同我们只能在飞机上只能携带一个行李箱(把所有的东西都塞到箱子里)。这样做的问题是:从概念上这样的类不是高内聚的,并且留下了很多理由去修改它。将你需要修改类的次数降低到最小很重要。 +这是因为,当有很多方法在类中时,修改其中一处,你很难知晓在代码库中哪些依赖的模块会被影响到 **坏:** @@ -1374,7 +1368,7 @@ class UserSettings **好:** ```php -class UserAuth +class UserAuth { private $user; @@ -1382,19 +1376,19 @@ class UserAuth { $this->user = $user; } - + public function verifyCredentials() { // ... } } -class UserSettings +class UserSettings { private $user; private $auth; - public function __construct($user) + public function __construct($user) { $this->user = $user; $this->auth = new UserAuth($user); @@ -1411,12 +1405,9 @@ class UserSettings **[⬆ 返回顶部](#目录)** -### Open/Closed Principle (OCP) - -As stated by Bertrand Meyer, "software entities (classes, modules, functions, -etc.) should be open for extension, but closed for modification." What does that -mean though? This principle basically states that you should allow users to -add new functionalities without changing existing code. +### 开闭原则 (OCP) +正如Bertrand Meyer所述,"软件的工件(classes, modules, functions,等), +应该对扩展开放,对修改关闭" 然而这句话意味着什么呢?这个原则大体上表示你应该允许在不改变已有代码的情况下增加新的功能 **坏:** @@ -1525,20 +1516,10 @@ class HttpRequester **[⬆ 返回顶部](#目录)** -### Liskov Substitution Principle (LSP) - -This is a scary term for a very simple concept. It's formally defined as "If S -is a subtype of T, then objects of type T may be replaced with objects of type S -(i.e., objects of type S may substitute objects of type T) without altering any -of the desirable properties of that program (correctness, task performed, -etc.)." That's an even scarier definition. +### 里氏替换原则 (LSP) +对一个简单的概念来说这是一个让人望而却步的术语。它的正式定义是"如果S是T的子类,在不改变程序原有既定属性的前提下,任何T的对象都可以使用S的对象替代(例如,使用S的对象可以替代T的对象)"这貌似是更吓人的阐述 -The best explanation for this is if you have a parent class and a child class, -then the base class and child class can be used interchangeably without getting -incorrect results. This might still be confusing, so let's take a look at the -classic Square-Rectangle example. Mathematically, a square is a rectangle, but -if you model it using the "is-a" relationship via inheritance, you quickly -get into trouble. +对这个概念最好的解释是:如果你有一个父类和一个子类,在不改变原有结果正确性的前提下父类和子类可以互换。这个听起来依旧让人有些迷惑,所以让我们来看一个经典的正方形-长方形的例子。从数学上讲,正方形是一种长方形,但是当你的模型通过继承使用了"is-a"的关系时,你将发现你遇到了麻烦 **坏:** @@ -1655,7 +1636,7 @@ function renderLargeRectangles($rectangles) $rectangle->setHeight(5); } - $area = $rectangle->getArea(); + $area = $rectangle->getArea(); $rectangle->render($area); } } @@ -1666,15 +1647,11 @@ renderLargeRectangles($shapes); **[⬆ 返回顶部](#目录)** -### Interface Segregation Principle (ISP) +### 接口隔离原则 (ISP) -ISP states that "Clients should not be forced to depend upon interfaces that -they do not use." +接口隔离原则表示:"委托方不应该被强制依赖于他不需要的接口" -A good example to look at that demonstrates this principle is for -classes that require large settings objects. Not requiring clients to setup -huge amounts of options is beneficial, because most of the time they won't need -all of the settings. Making them optional helps prevent having a "fat interface". +有一个清晰的例子来说明示范这条原则。当一个类需要一个大量的设置项,为了方便不会要求委托方去设置大量的选项,因为在大部分时间里他们不需要所有的设置项。使设置项可选有助于我们避免产生"胖接口" **坏:** @@ -1715,7 +1692,7 @@ class Robot implements Employee **好:** -Not every worker is an employee, but every employee is an worker. +不是每一个工人都是雇员,但是每一个雇员都是一个工人 ```php interface Workable @@ -1757,20 +1734,13 @@ class Robot implements Workable **[⬆ 返回顶部](#目录)** -### Dependency Inversion Principle (DIP) +### 依赖倒置原则 (DIP) -This principle states two essential things: -1. High-level modules should not depend on low-level modules. Both should -depend on abstractions. -2. Abstractions should not depend upon details. Details should depend on -abstractions. +这条原则说明两个基本的要点: +1.高阶的模块不应该依赖低阶的模块,它们都应该依赖于抽象 +2.抽象不应该依赖于实现,实现应该依赖于抽象 -This can be hard to understand at first, but if you've worked with PHP frameworks (like Symfony), you've seen an implementation of this principle in the form of Dependency -Injection (DI). While they are not identical concepts, DIP keeps high-level -modules from knowing the details of its low-level modules and setting them up. -It can accomplish this through DI. A huge benefit of this is that it reduces -the coupling between modules. Coupling is a very bad development pattern because -it makes your code hard to refactor. +这条起初看起来有点晦涩难懂,但是如果你使用过php框架(例如 Symfony),你应该见过依赖注入(DI)对这个概念的实现。虽然它们不是完全相通的概念,依赖倒置原则使高阶模块与低阶模块的实现细节和创建分离。可以使用依赖注入(DI)这种方式来实现它。更多的好处是它使模块之间解耦。耦合会导致你难于重构,它是一种非常糟糕的的开发模式 **坏:** @@ -1851,28 +1821,16 @@ class Manager ## 别写重复代码 (DRY) -Try to observe the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle. +试着去遵循[DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) 原则. -Do your absolute best to avoid duplicate code. Duplicate code is bad because -it means that there's more than one place to alter something if you need to -change some logic. +尽你最大的努力去避免复制代码,它是一种非常糟糕的行为,复制代码通常意味着当你需要变更一些逻辑时,你需要修改不止一处 -Imagine if you run a restaurant and you keep track of your inventory: all your -tomatoes, onions, garlic, spices, etc. If you have multiple lists that -you keep this on, then all have to be updated when you serve a dish with -tomatoes in them. If you only have one list, there's only one place to update! +试想一下,如果你在经营一家餐厅并且你在记录你仓库的进销记录:所有的土豆,洋葱,大蒜,辣椒等。 +如果你有多个列表来管理进销记录,当你用其中一些土豆做菜时你需要更新所有的列表。如果你只有一个列表,只有一个地方需要更新 -Oftentimes you have duplicate code because you have two or more slightly -different things, that share a lot in common, but their differences force you -to have two or more separate functions that do much of the same things. Removing -duplicate code means creating an abstraction that can handle this set of different -things with just one function/module/class. +通常情况下你复制代码是应该有两个或者多个略微不同的逻辑,它们大多数都是一样的,但是由于它们的区别致使你必须有两个或者多个隔离的但大部分相同的方法,移除重复的代码意味着用一个function/module/class创建一个能处理差异的抽象 -Getting the abstraction right is critical, that's why you should follow the -SOLID principles laid out in the [Classes](#classes) section. Bad abstractions can be -worse than duplicate code, so be careful! Having said this, if you can make -a good abstraction, do it! Don't repeat yourself, otherwise you'll find yourself -updating multiple places anytime you want to change one thing. +正确的抽象是非常关键的,这正是为什么你必须学习遵守在[Classes](#classes)章节展开的SOLID原则,不合理的抽象比复制代码更糟糕,所有务必谨慎!说到这么多,如果你能设计一个合理的抽象,实现它!不要重复,否则你会发现任何时候当你想修改一个逻辑时你必须修改多个地方 **Bad:** From d620bb1b01999c2b641a776c63846350ae1d5f94 Mon Sep 17 00:00:00 2001 From: gongbao Date: Sat, 16 Sep 2017 04:51:46 +0800 Subject: [PATCH 07/21] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=AC=94=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14c6cd74..4b2bb768 100644 --- a/README.md +++ b/README.md @@ -1234,7 +1234,7 @@ $car = (new Car()) **[⬆ back to top](#table-of-contents)** -### 组合优于基础 +### 组合优于继承 正如之前所说[*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) the Gang of Four 所著, 我们应该尽量优先选择组合而不是继承的方式。使用继承和组合都有很多好处。 From 96dfde7353a154d221070f892b766bf3643b229f Mon Sep 17 00:00:00 2001 From: zouyi6 Date: Sat, 16 Sep 2017 09:47:10 +0800 Subject: [PATCH 08/21] fix all links && sync contents to main repo && few bugs --- README.md | 677 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 374 insertions(+), 303 deletions(-) diff --git a/README.md b/README.md index daafb46c..8170bf31 100644 --- a/README.md +++ b/README.md @@ -9,43 +9,51 @@ * [使用便于搜索的名称 (part 1)](#使用便于搜索的名称part-1) * [使用便于搜索的名称 (part 2)](#使用便于搜索的名称part-2) * [使用自解释型变量](#使用自解释型变量) + * [使用自解释型变量](#使用自解释型变量) + * [避免深层嵌套,尽早返回 (part 1)](#避免深层嵌套,尽早返回-part-1) + * [避免深层嵌套,尽早返回 (part 2)](#避免深层嵌套,尽早返回-part-2) * [少用无意义的变量名](#少用无意义的变量名) - * [Don't add unneeded context](#dont-add-unneeded-context) + * [不要添加不必要上下文](#不要添加不必要上下文) * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值,没必要在方法里再做默认值检测) 3. [函数](#函数) - * [Function arguments (2 or fewer ideally)](#function-arguments-2-or-fewer-ideally) - * [Functions should do one thing](#functions-should-do-one-thing) - * [Function names should say what they do](#function-names-should-say-what-they-do) - * [Functions should only be one level of abstraction](#functions-should-only-be-one-level-of-abstraction) - * [Don't use flags as function parameters](#dont-use-flags-as-function-parameters) - * [Avoid Side Effects](#avoid-side-effects) - * [Don't write to global functions](#dont-write-to-global-functions) - * [Don't use a Singleton pattern](#dont-use-a-singleton-pattern) - * [Encapsulate conditionals](#encapsulate-conditionals) - * [Avoid negative conditionals](#avoid-negative-conditionals) - * [Avoid conditionals](#avoid-conditionals) - * [Avoid type-checking (part 1)](#avoid-type-checking-part-1) - * [Avoid type-checking (part 2)](#avoid-type-checking-part-2) - * [Remove dead code](#remove-dead-code) - 4. [对象和数据结构 Objects and Data Structures](#objects-and-data-structures) - * [Use getters and setters](#use-getters-and-setters) - * [Make objects have private/protected members](#make-objects-have-privateprotected-members) - 5. [Classes](#classes) - * [Use method chaining](#use-method-chaining) - * [Prefer composition over inheritance](#prefer-composition-over-inheritance) + * [函数参数(最好少于2个)](#函数参数-最好少于2个) + * [函数应该只做一件事](#函数应该只做一件事) + * [函数名应该是有意义的动词(或表明具体做了什么事)](#函数名应该是有意义的动词-或表明具体做了什么事) + * [函数里应当只有一层抽象abstraction](#函数里应当只有一层抽象abstraction) + * [不要用flag作为函数的参数](#不要用flag作为函数的参数) + * [避免副作用](#避免副作用) + * [不要写全局函数](#不要写全局函数) + * [不要使用单例模式](#不要使用单例模式) + * [封装条件语句](#封装条件语句) + * [避免用反义条件判断](#避免用反义条件判断) + * [避免条件判断](#避免条件判断) + * [避免类型检查 (part 1)](#避免类型检查-part-1) + * [避免类型检查 (part 2)](#避免类型检查-part-2) + * [移除僵尸代码](#移除僵尸代码) + 4. [对象和数据结构 Objects and Data Structures](#对象和数据结构) + * [使用 getters 和 setters Use object encapsulation](#使用-getters-和-setters) + * [对象属性多使用private/protected限定](#对象属性多使用private/protected限定) + 5. [类](#类) + * [组合优于继承](#组合优于继承) + * [避免连贯接口](#避免连贯接口) 6. [类的SOLID原则 SOLID](#solid) - * [S: 单一功能 Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) - * [O: 开闭原则 Open/Closed Principle (OCP)](#openclosed-principle-ocp) - * [L: 里氏替换 Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) - * [I: 接口隔离 Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) - * [D: 依赖反转 Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) - 7. [Don’t repeat yourself (DRY)](#dont-repeat-yourself-dry) - 8. [Translations](#translations) + * [S: 职责单一原则 Single Responsibility Principle (SRP)](#职责单一原则single-responsibility-principle-srp) + * [O: 开闭原则 Open/Closed Principle (OCP)](#开闭原则openclosed-principle-ocp) + * [L: 里氏替换原则 Liskov Substitution Principle (LSP)](#里氏替换原则liskov-substitution-principle-lsp) + * [I: 接口隔离原则 Interface Segregation Principle (ISP)](#接口隔离原则interface-segregation-principle-isp) + * [D: 依赖反转原则 Dependency Inversion Principle (DIP)](#依赖反转原则dependency-inversion-principle-dip) + 7. [别写重复代码 (DRY)](#别写重复代码-dry) + 8. [翻译](#翻译) ## 介绍 本文由 php-cpm 基于 yangweijie 的[clen php code](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容,欢迎大家指正。 +原文更新频率较高,我的翻译方法是直接用文本比较工具逐行对比。优先保证文字内容是最新的,再逐步提升翻译质量。 + +欢迎大家积极提PR。 + + 本文参考自 Robert C. Martin的[*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) 书中的软件工程师的原则 ,适用于PHP。 这不是风格指南。 这是一个关于开发可读、可复用并且可重构的PHP软件指南。 @@ -54,6 +62,7 @@ 本文受到 [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) 的启发 ## **变量** + ### 使用见字知意的变量名 **坏:** @@ -105,7 +114,6 @@ $result = $serializer->serialize($data, 448); $json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); ``` -**[⬆ 返回顶部](#目录)** ### 使用便于搜索的名称 (part 2) @@ -157,7 +165,7 @@ $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; preg_match($cityZipCodeRegex, $address, $matches); -list(, $city, $zipCode) = $matches; +[, $city, $zipCode] = $matches; saveCityZipCode($city, $zipCode); ``` @@ -175,14 +183,14 @@ saveCityZipCode($matches['city'], $matches['zipCode']); **[⬆ 返回顶部](#目录)** -### 避免深层潜逃,尽早返回 +### 避免深层嵌套,尽早返回 (part 1) -太多的if else语句通常会是你的代码难以阅读,直白优于隐晦 +太多的if else语句通常会导致你的代码难以阅读,直白优于隐晦 **糟糕:** ```php -function isShopOpen($day) +function isShopOpen($day): bool { if ($day) { if (is_string($day)) { @@ -208,9 +216,9 @@ function isShopOpen($day) **好的:** ```php -function isShopOpen($day) +function isShopOpen(string $day): bool { - if (empty($day) && ! is_string($day)) { + if (empty($day)) { return false; } @@ -222,10 +230,14 @@ function isShopOpen($day) } ``` +**[⬆ 返回顶部](#目录)** + +### 避免深层嵌套,尽早返回 (part 2) + **糟糕的:** ```php -function fibonacci($n) +function fibonacci(int $n) { if ($n < 50) { if ($n !== 0) { @@ -246,7 +258,7 @@ function fibonacci($n) **好的:** ```php -function fibonacci($n) +function fibonacci(int $n): int { if ($n === 0) { return 0; @@ -257,14 +269,14 @@ function fibonacci($n) } if ($n > 50) { - return 'Not supported'; + throw new \Exception('Not supported'); } return fibonacci($n - 1) + fibonacci($n - 2); } ``` -**[⬆ back to top](#table-of-contents)** +**[⬆ 返回顶部](#目录)** ### 少用无意义的变量名 @@ -339,23 +351,23 @@ class Car ### 合理使用参数默认值,没必要在方法里再做默认值检测 -**Not good:** +**不好:** This is not good because `$breweryName` can be `NULL`. ```php -function createMicrobrewery($breweryName = 'Hipster Brew Co.') +function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void {    // ... } ``` -**Not bad:** +**还行:** This opinion is more understandable than the previous version, but it better controls the value of the variable. ```php -function createMicrobrewery($name = null) +function createMicrobrewery($name = null): void {    $breweryName = $name ?: 'Hipster Brew Co.'; // ... @@ -367,7 +379,7 @@ function createMicrobrewery($name = null) If you support only PHP 7+, then you can use [type hinting](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) and be sure that the `$breweryName` will not be `NULL`. ```php -function createMicrobrewery(string $breweryName = 'Hipster Brew Co.') +function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void {    // ... } @@ -384,13 +396,14 @@ function createMicrobrewery(string $breweryName = 'Hipster Brew Co.') **坏:** ```php -function createMenu($title, $body, $buttonText, $cancellable) +function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void { // ... } ``` **好:** + ```php class MenuConfig { @@ -406,7 +419,7 @@ $config->body = 'Bar'; $config->buttonText = 'Baz'; $config->cancellable = true; -function createMenu(MenuConfig $config) +function createMenu(MenuConfig $config): void { // ... } @@ -414,13 +427,12 @@ function createMenu(MenuConfig $config) **[⬆ 返回顶部](#目录)** - ### 函数应该只做一件事 这是迄今为止软件工程里最重要的一个规则。当一个函数做超过一件事的时候,他们就难于实现、测试和理解。当你把一个函数拆分到只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。 **坏:** ```php -function emailClients($clients) +function emailClients(array $clients): void { foreach ($clients as $client) { $clientRecord = $db->find($client); @@ -434,18 +446,18 @@ function emailClients($clients) **好:** ```php -function emailClients($clients) +function emailClients(array $clients): void { $activeClients = activeClients($clients); array_walk($activeClients, 'email'); } -function activeClients($clients) +function activeClients(array $clients): array { return array_filter($clients, 'isClientActive'); } -function isClientActive($client) +function isClientActive(int $client): bool { $clientRecord = $db->find($client); @@ -463,14 +475,14 @@ class Email { //... - public function handle() + public function handle(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); -// 啥?handle处理一个消息干嘛了? +// 啥?handle处理一个消息干嘛了?是往一个文件里写码? $message->handle(); ``` @@ -481,7 +493,7 @@ class Email { //... - public function send() + public function send(): void { mail($this->to, $this->subject, $this->body); } @@ -494,7 +506,7 @@ $message->send(); **[⬆ 返回顶部](#目录)** -### 函数应当只有一层抽象abstraction +### 函数里应当只有一层抽象abstraction 当你抽象层次过多时时,函数处理的事情太多了。需要拆分功能来提高可重用性和易用性,以便简化测试。 (译者注:这里从示例代码看应该是指嵌套过多) @@ -502,7 +514,7 @@ $message->send(); **坏:** ```php -function parseBetterJSAlternative($code) +function parseBetterJSAlternative(string $code): void { $regexes = [ // ... @@ -532,7 +544,7 @@ function parseBetterJSAlternative($code) 我们把一些方法从循环中提取出来,但是`parseBetterJSAlternative()`方法还是很复杂,而且不利于测试。 ```php -function tokenize($code) +function tokenize(string $code): array { $regexes = [ // ... @@ -549,7 +561,7 @@ function tokenize($code) return $tokens; } -function lexer($tokens) +function lexer(array $tokens): array { $ast = []; foreach ($tokens as $token) { @@ -559,7 +571,7 @@ function lexer($tokens) return $ast; } -function parseBetterJSAlternative($code) +function parseBetterJSAlternative(string $code): void { $tokens = tokenize($code); $ast = lexer($tokens); @@ -576,7 +588,7 @@ function parseBetterJSAlternative($code) ```php class Tokenizer { - public function tokenize($code) + public function tokenize(string $code): array { $regexes = [ // ... @@ -596,7 +608,7 @@ class Tokenizer class Lexer { - public function lexify($tokens) + public function lexify(array $tokens): array { $ast = []; foreach ($tokens as $token) { @@ -618,7 +630,7 @@ class BetterJSAlternative $this->lexer = $lexer; } - public function parse($code) + public function parse(string $code): void { $tokens = $this->tokenizer->tokenize($code); $ast = $this->lexer->lexify($tokens); @@ -639,7 +651,7 @@ flag就是在告诉大家,这个方法里处理很多事。前面刚说过, **坏:** ```php -function createFile($name, $temp = false) +function createFile(string $name, bool $temp = false): void { if ($temp) { touch('./temp/'.$name); @@ -652,12 +664,12 @@ function createFile($name, $temp = false) **好:** ```php -function createFile($name) +function createFile(string $name): void { touch($name); } -function createTempFile($name) +function createTempFile(string $name): void { touch('./temp/'.$name); } @@ -678,7 +690,7 @@ function createTempFile($name) // If we had another function that used this name, now it'd be an array and it could break it. $name = 'Ryan McDermott'; -function splitIntoFirstAndLastName() +function splitIntoFirstAndLastName(): void { global $name; @@ -691,8 +703,9 @@ var_dump($name); // ['Ryan', 'McDermott']; ``` **好:** + ```php -function splitIntoFirstAndLastName($name) +function splitIntoFirstAndLastName(string $name): array { return explode(' ', $name); } @@ -707,12 +720,15 @@ var_dump($newName); // ['Ryan', 'McDermott']; **[⬆ 返回顶部](#目录)** ### 不要写全局函数 -在大多数语言中污染全局变量是一个坏的实践,因为你可能和其他类库冲突并且你api的用户不明白为什么直到他们获得产品的一个异常。让我们看一个例子:如果你想配置一个数组,你可能会写一个全局函数像`config()`,但是可能和试着做同样事的其他类库冲突。这就是为什么单例设计模式和简单配置会更好的原因。 +在大多数语言中污染全局变量是一个坏的实践,因为你可能和其他类库冲突 +并且调用你api的人直到他们捕获异常才知道踩坑了。让我们思考一种场景: +如果你想配置一个数组,你可能会写一个全局函数`config()`,但是他可能 +和试着做同样事的其他类库冲突。 **坏:** ```php -function config() +function config(): array { return [ 'foo' => 'bar', @@ -732,7 +748,7 @@ class Configuration $this->configuration = $configuration; } - public function get($key) + public function get(string $key): ?string { return isset($this->configuration[$key]) ? $this->configuration[$key] : null; } @@ -754,7 +770,13 @@ And now you must use instance of `Configuration` in your application. ### 不要使用单例模式 -单例是一种 [anti-pattern](https://en.wikipedia.org/wiki/Singleton_pattern). +单例是一种 [反模式](https://en.wikipedia.org/wiki/Singleton_pattern). Paraphrased from Brian Button: + 1. They are generally used as a **global instance**, why is that so bad? Because **you hide the dependencies** of your application in your code, instead of exposing them through the interfaces. Making something global to avoid passing it around is a [code smell](https://en.wikipedia.org/wiki/Code_smell). + 2. They violate the [single responsibility principle](#single-responsibility-principle-srp): by virtue of the fact that **they control their own creation and lifecycle**. + 3. They inherently cause code to be tightly [coupled](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29). This makes faking them out under **test rather difficult** in many cases. + 4. They carry state around for the lifetime of the application. Another hit to testing since **you can end up with a situation where tests need to be ordered** which is a big no for unit tests. Why? Because each unit test should be independent from the other. + +There is also very good thoughts by [Misko Hevery](http://misko.hevery.com/about/) about the [root of problem](http://misko.hevery.com/2008/08/25/root-cause-of-singletons/). **坏:** @@ -763,12 +785,12 @@ class DBConnection { private static $instance; - private function __construct($dsn) + private function __construct(string $dsn) { // ... } - public static function getInstance() + public static function getInstance(): DBConnection { if (self::$instance === null) { self::$instance = new self(); @@ -788,7 +810,7 @@ $singleton = DBConnection::getInstance(); ```php class DBConnection { - public function __construct(array $dsn) + public function __construct(string $dsn) { // ... } @@ -831,7 +853,7 @@ if ($article->isPublished()) { **坏:** ```php -function isDOMNodeNotPresent($node) +function isDOMNodeNotPresent(\DOMNode $node): bool { // ... } @@ -845,7 +867,7 @@ if (!isDOMNodeNotPresent($node)) **好:** ```php -function isDOMNodePresent($node) +function isDOMNodePresent(\DOMNode $node): bool { // ... } @@ -857,10 +879,14 @@ if (isDOMNodePresent($node)) { **[⬆ 返回顶部](#目录)** -### 避免条件声明 +### 避免条件判断 这看起来像一个不可能任务。当人们第一次听到这句话是都会这么说。 -"没有一个`if声明`" 答案是你可以使用多态来达到许多case语句里的任务。第二个问题很常见, “那么为什么我要那么做?” 答案是前面我们学过的一个整洁代码原则:一个函数应当只做一件事。当你有类和函数有很多`if`声明,你自己知道你的函数做了不止一件事。记住,只做一件事。 +"没有`if语句`我还能做啥?" 答案是你可以使用多态来实现多种场景 +的相同任务。第二个问题很常见, “这么做可以,但为什么我要这么做?” + 答案是前面我们学过的一个Clean Code原则:一个函数应当只做一件事。 + 当你有很多含有`if`语句的类和函数时,你的函数做了不止一件事。 + 记住,只做一件事。 **坏:** @@ -869,7 +895,7 @@ class Airplane { // ... - public function getCruisingAltitude() + public function getCruisingAltitude(): int { switch ($this->type) { case '777': @@ -890,14 +916,14 @@ interface Airplane { // ... - public function getCruisingAltitude(); + public function getCruisingAltitude(): int; } class Boeing777 implements Airplane { // ... - public function getCruisingAltitude() + public function getCruisingAltitude(): int { return $this->getMaxAltitude() - $this->getPassengerCount(); } @@ -907,7 +933,7 @@ class AirForceOne implements Airplane { // ... - public function getCruisingAltitude() + public function getCruisingAltitude(): int { return $this->getMaxAltitude(); } @@ -917,7 +943,7 @@ class Cessna implements Airplane { // ... - public function getCruisingAltitude() + public function getCruisingAltitude(): int { return $this->getMaxAltitude() - $this->getFuelExpenditure(); } @@ -927,6 +953,7 @@ class Cessna implements Airplane **[⬆ 返回顶部](#目录)** ### 避免类型检查 (part 1) + PHP是弱类型的,这意味着你的函数可以接收任何类型的参数。 有时候你为这自由所痛苦并且在你的函数渐渐尝试类型检查。 有很多方法去避免这么做。第一种是统一API。 @@ -934,10 +961,10 @@ PHP是弱类型的,这意味着你的函数可以接收任何类型的参数。 **坏:** ```php -function travelToTexas($vehicle) +function travelToTexas($vehicle): void { if ($vehicle instanceof Bicycle) { - $vehicle->peddleTo(new Location('texas')); + $vehicle->pedalTo(new Location('texas')); } elseif ($vehicle instanceof Car) { $vehicle->driveTo(new Location('texas')); } @@ -947,11 +974,12 @@ function travelToTexas($vehicle) **好:** ```php -function travelToTexas(Traveler $vehicle) +function travelToTexas(Traveler $vehicle): void { $vehicle->travelTo(new Location('texas')); } ``` + **[⬆ 返回顶部](#目录)** ### 避免类型检查 (part 2) @@ -963,7 +991,7 @@ function travelToTexas(Traveler $vehicle) **坏:** ```php -function combine($val1, $val2) +function combine($val1, $val2): int { if (!is_numeric($val1) || !is_numeric($val2)) { throw new \Exception('Must be of type Number'); @@ -976,11 +1004,12 @@ function combine($val1, $val2) **好:** ```php -function combine(int $val1, int $val2) +function combine(int $val1, int $val2): int { return $val1 + $val2; } ``` + **[⬆ 返回顶部](#目录)** ### 移除僵尸代码 @@ -989,12 +1018,12 @@ function combine(int $val1, int $val2) **坏:** ```php -function oldRequestModule($url) +function oldRequestModule(string $url): void { // ... } -function newRequestModule($url) +function newRequestModule(string $url): void { // ... } @@ -1006,7 +1035,7 @@ inventoryTracker('apples', $request, 'www.inventory-awesome.io'); **好:** ```php -function requestModule($url) +function requestModule(string $url): void { // ... } @@ -1021,24 +1050,16 @@ inventoryTracker('apples', $request, 'www.inventory-awesome.io'); ## 对象和数据结构 ### 使用 getters 和 setters -在PHP中你可以对方法使用`public`, `protected`, `private` 来控制对象属性的变更 -* 当你想对对象属性做获取之外的操作时,你不需要在代码中去寻找并修改每一个该属性访问方法 +在PHP中你可以对方法使用`public`, `protected`, `private` 来控制对象属性的变更。 -* Makes adding validation simple when doing a `set`. +* 当你想对对象属性做获取之外的操作时,你不需要在代码中去寻找并修改每一个该属性访问方法 * 当有`set`对应的属性方法时,易于增加参数的验证 -* Encapsulates the internal representation. * 封装内部的表示 -* Easy to add logging and error handling when getting and setting. * 使用set*和get*时,易于增加日志和错误控制 -* Inheriting this class, you can override default functionality. * 继承当前类时,可以复写默认的方法功能 -* You can lazy load your object's properties, let's say getting it from a -server. -* 当对象属性是从其他服务获取时,get*,set*易于使用延迟加载 +* 当对象属性是从远端服务器获取时,get*,set*易于使用延迟加载 -Additionally, this is part of Open/Closed principle, from object-oriented -design principles. -此外,这样的方式也符合OOP开发中的开闭原则 +此外,这样的方式也符合OOP开发中的[开闭原则](#openclosed-principle-ocp) **糟糕:** @@ -1061,12 +1082,12 @@ class BankAccount { private $balance; - public function __construct($balance = 1000) + public function __construct(int $balance = 1000) { $this->balance = $balance; } - public function withdrawBalance($amount) + public function withdrawBalance(int $amount): void { if ($amount > $this->balance) { throw new \Exception('Amount greater than available balance.'); @@ -1075,12 +1096,12 @@ class BankAccount $this->balance -= $amount; } - public function depositBalance($amount) + public function depositBalance(int $amount): void { $this->balance += $amount; } - public function getBalance() + public function getBalance(): int { return $this->balance; } @@ -1097,8 +1118,8 @@ $balance = $bankAccount->getBalance(); **[⬆ 返回顶部](#目录)** -### Make objects have private/protected members -对象属性多使用private/protected 限定 +### 对象属性多使用private/protected限定 + **糟糕:** @@ -1107,7 +1128,7 @@ class Employee { public $name; - public function __construct($name) + public function __construct(string $name) { $this->name = $name; } @@ -1124,12 +1145,12 @@ class Employee { private $name; - public function __construct($name) + public function __construct(string $name) { $this->name = $name; } - public function getName() + public function getName(): string { return $this->name; } @@ -1143,100 +1164,9 @@ echo 'Employee name: '.$employee->getName(); // Employee name: John Doe ## 类 -### 使用方法链 - -这是一种非常有用的,并且在其他类库中(PHPUnit 和 Doctrine)常用的模式 -它使你的代码更有表达力,减少冗余 -因为这个原因,来看看如何使用方法链来使你的代码变得清爽:在你的类的每一个`set`方法的最后简单的使用 `return $this`,然后进一步将类方法链起来 - -**糟糕的:** - -```php -class Car -{ - private $make = 'Honda'; - private $model = 'Accord'; - private $color = 'white'; - - public function setMake($make) - { - $this->make = $make; - } - - public function setModel($model) - { - $this->model = $model; - } - - public function setColor($color) - { - $this->color = $color; - } - - public function dump() - { - var_dump($this->make, $this->model, $this->color); - } -} - -$car = new Car(); -$car->setColor('pink'); -$car->setMake('Ford'); -$car->setModel('F-150'); -$car->dump(); -``` - -**好的:** - -```php -class Car -{ - private $make = 'Honda'; - private $model = 'Accord'; - private $color = 'white'; - - public function setMake($make) - { - $this->make = $make; - - // NOTE: Returning this for chaining - return $this; - } - - public function setModel($model) - { - $this->model = $model; - - // NOTE: Returning this for chaining - return $this; - } - - public function setColor($color) - { - $this->color = $color; - - // NOTE: Returning this for chaining - return $this; - } - - public function dump() - { - var_dump($this->make, $this->model, $this->color); - } -} - -$car = (new Car()) - ->setColor('pink') - ->setMake('Ford') - ->setModel('F-150') - ->dump(); -``` - -**[⬆ back to top](#table-of-contents)** - ### 组合优于继承 -正如之前所说[*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) the Gang of Four 所著, +正如 the Gang of Four 所著的[*设计模式*](https://en.wikipedia.org/wiki/Design_Patterns)之前所说, 我们应该尽量优先选择组合而不是继承的方式。使用继承和组合都有很多好处。 这个准则的主要意义在于当你本能的使用继承时,试着思考一下`组合`是否能更好对你的需求建模。 在一些情况下,是这样的。 @@ -1254,7 +1184,7 @@ class Employee private $name; private $email; - public function __construct($name, $email) + public function __construct(string $name, string $email) { $this->name = $name; $this->email = $email; @@ -1264,15 +1194,16 @@ class Employee } -// Employees "有" taxdata,EmployeeTaxData不是一种Employee,使用集成很糟糕 +// 不好,因为Employees "有" taxdata +// 而EmployeeTaxData不是Employee类型的 -class EmployeeTaxData extends Employee +class EmployeeTaxData extends Employee { private $ssn; private $salary; - public function __construct($name, $email, $ssn, $salary) + public function __construct(string $name, string $email, string $ssn, string $salary) { parent::__construct($name, $email); @@ -1284,7 +1215,7 @@ class EmployeeTaxData extends Employee } ``` -**Good:** +**好:** ```php class EmployeeTaxData @@ -1292,7 +1223,7 @@ class EmployeeTaxData private $ssn; private $salary; - public function __construct($ssn, $salary) + public function __construct(string $ssn, string $salary) { $this->ssn = $ssn; $this->salary = $salary; @@ -1307,13 +1238,13 @@ class Employee private $email; private $taxData; - public function __construct($name, $email) + public function __construct(string $name, string $email) { $this->name = $name; $this->email = $email; } - public function setTaxData($ssn, $salary) + public function setTaxData(string $ssn, string $salary) { $this->taxData = new EmployeeTaxData($ssn, $salary); } @@ -1322,22 +1253,131 @@ class Employee } ``` -**[⬆ back to top](#table-of-contents)** +**[⬆ 返回顶部](#目录)** + +### 避免连贯接口 + +[连贯接口Fluent interface](https://en.wikipedia.org/wiki/Fluent_interface)是一种 +旨在提高面向对象编程时代码可读性的API设计模式,他基于[方法链Method chaining](https://en.wikipedia.org/wiki/Method_chaining) + +While there can be some contexts, frequently builder objects, where this +pattern reduces the verbosity of the code (for example the [PHPUnit Mock Builder](https://phpunit.de/manual/current/en/test-doubles.html) +or the [Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html)), +more often it comes at some costs: + +1. Breaks [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29) +2. Breaks [Decorators](https://en.wikipedia.org/wiki/Decorator_pattern) +3. Is harder to [mock](https://en.wikipedia.org/wiki/Mock_object) in a test suite +4. Makes diffs of commits harder to read + +For more informations you can read the full [blog post](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) +on this topic written by [Marco Pivetta](https://github.com/Ocramius). + +**坏:** + +```php +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(); +``` + +**好:** + +```php +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 **SOLID** 是Michael Feathers推荐的便于记忆的首字母简写,它代表了Robert Martin命名的最重要的五个面对对象编码设计原则 - * [S: 指责单一原则 (SRP)](#single-responsibility-principle-srp) - * [O: 开闭原则原则 (OCP)](#openclosed-principle-ocp) - * [L: 里氏替换原则 (LSP)](#liskov-substitution-principle-lsp) - * [I: 接口隔离原则 (ISP)](#interface-segregation-principle-isp) - * [D: 依赖反转原则 (DIP)](#dependency-inversion-principle-dip) + + * [S: 职责单一原则 (SRP)](#职责单一原则-single-responsibility-principle-srp) + * [O: 开闭原则 (OCP)](#开闭原则-openclosed-principle-ocp) + * [L: 里氏替换原则 (LSP)](#里氏替换原则-liskov-substitution-principle-lsp) + * [I: 接口隔离原则 (ISP)](#接口隔离原则-interface-segregation-principle-isp) + * [D: 依赖反转原则 (DIP)](#依赖反转原则-dependency-inversion-principle-dip) -### Single Responsibility Principle (SRP) +### 职责单一原则 Single Responsibility Principle (SRP) -正如在Clean Code所述,"应该只为一个理由去修改类"。人们总是易于用一堆方法塞满一个类,如同我们只能在飞机上只能携带一个行李箱(把所有的东西都塞到箱子里)。这样做的问题是:从概念上这样的类不是高内聚的,并且留下了很多理由去修改它。将你需要修改类的次数降低到最小很重要。 -这是因为,当有很多方法在类中时,修改其中一处,你很难知晓在代码库中哪些依赖的模块会被影响到 +正如在Clean Code所述,"修改一个类应该只为一个理由"。 +人们总是易于用一堆方法塞满一个类,如同我们只能在飞机上 +只能携带一个行李箱(把所有的东西都塞到箱子里)。这样做 +的问题是:从概念上这样的类不是高内聚的,并且留下了很多 +理由去修改它。将你需要修改类的次数降低到最小很重要。 +这是因为,当有很多方法在类中时,修改其中一处,你很难知 +晓在代码库中哪些依赖的模块会被影响到。 **坏:** @@ -1346,19 +1386,19 @@ class UserSettings { private $user; - public function __construct($user) + public function __construct(User $user) { $this->user = $user; } - public function changeSettings($settings) + public function changeSettings(array $settings): void { if ($this->verifyCredentials()) { // ... } } - private function verifyCredentials() + private function verifyCredentials(): bool { // ... } @@ -1368,33 +1408,33 @@ class UserSettings **好:** ```php -class UserAuth +class UserAuth { private $user; - public function __construct($user) + public function __construct(User $user) { $this->user = $user; } - - public function verifyCredentials() + + public function verifyCredentials(): bool { // ... } } -class UserSettings +class UserSettings { private $user; private $auth; - public function __construct($user) + public function __construct(User $user) { $this->user = $user; $this->auth = new UserAuth($user); } - public function changeSettings($settings) + public function changeSettings(array $settings): void { if ($this->auth->verifyCredentials()) { // ... @@ -1405,9 +1445,11 @@ class UserSettings **[⬆ 返回顶部](#目录)** -### 开闭原则 (OCP) -正如Bertrand Meyer所述,"软件的工件(classes, modules, functions,等), -应该对扩展开放,对修改关闭" 然而这句话意味着什么呢?这个原则大体上表示你应该允许在不改变已有代码的情况下增加新的功能 +### 开闭原则 Open/Closed Principle (OCP) + +正如Bertrand Meyer所述,"软件的工件(classes, modules, functions,等) +应该对扩展开放,对修改关闭。" 然而这句话意味着什么呢?这个原则大体上表示你 +应该允许在不改变已有代码的情况下增加新的功能 **坏:** @@ -1416,7 +1458,7 @@ abstract class Adapter { protected $name; - public function getName() + public function getName(): string { return $this->name; } @@ -1446,12 +1488,12 @@ class HttpRequester { private $adapter; - public function __construct($adapter) + public function __construct(Adapter $adapter) { $this->adapter = $adapter; } - public function fetch($url) + public function fetch(string $url): Promise { $adapterName = $this->adapter->getName(); @@ -1462,12 +1504,12 @@ class HttpRequester } } - private function makeAjaxCall($url) + private function makeAjaxCall(string $url): Promise { // request and return promise } - private function makeHttpCall($url) + private function makeHttpCall(string $url): Promise { // request and return promise } @@ -1479,12 +1521,12 @@ class HttpRequester ```php interface Adapter { - public function request($url); + public function request(string $url): Promise; } class AjaxAdapter implements Adapter { - public function request($url) + public function request(string $url): Promise { // request and return promise } @@ -1492,7 +1534,7 @@ class AjaxAdapter implements Adapter class NodeAdapter implements Adapter { - public function request($url) + public function request(string $url): Promise { // request and return promise } @@ -1507,7 +1549,7 @@ class HttpRequester $this->adapter = $adapter; } - public function fetch($url) + public function fetch(string $url): Promise { return $this->adapter->request($url); } @@ -1516,10 +1558,17 @@ class HttpRequester **[⬆ 返回顶部](#目录)** -### 里氏替换原则 (LSP) -对一个简单的概念来说这是一个让人望而却步的术语。它的正式定义是"如果S是T的子类,在不改变程序原有既定属性的前提下,任何T的对象都可以使用S的对象替代(例如,使用S的对象可以替代T的对象)"这貌似是更吓人的阐述 +### 里氏替换原则 Liskov Substitution Principle (LSP) +这是一个简单的原则,却用了一个不好理解的术语。它的正式定义是 +"如果S是T的子类型,那么在不改变程序原有既定属性(检查、执行 +任务等)的前提下,任何T类型的对象都可以使用S类型的对象替代 +(例如,使用S的对象可以替代T的对象)" 这个定义更难理解:-)。 -对这个概念最好的解释是:如果你有一个父类和一个子类,在不改变原有结果正确性的前提下父类和子类可以互换。这个听起来依旧让人有些迷惑,所以让我们来看一个经典的正方形-长方形的例子。从数学上讲,正方形是一种长方形,但是当你的模型通过继承使用了"is-a"的关系时,你将发现你遇到了麻烦 +对这个概念最好的解释是:如果你有一个父类和一个子类,在不改变 +原有结果正确性的前提下父类和子类可以互换。这个听起来依旧让人 +有些迷惑,所以让我们来看一个经典的正方形-长方形的例子。从数学 +上讲,正方形是一种长方形,但是当你的模型通过继承使用了"is-a" +的关系时,就不对了。 **坏:** @@ -1529,22 +1578,22 @@ class Rectangle protected $width = 0; protected $height = 0; - public function render($area) + public function render(int $area): void { // ... } - public function setWidth($width) + public function setWidth(int $width): void { $this->width = $width; } - public function setHeight($height) + public function setHeight(int $height): void { $this->height = $height; } - public function getArea() + public function getArea(): int { return $this->width * $this->height; } @@ -1552,18 +1601,18 @@ class Rectangle class Square extends Rectangle { - public function setWidth($width) + public function setWidth(int $width): void { $this->width = $this->height = $width; } - public function setHeight(height) + public function setHeight(int $height): void { $this->width = $this->height = $height; } } -function renderLargeRectangles($rectangles) +function renderLargeRectangles(Rectangle $rectangles): void { foreach ($rectangles as $rectangle) { $rectangle->setWidth(4); @@ -1585,9 +1634,9 @@ abstract class Shape protected $width = 0; protected $height = 0; - abstract public function getArea(); + abstract public function getArea(): int; - public function render($area) + public function render(int $area): void { // ... } @@ -1595,17 +1644,17 @@ abstract class Shape class Rectangle extends Shape { - public function setWidth($width) + public function setWidth(int $width): void { $this->width = $width; } - public function setHeight($height) + public function setHeight(int $height): void { $this->height = $height; } - public function getArea() + public function getArea(): int { return $this->width * $this->height; } @@ -1615,18 +1664,18 @@ class Square extends Shape { private $length = 0; - public function setLength($length) + public function setLength(int $length): void { $this->length = $length; } - public function getArea() + public function getArea(): int { return pow($this->length, 2); } } -function renderLargeRectangles($rectangles) +function renderLargeRectangles(Shape $rectangles): void { foreach ($rectangles as $rectangle) { if ($rectangle instanceof Square) { @@ -1636,7 +1685,7 @@ function renderLargeRectangles($rectangles) $rectangle->setHeight(5); } - $area = $rectangle->getArea(); + $area = $rectangle->getArea(); $rectangle->render($area); } } @@ -1647,30 +1696,32 @@ renderLargeRectangles($shapes); **[⬆ 返回顶部](#目录)** -### 接口隔离原则 (ISP) +### 接口隔离原则 Interface Segregation Principle (ISP) -接口隔离原则表示:"委托方不应该被强制依赖于他不需要的接口" +接口隔离原则表示:"调用方不应该被强制依赖于他不需要的接口" -有一个清晰的例子来说明示范这条原则。当一个类需要一个大量的设置项,为了方便不会要求委托方去设置大量的选项,因为在大部分时间里他们不需要所有的设置项。使设置项可选有助于我们避免产生"胖接口" +有一个清晰的例子来说明示范这条原则。当一个类需要一个大量的设置项, +为了方便不会要求调用方去设置大量的选项,因为在通常他们不需要所有的 +设置项。使设置项可选有助于我们避免产生"胖接口" **坏:** ```php interface Employee { - public function work(); + public function work(): void; - public function eat(); + public function eat(): void; } class Human implements Employee { - public function work() + public function work(): void { // ....working } - public function eat() + public function eat(): void { // ...... eating in lunch break } @@ -1678,12 +1729,12 @@ class Human implements Employee class Robot implements Employee { - public function work() + public function work(): void { //.... working much more } - public function eat() + public function eat(): void { //.... robot can't eat, but it must implement this method } @@ -1697,12 +1748,12 @@ class Robot implements Employee ```php interface Workable { - public function work(); + public function work(): void; } interface Feedable { - public function eat(); + public function eat(): void; } interface Employee extends Feedable, Workable @@ -1711,12 +1762,12 @@ interface Employee extends Feedable, Workable class Human implements Employee { - public function work() + public function work(): void { // ....working } - public function eat() + public function eat(): void { //.... eating in lunch break } @@ -1725,7 +1776,7 @@ class Human implements Employee // robot can only work class Robot implements Workable { - public function work() + public function work(): void { // ....working } @@ -1734,20 +1785,23 @@ class Robot implements Workable **[⬆ 返回顶部](#目录)** -### 依赖倒置原则 (DIP) +### 依赖反转原则 Dependency Inversion Principle (DIP) 这条原则说明两个基本的要点: -1.高阶的模块不应该依赖低阶的模块,它们都应该依赖于抽象 -2.抽象不应该依赖于实现,实现应该依赖于抽象 +1. 高阶的模块不应该依赖低阶的模块,它们都应该依赖于抽象 +2. 抽象不应该依赖于实现,实现应该依赖于抽象 -这条起初看起来有点晦涩难懂,但是如果你使用过php框架(例如 Symfony),你应该见过依赖注入(DI)对这个概念的实现。虽然它们不是完全相通的概念,依赖倒置原则使高阶模块与低阶模块的实现细节和创建分离。可以使用依赖注入(DI)这种方式来实现它。更多的好处是它使模块之间解耦。耦合会导致你难于重构,它是一种非常糟糕的的开发模式 +这条起初看起来有点晦涩难懂,但是如果你使用过php框架(例如 Symfony),你应该见过 +依赖注入(DI)对这个概念的实现。虽然它们不是完全相通的概念,依赖倒置原则使高阶模块 +与低阶模块的实现细节和创建分离。可以使用依赖注入(DI)这种方式来实现它。更多的好处 +是它使模块之间解耦。耦合会导致你难于重构,它是一种非常糟糕的的开发模式 **坏:** ```php class Employee { - public function work() + public function work(): void { // ....working } @@ -1755,7 +1809,7 @@ class Employee class Robot extends Employee { - public function work() + public function work(): void { //.... working much more } @@ -1770,7 +1824,7 @@ class Manager $this->employee = $employee; } - public function manage() + public function manage(): void { $this->employee->work(); } @@ -1782,12 +1836,12 @@ class Manager ```php interface Employee { - public function work(); + public function work(): void; } class Human implements Employee { - public function work() + public function work(): void { // ....working } @@ -1795,7 +1849,7 @@ class Human implements Employee class Robot implements Employee { - public function work() + public function work(): void { //.... working much more } @@ -1810,7 +1864,7 @@ class Manager $this->employee = $employee; } - public function manage() + public function manage(): void { $this->employee->work(); } @@ -1823,19 +1877,28 @@ class Manager 试着去遵循[DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) 原则. -尽你最大的努力去避免复制代码,它是一种非常糟糕的行为,复制代码通常意味着当你需要变更一些逻辑时,你需要修改不止一处 +尽你最大的努力去避免复制代码,它是一种非常糟糕的行为,复制代码 +通常意味着当你需要变更一些逻辑时,你需要修改不止一处。 -试想一下,如果你在经营一家餐厅并且你在记录你仓库的进销记录:所有的土豆,洋葱,大蒜,辣椒等。 -如果你有多个列表来管理进销记录,当你用其中一些土豆做菜时你需要更新所有的列表。如果你只有一个列表,只有一个地方需要更新 +试想一下,如果你在经营一家餐厅并且你在记录你仓库的进销记录:所有 +的土豆,洋葱,大蒜,辣椒等。如果你有多个列表来管理进销记录,当你 +用其中一些土豆做菜时你需要更新所有的列表。如果你只有一个列表的话 +只有一个地方需要更新。 -通常情况下你复制代码是应该有两个或者多个略微不同的逻辑,它们大多数都是一样的,但是由于它们的区别致使你必须有两个或者多个隔离的但大部分相同的方法,移除重复的代码意味着用一个function/module/class创建一个能处理差异的抽象 +通常情况下你复制代码是应该有两个或者多个略微不同的逻辑,它们大多数 +都是一样的,但是由于它们的区别致使你必须有两个或者多个隔离的但大部 +分相同的方法,移除重复的代码意味着用一个function/module/class创 +建一个能处理差异的抽象。 -正确的抽象是非常关键的,这正是为什么你必须学习遵守在[Classes](#classes)章节展开的SOLID原则,不合理的抽象比复制代码更糟糕,所有务必谨慎!说到这么多,如果你能设计一个合理的抽象,实现它!不要重复,否则你会发现任何时候当你想修改一个逻辑时你必须修改多个地方 +正确的抽象是非常关键的,这正是为什么你必须学习遵守在[Classes](#classes)章节展开 +的SOLID原则,不合理的抽象比复制代码更糟糕,所有务必谨慎!说到这么多, +如果你能设计一个合理的抽象,实现它!不要重复,否则你会发现任何时候当你 +想修改一个逻辑时你必须修改多个地方。 -**Bad:** +**坏:** ```php -function showDeveloperList($developers) +function showDeveloperList(array $developers): void { foreach ($developers as $developer) { $expectedSalary = $developer->calculateExpectedSalary(); @@ -1851,7 +1914,7 @@ function showDeveloperList($developers) } } -function showManagerList($managers) +function showManagerList(array $managers): void { foreach ($managers as $manager) { $expectedSalary = $manager->calculateExpectedSalary(); @@ -1868,10 +1931,10 @@ function showManagerList($managers) } ``` -**Good:** +**好:** ```php -function showList($employees) +function showList(array $employees): void { foreach ($employees as $employee) { $expectedSalary = $employee->calculateExpectedSalary(); @@ -1888,12 +1951,12 @@ function showList($employees) } ``` -**Very good:** +**极好:** It is better to use a compact version of the code. ```php -function showList($employees) +function showList(array $employees): void { foreach ($employees as $employee) { render([ @@ -1907,12 +1970,20 @@ function showList($employees) **[⬆ 返回顶部](#目录)** -## Translations +## 翻译 -This is also available in other languages: +其他语言的翻译: - * ![cn](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/China.png) **Chinese:** +* :cn: **Chinese:** * [yangweijie/clean-code-php](https://github.com/yangweijie/clean-code-php) * [php-cpm/clean-code-php](https://github.com/php-cpm/clean-code-php) + * [gbcr/clean-code-php](https://github.com/gbcr/clean-code-php) +* :ru: **Russian:** + * [peter-gribanov/clean-code-php](https://github.com/peter-gribanov/clean-code-php) +* :brazil: **Portuguese:** + * [fabioars/clean-code-php](https://github.com/fabioars/clean-code-php) + * [jeanjar/clean-code-php](https://github.com/jeanjar/clean-code-php/tree/pt-br) +* :thailand: **Thai:** + * [panuwizzle/clean-code-php](https://github.com/panuwizzle/clean-code-php) -**[⬆ back to top](#目录)** +**[⬆ 返回顶部](#目录)** From 994eb74025c957a77848df80b52218b6ba3151ec Mon Sep 17 00:00:00 2001 From: zouyi6 Date: Sat, 16 Sep 2017 09:49:50 +0800 Subject: [PATCH 09/21] fix links --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8170bf31..88828428 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ * [使用便于搜索的名称 (part 2)](#使用便于搜索的名称part-2) * [使用自解释型变量](#使用自解释型变量) * [使用自解释型变量](#使用自解释型变量) - * [避免深层嵌套,尽早返回 (part 1)](#避免深层嵌套,尽早返回-part-1) - * [避免深层嵌套,尽早返回 (part 2)](#避免深层嵌套,尽早返回-part-2) + * [避免深层嵌套,尽早返回 (part 1)](#避免深层嵌套尽早返回-part-1) + * [避免深层嵌套,尽早返回 (part 2)](#避免深层嵌套尽早返回-part-2) * [少用无意义的变量名](#少用无意义的变量名) * [不要添加不必要上下文](#不要添加不必要上下文) - * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值,没必要在方法里再做默认值检测) + * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值没必要在方法里再做默认值检测) 3. [函数](#函数) * [函数参数(最好少于2个)](#函数参数-最好少于2个) * [函数应该只做一件事](#函数应该只做一件事) - * [函数名应该是有意义的动词(或表明具体做了什么事)](#函数名应该是有意义的动词-或表明具体做了什么事) + * [函数名应该是有意义的动词(或表明具体做了什么事)](#函数名应该是有意义的动词或表明具体做了什么事) * [函数里应当只有一层抽象abstraction](#函数里应当只有一层抽象abstraction) * [不要用flag作为函数的参数](#不要用flag作为函数的参数) * [避免副作用](#避免副作用) @@ -32,16 +32,16 @@ * [移除僵尸代码](#移除僵尸代码) 4. [对象和数据结构 Objects and Data Structures](#对象和数据结构) * [使用 getters 和 setters Use object encapsulation](#使用-getters-和-setters) - * [对象属性多使用private/protected限定](#对象属性多使用private/protected限定) + * [对象属性多使用private/protected限定](#对象属性多使用privateprotected限定) 5. [类](#类) * [组合优于继承](#组合优于继承) * [避免连贯接口](#避免连贯接口) 6. [类的SOLID原则 SOLID](#solid) - * [S: 职责单一原则 Single Responsibility Principle (SRP)](#职责单一原则single-responsibility-principle-srp) - * [O: 开闭原则 Open/Closed Principle (OCP)](#开闭原则openclosed-principle-ocp) - * [L: 里氏替换原则 Liskov Substitution Principle (LSP)](#里氏替换原则liskov-substitution-principle-lsp) - * [I: 接口隔离原则 Interface Segregation Principle (ISP)](#接口隔离原则interface-segregation-principle-isp) - * [D: 依赖反转原则 Dependency Inversion Principle (DIP)](#依赖反转原则dependency-inversion-principle-dip) + * [S: 职责单一原则 Single Responsibility Principle (SRP)](#职责单一原则-single-responsibility-principle-srp) + * [O: 开闭原则 Open/Closed Principle (OCP)](#开闭原则-openclosed-principle-ocp) + * [L: 里氏替换原则 Liskov Substitution Principle (LSP)](#里氏替换原则-liskov-substitution-principle-lsp) + * [I: 接口隔离原则 Interface Segregation Principle (ISP)](#接口隔离原则-interface-segregation-principle-isp) + * [D: 依赖反转原则 Dependency Inversion Principle (DIP)](#依赖反转原则-dependency-inversion-principle-dip) 7. [别写重复代码 (DRY)](#别写重复代码-dry) 8. [翻译](#翻译) From c948daecfbe53a89b83536513a09d3fe5dc145d4 Mon Sep 17 00:00:00 2001 From: zouyi6 Date: Sat, 16 Sep 2017 10:08:55 +0800 Subject: [PATCH 10/21] sync travis files && modify contribute announce --- .travis-build.php | 70 +++++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 11 ++++++++ README.md | 14 ++++++---- 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 .travis-build.php create mode 100644 .travis.yml diff --git a/.travis-build.php b/.travis-build.php new file mode 100644 index 00000000..93333509 --- /dev/null +++ b/.travis-build.php @@ -0,0 +1,70 @@ +setFlags(SplFileObject::DROP_NEW_LINE); + +$cliRedBackground = "\033[37;41m"; +$cliReset = "\033[0m"; +$exitStatus = 0; + +$indentationSteps = 3; +$manIndex = 0; +$linesWithSpaces = []; +$tableOfContentsStarted = null; +$currentTableOfContentsChapters = []; +$chaptersFound = []; +foreach ($readMeFile as $lineNumber => $line) { + if (preg_match('/\s$/', $line)) { + $linesWithSpaces[] = sprintf('%5s: %s', 1 + $lineNumber, $line); + } + if (preg_match('/^(?##+)\s(?.+)/', $line, $matches)) { + if (null === $tableOfContentsStarted) { + $tableOfContentsStarted = true; + continue; + } + $tableOfContentsStarted = false; + + $chaptersFound[] = sprintf('%s [%s](#%s)', + strlen($matches['depth']) === 2 + ? sprintf(' %s.', ++$manIndex) + : ' *' + , + $matches['title'], + preg_replace(['/ /', '/[^-\w]+/'], ['-', ''], strtolower($matches['title'])) + ); + } + if ($tableOfContentsStarted === true && isset($line[0])) { + $currentTableOfContentsChapters[] = $line; + } +} + +if (count($linesWithSpaces)) { + fwrite(STDERR, sprintf("${cliRedBackground}The following lines end with a space character:${cliReset}\n%s\n\n", + implode(PHP_EOL, $linesWithSpaces) + )); + $exitStatus = 1; +} + +$currentTableOfContentsChaptersFilename = __DIR__ . '/current-chapters'; +$chaptersFoundFilename = __DIR__ . '/chapters-found'; + +file_put_contents($currentTableOfContentsChaptersFilename, implode(PHP_EOL, $currentTableOfContentsChapters)); +file_put_contents($chaptersFoundFilename, implode(PHP_EOL, $chaptersFound)); + +$tableOfContentsDiff = shell_exec(sprintf('diff --unified %s %s', + escapeshellarg($currentTableOfContentsChaptersFilename), + escapeshellarg($chaptersFoundFilename) +)); + +@ unlink($currentTableOfContentsChaptersFilename); +@ unlink($chaptersFoundFilename); + +if (!empty($tableOfContentsDiff)) { + fwrite(STDERR, sprintf("${cliRedBackground}The table of contents is not aligned:${cliReset}\n%s\n\n", + $tableOfContentsDiff + )); + $exitStatus = 1; +} + +exit($exitStatus); diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..e4ea3575 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: php + +sudo: false + +php: + - nightly + +script: php .travis-build.php + +notifications: + email: false diff --git a/README.md b/README.md index 88828428..c7185b7b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # Clean Code PHP +## 翻译说明 + +本文由 php-cpm 基于 [yangweijie版本](https://github.com/yangweijie/clean-code-php) 的[clean-code-php](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容。 + +原文更新频率较高,我的翻译方法是直接用文本比较工具逐行对比。优先保证文字内容是最新的,再逐步提升翻译质量。 + +阅读过程中如果遇到各种链接失效、内容老旧、术语使用错误和其他翻译错误等问题,欢迎大家积极提交PR。 + ## 目录 1. [介绍](#介绍) @@ -47,12 +55,6 @@ ## 介绍 -本文由 php-cpm 基于 yangweijie 的[clen php code](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容,欢迎大家指正。 - -原文更新频率较高,我的翻译方法是直接用文本比较工具逐行对比。优先保证文字内容是最新的,再逐步提升翻译质量。 - -欢迎大家积极提PR。 - 本文参考自 Robert C. Martin的[*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) 书中的软件工程师的原则 ,适用于PHP。 这不是风格指南。 这是一个关于开发可读、可复用并且可重构的PHP软件指南。 From 07bcc2d7f772e3a5873a55e52fac5ba33bc4fa56 Mon Sep 17 00:00:00 2001 From: zouyi6 <zouyi6@wanda.cn> Date: Sat, 16 Sep 2017 10:15:46 +0800 Subject: [PATCH 11/21] version announce --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c7185b7b..4accc892 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ 阅读过程中如果遇到各种链接失效、内容老旧、术语使用错误和其他翻译错误等问题,欢迎大家积极提交PR。 +虽然很多开发者还在使用PHP5,但是本文中的大部分示例的运行环境需要PHP 7.1+。 + ## 目录 1. [介绍](#介绍) From c7b37e74358d90968ed4f1d206c459e2a6842d89 Mon Sep 17 00:00:00 2001 From: zouyi6 <zouyi6@wanda.cn> Date: Tue, 26 Sep 2017 07:24:01 +0800 Subject: [PATCH 12/21] add more trans --- README.md | 62 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 4accc892..f0d31613 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,5 @@ # Clean Code PHP -## 翻译说明 - -本文由 php-cpm 基于 [yangweijie版本](https://github.com/yangweijie/clean-code-php) 的[clean-code-php](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容。 - -原文更新频率较高,我的翻译方法是直接用文本比较工具逐行对比。优先保证文字内容是最新的,再逐步提升翻译质量。 - -阅读过程中如果遇到各种链接失效、内容老旧、术语使用错误和其他翻译错误等问题,欢迎大家积极提交PR。 - -虽然很多开发者还在使用PHP5,但是本文中的大部分示例的运行环境需要PHP 7.1+。 - ## 目录 1. [介绍](#介绍) @@ -65,6 +55,16 @@ 本文受到 [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) 的启发 +虽然很多开发者还在使用PHP5,但是本文中的大部分示例的运行环境需要PHP 7.1+。 + +## 翻译说明 + +本文由 php-cpm 基于 [yangweijie版本](https://github.com/yangweijie/clean-code-php) 的[clean-code-php](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容。 + +原文更新频率较高,我的翻译方法是直接用文本比较工具逐行对比。优先保证文字内容是最新的,再逐步提升翻译质量。 + +阅读过程中如果遇到各种链接失效、内容老旧、术语使用错误和其他翻译错误等问题,欢迎大家积极提交PR。 + ## **变量** ### 使用见字知意的变量名 @@ -230,7 +230,7 @@ function isShopOpen(string $day): bool 'friday', 'saturday', 'sunday' ]; - return in_array(strtolower($day), $openingDays); + return in_array(strtolower($day), $openingDays, true); } ``` @@ -264,12 +264,8 @@ function fibonacci(int $n) ```php function fibonacci(int $n): int { - if ($n === 0) { - return 0; - } - - if ($n === 1) { - return 1; + if ($n === 0 || $n === 1) { + return $n; } if ($n > 50) { @@ -580,7 +576,7 @@ function parseBetterJSAlternative(string $code): void $tokens = tokenize($code); $ast = lexer($tokens); foreach ($ast as $node) { - // parse... + // 解析逻辑... } } ``` @@ -639,7 +635,7 @@ class BetterJSAlternative $tokens = $this->tokenizer->tokenize($code); $ast = $this->lexer->lexify($tokens); foreach ($ast as $node) { - // parse... + // 解析逻辑... } } } @@ -769,18 +765,17 @@ $configuration = new Configuration([ And now you must use instance of `Configuration` in your application. - **[⬆ 返回顶部](#目录)** ### 不要使用单例模式 -单例是一种 [反模式](https://en.wikipedia.org/wiki/Singleton_pattern). Paraphrased from Brian Button: - 1. They are generally used as a **global instance**, why is that so bad? Because **you hide the dependencies** of your application in your code, instead of exposing them through the interfaces. Making something global to avoid passing it around is a [code smell](https://en.wikipedia.org/wiki/Code_smell). - 2. They violate the [single responsibility principle](#single-responsibility-principle-srp): by virtue of the fact that **they control their own creation and lifecycle**. - 3. They inherently cause code to be tightly [coupled](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29). This makes faking them out under **test rather difficult** in many cases. - 4. They carry state around for the lifetime of the application. Another hit to testing since **you can end up with a situation where tests need to be ordered** which is a big no for unit tests. Why? Because each unit test should be independent from the other. +单例是一种 [反模式](https://en.wikipedia.org/wiki/Singleton_pattern). 以下是解释:Paraphrased from Brian Button: + 1. 总是被用成全局实例。They are generally used as a **global instance**, why is that so bad? Because **you hide the dependencies** of your application in your code, instead of exposing them through the interfaces. Making something global to avoid passing it around is a [code smell](https://en.wikipedia.org/wiki/Code_smell). + 2. 违反了[单一响应原则]()They violate the [single responsibility principle](#single-responsibility-principle-srp): by virtue of the fact that **they control their own creation and lifecycle**. + 3. 导致代码强耦合They inherently cause code to be tightly [coupled](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29). This makes faking them out under **test rather difficult** in many cases. + 4. 在整个程序的生命周期中始终携带状态。They carry state around for the lifetime of the application. Another hit to testing since **you can end up with a situation where tests need to be ordered** which is a big no for unit tests. Why? Because each unit test should be independent from the other. -There is also very good thoughts by [Misko Hevery](http://misko.hevery.com/about/) about the [root of problem](http://misko.hevery.com/2008/08/25/root-cause-of-singletons/). +这里有一篇非常好的讨论单例模式的[根本问题((http://misko.hevery.com/2008/08/25/root-cause-of-singletons/)的文章,是[Misko Hevery](http://misko.hevery.com/about/) 写的。 **坏:** @@ -1124,6 +1119,13 @@ $balance = $bankAccount->getBalance(); ### 对象属性多使用private/protected限定 +* 对`public`方法和属性进行修改非常危险,因为外部代码容易依赖他,而你没办法控制。**对之修改影响所有这个类的使用者。** `public` methods and properties are most dangerous for changes, because some outside code may easily rely on them and you can't control what code relies on them. **Modifications in class are dangerous for all users of class.** +* 对`protected`的修改跟对`public`修改差不多危险,因为他们对子类可用,他俩的唯一区别就是可调用的位置不一样,**对之修改影响所有集成这个类的地方。** `protected` modifier are as dangerous as public, because they are available in scope of any child class. This effectively means that difference between public and protected is only in access mechanism, but encapsulation guarantee remains the same. **Modifications in class are dangerous for all descendant classes.** +* 对`private`的修改保证了这部分代码**只会影响当前类**`private` modifier guarantees that code is **dangerous to modify only in boundaries of single class** (you are safe for modifications and you won't have [Jenga effect](http://www.urbandictionary.com/define.php?term=Jengaphobia&defid=2494196)). + +所以,当你需要控制类里的代码可以被访问时才用`public/protected`,其他时候都用`private`。 + +可以读一读这篇 [博客文章](http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html) ,[Fabien Potencier](https://github.com/fabpot)写的. **糟糕:** @@ -1175,7 +1177,9 @@ echo 'Employee name: '.$employee->getName(); // Employee name: John Doe 这个准则的主要意义在于当你本能的使用继承时,试着思考一下`组合`是否能更好对你的需求建模。 在一些情况下,是这样的。 -接下来你或许会想,“那我应该在什么时候使用继承?” 答案依赖于你的问题,当然下面有一些何时继承比组合更好的说明: +接下来你或许会想,“那我应该在什么时候使用继承?” +答案依赖于你的问题,当然下面有一些何时继承比组合更好的说明: + 1. 你的继承表达了“是一个”而不是“有一个”的关系(人类-》动物,用户-》用户详情) 2. 你可以复用基类的代码(人类可以像动物一样移动) 3. 你想通过修改基类对所有派生类做全局的修改(当动物移动时,修改她们的能量消耗) @@ -1979,11 +1983,11 @@ function showList(array $employees): void 其他语言的翻译: * :cn: **Chinese:** - * [yangweijie/clean-code-php](https://github.com/yangweijie/clean-code-php) * [php-cpm/clean-code-php](https://github.com/php-cpm/clean-code-php) - * [gbcr/clean-code-php](https://github.com/gbcr/clean-code-php) * :ru: **Russian:** * [peter-gribanov/clean-code-php](https://github.com/peter-gribanov/clean-code-php) +* :es: **Spanish:** + * [fikoborquez/clean-code-php](https://github.com/fikoborquez/clean-code-php) * :brazil: **Portuguese:** * [fabioars/clean-code-php](https://github.com/fabioars/clean-code-php) * [jeanjar/clean-code-php](https://github.com/jeanjar/clean-code-php/tree/pt-br) From 30deb02e0a303b66f3a7fdbc277437c7456ef32e Mon Sep 17 00:00:00 2001 From: zouyi6 <zouyi6@wanda.cn> Date: Mon, 20 Nov 2017 10:10:19 +0800 Subject: [PATCH 13/21] update --- README.md | 72 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index f0d31613..ec8fa837 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,11 @@ getUserProfile(); ```php getUser(); ``` + **[⬆ 返回顶部](#目录)** ### 使用便于搜索的名称 (part 1) + 写代码是用来读的。所以写出可读性高、便于搜索的代码至关重要。 命名变量时如果没有有意义、不好理解,那就是在伤害读者。 请让你的代码便于搜索。 @@ -148,13 +150,13 @@ if ($user->access & User::ACCESS_UPDATE) { **[⬆ 返回顶部](#目录)** - ### 使用自解释型变量 **坏:** + ```php $address = 'One Infinite Loop, Cupertino 95014'; -$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; +$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches[1], $matches[2]); @@ -166,7 +168,7 @@ saveCityZipCode($matches[1], $matches[2]); ```php $address = 'One Infinite Loop, Cupertino 95014'; -$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; +$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); [, $city, $zipCode] = $matches; @@ -179,7 +181,7 @@ saveCityZipCode($city, $zipCode); ```php $address = 'One Infinite Loop, Cupertino 95014'; -$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(?<city>.+?)\s*(?<zipCode>\d{5})?$/'; +$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches['city'], $matches['zipCode']); @@ -217,7 +219,7 @@ function isShopOpen($day): bool } ``` -**好的:** +**好:** ```php function isShopOpen(string $day): bool @@ -259,7 +261,7 @@ function fibonacci(int $n) } ``` -**好的:** +**好:** ```php function fibonacci(int $n): int @@ -353,7 +355,7 @@ class Car **不好:** -This is not good because `$breweryName` can be `NULL`. +不好,`$breweryName` 可能为 `NULL`. ```php function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void @@ -364,7 +366,7 @@ function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void **还行:** -This opinion is more understandable than the previous version, but it better controls the value of the variable. +比上一个好理解一些,但最好能控制变量的值 ```php function createMicrobrewery($name = null): void @@ -388,7 +390,9 @@ function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void **[⬆ 返回顶部](#目录)** ## **函数** + ### 函数参数(最好少于2个) + 限制函数参数个数极其重要,这样测试你的函数容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。 无参数是理想情况。1个或2个都可以,最好避免3个。再多就需要加固了。通常如果你的函数有超过两个参数,说明他要处理的事太多了。 如果必须要传入很多数据,建议封装一个高级别对象作为参数。 @@ -428,9 +432,11 @@ function createMenu(MenuConfig $config): void **[⬆ 返回顶部](#目录)** ### 函数应该只做一件事 + 这是迄今为止软件工程里最重要的一个规则。当一个函数做超过一件事的时候,他们就难于实现、测试和理解。当你把一个函数拆分到只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。 **坏:** + ```php function emailClients(array $clients): void { @@ -464,6 +470,7 @@ function isClientActive(int $client): bool return $clientRecord->isActive(); } ``` + **[⬆ 返回顶部](#目录)** ### 函数名应该是有意义的动词(或表明具体做了什么事) @@ -677,6 +684,7 @@ function createTempFile(string $name): void **[⬆ 返回顶部](#目录)** ### 避免副作用 + 一个函数做了比获取一个值然后返回另外一个值或值们会产生副作用如果。副作用可能是写入一个文件,修改某些全局变量或者偶然的把你全部的钱给了陌生人。 现在,你的确需要在一个程序或者场合里要有副作用,像之前的例子,你也许需要写一个文件。你想要做的是把你做这些的地方集中起来。不要用几个函数和类来写入一个特定的文件。用一个服务来做它,一个只有一个。 @@ -755,7 +763,7 @@ class Configuration } ``` -Load configuration and create instance of `Configuration` class +加载配置并创建 `Configuration` 类的实例 ```php $configuration = new Configuration([ @@ -763,7 +771,7 @@ $configuration = new Configuration([ ]); ``` -And now you must use instance of `Configuration` in your application. +现在你必须在程序中用 `Configuration` 的实例了 **[⬆ 返回顶部](#目录)** @@ -818,13 +826,13 @@ class DBConnection } ``` -Create instance of `DBConnection` class and configure it with [DSN](http://php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters). +创建 `DBConnection` 类的实例并通过 [DSN](http://php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters) 配置. ```php $connection = new DBConnection($dsn); ``` -And now you must use instance of `DBConnection` in your application. +现在你必须在程序中 使用 `DBConnection` 的实例了 **[⬆ 返回顶部](#目录)** @@ -845,6 +853,7 @@ if ($article->isPublished()) { // ... } ``` + **[⬆ 返回顶部](#目录)** ### 避免用反义条件判断 @@ -982,6 +991,7 @@ function travelToTexas(Traveler $vehicle): void **[⬆ 返回顶部](#目录)** ### 避免类型检查 (part 2) + 如果你正使用基本原始值比如字符串、整形和数组,要求版本是PHP 7+,不用多态,需要类型检测, 那你应当考虑[类型声明](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration)或者严格模式。 提供了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。 @@ -1012,6 +1022,7 @@ function combine(int $val1, int $val2): int **[⬆ 返回顶部](#目录)** ### 移除僵尸代码 + 僵尸代码和重复代码一样坏。没有理由保留在你的代码库中。如果从来没被调用过,就删掉! 因为还在代码版本库里,因此很安全。 @@ -1049,6 +1060,7 @@ inventoryTracker('apples', $request, 'www.inventory-awesome.io'); ## 对象和数据结构 ### 使用 getters 和 setters + 在PHP中你可以对方法使用`public`, `protected`, `private` 来控制对象属性的变更。 * 当你想对对象属性做获取之外的操作时,你不需要在代码中去寻找并修改每一个该属性访问方法 @@ -1060,7 +1072,7 @@ inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 此外,这样的方式也符合OOP开发中的[开闭原则](#openclosed-principle-ocp) -**糟糕:** +**坏:** ```php class BankAccount @@ -1086,7 +1098,7 @@ class BankAccount $this->balance = $balance; } - public function withdrawBalance(int $amount): void + public function withdraw(int $amount): void { if ($amount > $this->balance) { throw new \Exception('Amount greater than available balance.'); @@ -1095,12 +1107,12 @@ class BankAccount $this->balance -= $amount; } - public function depositBalance(int $amount): void + public function deposit(int $amount): void { $this->balance += $amount; } - public function getBalance(): int +    public function getBalance(): int { return $this->balance; } @@ -1109,7 +1121,7 @@ class BankAccount $bankAccount = new BankAccount(); // Buy shoes... -$bankAccount->withdrawBalance($shoesPrice); +$bankAccount->withdraw($shoesPrice); // Get balance $balance = $bankAccount->getBalance(); @@ -1127,7 +1139,7 @@ $balance = $bankAccount->getBalance(); 可以读一读这篇 [博客文章](http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html) ,[Fabien Potencier](https://github.com/fabpot)写的. -**糟糕:** +**坏:** ```php class Employee @@ -1273,13 +1285,13 @@ pattern reduces the verbosity of the code (for example the [PHPUnit Mock Builder or the [Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html)), more often it comes at some costs: -1. Breaks [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29) -2. Breaks [Decorators](https://en.wikipedia.org/wiki/Decorator_pattern) -3. Is harder to [mock](https://en.wikipedia.org/wiki/Mock_object) in a test suite -4. Makes diffs of commits harder to read +1. 破坏了 [对象封装](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29) +2. 破坏了 [装饰器模式](https://en.wikipedia.org/wiki/Decorator_pattern) +3. 在测试组件中不好做[mock](https://en.wikipedia.org/wiki/Mock_object) +4. 导致提交的diff不好阅读 -For more informations you can read the full [blog post](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) -on this topic written by [Marco Pivetta](https://github.com/Ocramius). +了解更多请阅读 [连贯接口为什么不好](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) +,作者 [Marco Pivetta](https://github.com/Ocramius). **坏:** @@ -1620,7 +1632,10 @@ class Square extends Rectangle } } -function renderLargeRectangles(Rectangle $rectangles): void +/** + * @param Rectangle[] $rectangles + */ +function renderLargeRectangles(array $rectangles): void { foreach ($rectangles as $rectangle) { $rectangle->setWidth(4); @@ -1683,7 +1698,10 @@ class Square extends Shape } } -function renderLargeRectangles(Shape $rectangles): void +/** + * @param Rectangle[] $rectangles + */ +function renderLargeRectangles(array $rectangles): void { foreach ($rectangles as $rectangle) { if ($rectangle instanceof Square) { @@ -1961,7 +1979,7 @@ function showList(array $employees): void **极好:** -It is better to use a compact version of the code. +最好让代码紧凑一点 ```php function showList(array $employees): void From 97f728b150098ad5d176b8c4c525f1965d7c19aa Mon Sep 17 00:00:00 2001 From: zouyi6 <zouyi6@wanda.cn> Date: Mon, 25 Dec 2017 09:24:59 +0800 Subject: [PATCH 14/21] sync with src repo --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ec8fa837..5a1b032b 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ ## 翻译说明 -本文由 php-cpm 基于 [yangweijie版本](https://github.com/yangweijie/clean-code-php) 的[clean-code-php](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容。 +翻译完成度100%,最后更新时间2017-12-25。本文由 php-cpm 基于 [yangweijie版本](https://github.com/yangweijie/clean-code-php) 的[clean-code-php](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容。 原文更新频率较高,我的翻译方法是直接用文本比较工具逐行对比。优先保证文字内容是最新的,再逐步提升翻译质量。 @@ -2000,7 +2000,7 @@ function showList(array $employees): void 其他语言的翻译: -* :cn: **Chinese:** +* :cn: **Chinese:** * [php-cpm/clean-code-php](https://github.com/php-cpm/clean-code-php) * :ru: **Russian:** * [peter-gribanov/clean-code-php](https://github.com/peter-gribanov/clean-code-php) @@ -2011,5 +2011,7 @@ function showList(array $employees): void * [jeanjar/clean-code-php](https://github.com/jeanjar/clean-code-php/tree/pt-br) * :thailand: **Thai:** * [panuwizzle/clean-code-php](https://github.com/panuwizzle/clean-code-php) - +* :fr: **French:** + * [errorname/clean-code-php](https://github.com/errorname/clean-code-php) + **[⬆ 返回顶部](#目录)** From e5eb13c9ec2fb122ca60f169d5e91f68c76b3400 Mon Sep 17 00:00:00 2001 From: zouyi <zouyi@leying365.com> Date: Fri, 12 Jan 2018 12:02:12 +0800 Subject: [PATCH 15/21] update --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5a1b032b..5f2841ab 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ * [少用无意义的变量名](#少用无意义的变量名) * [不要添加不必要上下文](#不要添加不必要上下文) * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值没必要在方法里再做默认值检测) - 3. [函数](#函数) + 3. [Comparaison](#comparaison) + * [Use identical comparison](#identical_comparison) + 4. [函数](#函数) * [函数参数(最好少于2个)](#函数参数-最好少于2个) * [函数应该只做一件事](#函数应该只做一件事) * [函数名应该是有意义的动词(或表明具体做了什么事)](#函数名应该是有意义的动词或表明具体做了什么事) @@ -30,20 +32,20 @@ * [避免类型检查 (part 1)](#避免类型检查-part-1) * [避免类型检查 (part 2)](#避免类型检查-part-2) * [移除僵尸代码](#移除僵尸代码) - 4. [对象和数据结构 Objects and Data Structures](#对象和数据结构) + 5. [对象和数据结构 Objects and Data Structures](#对象和数据结构) * [使用 getters 和 setters Use object encapsulation](#使用-getters-和-setters) * [对象属性多使用private/protected限定](#对象属性多使用privateprotected限定) - 5. [类](#类) + 6. [类](#类) * [组合优于继承](#组合优于继承) * [避免连贯接口](#避免连贯接口) - 6. [类的SOLID原则 SOLID](#solid) + 7. [类的SOLID原则 SOLID](#solid) * [S: 职责单一原则 Single Responsibility Principle (SRP)](#职责单一原则-single-responsibility-principle-srp) * [O: 开闭原则 Open/Closed Principle (OCP)](#开闭原则-openclosed-principle-ocp) * [L: 里氏替换原则 Liskov Substitution Principle (LSP)](#里氏替换原则-liskov-substitution-principle-lsp) * [I: 接口隔离原则 Interface Segregation Principle (ISP)](#接口隔离原则-interface-segregation-principle-isp) * [D: 依赖反转原则 Dependency Inversion Principle (DIP)](#依赖反转原则-dependency-inversion-principle-dip) - 7. [别写重复代码 (DRY)](#别写重复代码-dry) - 8. [翻译](#翻译) + 8. [别写重复代码 (DRY)](#别写重复代码-dry) + 9. [翻译](#翻译) ## 介绍 @@ -120,7 +122,6 @@ $result = $serializer->serialize($data, 448); $json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); ``` - ### 使用便于搜索的名称 (part 2) **坏:** @@ -139,7 +140,7 @@ class User { const ACCESS_READ = 1; const ACCESS_CREATE = 2; -    const ACCESS_UPDATE = 4; + const ACCESS_UPDATE = 4; const ACCESS_DELETE = 8; } @@ -378,7 +379,7 @@ function createMicrobrewery($name = null): void **好:** -If you support only PHP 7+, then you can use [type hinting](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) and be sure that the `$breweryName` will not be `NULL`. +如果你的程序只支持 PHP 7+, 那你可以用 [type hinting](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) 保证变量 `$breweryName` 不是 `NULL`. ```php function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void @@ -389,6 +390,39 @@ function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void **[⬆ 返回顶部](#目录)** +## Comparison + +### Use [identical comparison](http://php.net/manual/en/language.operators.comparison.php) + +**不好:** + +```php +$a = '42'; +$b = 42; + +// 简易对比会将字符串转为整形 + +if( $a != $b ) { + //这里始终执行不到 +} + +``` +对比 $a != $b 返回了 false 但应该返回 true ! +字符串 '42' 跟整数 42 不相等 + +**好:** + +使用恒等判断检查类型和数据 +```php +if( $a !== $b ) { + //The expression is verified +} +``` + +The comparison $a !== $b return true. + +**[⬆ 返回顶部](#目录)** + ## **函数** ### 函数参数(最好少于2个) @@ -1280,6 +1314,10 @@ class Employee [连贯接口Fluent interface](https://en.wikipedia.org/wiki/Fluent_interface)是一种 旨在提高面向对象编程时代码可读性的API设计模式,他基于[方法链Method chaining](https://en.wikipedia.org/wiki/Method_chaining) +有上下文的地方可以降低代码复杂度,例如[PHPUnit Mock Builder](https://phpunit.de/manual/current/en/test-doubles.html) +和[Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html) +,更多的情况会带来较大代价: + While there can be some contexts, frequently builder objects, where this pattern reduces the verbosity of the code (for example the [PHPUnit Mock Builder](https://phpunit.de/manual/current/en/test-doubles.html) or the [Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html)), From 3328d4e2da44104749af2eb76e45a5cc3a88a78b Mon Sep 17 00:00:00 2001 From: zouyi <zouyi@leying365.com> Date: Thu, 19 Apr 2018 09:42:46 +0800 Subject: [PATCH 16/21] update --- README.md | 189 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 123 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 5f2841ab..ccc6a999 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,16 @@ 2. [变量](#变量) * [使用见字知意的变量名](#使用见字知意的变量名) * [同一个实体要用相同的变量名](#同一个实体要用相同的变量名) - * [使用便于搜索的名称 (part 1)](#使用便于搜索的名称part-1) - * [使用便于搜索的名称 (part 2)](#使用便于搜索的名称part-2) - * [使用自解释型变量](#使用自解释型变量) + * [使用便于搜索的名称 (part 1)](#使用便于搜索的名称-part-1) + * [使用便于搜索的名称 (part 2)](#使用便于搜索的名称-part-2) * [使用自解释型变量](#使用自解释型变量) * [避免深层嵌套,尽早返回 (part 1)](#避免深层嵌套尽早返回-part-1) * [避免深层嵌套,尽早返回 (part 2)](#避免深层嵌套尽早返回-part-2) * [少用无意义的变量名](#少用无意义的变量名) * [不要添加不必要上下文](#不要添加不必要上下文) * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值没必要在方法里再做默认值检测) - 3. [Comparaison](#comparaison) - * [Use identical comparison](#identical_comparison) + 3. [Comparison](#comparison) + * [Use identical comparison](#use-identical-comparison) 4. [函数](#函数) * [函数参数(最好少于2个)](#函数参数-最好少于2个) * [函数应该只做一件事](#函数应该只做一件事) @@ -38,6 +37,7 @@ 6. [类](#类) * [组合优于继承](#组合优于继承) * [避免连贯接口](#避免连贯接口) + * [Prefer `final` classes](#prefer-final-classes) 7. [类的SOLID原则 SOLID](#solid) * [S: 职责单一原则 Single Responsibility Principle (SRP)](#职责单一原则-single-responsibility-principle-srp) * [O: 开闭原则 Open/Closed Principle (OCP)](#开闭原则-openclosed-principle-ocp) @@ -396,30 +396,34 @@ function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void **不好:** +简易对比会将字符串转为整形 + ```php $a = '42'; $b = 42; -// 简易对比会将字符串转为整形 - if( $a != $b ) { //这里始终执行不到 } - ``` -对比 $a != $b 返回了 false 但应该返回 true ! + +对比 $a != $b 返回了 `FALSE` 但应该返回 `TRUE` ! 字符串 '42' 跟整数 42 不相等 **好:** 使用恒等判断检查类型和数据 + ```php -if( $a !== $b ) { - //The expression is verified +$a = '42'; +$b = 42; + +if ($a !== $b) { + // The expression is verified } ``` -The comparison $a !== $b return true. +The comparison `$a !== $b` returns `TRUE`. **[⬆ 返回顶部](#目录)** @@ -1416,6 +1420,74 @@ $car->dump(); **[⬆ 返回顶部](#目录)** +### Prefer final classes + +The `final` should be used whenever possible: + +1. It prevents uncontrolled inheritance chain. +2. It encourages [composition](#prefer-composition-over-inheritance). +3. It encourages the [Single Responsibility Pattern](#single-responsibility-principle-srp). +4. It encourages developers to use your public methods instead of extending the class to get access on protected ones. +5. It allows you to change your code without any break of applications that use your class. + +The only condition is that your class should implement an interface and no other public methods are defined. + +For more informations you can read [the blog post](https://ocramius.github.io/blog/when-to-declare-classes-final/) on this topic written by [Marco Pivetta (Ocramius)](https://ocramius.github.io/). + +**Bad:** + +```php +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; + } +} +``` + +**Good:** + +```php +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 **SOLID** 是Michael Feathers推荐的便于记忆的首字母简写,它代表了Robert Martin命名的最重要的五个面对对象编码设计原则 @@ -1636,11 +1708,6 @@ class Rectangle protected $width = 0; protected $height = 0; - public function render(int $area): void - { - // ... - } - public function setWidth(int $width): void { $this->width = $width; @@ -1670,48 +1737,44 @@ class Square extends Rectangle } } -/** - * @param Rectangle[] $rectangles - */ -function renderLargeRectangles(array $rectangles): void +function printArea(Rectangle $rectangle): 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); - } + $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 Rectangle(), new Square()]; -renderLargeRectangles($rectangles); +$rectangles = [new Rectangle(), new Square()]; + +foreach ($rectangles as $rectangle) { + printArea($rectangle); +} ``` **好:** -```php -abstract class Shape -{ - protected $width = 0; - protected $height = 0; +The best way is separate the quadrangles and allocation of a more general subtype for both shapes. - abstract public function getArea(): int; +Despite the apparent similarity of the square and the rectangle, they are different. +A square has much in common with a rhombus, and a rectangle with a parallelogram, but they are not subtype. +A square, a rectangle, a rhombus and a parallelogram are separate shapes with their own properties, albeit similar. - public function render(int $area): void - { - // ... - } +```php +interface Shape +{ + public function getArea(): int; } -class Rectangle extends Shape +class Rectangle implements Shape { - public function setWidth(int $width): void - { - $this->width = $width; - } + private $width = 0; + private $height = 0; - public function setHeight(int $height): void + public function __construct(int $width, int $height) { + $this->width = $width; $this->height = $height; } @@ -1721,41 +1784,31 @@ class Rectangle extends Shape } } -class Square extends Shape +class Square implements Shape { private $length = 0; - public function setLength(int $length): void + public function __construct(int $length) { $this->length = $length; } public function getArea(): int { - return pow($this->length, 2); - } +        return $this->length ** 2; +    } } -/** - * @param Rectangle[] $rectangles - */ -function renderLargeRectangles(array $rectangles): void +function printArea(Shape $shape): 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); - } + echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL; } -$shapes = [new Rectangle(), new Rectangle(), new Square()]; -renderLargeRectangles($shapes); +$shapes = [new Rectangle(4, 5), new Square(5)]; + +foreach ($shapes as $shape) { + printArea($shape); +} ``` **[⬆ 返回顶部](#目录)** @@ -2051,5 +2104,9 @@ function showList(array $employees): void * [panuwizzle/clean-code-php](https://github.com/panuwizzle/clean-code-php) * :fr: **French:** * [errorname/clean-code-php](https://github.com/errorname/clean-code-php) +* :vietnam: **Vietnamese** + * [viethuongdev/clean-code-php](https://github.com/viethuongdev/clean-code-php) +* :kr: **Korean:** + * [yujineeee/clean-code-php](https://github.com/yujineeee/clean-code-php) **[⬆ 返回顶部](#目录)** From 94b031009943daddfb99e588054fafff54b837db Mon Sep 17 00:00:00 2001 From: zouyi <zouyi@leying365.com> Date: Thu, 19 Apr 2018 10:15:08 +0800 Subject: [PATCH 17/21] update --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ccc6a999..28a1d808 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ * [少用无意义的变量名](#少用无意义的变量名) * [不要添加不必要上下文](#不要添加不必要上下文) * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值没必要在方法里再做默认值检测) - 3. [Comparison](#comparison) - * [Use identical comparison](#use-identical-comparison) + 3. [表达式](#表达式) + * [使用恒等式](#使用恒等式) 4. [函数](#函数) * [函数参数(最好少于2个)](#函数参数-最好少于2个) * [函数应该只做一件事](#函数应该只做一件事) @@ -37,7 +37,7 @@ 6. [类](#类) * [组合优于继承](#组合优于继承) * [避免连贯接口](#避免连贯接口) - * [Prefer `final` classes](#prefer-final-classes) + * [推荐使用 final 类](#推荐使用-final-类) 7. [类的SOLID原则 SOLID](#solid) * [S: 职责单一原则 Single Responsibility Principle (SRP)](#职责单一原则-single-responsibility-principle-srp) * [O: 开闭原则 Open/Closed Principle (OCP)](#开闭原则-openclosed-principle-ocp) @@ -390,9 +390,9 @@ function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void **[⬆ 返回顶部](#目录)** -## Comparison +## 表达式 -### Use [identical comparison](http://php.net/manual/en/language.operators.comparison.php) +### [使用恒等式](http://php.net/manual/en/language.operators.comparison.php) **不好:** @@ -1420,15 +1420,15 @@ $car->dump(); **[⬆ 返回顶部](#目录)** -### Prefer final classes +### 推荐使用 final 类 -The `final` should be used whenever possible: +能用时尽量使用 `final` 关键字: -1. It prevents uncontrolled inheritance chain. -2. It encourages [composition](#prefer-composition-over-inheritance). -3. It encourages the [Single Responsibility Pattern](#single-responsibility-principle-srp). -4. It encourages developers to use your public methods instead of extending the class to get access on protected ones. -5. It allows you to change your code without any break of applications that use your class. +1. 阻止不受控的继承链 +2. 鼓励 [组合](#prefer-composition-over-inheritance). +3. 鼓励 [单一职责模式](#single-responsibility-principle-srp). +4. 鼓励开发者用你的公开方法而非通过继承类获取受保护方法的访问权限. +5. 使得在不破坏使用你的类的应用的情况下修改代码成为可能. The only condition is that your class should implement an interface and no other public methods are defined. @@ -1755,11 +1755,11 @@ foreach ($rectangles as $rectangle) { **好:** -The best way is separate the quadrangles and allocation of a more general subtype for both shapes. +最好是将这两种四边形分别对待,用一个适合两种类型的更通用子类型来代替。 -Despite the apparent similarity of the square and the rectangle, they are different. -A square has much in common with a rhombus, and a rectangle with a parallelogram, but they are not subtype. -A square, a rectangle, a rhombus and a parallelogram are separate shapes with their own properties, albeit similar. +尽管正方形和长方形看起来很相似,但他们是不同的。 +正方形更接近菱形,而长方形更接近平行四边形。但他们不是子类型。 +尽管相似,正方形、长方形、菱形、平行四边形都是有自己属性的不同形状。 ```php interface Shape From 91cc9daeedcc49bd706c82b0825f3bb15aa8111f Mon Sep 17 00:00:00 2001 From: zouyikb <zouyi@knowbox.cn> Date: Wed, 28 Nov 2018 14:08:23 +0800 Subject: [PATCH 18/21] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=88=B0=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 97 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 28a1d808..26cfa1c0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ 4. [函数](#函数) * [函数参数(最好少于2个)](#函数参数-最好少于2个) * [函数应该只做一件事](#函数应该只做一件事) - * [函数名应该是有意义的动词(或表明具体做了什么事)](#函数名应该是有意义的动词或表明具体做了什么事) + * [函数名应体现他做了什么事](#函数名应体现他做了什么事) * [函数里应当只有一层抽象abstraction](#函数里应当只有一层抽象abstraction) * [不要用flag作为函数的参数](#不要用flag作为函数的参数) * [避免副作用](#避免副作用) @@ -33,17 +33,17 @@ * [移除僵尸代码](#移除僵尸代码) 5. [对象和数据结构 Objects and Data Structures](#对象和数据结构) * [使用 getters 和 setters Use object encapsulation](#使用-getters-和-setters) - * [对象属性多使用private/protected限定](#对象属性多使用privateprotected限定) + * [给对象使用私有或受保护的成员变量](#给对象使用私有或受保护的成员变量) 6. [类](#类) - * [组合优于继承](#组合优于继承) + * [少用继承多用组合](#少用继承多用组合) * [避免连贯接口](#避免连贯接口) * [推荐使用 final 类](#推荐使用-final-类) 7. [类的SOLID原则 SOLID](#solid) - * [S: 职责单一原则 Single Responsibility Principle (SRP)](#职责单一原则-single-responsibility-principle-srp) - * [O: 开闭原则 Open/Closed Principle (OCP)](#开闭原则-openclosed-principle-ocp) - * [L: 里氏替换原则 Liskov Substitution Principle (LSP)](#里氏替换原则-liskov-substitution-principle-lsp) - * [I: 接口隔离原则 Interface Segregation Principle (ISP)](#接口隔离原则-interface-segregation-principle-isp) - * [D: 依赖反转原则 Dependency Inversion Principle (DIP)](#依赖反转原则-dependency-inversion-principle-dip) + * [S: 单一职责原则 Single Responsibility Principle (SRP)](#单一职责原则) + * [O: 开闭原则 Open/Closed Principle (OCP)](#开闭原则) + * [L: 里氏替换原则 Liskov Substitution Principle (LSP)](#里氏替换原则) + * [I: 接口隔离原则 Interface Segregation Principle (ISP)](#接口隔离原则) + * [D: 依赖倒置原则 Dependency Inversion Principle (DIP)](#依赖倒置原则) 8. [别写重复代码 (DRY)](#别写重复代码-dry) 9. [翻译](#翻译) @@ -427,7 +427,7 @@ The comparison `$a !== $b` returns `TRUE`. **[⬆ 返回顶部](#目录)** -## **函数** +## 函数 ### 函数参数(最好少于2个) @@ -511,7 +511,7 @@ function isClientActive(int $client): bool **[⬆ 返回顶部](#目录)** -### 函数名应该是有意义的动词(或表明具体做了什么事) +### 函数名应体现他做了什么事 **坏:** @@ -527,7 +527,7 @@ class Email } $message = new Email(...); -// 啥?handle处理一个消息干嘛了?是往一个文件里写码? +// 啥?handle处理一个消息干嘛了?是往一个文件里写吗? $message->handle(); ``` @@ -690,8 +690,8 @@ class BetterJSAlternative **[⬆ 返回顶部](#目录)** - ### 不要用flag作为函数的参数 + flag就是在告诉大家,这个方法里处理很多事。前面刚说过,一个函数应当只做一件事。 把不同flag的代码拆分到多个函数里。 **坏:** @@ -1108,7 +1108,7 @@ inventoryTracker('apples', $request, 'www.inventory-awesome.io'); * 继承当前类时,可以复写默认的方法功能 * 当对象属性是从远端服务器获取时,get*,set*易于使用延迟加载 -此外,这样的方式也符合OOP开发中的[开闭原则](#openclosed-principle-ocp) +此外,这样的方式也符合OOP开发中的[开闭原则](#开闭原则) **坏:** @@ -1167,7 +1167,7 @@ $balance = $bankAccount->getBalance(); **[⬆ 返回顶部](#目录)** -### 对象属性多使用private/protected限定 +### 给对象使用私有或受保护的成员变量 * 对`public`方法和属性进行修改非常危险,因为外部代码容易依赖他,而你没办法控制。**对之修改影响所有这个类的使用者。** `public` methods and properties are most dangerous for changes, because some outside code may easily rely on them and you can't control what code relies on them. **Modifications in class are dangerous for all users of class.** * 对`protected`的修改跟对`public`修改差不多危险,因为他们对子类可用,他俩的唯一区别就是可调用的位置不一样,**对之修改影响所有集成这个类的地方。** `protected` modifier are as dangerous as public, because they are available in scope of any child class. This effectively means that difference between public and protected is only in access mechanism, but encapsulation guarantee remains the same. **Modifications in class are dangerous for all descendant classes.** @@ -1220,7 +1220,7 @@ echo 'Employee name: '.$employee->getName(); // Employee name: John Doe ## 类 -### 组合优于继承 +### 少用继承多用组合 正如 the Gang of Four 所著的[*设计模式*](https://en.wikipedia.org/wiki/Design_Patterns)之前所说, 我们应该尽量优先选择组合而不是继承的方式。使用继承和组合都有很多好处。 @@ -1252,8 +1252,8 @@ class Employee } -// 不好,因为Employees "有" taxdata -// 而EmployeeTaxData不是Employee类型的 +// 不好,因为 Employees "有" taxdata +// 而 EmployeeTaxData 不是 Employee 类型的 class EmployeeTaxData extends Employee @@ -1425,8 +1425,8 @@ $car->dump(); 能用时尽量使用 `final` 关键字: 1. 阻止不受控的继承链 -2. 鼓励 [组合](#prefer-composition-over-inheritance). -3. 鼓励 [单一职责模式](#single-responsibility-principle-srp). +2. 鼓励 [组合](#少用继承多用组合). +3. 鼓励 [单一职责模式](#单一职责模式). 4. 鼓励开发者用你的公开方法而非通过继承类获取受保护方法的访问权限. 5. 使得在不破坏使用你的类的应用的情况下修改代码成为可能. @@ -1492,14 +1492,16 @@ final class Car implements Vehicle **SOLID** 是Michael Feathers推荐的便于记忆的首字母简写,它代表了Robert Martin命名的最重要的五个面对对象编码设计原则 - * [S: 职责单一原则 (SRP)](#职责单一原则-single-responsibility-principle-srp) - * [O: 开闭原则 (OCP)](#开闭原则-openclosed-principle-ocp) - * [L: 里氏替换原则 (LSP)](#里氏替换原则-liskov-substitution-principle-lsp) - * [I: 接口隔离原则 (ISP)](#接口隔离原则-interface-segregation-principle-isp) - * [D: 依赖反转原则 (DIP)](#依赖反转原则-dependency-inversion-principle-dip) + * [S: 单一职责原则 (SRP)](#职责原则) + * [O: 开闭原则 (OCP)](#开闭原则) + * [L: 里氏替换原则 (LSP)](#里氏替换原则) + * [I: 接口隔离原则 (ISP)](#接口隔离原则) + * [D: 依赖倒置原则 (DIP)](#依赖倒置原则) + +### 单一职责原则 -### 职责单一原则 Single Responsibility Principle (SRP) +Single Responsibility Principle (SRP) 正如在Clean Code所述,"修改一个类应该只为一个理由"。 人们总是易于用一堆方法塞满一个类,如同我们只能在飞机上 @@ -1575,9 +1577,11 @@ class UserSettings **[⬆ 返回顶部](#目录)** -### 开闭原则 Open/Closed Principle (OCP) +### 开闭原则 -正如Bertrand Meyer所述,"软件的工件(classes, modules, functions,等) +Open/Closed Principle (OCP) + +正如Bertrand Meyer所述,"软件的工件( classes, modules, functions 等) 应该对扩展开放,对修改关闭。" 然而这句话意味着什么呢?这个原则大体上表示你 应该允许在不改变已有代码的情况下增加新的功能 @@ -1688,7 +1692,10 @@ class HttpRequester **[⬆ 返回顶部](#目录)** -### 里氏替换原则 Liskov Substitution Principle (LSP) +### 里氏替换原则 + +Liskov Substitution Principle (LSP) + 这是一个简单的原则,却用了一个不好理解的术语。它的正式定义是 "如果S是T的子类型,那么在不改变程序原有既定属性(检查、执行 任务等)的前提下,任何T类型的对象都可以使用S类型的对象替代 @@ -1813,7 +1820,9 @@ foreach ($shapes as $shape) { **[⬆ 返回顶部](#目录)** -### 接口隔离原则 Interface Segregation Principle (ISP) +### 接口隔离原则 + +Interface Segregation Principle (ISP) 接口隔离原则表示:"调用方不应该被强制依赖于他不需要的接口" @@ -1831,7 +1840,7 @@ interface Employee public function eat(): void; } -class Human implements Employee +class HumanEmployee implements Employee { public function work(): void { @@ -1844,7 +1853,7 @@ class Human implements Employee } } -class Robot implements Employee +class RobotEmployee implements Employee { public function work(): void { @@ -1877,7 +1886,7 @@ interface Employee extends Feedable, Workable { } -class Human implements Employee +class HumanEmployee implements Employee { public function work(): void { @@ -1891,7 +1900,7 @@ class Human implements Employee } // robot can only work -class Robot implements Workable +class RobotEmployee implements Workable { public function work(): void { @@ -1902,16 +1911,18 @@ class Robot implements Workable **[⬆ 返回顶部](#目录)** -### 依赖反转原则 Dependency Inversion Principle (DIP) +### 依赖倒置原则 + +Dependency Inversion Principle (DIP) 这条原则说明两个基本的要点: 1. 高阶的模块不应该依赖低阶的模块,它们都应该依赖于抽象 2. 抽象不应该依赖于实现,实现应该依赖于抽象 -这条起初看起来有点晦涩难懂,但是如果你使用过php框架(例如 Symfony),你应该见过 -依赖注入(DI)对这个概念的实现。虽然它们不是完全相通的概念,依赖倒置原则使高阶模块 -与低阶模块的实现细节和创建分离。可以使用依赖注入(DI)这种方式来实现它。更多的好处 -是它使模块之间解耦。耦合会导致你难于重构,它是一种非常糟糕的的开发模式 +这条起初看起来有点晦涩难懂,但是如果你使用过 PHP 框架(例如 Symfony),你应该见过 +依赖注入(DI),它是对这个概念的实现。虽然它们不是完全相等的概念,依赖倒置原则使高阶模块 +与低阶模块的实现细节和创建分离。可以使用依赖注入(DI)这种方式来实现它。最大的好处 +是它使模块之间解耦。耦合会导致你难于重构,它是一种非常糟糕的的开发模式。 **坏:** @@ -2007,10 +2018,10 @@ class Manager 分相同的方法,移除重复的代码意味着用一个function/module/class创 建一个能处理差异的抽象。 -正确的抽象是非常关键的,这正是为什么你必须学习遵守在[Classes](#classes)章节展开 -的SOLID原则,不合理的抽象比复制代码更糟糕,所有务必谨慎!说到这么多, -如果你能设计一个合理的抽象,实现它!不要重复,否则你会发现任何时候当你 -想修改一个逻辑时你必须修改多个地方。 +用对抽象非常关键,这正是为什么你必须学习遵守在[类](#类)章节写 +的SOLID原则,不合理的抽象比复制代码更糟糕,所以务必谨慎!说了这么多, +如果你能设计一个合理的抽象,那就这么干!别写重复代码,否则你会发现 +任何时候当你想修改一个逻辑时你必须修改多个地方。 **坏:** @@ -2108,5 +2119,7 @@ function showList(array $employees): void * [viethuongdev/clean-code-php](https://github.com/viethuongdev/clean-code-php) * :kr: **Korean:** * [yujineeee/clean-code-php](https://github.com/yujineeee/clean-code-php) +* :tr: **Turkish:** + * [anilozmen/clean-code-php](https://github.com/anilozmen/clean-code-php) **[⬆ 返回顶部](#目录)** From 7d05644b40c32a94334f47f81bdf7e2bb5583fe5 Mon Sep 17 00:00:00 2001 From: zouyikb <zouyi@knowbox.cn> Date: Wed, 4 Sep 2019 16:21:27 +0800 Subject: [PATCH 19/21] update --- README.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 26cfa1c0..dd06343a 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ getUser(); **坏:** ```php -// What the heck is 448 for? +// 448 ™ 干啥的? $result = $serializer->serialize($data, 448); ``` @@ -127,10 +127,19 @@ $json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT **坏:** ```php -// What the heck is 4 for? +class User +{ + // 7 ™ 干啥的? + public $access = 7; +} + +// 4 ™ 干啥的? if ($user->access & 4) { // ... } + +// 这里会发生什么? +$user->access ^= 2; ``` **好:** @@ -142,11 +151,17 @@ class User const ACCESS_CREATE = 2; const ACCESS_UPDATE = 4; const ACCESS_DELETE = 8; + + // 默认情况下用户 具有读、写和更新权限 + public $access = self::ACCESS_READ | self::ACCESS_CREATE | self::ACCESS_UPDATE; } if ($user->access & User::ACCESS_UPDATE) { // do edit ... } + +// 禁用创建权限 +$user->access ^= User::ACCESS_CREATE; ``` **[⬆ 返回顶部](#目录)** @@ -271,7 +286,7 @@ function fibonacci(int $n): int return $n; } - if ($n > 50) { + if ($n >= 50) { throw new \Exception('Not supported'); } @@ -1020,7 +1035,7 @@ function travelToTexas($vehicle): void **好:** ```php -function travelToTexas(Traveler $vehicle): void +function travelToTexas(Vehicle $vehicle): void { $vehicle->travelTo(new Location('texas')); } From f5134a6a353b9e73ff195002c0ab82233a803002 Mon Sep 17 00:00:00 2001 From: zouyi <zouyi@leying365.com> Date: Mon, 26 Oct 2020 10:34:07 +0800 Subject: [PATCH 20/21] update --- .travis-build.php | 23 +++--- README.md | 180 ++++++++++++++++++++++++++-------------------- 2 files changed, 118 insertions(+), 85 deletions(-) diff --git a/.travis-build.php b/.travis-build.php index 93333509..17a63fee 100644 --- a/.travis-build.php +++ b/.travis-build.php @@ -25,14 +25,21 @@ } $tableOfContentsStarted = false; - $chaptersFound[] = sprintf('%s [%s](#%s)', - strlen($matches['depth']) === 2 - ? sprintf(' %s.', ++$manIndex) - : ' *' - , - $matches['title'], - preg_replace(['/ /', '/[^-\w]+/'], ['-', ''], strtolower($matches['title'])) - ); + if (strlen($matches['depth']) === 2) { + $depth = sprintf(' %s.', ++$manIndex); + } else { + $depth = sprintf(' %s*', str_repeat(' ', strlen($matches['depth']) - 1)); + } + + // ignore links in title + $matches['title'] = preg_replace('/\[([^\]]+)\]\((?:[^\)]+)\)/u', '$1', $matches['title']); + + $link = $matches['title']; + $link = strtolower($link); + $link = str_replace(' ', '-', $link); + $link = preg_replace('/[^-\w]+/u', '', $link); + + $chaptersFound[] = sprintf('%s [%s](#%s)', $depth, $matches['title'], $link); } if ($tableOfContentsStarted === true && isset($line[0])) { $currentTableOfContentsChapters[] = $line; diff --git a/README.md b/README.md index dd06343a..9ebd8654 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ ## 翻译说明 -翻译完成度100%,最后更新时间2017-12-25。本文由 php-cpm 基于 [yangweijie版本](https://github.com/yangweijie/clean-code-php) 的[clean-code-php](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容。 +翻译完成度100%,最后更新时间2020-10-26。本文由 php-cpm 基于 [yangweijie版本](https://github.com/yangweijie/clean-code-php) 的[clean-code-php](https://github.com/jupeter/clean-code-php)翻译并同步大量原文内容。 原文更新频率较高,我的翻译方法是直接用文本比较工具逐行对比。优先保证文字内容是最新的,再逐步提升翻译质量。 @@ -147,10 +147,10 @@ $user->access ^= 2; ```php class User { - const ACCESS_READ = 1; - const ACCESS_CREATE = 2; - const ACCESS_UPDATE = 4; - const ACCESS_DELETE = 8; + public const ACCESS_READ = 1; + public const ACCESS_CREATE = 2; + public const ACCESS_UPDATE = 4; + public const ACCESS_DELETE = 8; // 默认情况下用户 具有读、写和更新权限 public $access = self::ACCESS_READ | self::ACCESS_CREATE | self::ACCESS_UPDATE; @@ -442,62 +442,53 @@ The comparison `$a !== $b` returns `TRUE`. **[⬆ 返回顶部](#目录)** -## 函数 - -### 函数参数(最好少于2个) - -限制函数参数个数极其重要,这样测试你的函数容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。 +### Null coalescing operator -无参数是理想情况。1个或2个都可以,最好避免3个。再多就需要加固了。通常如果你的函数有超过两个参数,说明他要处理的事太多了。 如果必须要传入很多数据,建议封装一个高级别对象作为参数。 +Null coalescing is a new operator [introduced in PHP 7](https://www.php.net/manual/en/migration70.new-features.php). The null coalescing operator `??` has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with `isset()`. It returns its first operand if it exists and is not `null`; otherwise it returns its second operand. -**坏:** +**Bad:** ```php -function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void -{ - // ... +if (isset($_GET['name'])) { + $name = $_GET['name']; +} elseif (isset($_POST['name'])) { + $name = $_POST['name']; +} else { + $name = 'nobody'; } ``` -**好:** - +**Good:** ```php -class MenuConfig -{ - public $title; - public $body; - public $buttonText; - public $cancellable = false; -} +$name = $_GET['name'] ?? $_POST['name'] ?? 'nobody'; +``` -$config = new MenuConfig(); -$config->title = 'Foo'; -$config->body = 'Bar'; -$config->buttonText = 'Baz'; -$config->cancellable = true; +**[⬆ back to top](#table-of-contents)** -function createMenu(MenuConfig $config): void -{ - // ... -} -``` +## 函数 -**[⬆ 返回顶部](#目录)** +### 函数参数(最好少于2个) -### 函数应该只做一件事 +限制函数参数个数极其重要,这样测试你的函数容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。 -这是迄今为止软件工程里最重要的一个规则。当一个函数做超过一件事的时候,他们就难于实现、测试和理解。当你把一个函数拆分到只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。 +无参数是理想情况。1个或2个都可以,最好避免3个。再多就需要加固了。通常如果你的函数有超过两个参数,说明他要处理的事太多了。 如果必须要传入很多数据,建议封装一个高级别对象作为参数。 **坏:** ```php -function emailClients(array $clients): void +class Questionnaire { - foreach ($clients as $client) { - $clientRecord = $db->find($client); - if ($clientRecord->isActive()) { - email($client); - } + public function __construct( + string $firstname, + string $lastname, + string $patronymic, + string $region, + string $district, + string $city, + string $phone, + string $email + ) { + // ... } } ``` @@ -505,22 +496,58 @@ function emailClients(array $clients): void **好:** ```php -function emailClients(array $clients): void +class Name { - $activeClients = activeClients($clients); - array_walk($activeClients, 'email'); + private $firstname; + private $lastname; + private $patronymic; + + public function __construct(string $firstname, string $lastname, string $patronymic) + { + $this->firstname = $firstname; + $this->lastname = $lastname; + $this->patronymic = $patronymic; + } + + // getters ... } -function activeClients(array $clients): array +class City { - return array_filter($clients, 'isClientActive'); + private $region; + private $district; + private $city; + + public function __construct(string $region, string $district, string $city) + { + $this->region = $region; + $this->district = $district; + $this->city = $city; + } + + // getters ... } -function isClientActive(int $client): bool +class Contact { - $clientRecord = $db->find($client); + private $phone; + private $email; - return $clientRecord->isActive(); + public function __construct(string $phone, string $email) + { + $this->phone = $phone; + $this->email = $email; + } + + // getters ... +} + +class Questionnaire +{ + public function __construct(Name $name, City $city, Contact $contact) + { + // ... + } } ``` @@ -574,7 +601,7 @@ $message->send(); **坏:** ```php -function parseBetterJSAlternative(string $code): void +function parseBetterPHPAlternative(string $code): void { $regexes = [ // ... @@ -631,7 +658,7 @@ function lexer(array $tokens): array return $ast; } -function parseBetterJSAlternative(string $code): void +function parseBetterPHPAlternative(string $code): void { $tokens = tokenize($code); $ast = lexer($tokens); @@ -643,7 +670,7 @@ function parseBetterJSAlternative(string $code): void **好:** -最好的解决方案是把 `parseBetterJSAlternative()`方法的依赖移除。 +最好的解决方案是把 `parseBetterPHPAlternative()`方法的依赖移除。 ```php class Tokenizer @@ -679,7 +706,7 @@ class Lexer } } -class BetterJSAlternative +class BetterPHPAlternative { private $tokenizer; private $lexer; @@ -701,8 +728,6 @@ class BetterJSAlternative } ``` -这样我们可以对依赖做mock,并测试`BetterJSAlternative::parse()`运行是否符合预期。 - **[⬆ 返回顶部](#目录)** ### 不要用flag作为函数的参数 @@ -811,7 +836,8 @@ class Configuration public function get(string $key): ?string { - return isset($this->configuration[$key]) ? $this->configuration[$key] : null; + // null coalescing operator + return $this->configuration[$key] ?? null; } } ``` @@ -1252,7 +1278,7 @@ echo 'Employee name: '.$employee->getName(); // Employee name: John Doe **糟糕的:** ```php -class Employee +class Employee { private $name; private $email; @@ -1271,11 +1297,11 @@ class Employee // 而 EmployeeTaxData 不是 Employee 类型的 -class EmployeeTaxData extends Employee +class EmployeeTaxData extends Employee { private $ssn; private $salary; - + public function __construct(string $name, string $email, string $ssn, string $salary) { parent::__construct($name, $email); @@ -1291,7 +1317,7 @@ class EmployeeTaxData extends Employee **好:** ```php -class EmployeeTaxData +class EmployeeTaxData { private $ssn; private $salary; @@ -1305,7 +1331,7 @@ class EmployeeTaxData // ... } -class Employee +class Employee { private $name; private $email; @@ -1317,9 +1343,9 @@ class Employee $this->email = $email; } - public function setTaxData(string $ssn, string $salary) + public function setTaxData(EmployeeTaxData $taxData) { - $this->taxData = new EmployeeTaxData($ssn, $salary); + $this->taxData = $taxData; } // ... @@ -1455,16 +1481,16 @@ For more informations you can read [the blog post](https://ocramius.github.io/bl final class Car { private $color; - + public function __construct($color) { $this->color = $color; } - + /** * @return string The color of the vehicle */ - public function getColor() + public function getColor() { return $this->color; } @@ -1485,16 +1511,16 @@ interface Vehicle final class Car implements Vehicle { private $color; - + public function __construct($color) { $this->color = $color; } - + /** * {@inheritdoc} */ - public function getColor() + public function getColor() { return $this->color; } @@ -1555,7 +1581,7 @@ class UserSettings **好:** ```php -class UserAuth +class UserAuth { private $user; @@ -1563,19 +1589,19 @@ class UserAuth { $this->user = $user; } - + public function verifyCredentials(): bool { // ... } } -class UserSettings +class UserSettings { private $user; private $auth; - public function __construct(User $user) + public function __construct(User $user) { $this->user = $user; $this->auth = new UserAuth($user); @@ -1763,7 +1789,7 @@ 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; } @@ -2136,5 +2162,5 @@ function showList(array $employees): void * [yujineeee/clean-code-php](https://github.com/yujineeee/clean-code-php) * :tr: **Turkish:** * [anilozmen/clean-code-php](https://github.com/anilozmen/clean-code-php) - + **[⬆ 返回顶部](#目录)** From 0c71f30a71826f138b975bad54ac45ad597cff69 Mon Sep 17 00:00:00 2001 From: zouyi <zougangmu@qq.com> Date: Thu, 18 Nov 2021 11:29:49 +0800 Subject: [PATCH 21/21] update translation --- .github/workflows/coding_standard.yaml | 21 +++ .gitignore | 2 + .travis-build.php | 77 --------- .travis.yml | 11 -- README.md | 225 +++++++++++++------------ composer.json | 12 ++ ecs.php | 27 +++ 7 files changed, 178 insertions(+), 197 deletions(-) create mode 100644 .github/workflows/coding_standard.yaml create mode 100644 .gitignore delete mode 100644 .travis-build.php delete mode 100644 .travis.yml create mode 100644 composer.json create mode 100644 ecs.php diff --git a/.github/workflows/coding_standard.yaml b/.github/workflows/coding_standard.yaml new file mode 100644 index 00000000..262b528b --- /dev/null +++ b/.github/workflows/coding_standard.yaml @@ -0,0 +1,21 @@ +name: Coding Standard + +on: + pull_request: null + push: null + +jobs: + coding_standard: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + # see https://github.com/shivammathur/setup-php + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + coverage: none + + - run: composer install --no-progress --ansi + + - run: vendor/bin/ecs check-markdown README.md --ansi diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..49c63d28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +/vendor \ No newline at end of file diff --git a/.travis-build.php b/.travis-build.php deleted file mode 100644 index 17a63fee..00000000 --- a/.travis-build.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php - -$readMeFilepath = __DIR__ . '/README.md'; -$readMeFile = new SplFileObject($readMeFilepath); -$readMeFile->setFlags(SplFileObject::DROP_NEW_LINE); - -$cliRedBackground = "\033[37;41m"; -$cliReset = "\033[0m"; -$exitStatus = 0; - -$indentationSteps = 3; -$manIndex = 0; -$linesWithSpaces = []; -$tableOfContentsStarted = null; -$currentTableOfContentsChapters = []; -$chaptersFound = []; -foreach ($readMeFile as $lineNumber => $line) { - if (preg_match('/\s$/', $line)) { - $linesWithSpaces[] = sprintf('%5s: %s', 1 + $lineNumber, $line); - } - if (preg_match('/^(?<depth>##+)\s(?<title>.+)/', $line, $matches)) { - if (null === $tableOfContentsStarted) { - $tableOfContentsStarted = true; - continue; - } - $tableOfContentsStarted = false; - - if (strlen($matches['depth']) === 2) { - $depth = sprintf(' %s.', ++$manIndex); - } else { - $depth = sprintf(' %s*', str_repeat(' ', strlen($matches['depth']) - 1)); - } - - // ignore links in title - $matches['title'] = preg_replace('/\[([^\]]+)\]\((?:[^\)]+)\)/u', '$1', $matches['title']); - - $link = $matches['title']; - $link = strtolower($link); - $link = str_replace(' ', '-', $link); - $link = preg_replace('/[^-\w]+/u', '', $link); - - $chaptersFound[] = sprintf('%s [%s](#%s)', $depth, $matches['title'], $link); - } - if ($tableOfContentsStarted === true && isset($line[0])) { - $currentTableOfContentsChapters[] = $line; - } -} - -if (count($linesWithSpaces)) { - fwrite(STDERR, sprintf("${cliRedBackground}The following lines end with a space character:${cliReset}\n%s\n\n", - implode(PHP_EOL, $linesWithSpaces) - )); - $exitStatus = 1; -} - -$currentTableOfContentsChaptersFilename = __DIR__ . '/current-chapters'; -$chaptersFoundFilename = __DIR__ . '/chapters-found'; - -file_put_contents($currentTableOfContentsChaptersFilename, implode(PHP_EOL, $currentTableOfContentsChapters)); -file_put_contents($chaptersFoundFilename, implode(PHP_EOL, $chaptersFound)); - -$tableOfContentsDiff = shell_exec(sprintf('diff --unified %s %s', - escapeshellarg($currentTableOfContentsChaptersFilename), - escapeshellarg($chaptersFoundFilename) -)); - -@ unlink($currentTableOfContentsChaptersFilename); -@ unlink($chaptersFoundFilename); - -if (!empty($tableOfContentsDiff)) { - fwrite(STDERR, sprintf("${cliRedBackground}The table of contents is not aligned:${cliReset}\n%s\n\n", - $tableOfContentsDiff - )); - $exitStatus = 1; -} - -exit($exitStatus); diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e4ea3575..00000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: php - -sudo: false - -php: - - nightly - -script: php .travis-build.php - -notifications: - email: false diff --git a/README.md b/README.md index 9ebd8654..0c57115a 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ * [避免深层嵌套,尽早返回 (part 1)](#避免深层嵌套尽早返回-part-1) * [避免深层嵌套,尽早返回 (part 2)](#避免深层嵌套尽早返回-part-2) * [少用无意义的变量名](#少用无意义的变量名) - * [不要添加不必要上下文](#不要添加不必要上下文) - * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值没必要在方法里再做默认值检测) - 3. [表达式](#表达式) + * [不要添加不必要上下文](#不要添加不必要上下文) 3. [表达式](#表达式) * [使用恒等式](#使用恒等式) + * [Null合并运算符](#null合并运算符) 4. [函数](#函数) + * [合理使用参数默认值,没必要在方法里再做默认值检测](#合理使用参数默认值没必要在方法里再做默认值检测) * [函数参数(最好少于2个)](#函数参数-最好少于2个) * [函数应该只做一件事](#函数应该只做一件事) * [函数名应体现他做了什么事](#函数名应体现他做了什么事) @@ -148,8 +148,11 @@ $user->access ^= 2; class User { public const ACCESS_READ = 1; + public const ACCESS_CREATE = 2; + public const ACCESS_UPDATE = 4; + public const ACCESS_DELETE = 8; // 默认情况下用户 具有读、写和更新权限 @@ -223,15 +226,12 @@ function isShopOpen($day): bool return true; } elseif ($day === 'sunday') { return true; - } else { - return false; } - } else { return false; } - } else { return false; } + return false; } ``` @@ -244,9 +244,7 @@ function isShopOpen(string $day): bool return false; } - $openingDays = [ - 'friday', 'saturday', 'sunday' - ]; + $openingDays = ['friday', 'saturday', 'sunday']; return in_array(strtolower($day), $openingDays, true); } @@ -265,15 +263,12 @@ function fibonacci(int $n) if ($n !== 0) { if ($n !== 1) { return fibonacci($n - 1) + fibonacci($n - 2); - } else { - return 1; } - } else { - return 0; + return 1; } - } else { - return 'Not supported'; + return 0; } + return 'Not supported'; } ``` @@ -287,7 +282,7 @@ function fibonacci(int $n): int } if ($n >= 50) { - throw new \Exception('Not supported'); + throw new Exception('Not supported'); } return fibonacci($n - 1) + fibonacci($n - 2); @@ -345,7 +340,9 @@ foreach ($locations as $location) { class Car { public $carMake; + public $carModel; + public $carColor; //... @@ -358,7 +355,9 @@ class Car class Car { public $make; + public $model; + public $color; //... @@ -367,43 +366,6 @@ class Car **[⬆ 返回顶部](#目录)** -### 合理使用参数默认值,没必要在方法里再做默认值检测 - -**不好:** - -不好,`$breweryName` 可能为 `NULL`. - -```php -function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void -{ -    // ... -} -``` - -**还行:** - -比上一个好理解一些,但最好能控制变量的值 - -```php -function createMicrobrewery($name = null): void -{ -    $breweryName = $name ?: 'Hipster Brew Co.'; - // ... -} -``` - -**好:** - -如果你的程序只支持 PHP 7+, 那你可以用 [type hinting](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) 保证变量 `$breweryName` 不是 `NULL`. - -```php -function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void -{ -    // ... -} -``` - -**[⬆ 返回顶部](#目录)** ## 表达式 @@ -417,7 +379,7 @@ function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void $a = '42'; $b = 42; -if( $a != $b ) { +if ($a != $b) { //这里始终执行不到 } ``` @@ -442,11 +404,11 @@ The comparison `$a !== $b` returns `TRUE`. **[⬆ 返回顶部](#目录)** -### Null coalescing operator +### Null合并运算符 -Null coalescing is a new operator [introduced in PHP 7](https://www.php.net/manual/en/migration70.new-features.php). The null coalescing operator `??` has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with `isset()`. It returns its first operand if it exists and is not `null`; otherwise it returns its second operand. +Null合并运算符是 [PHP 7新特性](https://www.php.net/manual/en/migration70.new-features.php). Null合并运算符 `??` 是用来简化判断`isset()`的语法糖。如果第一个操作数存在且不为`null`则返回;否则返回第二个操作数。 -**Bad:** +**不好:** ```php if (isset($_GET['name'])) { @@ -458,15 +420,54 @@ if (isset($_GET['name'])) { } ``` -**Good:** +**好:** ```php $name = $_GET['name'] ?? $_POST['name'] ?? 'nobody'; ``` -**[⬆ back to top](#table-of-contents)** +**[⬆ 返回顶部](#目录)** + ## 函数 +### 合理使用参数默认值,没必要在方法里再做默认值检测 + +**不好:** + +不好,`$breweryName` 可能为 `NULL`. + +```php +function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void +{ +    // ... +} +``` + +**还行:** + +比上一个好理解一些,但最好能控制变量的值 + +```php +function createMicrobrewery($name = null): void +{ + $breweryName = $name ?: 'Hipster Brew Co.'; + // ... +} +``` + +**好:** + +如果你的程序只支持 PHP 7+, 那你可以用 [type hinting](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) 保证变量 `$breweryName` 不是 `NULL`. + +```php +function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void +{ + // ... +} +``` + +**[⬆ 返回顶部](#目录)** + ### 函数参数(最好少于2个) 限制函数参数个数极其重要,这样测试你的函数容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。 @@ -499,7 +500,9 @@ class Questionnaire class Name { private $firstname; + private $lastname; + private $patronymic; public function __construct(string $firstname, string $lastname, string $patronymic) @@ -515,7 +518,9 @@ class Name class City { private $region; + private $district; + private $city; public function __construct(string $region, string $district, string $city) @@ -531,6 +536,7 @@ class City class Contact { private $phone; + private $email; public function __construct(string $phone, string $email) @@ -576,7 +582,7 @@ $message->handle(); **好:** ```php -class Email +class Email { //... @@ -739,7 +745,7 @@ flag就是在告诉大家,这个方法里处理很多事。前面刚说过, function createFile(string $name, bool $temp = false): void { if ($temp) { - touch('./temp/'.$name); + touch('./temp/' . $name); } else { touch($name); } @@ -756,7 +762,7 @@ function createFile(string $name): void function createTempFile(string $name): void { - touch('./temp/'.$name); + touch('./temp/' . $name); } ``` **[⬆ 返回顶部](#目录)** @@ -785,7 +791,8 @@ function splitIntoFirstAndLastName(): void splitIntoFirstAndLastName(); -var_dump($name); // ['Ryan', 'McDermott']; +var_dump($name); +// ['Ryan', 'McDermott']; ``` **好:** @@ -799,8 +806,11 @@ function splitIntoFirstAndLastName(string $name): array $name = 'Ryan McDermott'; $newName = splitIntoFirstAndLastName($name); -var_dump($name); // 'Ryan McDermott'; -var_dump($newName); // ['Ryan', 'McDermott']; +var_dump($name); +// 'Ryan McDermott'; + +var_dump($newName); +// ['Ryan', 'McDermott']; ``` **[⬆ 返回顶部](#目录)** @@ -816,9 +826,9 @@ var_dump($newName); // ['Ryan', 'McDermott']; ```php function config(): array { - return [ + return [ 'foo' => 'bar', - ] + ]; } ``` @@ -836,7 +846,7 @@ class Configuration public function get(string $key): ?string { - // null coalescing operator + // null coalescing operator return $this->configuration[$key] ?? null; } } @@ -876,7 +886,7 @@ class DBConnection // ... } - public static function getInstance(): DBConnection + public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self(); @@ -901,7 +911,7 @@ class DBConnection // ... } - // ... + // ... } ``` @@ -940,13 +950,12 @@ if ($article->isPublished()) { **坏:** ```php -function isDOMNodeNotPresent(\DOMNode $node): bool +function isDOMNodeNotPresent(DOMNode $node): bool { // ... } -if (!isDOMNodeNotPresent($node)) -{ +if (! isDOMNodeNotPresent($node)) { // ... } ``` @@ -954,7 +963,7 @@ if (!isDOMNodeNotPresent($node)) **好:** ```php -function isDOMNodePresent(\DOMNode $node): bool +function isDOMNodePresent(DOMNode $node): bool { // ... } @@ -1081,8 +1090,8 @@ function travelToTexas(Vehicle $vehicle): void ```php function combine($val1, $val2): int { - if (!is_numeric($val1) || !is_numeric($val2)) { - throw new \Exception('Must be of type Number'); + if (! is_numeric($val1) || ! is_numeric($val2)) { + throw new Exception('Must be of type Number'); } return $val1 + $val2; @@ -1232,7 +1241,8 @@ class Employee } $employee = new Employee('John Doe'); -echo 'Employee name: '.$employee->name; // Employee name: John Doe +// Employee name: John Doe +echo 'Employee name: ' . $employee->name; ``` **好:** @@ -1254,7 +1264,8 @@ class Employee } $employee = new Employee('John Doe'); -echo 'Employee name: '.$employee->getName(); // Employee name: John Doe +// Employee name: John Doe +echo 'Employee name: ' . $employee->getName(); ``` **[⬆ 返回顶部](#目录)** @@ -1281,6 +1292,7 @@ echo 'Employee name: '.$employee->getName(); // Employee name: John Doe class Employee { private $name; + private $email; public function __construct(string $name, string $email) @@ -1300,6 +1312,7 @@ class Employee class EmployeeTaxData extends Employee { private $ssn; + private $salary; public function __construct(string $name, string $email, string $ssn, string $salary) @@ -1320,6 +1333,7 @@ class EmployeeTaxData extends Employee class EmployeeTaxData { private $ssn; + private $salary; public function __construct(string $ssn, string $salary) @@ -1334,7 +1348,9 @@ class EmployeeTaxData class Employee { private $name; + private $email; + private $taxData; public function __construct(string $name, string $email) @@ -1343,7 +1359,7 @@ class Employee $this->email = $email; } - public function setTaxData(EmployeeTaxData $taxData) + public function setTaxData(EmployeeTaxData $taxData): void { $this->taxData = $taxData; } @@ -1382,7 +1398,9 @@ more often it comes at some costs: class Car { private $make = 'Honda'; + private $model = 'Accord'; + private $color = 'white'; public function setMake(string $make): self @@ -1416,10 +1434,10 @@ class Car } $car = (new Car()) - ->setColor('pink') - ->setMake('Ford') - ->setModel('F-150') - ->dump(); + ->setColor('pink') + ->setMake('Ford') + ->setModel('F-150') + ->dump(); ``` **好:** @@ -1428,7 +1446,9 @@ $car = (new Car()) class Car { private $make = 'Honda'; + private $model = 'Accord'; + private $color = 'white'; public function setMake(string $make): void @@ -1517,9 +1537,6 @@ final class Car implements Vehicle $this->color = $color; } - /** - * {@inheritdoc} - */ public function getColor() { return $this->color; @@ -1599,6 +1616,7 @@ class UserAuth class UserSettings { private $user; + private $auth; public function __construct(User $user) @@ -1754,6 +1772,7 @@ Liskov Substitution Principle (LSP) class Rectangle { protected $width = 0; + protected $height = 0; public function setWidth(int $width): void @@ -1791,7 +1810,7 @@ function printArea(Rectangle $rectangle): void $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; + echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()) . PHP_EOL; } $rectangles = [new Rectangle(), new Square()]; @@ -2073,11 +2092,7 @@ function showDeveloperList(array $developers): void $expectedSalary = $developer->calculateExpectedSalary(); $experience = $developer->getExperience(); $githubLink = $developer->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; + $data = [$expectedSalary, $experience, $githubLink]; render($data); } @@ -2089,11 +2104,7 @@ function showManagerList(array $managers): void $expectedSalary = $manager->calculateExpectedSalary(); $experience = $manager->getExperience(); $githubLink = $manager->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; + $data = [$expectedSalary, $experience, $githubLink]; render($data); } @@ -2109,11 +2120,7 @@ function showList(array $employees): void $expectedSalary = $employee->calculateExpectedSalary(); $experience = $employee->getExperience(); $githubLink = $employee->getGithubLink(); - $data = [ - $expectedSalary, - $experience, - $githubLink - ]; + $data = [$expectedSalary, $experience, $githubLink]; render($data); } @@ -2128,11 +2135,7 @@ function showList(array $employees): void function showList(array $employees): void { foreach ($employees as $employee) { - render([ - $employee->calculateExpectedSalary(), - $employee->getExperience(), - $employee->getGithubLink() - ]); + render([$employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink()]); } } ``` @@ -2156,11 +2159,15 @@ function showList(array $employees): void * [panuwizzle/clean-code-php](https://github.com/panuwizzle/clean-code-php) * :fr: **French:** * [errorname/clean-code-php](https://github.com/errorname/clean-code-php) -* :vietnam: **Vietnamese** +* :vietnam: **Vietnamese:** * [viethuongdev/clean-code-php](https://github.com/viethuongdev/clean-code-php) * :kr: **Korean:** * [yujineeee/clean-code-php](https://github.com/yujineeee/clean-code-php) * :tr: **Turkish:** * [anilozmen/clean-code-php](https://github.com/anilozmen/clean-code-php) +* :iran: **Persian:** + * [amirshnll/clean-code-php](https://github.com/amirshnll/clean-code-php) +* :bangladesh: **Bangla:** + * [nayeemdev/clean-code-php](https://github.com/nayeemdev/clean-code-php) **[⬆ 返回顶部](#目录)** diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..9c258aa3 --- /dev/null +++ b/composer.json @@ -0,0 +1,12 @@ +{ + "name": "jupeter/clean-code-php", + "description": "Clean Code concepts adapted for PHP", + "require": { + "php": ">=7.2", + "symplify/easy-coding-standard": "^9.3" + }, + "scripts": { + "check-cs": "vendor/bin/ecs check-markdown README.md", + "fix-cs": "vendor/bin/ecs check-markdown README.md --fix" + } +} diff --git a/ecs.php b/ecs.php new file mode 100644 index 00000000..b2e209ef --- /dev/null +++ b/ecs.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +use PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer; +use PhpCsFixer\Fixer\Strict\DeclareStrictTypesFixer; +use PhpCsFixer\Fixer\Strict\StrictComparisonFixer; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symplify\EasyCodingStandard\ValueObject\Option; +use Symplify\EasyCodingStandard\ValueObject\Set\SetList; + +return static function (ContainerConfigurator $containerConfigurator): void +{ + $containerConfigurator->import(SetList::COMMON); + $containerConfigurator->import(SetList::CLEAN_CODE); + $containerConfigurator->import(SetList::PSR_12); + $containerConfigurator->import(SetList::SYMPLIFY); + + $parameters = $containerConfigurator->parameters(); + $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php']); + + $parameters->set(Option::SKIP, [ + BlankLineAfterOpeningTagFixer::class => null, + StrictComparisonFixer::class => null, + DeclareStrictTypesFixer::class => null, + ]); +};