├── .gitattributes ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=PHP 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan McDermott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clean-code-php 2 | 3 | ## Table of Contents 4 | 1. [介绍](#介绍) 5 | 2. [变量](#变量) 6 | 3. [函数](#函数) 7 | 8 | ## 介绍 9 | 本文由 yangweijie 翻译自[clen php code](https://github.com/jupeter/clean-code-php),团建用,欢迎大家指正。 10 | 11 | 摘录自 Robert C. Martin的[*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) 书中的软件工程师的原则 12 | ,适用于PHP。 这不是风格指南。 这是一个关于开发可读、可复用并且可重构的PHP软件指南。 13 | 14 | 并不是这里所有的原则都得遵循,甚至很少的能被普遍接受。 这些虽然只是指导,但是都是*Clean Code*作者多年总结出来的。 15 | 16 | Inspired from [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) 17 | 18 | ## **变量** 19 | ### 使用有意义且可拼写的变量名 20 | 21 | **Bad:** 22 | ```php 23 | $ymdstr = $moment->format('y-m-d'); 24 | ``` 25 | 26 | **Good**: 27 | ```javascript 28 | $currentDate = $moment->format('y-m-d'); 29 | ``` 30 | **[⬆ 返回顶部](#table-of-contents)** 31 | 32 | ### 同种类型的变量使用相同词汇 33 | 34 | **Bad:** 35 | ```php 36 | getUserInfo(); 37 | getClientData(); 38 | getCustomerRecord(); 39 | ``` 40 | 41 | **Good**: 42 | ```php 43 | getUser(); 44 | ``` 45 | **[⬆ 返回顶部](#table-of-contents)** 46 | 47 | ### 使用易检索的名称 48 | 我们会读比写要多的代码。通过是命名易搜索,让我们写出可读性和易搜索代码很重要。 49 | 50 | **Bad:** 51 | ```php 52 | // What the heck is 86400 for? 53 | addExpireAt(86400); 54 | 55 | ``` 56 | 57 | **Good**: 58 | ```php 59 | // Declare them as capitalized `const` globals. 60 | interface DateGlobal { 61 | const SECONDS_IN_A_DAY = 86400; 62 | } 63 | 64 | addExpireAt(DateGlobal::SECONDS_IN_A_DAY); 65 | ``` 66 | **[⬆ 返回顶部](#table-of-contents)** 67 | 68 | 69 | ### 使用解释型变量 70 | **Bad:** 71 | ```php 72 | $address = 'One Infinite Loop, Cupertino 95014'; 73 | $cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; 74 | preg_match($cityZipCodeRegex, $address, $matches); 75 | saveCityZipCode($matches[1], $matches[2]); 76 | ``` 77 | 78 | **Good**: 79 | ```php 80 | $address = 'One Infinite Loop, Cupertino 95014'; 81 | $cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/'; 82 | preg_match($cityZipCodeRegex, $address, $matches); 83 | list(, $city, $zipCode) = $matchers; 84 | saveCityZipCode($city, $zipCode); 85 | ``` 86 | **[⬆ 返回顶部](#table-of-contents)** 87 | 88 | ### 避免心理映射 89 | 明确比隐性好。 90 | 91 | **Bad:** 92 | ```php 93 | $l = ['Austin', 'New York', 'San Francisco']; 94 | foreach($i=0; $i 'Honda', 129 | 'carModel' => 'Accord', 130 | 'carColor' => 'Blue', 131 | ]; 132 | 133 | function paintCar(&$car) { 134 | $car['carColor'] = 'Red'; 135 | } 136 | ``` 137 | 138 | **Good**: 139 | ```php 140 | $car = [ 141 | 'make' => 'Honda', 142 | 'model' => 'Accord', 143 | 'color' => 'Blue', 144 | ]; 145 | 146 | function paintCar(&$car) { 147 | $car['color'] = 'Red'; 148 | } 149 | ``` 150 | **[⬆ 返回顶部](#table-of-contents)** 151 | 152 | ###使用参数默认值代替短路或条件语句。 153 | **Bad:** 154 | ```php 155 | function createMicrobrewery($name = null) { 156 | $breweryName = $name ?: 'Hipster Brew Co.'; 157 | // ... 158 | } 159 | 160 | ``` 161 | 162 | **Good**: 163 | ```php 164 | function createMicrobrewery($breweryName = 'Hipster Brew Co.') { 165 | // ... 166 | } 167 | 168 | ``` 169 | 170 | **[⬆ 返回顶部](#table-of-contents)** 171 | 172 | ## **函数** 173 | ### 函数参数最好少于2个 174 | 限制函数参数个数极其重要因为它是你函数测试容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。 175 | 176 | 无参数是理想情况。1个或2个都可以,最好避免3个。再多旧需要加固了。通常如果你的函数有超过两个参数,说明他多做了一些事。 在参数少的情况里,大多数时候一个高级别对象(数组)作为参数就足够应付。 177 | 178 | **Bad:** 179 | ```php 180 | function createMenu($title, $body, $buttonText, $cancellable) { 181 | // ... 182 | } 183 | ``` 184 | 185 | **Good**: 186 | ```php 187 | class menuConfig() { 188 | public $title; 189 | public $body; 190 | public $buttonText; 191 | public $cancellable = false; 192 | } 193 | 194 | $config = new MenuConfig(); 195 | $config->title = 'Foo'; 196 | $config->body = 'Bar'; 197 | $config->buttonText = 'Baz'; 198 | $config->cancellable = true; 199 | 200 | function createMenu(MenuConfig $config) { 201 | // ... 202 | } 203 | 204 | ``` 205 | **[⬆ 返回顶部](#table-of-contents)** 206 | 207 | 208 | ### 函数应该只做一件事 209 | 这是迄今为止软件工程里最重要的一个规则。当函数做超过一件事的时候,他们就难于实现、测试和理解。当你隔离函数只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。 210 | 211 | **Bad:** 212 | ```php 213 | function emailClients($clients) { 214 | foreach ($clients as $client) { 215 | $clientRecord = $db->find($client); 216 | if($clientRecord->isActive()) { 217 | email($client); 218 | } 219 | } 220 | } 221 | ``` 222 | 223 | **Good**: 224 | ```php 225 | function emailClients($clients) { 226 | $activeClients = activeClients($clients); 227 | array_walk($activeClients, 'email'); 228 | } 229 | 230 | function activeClients($clients) { 231 | return array_filter($clients, 'isClientActive'); 232 | } 233 | 234 | function isClientActive($client) { 235 | $clientRecord = $db->find($client); 236 | return $clientRecord->isActive(); 237 | } 238 | ``` 239 | **[⬆ 返回顶部](#table-of-contents)** 240 | 241 | ### 函数名应当描述他们所做的事 242 | 243 | **Bad:** 244 | ```php 245 | function addToDate($date, $month) { 246 | // ... 247 | } 248 | 249 | $date = new \DateTime(); 250 | 251 | // It's hard to to tell from the function name what is added 252 | addToDate($date, 1); 253 | ``` 254 | 255 | **Good**: 256 | ```php 257 | function addMonthToDate($month, $date) { 258 | // ... 259 | } 260 | 261 | $date = new \DateTime(); 262 | addMonthToDate(1, $date); 263 | ``` 264 | **[⬆ 返回顶部](#table-of-contents)** 265 | 266 | ### 函数应当只为一层抽象,当你超过一层抽象时,函数正在做多件事。拆分功能易达到可重用性和易用性。. 267 | 268 | **Bad:** 269 | ```php 270 | function parseBetterJSAlternative($code) { 271 | $regexes = [ 272 | // ... 273 | ]; 274 | 275 | $statements = split(' ', $code); 276 | $tokens = []; 277 | foreach($regexes as $regex) { 278 | foreach($statements as $statement) { 279 | // ... 280 | } 281 | } 282 | 283 | $ast = []; 284 | foreach($tokens as $token) { 285 | // lex... 286 | } 287 | 288 | foreach($ast as $node) { 289 | // parse... 290 | } 291 | } 292 | ``` 293 | 294 | **Good**: 295 | ```php 296 | function tokenize($code) { 297 | $regexes = [ 298 | // ... 299 | ]; 300 | 301 | $statements = split(' ', $code); 302 | $tokens = []; 303 | foreach($regexes as $regex) { 304 | foreach($statements as $statement) { 305 | $tokens[] = /* ... */; 306 | }); 307 | }); 308 | 309 | return tokens; 310 | } 311 | 312 | function lexer($tokens) { 313 | $ast = []; 314 | foreach($tokens as $token) { 315 | $ast[] = /* ... */; 316 | }); 317 | 318 | return ast; 319 | } 320 | 321 | function parseBetterJSAlternative($code) { 322 | $tokens = tokenize($code); 323 | $ast = lexer($tokens); 324 | foreach($ast as $node) { 325 | // parse... 326 | }); 327 | } 328 | ``` 329 | **[⬆ 返回顶部](#table-of-contents)** 330 | 331 | ### 删除重复的代码 332 | 尽你最大的努力来避免重复的代码。重复代码不好,因为它意味着如果你修改一些逻辑,那就有不止一处地方要同步修改了。 333 | 334 | 想象一下如果你经营着一家餐厅并跟踪它的库存: 你全部的西红柿、洋葱、大蒜、香料等。如果你保留有多个列表,当你服务一个有着西红柿的菜,那么所有记录都得更新。如果你只有一个列表,那么只需要修改一个地方! 335 | 336 | 经常你容忍重复代码,因为你有两个或更多有共同部分但是少许差异的东西强制你用两个或更多独立的函数来做相同的事。移除重复代码意味着创造一个处理这组不同事物的一个抽象,只需要一个函数/模块/类。 337 | 338 | 抽象正确非常重要,这也是为什么你应当遵循SOLID原则(奠定*Class*基础的原则)。坏的抽象可能比重复代码还要糟,因为要小心。在这个前提下,如果你可以抽象好,那就开始做把!不要重复你自己,否则任何你想改变一件事的时候你都发现在即在更新维护多处。 339 | 340 | **Bad:** 341 | ```php 342 | function showDeveloperList($developers) { 343 | foreach($developers as $developer) { 344 | $expectedSalary = $developer->calculateExpectedSalary(); 345 | $experience = $developer->getExperience(); 346 | $githubLink = $developer->getGithubLink(); 347 | $data = [ 348 | $expectedSalary, 349 | $experience, 350 | $githubLink 351 | ]; 352 | 353 | render($data); 354 | } 355 | } 356 | 357 | function showManagerList($managers) { 358 | foreach($managers as $manager) { 359 | $expectedSalary = $manager->calculateExpectedSalary(); 360 | $experience = $manager->getExperience(); 361 | $githubLink = $manager->getGithubLink(); 362 | $data = [ 363 | $expectedSalary, 364 | $experience, 365 | $githubLink 366 | ]; 367 | 368 | render($data); 369 | } 370 | } 371 | ``` 372 | 373 | **Good**: 374 | ```php 375 | function showList($employees) { 376 | foreach($employees as $employe) { 377 | $expectedSalary = $employe->calculateExpectedSalary(); 378 | $experience = $employe->getExperience(); 379 | $githubLink = $employe->getGithubLink(); 380 | $data = [ 381 | $expectedSalary, 382 | $experience, 383 | $githubLink 384 | ]; 385 | 386 | render($data); 387 | } 388 | } 389 | ``` 390 | **[⬆ 返回顶部](#table-of-contents)** 391 | 392 | ### 通过对象赋值设置默认值 393 | 394 | **Bad:** 395 | ```php 396 | $menuConfig = [ 397 | 'title' => null, 398 | 'body' => 'Bar', 399 | 'buttonText' => null, 400 | 'cancellable' => true, 401 | ]; 402 | 403 | function createMenu(&$config) { 404 | $config['title'] = $config['title'] ?: 'Foo'; 405 | $config['body'] = $config['body'] ?: 'Bar'; 406 | $config['buttonText'] = $config['buttonText'] ?: 'Baz'; 407 | $config['cancellable'] = $config['cancellable'] ?: true; 408 | } 409 | 410 | createMenu($menuConfig); 411 | ``` 412 | 413 | **Good**: 414 | ```php 415 | $menuConfig = [ 416 | 'title' => 'Order', 417 | // User did not include 'body' key 418 | 'buttonText' => 'Send', 419 | 'cancellable' => true, 420 | ]; 421 | 422 | function createMenu(&$config) { 423 | $config = array_merge([ 424 | 'title' => 'Foo', 425 | 'body' => 'Bar', 426 | 'buttonText' => 'Baz', 427 | 'cancellable' => true, 428 | ], $config); 429 | 430 | // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} 431 | // ... 432 | } 433 | 434 | createMenu($menuConfig); 435 | ``` 436 | **[⬆ 返回顶部](#table-of-contents)** 437 | 438 | 439 | ### 不要用标志作为函数的参数,标志告诉你的用户函数做很多事了。函数应当只做一件事。 根据布尔值区别的路径来拆分你的复杂函数。 440 | 441 | **Bad:** 442 | ```php 443 | function createFile(name, temp = false) { 444 | if (temp) { 445 | touch('./temp/'.$name); 446 | } else { 447 | touch($name); 448 | } 449 | } 450 | ``` 451 | 452 | **Good**: 453 | ```php 454 | function createFile($name) { 455 | touch(name); 456 | } 457 | 458 | function createTempFile($name) { 459 | touch('./temp/'.$name); 460 | } 461 | ``` 462 | **[⬆ 返回顶部](#table-of-contents)** 463 | 464 | ### 避免副作用 465 | 一个函数做了比获取一个值然后返回另外一个值或值们会产生副作用如果。副作用可能是写入一个文件,修改某些全局变量或者偶然的把你全部的钱给了陌生人。 466 | 467 | 现在,你的确需要在一个程序或者场合里要有副作用,像之前的例子,你也许需要写一个文件。你想要做的是把你做这些的地方集中起来。不要用几个函数和类来写入一个特定的文件。用一个服务来做它,一个只有一个。 468 | 469 | 重点是避免常见陷阱比如对象间共享无结构的数据,使用可以写入任何的可变数据类型,不集中处理副作用发生的地方。如果你做了这些你就会比大多数程序员快乐。 470 | 471 | **Bad:** 472 | ```php 473 | // Global variable referenced by following function. 474 | // If we had another function that used this name, now it'd be an array and it could break it. 475 | $name = 'Ryan McDermott'; 476 | 477 | function splitIntoFirstAndLastName() { 478 | $name = preg_split('/ /', $name); 479 | } 480 | 481 | splitIntoFirstAndLastName(); 482 | 483 | var_dump($name); // ['Ryan', 'McDermott']; 484 | ``` 485 | 486 | **Good**: 487 | ```php 488 | $name = 'Ryan McDermott'; 489 | 490 | function splitIntoFirstAndLastName($name) { 491 | return preg_split('/ /', $name); 492 | } 493 | 494 | $name = 'Ryan McDermott'; 495 | $newName = splitIntoFirstAndLastName(name); 496 | 497 | var_export($name); // 'Ryan McDermott'; 498 | var_export($newName); // ['Ryan', 'McDermott']; 499 | ``` 500 | **[⬆ 返回顶部](#table-of-contents)** 501 | 502 | ### 不要写全局函数 503 | 在大多数语言中污染全局变量是一个坏的实践,因为你可能和其他类库冲突并且你api的用户不明白为什么直到他们获得产品的一个异常。让我们看一个例子:如果你想配置一个数组,你可能会写一个全局函数像`config()`,但是可能和试着做同样事的其他类库冲突。这就是为什么单例设计模式和简单配置会更好的原因。 504 | 505 | **Bad:** 506 | ```php 507 | function config() { 508 | return [ 509 | 'foo': 'bar', 510 | ] 511 | }; 512 | ``` 513 | 514 | **Good:** 515 | ```php 516 | class Configuration { 517 | private static $instance; 518 | private function __construct($configuration) {/* */} 519 | public static function getInstance() { 520 | if(self::$instance === null) { 521 | self::$instance = new Configuration(); 522 | } 523 | return self::$instance; 524 | } 525 | public function get($key) {/* */} 526 | public function getAll() {/* */} 527 | } 528 | 529 | $singleton = Configuration::getInstance(); 530 | ``` 531 | **[⬆ 返回顶部](#table-of-contents)** 532 | 533 | ### 封装条件语句 534 | 535 | **Bad:** 536 | ```php 537 | if ($fsm->state === 'fetching' && is_empty($listNode)) { 538 | // ... 539 | } 540 | ``` 541 | 542 | **Good**: 543 | ```php 544 | function shouldShowSpinner($fsm, $listNode) { 545 | return $fsm->state === 'fetching' && is_empty(listNode); 546 | } 547 | 548 | if (shouldShowSpinner($fsmInstance, $listNodeInstance)) { 549 | // ... 550 | } 551 | ``` 552 | **[⬆ 返回顶部](#table-of-contents)** 553 | 554 | ### 避免消极条件 555 | 556 | **Bad:** 557 | ```php 558 | function isDOMNodeNotPresent($node) { 559 | // ... 560 | } 561 | 562 | if (!isDOMNodeNotPresent($node)) { 563 | // ... 564 | } 565 | ``` 566 | 567 | **Good**: 568 | ```php 569 | function isDOMNodePresent($node) { 570 | // ... 571 | } 572 | 573 | if (isDOMNodePresent($node)) { 574 | // ... 575 | } 576 | ``` 577 | **[⬆ 返回顶部](#table-of-contents)** 578 | 579 | ### 避免条件声明 580 | 这看起来像一个不可能任务。当人们第一次听到这句话是都会这么说。 581 | "没有一个`if声明`" 答案是你可以使用多态来达到许多case语句里的任务。第二个问题很常见, “那么为什么我要那么做?” 答案是前面我们学过的一个整洁代码原则:一个函数应当只做一件事。当你有类和函数有很多`if`声明,你自己知道你的函数做了不止一件事。记住,只做一件事。 582 | 583 | **Bad:** 584 | ```php 585 | class Airplane { 586 | // ... 587 | public function getCruisingAltitude() { 588 | switch (this.type) { 589 | case '777': 590 | return $this->getMaxAltitude() - $this->getPassengerCount(); 591 | case 'Air Force One': 592 | return $this->getMaxAltitude(); 593 | case 'Cessna': 594 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 595 | } 596 | } 597 | } 598 | ``` 599 | 600 | **Good**: 601 | ```php 602 | class Airplane { 603 | // ... 604 | } 605 | 606 | class Boeing777 extends Airplane { 607 | // ... 608 | public function getCruisingAltitude() { 609 | return $this->getMaxAltitude() - $this->getPassengerCount(); 610 | } 611 | } 612 | 613 | class AirForceOne extends Airplane { 614 | // ... 615 | public function getCruisingAltitude() { 616 | return $this->getMaxAltitude(); 617 | } 618 | } 619 | 620 | class Cessna extends Airplane { 621 | // ... 622 | public function getCruisingAltitude() { 623 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 624 | } 625 | } 626 | ``` 627 | **[⬆ 返回顶部](#table-of-contents)** 628 | 629 | ### Avoid 避免类型检查 (part 1) 630 | PHP是弱类型的,这意味着你的函数可以接收任何类型的参数。 631 | 有时候你为这自由所痛苦并且在你的函数渐渐尝试类型检查。有很多方法去避免这么做。第一种是考虑API的一致性。 632 | 633 | **Bad:** 634 | ```php 635 | function travelToTexas($vehicle) { 636 | if ($vehicle instanceof Bicycle) { 637 | $vehicle->peddle($this->currentLocation, new Location('texas')); 638 | } else if ($vehicle instanceof Car) { 639 | $vehicle->drive($this->currentLocation, new Location('texas')); 640 | } 641 | } 642 | ``` 643 | 644 | **Good**: 645 | ```php 646 | function travelToTexas($vehicle) { 647 | $vehicle->move($this->currentLocation, new Location('texas')); 648 | } 649 | ``` 650 | **[⬆ 返回顶部](#table-of-contents)** 651 | 652 | ### 避免类型检查 (part 2) 653 | 如果你正使用基本原始值比如字符串、整形和数组,你不能用多态,你仍然感觉需要类型检测,你应当考虑类型声明或者严格模式。 这给你了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。保持你的PHP 整洁,写好测试,做好代码回顾。做不到就用PHP严格类型声明和严格模式来确保安全。 654 | 655 | **Bad:** 656 | ```php 657 | function combine($val1, $val2) { 658 | if (is_numeric($val1) && is_numeric(val2)) { 659 | return val1 + val2; 660 | } 661 | 662 | throw new \Exception('Must be of type Number'); 663 | } 664 | ``` 665 | 666 | **Good**: 667 | ```php 668 | function combine(int $val1, int $val2) { 669 | return $val1 + $val2; 670 | } 671 | ``` 672 | **[⬆ 返回顶部](#table-of-contents)** 673 | 674 | ### 移除僵尸代码 675 | 僵尸代码和重复代码一样坏。没有理由保留在你的代码库中。如果从来被调用过,见鬼去!在你的版本库里是如果你仍然需要他的话,因此这么做很安全。 676 | 677 | **Bad:** 678 | ```php 679 | function oldRequestModule($url) { 680 | // ... 681 | } 682 | 683 | function newRequestModule($url) { 684 | // ... 685 | } 686 | 687 | $req = new newRequestModule(); 688 | inventoryTracker('apples', $req, 'www.inventory-awesome.io'); 689 | 690 | ``` 691 | 692 | **Good**: 693 | ```php 694 | function newRequestModule($url) { 695 | // ... 696 | } 697 | 698 | $req = new newRequestModule(); 699 | inventoryTracker('apples', $req, 'www.inventory-awesome.io'); 700 | ``` 701 | **[⬆ 返回顶部](#table-of-contents)** 702 | 703 | 704 | ##有问题反馈 705 | 在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流 706 | 707 | * 邮件(yangweijiest#gmail.com, 把#换成@) 708 | * QQ: 917647288 709 | * weibo: [@黑白世界4648](http://weibo.com/1342658313) 710 | * 人人: [@杨维杰](http://www.renren.com/247050624) 711 | --------------------------------------------------------------------------------