Loading... # [如何编写编写干净的 PHP 代码](https://github.com/piotrplenik/clean-code-php) 介绍 -- 软件工程原理,摘自 Robert C. Martin 的著作 [_Clean Code_](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882), 适用于PHP。这不是风格指南。这是一份生产指南 PHP 中可读、可重用和可重构的软件。 并非这里的每一项原则都必须严格遵守,甚至更少是普遍遵守的 商定。这些是指导方针,仅此而已,但它们是许多准则的编纂 _Clean Code_ 作者多年的集体经验。 灵感来自 [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript)。 尽管许多开发人员仍在使用 PHP 5,但本文中的大多数示例仅适用于 PHP 7.1+。 [变量](https://github.com/piotrplenik/clean-code-php#variables) ------------------------------------------------------------- ### [使用有意义且可发音的变量名称](https://github.com/piotrplenik/clean-code-php#use-meaningful-and-pronounceable-variable-names) 坏: ```php $ymdstr = $moment->format('y-m-d'); ``` 好: ```php $currentDate = $moment->format('y-m-d'); ``` ### [对相同类型的变量使用相同的词汇](https://github.com/piotrplenik/clean-code-php#use-the-same-vocabulary-for-the-same-type-of-variable) 坏: ```php getUserInfo(); getUserData(); getUserRecord(); getUserProfile(); ``` 好: ```php getUser(); ``` ### [使用可搜索的名称(第 1 部分)](https://github.com/piotrplenik/clean-code-php#use-searchable-names-part-1) 我们将阅读比我们编写的更多的代码。重要的是,我们编写的代码是 可读和可搜索。通过_不_命名最终有意义的变量 了解我们的程序,我们伤害了我们的读者。 使你的名字可搜索。 坏: ```php // 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); ``` ### [使用可搜索的名称(第 2 部分)](https://github.com/piotrplenik/clean-code-php#use-searchable-names-part-2) 坏: ```php class User { // What the heck is 7 for? public $access = 7; } // What the heck is 4 for? if ($user->access & 4) { // ... } // What's going on here? $user->access ^= 2; ``` 好: ```php class User { public const ACCESS_READ = 1; public const ACCESS_CREATE = 2; public const ACCESS_UPDATE = 4; public const ACCESS_DELETE = 8; // User as default can read, create and update something public $access = self::ACCESS_READ | self::ACCESS_CREATE | self::ACCESS_UPDATE; } if ($user->access & User::ACCESS_UPDATE) { // do edit ... } // Deny access rights to create something $user->access ^= User::ACCESS_CREATE; ``` ### [使用解释变量](https://github.com/piotrplenik/clean-code-php#use-explanatory-variables) 坏: ```php $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches[1], $matches[2]); ``` 不错: 它更好,但我们仍然严重依赖正则表达式。 ```php $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); [, $city, $zipCode] = $matches; saveCityZipCode($city, $zipCode); ``` 好: 通过命名子模式来减少对正则表达式的依赖。 ```php $address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches['city'], $matches['zipCode']); ``` ### [避免嵌套太深,提早返回(第 1 部分)](https://github.com/piotrplenik/clean-code-php#avoid-nesting-too-deeply-and-return-early-part-1) 过多的 if-else 语句会使代码难以理解。显式更好 比隐含。 坏: ```php function isShopOpen($day): bool { if ($day) { if (is_string($day)) { $day = strtolower($day); if ($day === 'friday') { return true; } elseif ($day === 'saturday') { return true; } elseif ($day === 'sunday') { return true; } return false; } return false; } return false; } ``` 好: ```php function isShopOpen(string $day): bool { if (empty($day)) { return false; } $openingDays = ['friday', 'saturday', 'sunday']; return in_array(strtolower($day), $openingDays, true); } ``` ### [避免嵌套太深,提早返回(第 2 部分)](https://github.com/piotrplenik/clean-code-php#avoid-nesting-too-deeply-and-return-early-part-2) 坏: ```php function fibonacci(int $n) { if ($n < 50) { if ($n !== 0) { if ($n !== 1) { return fibonacci($n - 1) + fibonacci($n - 2); } return 1; } return 0; } return 'Not supported'; } ``` 好: ```php function fibonacci(int $n): int { if ($n === 0 || $n === 1) { return $n; } if ($n >= 50) { throw new Exception('Not supported'); } return fibonacci($n - 1) + fibonacci($n - 2); } ``` ### [避免思维导图](https://github.com/piotrplenik/clean-code-php#avoid-mental-mapping) 不要强迫代码的读者翻译变量的含义。 显式比隐式好。 坏: ```php $l = ['Austin', 'New York', 'San Francisco']; for ($i = 0; $i < count($l); $i++) { $li = $l[$i]; doStuff(); doSomeOtherStuff(); // ... // ... // ... // Wait, what is `$li` for again? dispatch($li); } ``` 好: ```php $locations = ['Austin', 'New York', 'San Francisco']; foreach ($locations as $location) { doStuff(); doSomeOtherStuff(); // ... // ... // ... dispatch($location); } ``` ### [不要添加不需要的上下文](https://github.com/piotrplenik/clean-code-php#dont-add-unneeded-context) 如果你的类/对象名称告诉你一些事情,不要在你的 变量名称。 坏: ```php class Car { public $carMake; public $carModel; public $carColor; //... } ``` 好: ```php class Car { public $make; public $model; public $color; //... } ``` [比较](https://github.com/piotrplenik/clean-code-php#comparison) -------------------------------------------------------------- ### [使用相同的比较](http://php.net/manual/en/language.operators.comparison.php) 不好: 简单的比较会将字符串转换为整数。 ```php $a = '42'; $b = 42; if ($a != $b) { // The expression will always pass } ``` 比较回来了,但实际上是! 字符串与整数不同。`$a != $b``FALSE``TRUE``42``42` 好: 相同的比较将比较类型和值。 ```php $a = '42'; $b = 42; if ($a !== $b) { // The expression is verified } ``` 比较返回 。`$a !== $b``TRUE` ### [Null 合并运算符](https://github.com/piotrplenik/clean-code-php#null-coalescing-operator) Null 合并是 [PHP 7 中引入](https://www.php.net/manual/en/migration70.new-features.php)的一个新运算符。已将 null 合并运算符添加为句法糖,用于需要将三元与 结合使用的常见情况。如果它存在并且不存在,则返回其第一个操作数;否则,它将返回其第二个操作数。`??``isset()``null` 坏: ```php if (isset($_GET['name'])) { $name = $_GET['name']; } elseif (isset($_POST['name'])) { $name = $_POST['name']; } else { $name = 'nobody'; } ``` 好: ```php $name = $_GET['name'] ?? $_POST['name'] ?? 'nobody'; ``` [功能](https://github.com/piotrplenik/clean-code-php#functions) ------------------------------------------------------------- ### [使用默认参数而不是短路或条件](https://github.com/piotrplenik/clean-code-php#use-default-arguments-instead-of-short-circuiting-or-conditionals) 不好: 这不好,因为可以.`$breweryName``NULL` ```php function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void { // ... } ``` 不错: 这种观点比以前的版本更容易理解,但它更好地控制了变量的值。 ```php function createMicrobrewery($name = null): void { $breweryName = $name ?: 'Hipster Brew Co.'; // ... } ``` 好: 您可以使用[类型提示](https://www.php.net/manual/en/language.types.declarations.php),并确保 不会是 .`$breweryName``NULL` ```php function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void { // ... } ``` ### [函数参数(理想情况下为 2 个或更少)](https://github.com/piotrplenik/clean-code-php#function-arguments-2-or-fewer-ideally) 限制函数参数的数量非常重要,因为它使 更轻松地测试您的函数。拥有三个以上会导致组合爆炸 你必须用每个单独的参数测试大量不同的情况。 零参数是理想的情况。一两个论点是可以的,应该避免三个论点。 除此之外,任何事情都应该被合并。通常,如果您有两个以上的 参数,那么你的函数试图做太多。如果不是,大多数 在更高级别的对象作为参数就足够了。 坏: ```php class Questionnaire { public function __construct( string $firstname, string $lastname, string $patronymic, string $region, string $district, string $city, string $phone, string $email ) { // ... } } ``` 好: ```php class Name { 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 ... } class City { 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 ... } class Contact { private $phone; private $email; 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) { // ... } } ``` ### [函数名称应说明它们的作用](https://github.com/piotrplenik/clean-code-php#function-names-should-say-what-they-do) 坏: ```php class Email { //... public function handle(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); // What is this? A handle for the message? Are we writing to a file now? $message->handle(); ``` 好: ```php class Email { //... public function send(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); // Clear and obvious $message->send(); ``` ### [函数应该只是一个抽象级别](https://github.com/piotrplenik/clean-code-php#functions-should-only-be-one-level-of-abstraction) 当你有多个抽象级别时,你的函数通常是 做得太多了。拆分功能可提高可重用性,更轻松 测试。 坏: ```php function parseBetterPHPAlternative(string $code): void { $regexes = [ // ... ]; $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { // ... } } $ast = []; foreach ($tokens as $token) { // lex... } foreach ($ast as $node) { // parse... } } ``` 也不好: 我们已经执行了一些功能,但功能仍然非常复杂且无法测试。`parseBetterPHPAlternative()` ```php function tokenize(string $code): array { $regexes = [ // ... ]; $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { $tokens[] = /* ... */; } } return $tokens; } function lexer(array $tokens): array { $ast = []; foreach ($tokens as $token) { $ast[] = /* ... */; } return $ast; } function parseBetterPHPAlternative(string $code): void { $tokens = tokenize($code); $ast = lexer($tokens); foreach ($ast as $node) { // parse... } } ``` 好: 最好的解决方案是移出函数的依赖关系。`parseBetterPHPAlternative()` ```php class Tokenizer { public function tokenize(string $code): array { $regexes = [ // ... ]; $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { $tokens[] = /* ... */; } } return $tokens; } } class Lexer { public function lexify(array $tokens): array { $ast = []; foreach ($tokens as $token) { $ast[] = /* ... */; } return $ast; } } class BetterPHPAlternative { private $tokenizer; private $lexer; public function __construct(Tokenizer $tokenizer, Lexer $lexer) { $this->tokenizer = $tokenizer; $this->lexer = $lexer; } public function parse(string $code): void { $tokens = $this->tokenizer->tokenize($code); $ast = $this->lexer->lexify($tokens); foreach ($ast as $node) { // parse... } } } ``` ### [不要使用标志作为函数参数](https://github.com/piotrplenik/clean-code-php#dont-use-flags-as-function-parameters) 标志告诉用户此函数执行多项操作。函数应 做一件事。如果函数遵循不同的代码路径,请拆分函数 基于布尔值。 坏: ```php function createFile(string $name, bool $temp = false): void { if ($temp) { touch('./temp/' . $name); } else { touch($name); } } ``` 好: ```php function createFile(string $name): void { touch($name); } function createTempFile(string $name): void { touch('./temp/' . $name); } ``` ### [避免副作用](https://github.com/piotrplenik/clean-code-php#avoid-side-effects) 如果函数执行除 和 中的值之外的任何操作,则会产生副作用 返回另一个或多个值。副作用可能是写入文件、修改 一些全局变量,或者不小心把你所有的钱都汇给一个陌生人。 现在,您确实需要偶尔在程序中出现副作用。和以前一样 例如,您可能需要写入文件。您要做的是集中在哪里 你正在这样做。不要有多个函数和类写入特定的 文件。有一个服务可以做到这一点。一个也是唯一一个。 重点是避免常见的陷阱,例如在没有 任何结构,使用可变数据类型,可以由任何内容写入,而不是 集中出现副作用的位置。如果你能做到这一点,你会更快乐 比绝大多数其他程序员都要好。 坏: ```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(): void { global $name; $name = explode(' ', $name); } splitIntoFirstAndLastName(); var_dump($name); // ['Ryan', 'McDermott']; ``` 好: ```php function splitIntoFirstAndLastName(string $name): array { return explode(' ', $name); } $name = 'Ryan McDermott'; $newName = splitIntoFirstAndLastName($name); var_dump($name); // 'Ryan McDermott'; var_dump($newName); // ['Ryan', 'McDermott']; ``` ### [不要写入全局函数](https://github.com/piotrplenik/clean-code-php#dont-write-to-global-functions) 在许多语言中,污染全局变量是一种不好的做法,因为您可能会与另一种语言发生冲突 库和 API 的用户将不明智,直到他们在 生产。让我们考虑一个例子:如果你想拥有配置数组怎么办? 你可以像 一样编写全局函数,但它可能会与另一个库发生冲突 试图做同样的事情。`config()` 坏: ```php function config(): array { return [ 'foo' => 'bar', ]; } ``` 好: ```php class Configuration { private $configuration = []; public function __construct(array $configuration) { $this->configuration = $configuration; } public function get(string $key): ?string { // null coalescing operator return $this->configuration[$key] ?? null; } } ``` 加载配置并创建类的实例`Configuration` ```php $configuration = new Configuration([ 'foo' => 'bar', ]); ``` 现在,您必须在应用程序中使用实例。`Configuration` ### [不要使用单一实例模式](https://github.com/piotrplenik/clean-code-php#dont-use-a-singleton-pattern) Singleton 是一种[反模式](https://en.wikipedia.org/wiki/Singleton_pattern)。转述自布莱恩·巴顿(Brian Button): 1. 它们通常被用作全局实例,为什么这么糟糕?因为您将应用程序的依赖项隐藏在代码中,而不是通过接口公开它们。制作全球性的东西以避免传递它是一种[代码味道](https://en.wikipedia.org/wiki/Code_smell)。 2. 他们违反了[单一责任原则](https://github.com/piotrplenik/clean-code-php#single-responsibility-principle-srp):因为他们控制着自己的创造和生命周期。 3. 它们固有地导致代码紧密[耦合](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29)。这使得在许多情况下在测试中伪造它们变得相当困难。 4. 它们在应用程序的生存期内携带状态。测试的另一个打击,因为你最终可能会遇到需要订购测试的情况,这对单元测试来说是一个很大的否定。为什么?因为每个单元测试都应该彼此独立。 [Misko Hevery](http://misko.hevery.com/about/)对[问题的根源](http://misko.hevery.com/2008/08/25/root-cause-of-singletons/)也有很好的想法。 坏: ```php class DBConnection { private static $instance; private function __construct(string $dsn) { // ... } public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } // ... } $singleton = DBConnection::getInstance(); ``` 好: ```php class DBConnection { public function __construct(string $dsn) { // ... } // ... } ``` 创建类的实例并使用 [DSN](http://php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters) 对其进行配置。`DBConnection` ```php $connection = new DBConnection($dsn); ``` 现在,您必须在应用程序中使用实例。`DBConnection` ### [封装条件语句](https://github.com/piotrplenik/clean-code-php#encapsulate-conditionals) 坏: ```php if ($article->state === 'published') { // ... } ``` 好: ```php if ($article->isPublished()) { // ... } ``` ### [避免负面条件](https://github.com/piotrplenik/clean-code-php#avoid-negative-conditionals) 坏: ```php function isDOMNodeNotPresent(DOMNode $node): bool { // ... } if (! isDOMNodeNotPresent($node)) { // ... } ``` 好: ```php function isDOMNodePresent(DOMNode $node): bool { // ... } if (isDOMNodePresent($node)) { // ... } ``` ### [避免使用条件](https://github.com/piotrplenik/clean-code-php#avoid-conditionals) 这似乎是一项不可能完成的任务。第一次听到这句话时,大多数人会说, “没有声明,我怎么能做任何事情?”答案是 在许多情况下,您可以使用多态性来实现相同的任务。第二个 问题通常是,“嗯,这很好,但我为什么要这样做?这 答案是我们之前学到的一个干净的代码概念:一个函数应该只做 一件事。当您具有具有语句的类和函数时,您 告诉用户您的函数执行了不止一件事。记得 只做一件事。`if``if` 坏: ```php class Airplane { // ... public function getCruisingAltitude(): int { switch ($this->type) { case '777': return $this->getMaxAltitude() - $this->getPassengerCount(); case 'Air Force One': return $this->getMaxAltitude(); case 'Cessna': return $this->getMaxAltitude() - $this->getFuelExpenditure(); } } } ``` 好: ```php interface Airplane { // ... public function getCruisingAltitude(): int; } class Boeing777 implements Airplane { // ... public function getCruisingAltitude(): int { return $this->getMaxAltitude() - $this->getPassengerCount(); } } class AirForceOne implements Airplane { // ... public function getCruisingAltitude(): int { return $this->getMaxAltitude(); } } class Cessna implements Airplane { // ... public function getCruisingAltitude(): int { return $this->getMaxAltitude() - $this->getFuelExpenditure(); } } ``` ### [避免类型检查(第 1 部分)](https://github.com/piotrplenik/clean-code-php#avoid-type-checking-part-1) PHP 是非类型化的,这意味着您的函数可以接受任何类型的参数。 有时你会被这种自由所咬,它变得很诱人 函数中的类型检查。有很多方法可以避免这样做。 首先要考虑的是一致的 API。 坏: ```php function travelToTexas($vehicle): void { if ($vehicle instanceof Bicycle) { $vehicle->pedalTo(new Location('texas')); } elseif ($vehicle instanceof Car) { $vehicle->driveTo(new Location('texas')); } } ``` 好: ```php function travelToTexas(Vehicle $vehicle): void { $vehicle->travelTo(new Location('texas')); } ``` ### [避免类型检查(第 2 部分)](https://github.com/piotrplenik/clean-code-php#avoid-type-checking-part-2) 如果您正在使用字符串、整数和数组等基本基元值, 你使用 PHP 7+,你不能使用多态性,但你仍然觉得有必要 类型检查,您应该考虑[类型声明](https://www.php.net/manual/en/language.types.declarations.php)或严格模式。它为您提供了基于标准PHP语法的静态类型。 手动类型检查的问题在于,这样做需要很多 额外的措辞是,你得到的虚假“类型安全”并不能弥补损失 可读性。保持你的PHP干净,编写好的测试,并有良好的代码审查。 否则,除了PHP严格类型声明或严格模式之外,还可以执行所有这些操作。 坏: ```php function combine($val1, $val2): int { if (! is_numeric($val1) || ! is_numeric($val2)) { throw new Exception('Must be of type Number'); } return $val1 + $val2; } ``` 好: ```php function combine(int $val1, int $val2): int { return $val1 + $val2; } ``` ### [删除死代码](https://github.com/piotrplenik/clean-code-php#remove-dead-code) 死代码和重复代码一样糟糕。没有理由保留它 您的代码库。如果它没有被调用,请摆脱它!它仍然是安全的 如果您仍然需要它,请在您的版本历史记录中。 坏: ```php function oldRequestModule(string $url): void { // ... } function newRequestModule(string $url): void { // ... } $request = newRequestModule($requestUrl); inventoryTracker('apples', $request, 'www.inventory-awesome.io'); ``` 好: ```php function requestModule(string $url): void { // ... } $request = requestModule($requestUrl); inventoryTracker('apples', $request, 'www.inventory-awesome.io'); ``` [对象和数据结构](https://github.com/piotrplenik/clean-code-php#objects-and-data-structures) ------------------------------------------------------------------------------------ ### [使用对象封装](https://github.com/piotrplenik/clean-code-php#use-object-encapsulation) 在 PHP 中,您可以为方法设置 、 和关键字。 使用它,您可以控制对象的属性修改。`public``protected``private` * 当您想在获取对象属性之外执行更多操作时,您没有 查找和更改代码库中的每个访问器。 * 使在执行 .`set` * 封装内部表示形式。 * 在获取和设置时易于添加日志记录和错误处理。 * 继承此类后,可以重写默认功能。 * 您可以延迟加载对象的属性,例如从 服务器。 此外,这是[开/闭](https://github.com/piotrplenik/clean-code-php#openclosed-principle-ocp)原则的一部分。 坏: ```php class BankAccount { public $balance = 1000; } $bankAccount = new BankAccount(); // Buy shoes... $bankAccount->balance -= 100; ``` 好: ```php class BankAccount { private $balance; public function __construct(int $balance = 1000) { $this->balance = $balance; } public function withdraw(int $amount): void { if ($amount > $this->balance) { throw new \Exception('Amount greater than available balance.'); } $this->balance -= $amount; } public function deposit(int $amount): void { $this->balance += $amount; } public function getBalance(): int { return $this->balance; } } $bankAccount = new BankAccount(); // Buy shoes... $bankAccount->withdraw($shoesPrice); // Get balance $balance = $bankAccount->getBalance(); ``` ### [使对象具有私有/受保护的成员](https://github.com/piotrplenik/clean-code-php#make-objects-have-privateprotected-members) * `public`方法和属性对于更改来说是最危险的,因为某些外部代码可能很容易依赖它们,并且您无法控制哪些代码依赖于它们。类中的修改对类的所有用户都是危险的。 * `protected`修饰符和 public 一样危险,因为它们在任何子类的范围内都可用。这实际上意味着公共和受保护之间的区别仅在于访问机制,但封装保证保持不变。类中的修改对于所有后代类都是危险的。 * `private`modifier 保证代码仅在单个类的边界内修改是危险的(修改是安全的,并且不会产生 [Jenga 效应](http://www.urbandictionary.com/define.php?term=Jengaphobia&defid=2494196))。 因此,在默认情况下以及需要为外部类提供访问权限时使用。`private``public/protected` 有关更多信息,您可以阅读 [Fabien Potencier](https://github.com/fabpot) 撰写的有关此主题的[博客文章](http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html)。 坏: ```php class Employee { public $name; public function __construct(string $name) { $this->name = $name; } } $employee = new Employee('John Doe'); // Employee name: John Doe echo 'Employee name: ' . $employee->name; ``` 好: ```php class Employee { private $name; public function __construct(string $name) { $this->name = $name; } public function getName(): string { return $this->name; } } $employee = new Employee('John Doe'); // Employee name: John Doe echo 'Employee name: ' . $employee->getName(); ``` [类](https://github.com/piotrplenik/clean-code-php#classes) ---------------------------------------------------------- ### [首选组合而不是继承](https://github.com/piotrplenik/clean-code-php#prefer-composition-over-inheritance) 正如四人帮[_在《设计模式_](https://en.wikipedia.org/wiki/Design_Patterns)》中所说的那样, 在可能的情况下,您应该更喜欢组合而不是继承。有很多 使用继承的充分理由和使用组合的充分理由。 这句格言的要点是,如果你的思想本能地去做 继承,试着想想组合是否可以更好地模拟你的问题。在一些 案例可以。 那么,您可能想知道,“我什么时候应该使用继承?它 取决于你手头的问题,但这是一个不错的继承清单 比构图更有意义: 1. 你的继承代表一种“是”的关系,而不是“有”的关系 关系(人-动物与用户->>UserDetails)。 2. 您可以重用基类中的代码(人类可以像所有动物一样移动)。 3. 您希望通过更改基类来对派生类进行全局更改。 (改变所有动物移动时的热量消耗)。 坏: ```php class Employee { private $name; private $email; public function __construct(string $name, string $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(string $name, string $email, string $ssn, string $salary) { parent::__construct($name, $email); $this->ssn = $ssn; $this->salary = $salary; } // ... } ``` 好: ```php class EmployeeTaxData { private $ssn; private $salary; public function __construct(string $ssn, string $salary) { $this->ssn = $ssn; $this->salary = $salary; } // ... } class Employee { private $name; private $email; private $taxData; public function __construct(string $name, string $email) { $this->name = $name; $this->email = $email; } public function setTaxData(EmployeeTaxData $taxData): void { $this->taxData = $taxData; } // ... } ``` ### [避免使用流畅的界面](https://github.com/piotrplenik/clean-code-php#avoid-fluent-interfaces) [Fluent 接口](https://en.wikipedia.org/wiki/Fluent_interface)是一个对象 面向 API,旨在通过使用[方法链接](https://en.wikipedia.org/wiki/Method_chaining)提高源代码的可读性。 虽然可能有一些上下文,通常是构建器对象,其中 pattern 降低了代码的冗长程度(例如 [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)), 更多时候,它需要付出一些代价: 1. 中断[封装](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29)。 2. 中断[装饰器](https://en.wikipedia.org/wiki/Decorator_pattern)。 3. 在测试套件中更难[嘲笑](https://en.wikipedia.org/wiki/Mock_object)。 4. 使提交的差异更难阅读。 有关更多信息,您可以阅读 [Marco Picetta](https://github.com/Ocramius) 撰写的有关此主题的完整[博客文章](https://ocramius.github.io/blog/fluent-interfaces-are-evil/)。 坏: ```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(); ``` ### [首选期末课程](https://github.com/piotrplenik/clean-code-php#prefer-final-classes) 应尽可能使用关键字:`final` 1. 它可以防止不受控制的继承链。 2. 它鼓励[作文](https://github.com/piotrplenik/clean-code-php#prefer-composition-over-inheritance)。 3. 它鼓励[单一责任原则](https://github.com/piotrplenik/clean-code-php#single-responsibility-principle-srp)。 4. 它鼓励开发人员使用您的公共方法,而不是扩展类来访问受保护的方法。 5. 它允许您更改代码,而不会破坏使用您的类的应用程序。 唯一的条件是你的类应该实现一个接口,并且没有定义其他公共方法。 有关更多信息,您可以阅读 [Marco Pivetta (Ocramius)](https://ocramius.github.io/) 撰写的有关此主题的[博客文章](https://ocramius.github.io/blog/when-to-declare-classes-final/)。 坏: ```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; } } ``` 好: ```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; } public function getColor() { return $this->color; } } ``` [固体](https://github.com/piotrplenik/clean-code-php#solid) --------------------------------------------------------- SOLID 是 Michael Feathers 为罗伯特·马丁 (Robert Martin) 命名的前五个原则引入的首字母缩写词,意思是面向对象编程和设计的五个基本原则。 * [S:单一责任原则 (SRP)](https://github.com/piotrplenik/clean-code-php#single-responsibility-principle-srp) * [O:开/闭原理 (OCP)](https://github.com/piotrplenik/clean-code-php#openclosed-principle-ocp) * [L:李氏替代原理(LSP)](https://github.com/piotrplenik/clean-code-php#liskov-substitution-principle-lsp) * [I:接口隔离原则(ISP)](https://github.com/piotrplenik/clean-code-php#interface-segregation-principle-isp) * [D:依赖反转原则 (DIP)](https://github.com/piotrplenik/clean-code-php#dependency-inversion-principle-dip) ### [单一责任原则 (SRP)](https://github.com/piotrplenik/clean-code-php#single-responsibility-principle-srp) 正如 Clean Code 中所述,“一个类不应该有多个原因 改变”。将一个具有许多功能的类塞进包装是很诱人的,例如 当您在航班上只能携带一个行李箱时。这样做的问题是 你的班级在概念上不会有凝聚力,它会给出很多理由 来改变。尽量减少更改类所需的次数非常重要。 这很重要,因为如果一个类中有太多功能,并且您修改了其中的一部分, 可能很难理解这将如何影响其他依赖模块 您的代码库。 坏: ```php class UserSettings { private $user; public function __construct(User $user) { $this->user = $user; } public function changeSettings(array $settings): void { if ($this->verifyCredentials()) { // ... } } private function verifyCredentials(): bool { // ... } } ``` 好: ```php class UserAuth { private $user; public function __construct(User $user) { $this->user = $user; } public function verifyCredentials(): bool { // ... } } class UserSettings { private $user; private $auth; public function __construct(User $user) { $this->user = $user; $this->auth = new UserAuth($user); } public function changeSettings(array $settings): void { if ($this->auth->verifyCredentials()) { // ... } } } ``` ### [开/闭原理 (OCP)](https://github.com/piotrplenik/clean-code-php#openclosed-principle-ocp) 正如 Bertrand Meyer 所说,“软件实体(类、模块、函数、 等)应该开放以进行扩展,但关闭以进行修改。那有什么作用 但意思是?这个原则基本上表明你应该允许用户 在不更改现有代码的情况下添加新功能。 坏: ```php abstract class Adapter { protected $name; public function getName(): string { return $this->name; } } class AjaxAdapter extends Adapter { public function __construct() { parent::__construct(); $this->name = 'ajaxAdapter'; } } class NodeAdapter extends Adapter { public function __construct() { parent::__construct(); $this->name = 'nodeAdapter'; } } class HttpRequester { private $adapter; public function __construct(Adapter $adapter) { $this->adapter = $adapter; } public function fetch(string $url): Promise { $adapterName = $this->adapter->getName(); if ($adapterName === 'ajaxAdapter') { return $this->makeAjaxCall($url); } elseif ($adapterName === 'httpNodeAdapter') { return $this->makeHttpCall($url); } } private function makeAjaxCall(string $url): Promise { // request and return promise } private function makeHttpCall(string $url): Promise { // request and return promise } } ``` 好: ```php interface Adapter { public function request(string $url): Promise; } class AjaxAdapter implements Adapter { public function request(string $url): Promise { // request and return promise } } class NodeAdapter implements Adapter { public function request(string $url): Promise { // request and return promise } } class HttpRequester { private $adapter; public function __construct(Adapter $adapter) { $this->adapter = $adapter; } public function fetch(string $url): Promise { return $this->adapter->request($url); } } ``` ### [Liskov 替代原理 (LSP)](https://github.com/piotrplenik/clean-code-php#liskov-substitution-principle-lsp) 对于一个非常简单的概念来说,这是一个可怕的术语。它的正式定义是“如果 S 是 T 的子类型,则 T 类型的对象可以替换为 S 类型的对象 (即,S 类型的对象可以替换 T 类型的对象),而不改变任何 该程序的理想属性(正确性、执行的任务、 等等)。这是一个更可怕的定义。 对此最好的解释是,如果你有一个父类和一个子类, 那么基类和子类可以互换使用,而不会得到 结果不正确。这可能仍然令人困惑,所以让我们来看看 经典的 Square-Rectangle 示例。在数学上,正方形是一个矩形,但是 如果通过继承使用“is-a”关系对其进行建模,则很快 惹上麻烦了。 坏: ```php class Rectangle { protected $width = 0; protected $height = 0; public function setWidth(int $width): void { $this->width = $width; } public function setHeight(int $height): void { $this->height = $height; } public function getArea(): int { return $this->width * $this->height; } } class Square extends Rectangle { public function setWidth(int $width): void { $this->width = $this->height = $width; } public function setHeight(int $height): void { $this->width = $this->height = $height; } } function printArea(Rectangle $rectangle): void { $rectangle->setWidth(4); $rectangle->setHeight(5); // BAD: Will return 25 for Square. Should be 20. echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()) . PHP_EOL; } $rectangles = [new Rectangle(), new Square()]; foreach ($rectangles as $rectangle) { printArea($rectangle); } ``` 好: 最好的方法是将四边形分开,并为两种形状分配一个更通用的子类型。 尽管正方形和矩形表面上相似,但它们是不同的。 正方形与菱形有很多共同之处,矩形与平行四边形有很多共同之处,但它们不是亚型。 正方形、矩形、菱形和平行四边形是独立的形状,具有各自的属性,尽管相似。 ```php interface Shape { public function getArea(): int; } class Rectangle implements Shape { private $width = 0; private $height = 0; public function __construct(int $width, int $height) { $this->width = $width; $this->height = $height; } public function getArea(): int { return $this->width * $this->height; } } class Square implements Shape { private $length = 0; public function __construct(int $length) { $this->length = $length; } public function getArea(): int { return $this->length ** 2; } } function printArea(Shape $shape): void { echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL; } $shapes = [new Rectangle(4, 5), new Square(5)]; foreach ($shapes as $shape) { printArea($shape); } ``` ### [接口隔离原则 (ISP)](https://github.com/piotrplenik/clean-code-php#interface-segregation-principle-isp) ISP 指出,“不应强迫客户端依赖于 他们不使用。 一个很好的例子可以证明这个原则是 需要大型设置对象的类。不需要客户端设置 大量的选择是有益的,因为大多数时候他们不需要 所有设置。使它们成为可选有助于防止出现“胖接口”。 坏: ```php interface Employee { public function work(): void; public function eat(): void; } class HumanEmployee implements Employee { public function work(): void { // ....working } public function eat(): void { // ...... eating in lunch break } } class RobotEmployee implements Employee { public function work(): void { //.... working much more } public function eat(): void { //.... robot can't eat, but it must implement this method } } ``` 好: 不是每个工人都是雇员,但每个雇员都是工人。 ```php interface Workable { public function work(): void; } interface Feedable { public function eat(): void; } interface Employee extends Feedable, Workable { } class HumanEmployee implements Employee { public function work(): void { // ....working } public function eat(): void { //.... eating in lunch break } } // robot can only work class RobotEmployee implements Workable { public function work(): void { // ....working } } ``` ### [依赖关系反转原则 (DIP)](https://github.com/piotrplenik/clean-code-php#dependency-inversion-principle-dip) 该原则规定了两件基本的事情: 1. 高级模块不应依赖于低级模块。两者都应该 依赖于抽象。 2. 抽象不应依赖于细节。细节应取决于 抽象。 一开始可能很难理解,但如果你使用过PHP框架(如Symfony),你就会看到这个原则以依赖的形式实现 注射 (DI)。虽然它们不是相同的概念,但 DIP 保持了高层次 模块,了解其低级模块的详细信息并设置它们。 它可以通过 DI 来实现这一点。这样做的一个巨大好处是它减少了 模块之间的耦合。耦合是一种非常糟糕的开发模式,因为 它使你的代码难以重构。 坏: ```php class Employee { public function work(): void { // ....working } } class Robot extends Employee { public function work(): void { //.... working much more } } class Manager { private $employee; public function __construct(Employee $employee) { $this->employee = $employee; } public function manage(): void { $this->employee->work(); } } ``` 好: ```php interface Employee { public function work(): void; } class Human implements Employee { public function work(): void { // ....working } } class Robot implements Employee { public function work(): void { //.... working much more } } class Manager { private $employee; public function __construct(Employee $employee) { $this->employee = $employee; } public function manage(): void { $this->employee->work(); } } ``` [不要重复自己 (DRY)](https://github.com/piotrplenik/clean-code-php#dont-repeat-yourself-dry) -------------------------------------------------------------------------------------- 尽量遵守 [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) 原则。 尽最大努力避免重复代码。重复代码很糟糕,因为 这意味着如果需要,有不止一个地方可以更改某些内容 改变一些逻辑。 想象一下,如果你经营一家餐馆,你跟踪你的库存:所有的 西红柿、洋葱、大蒜、香料等。如果您有多个列表 你保持这个状态,那么当你端上一道菜时,一切都必须更新 西红柿在里面。如果您只有一个列表,则只有一个位置可以更新! 通常,您有重复的代码,因为您有两个或更多代码 不同的东西,有很多共同点,但它们的差异迫使你 具有两个或多个单独的函数,它们执行许多相同的操作。删除 重复代码意味着创建一个抽象,可以处理这组不同的 只有一个函数/模块/类的东西。 获得正确的抽象是至关重要的,这就是为什么你应该遵循 [“类](https://github.com/piotrplenik/clean-code-php#classes)”部分中列出的 SOLID 原则。糟糕的抽象可能是 比重复代码更糟糕,所以要小心!话虽如此,如果你能做到 一个好的抽象,去做吧!不要重复自己,否则你会发现自己 随时更新多个位置,想要更改一件事。 坏: ```php function showDeveloperList(array $developers): void { foreach ($developers as $developer) { $expectedSalary = $developer->calculateExpectedSalary(); $experience = $developer->getExperience(); $githubLink = $developer->getGithubLink(); $data = [$expectedSalary, $experience, $githubLink]; render($data); } } function showManagerList(array $managers): void { foreach ($managers as $manager) { $expectedSalary = $manager->calculateExpectedSalary(); $experience = $manager->getExperience(); $githubLink = $manager->getGithubLink(); $data = [$expectedSalary, $experience, $githubLink]; render($data); } } ``` 好: ```php function showList(array $employees): void { foreach ($employees as $employee) { $expectedSalary = $employee->calculateExpectedSalary(); $experience = $employee->getExperience(); $githubLink = $employee->getGithubLink(); $data = [$expectedSalary, $experience, $githubLink]; render($data); } } ``` 非常好: 最好使用代码的紧凑版本。 ```php function showList(array $employees): void { foreach ($employees as $employee) { render([$employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink()]); } } ``` 最后修改:2023 年 12 月 11 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏