предисловие
Источник статьиGitHub.com/PHP-CPM/ Кроме…
Clean Code PHP
содержание
- вводить
-
Переменная
- Используйте буквальные имена переменных
- Используйте одно и то же имя переменной для одного и того же объекта
- Используйте удобные для поиска имена (часть 1)
- Используйте удобные для поиска имена (часть 2)
- Используйте понятные переменные
- Избегайте глубокой вложенности, возвращайтесь раньше (часть 1)
- Избегайте глубокой вложенности, возвращайтесь раньше (часть 2)
- Избегайте бессмысленных имен переменных
- Не добавляйте ненужный контекст
- Разумное использование значений параметров по умолчанию, нет необходимости определять значения по умолчанию в методе.
- выражение
-
функция
- Параметры функции (желательно меньше 2)
- Функция должна делать только одну вещь
- Имя функции должно отражать то, что она сделала
- В функции должен быть только один уровень абстракции
- Не используйте флаг в качестве параметра функции
- избежать побочных эффектов
- Не пишите глобальные функции
- Не используйте шаблон singleton
- Инкапсулировать условный оператор
- Избегайте суждений с антонимичными условиями
- Избегайте условного суждения
- Избегайте проверки типов (часть 1)
- Избегайте проверки типов (часть 2)
- удалить код зомби
- Объекты и структуры данных
- своего рода
- Принципы SOLID для классов SOLID
- Не пишите повторяющийся код (DRY)
- перевести
вводить
Эта статья относится к книге Роберта С. Мартина.Clean CodeПринципы инженеров-программистов в книге , для PHP. Это не руководство по стилю. Это руководство по разработке удобочитаемого, многократно используемого и реконфигурируемого программного обеспечения PHP.
Не все приведенные здесь принципы должны соблюдаться, и даже некоторые из них являются общепризнанными. Это всего лишь рекомендации, но ониClean CodeАвтор подвел итог годам.
Эта статья подлежитclean-code-javascriptвдохновение
Хотя многие разработчики все еще используют PHP5, для работы большинства примеров в этой статье требуется PHP 7.1+.
инструкции по переводу
Перевод выполнен на 100%, последний раз обновлялся 25 декабря 2017 г. Эта статья основана на php-cpmверсия янвэйцзеизclean-code-phpПереводите и синхронизируйте большие объемы оригинального контента.
Исходный текст часто обновляется, и мой метод перевода заключается в использовании инструмента сравнения текста для сравнения строк за строками. Приоритет отдается обеспечению актуальности текстового контента, а затем постепенно улучшается качество перевода.
Если вы столкнетесь с различными проблемами, такими как недействительные ссылки, устаревшее содержание, неправильная терминология и другие ошибки перевода в процессе чтения, вы можете активно отправлять PR.
Переменная
Используйте буквальные имена переменных
Плохой:
$ymdstr = $moment->format('y-m-d');
хорошо:
$currentDate = $moment->format('y-m-d');
Используйте одно и то же имя переменной для одного и того же объекта
Плохой:
getUserInfo();
getUserData();
getUserRecord();
getUserProfile();
хорошо:
getUser();
Используйте удобные для поиска имена (часть 1)
Написание кода предназначено для чтения. Вот почему так важно писать код, который будет удобочитаемым и удобным для поиска. Если вы называете переменные без смысла или понимания, вы причиняете вред читателю. Пожалуйста, сделайте ваш код доступным для поиска.
Плохой:
// What the heck is 448 for?
$result = $serializer->serialize($data, 448);
хорошо:
$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Используйте удобные для поиска имена (часть 2)
Плохой:
// What the heck is 4 for?
if ($user->access & 4) {
// ...
}
хорошо:
class User
{
const ACCESS_READ = 1;
const ACCESS_CREATE = 2;
const ACCESS_UPDATE = 4;
const ACCESS_DELETE = 8;
}
if ($user->access & User::ACCESS_UPDATE) {
// do edit ...
}
Используйте понятные переменные
Плохой:
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);
хорошо:
Лучше, но сильно зависит от знакомства с регулярными выражениями
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
[, $city, $zipCode] = $matches;
saveCityZipCode($city, $zipCode);
хорошо:
Используйте подправила с именами, вы можете понять их, не зная правил
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches['city'], $matches['zipCode']);
Избегайте глубокой вложенности, возвращайтесь раньше (часть 1)
Слишком много операторов if else часто затрудняют чтение кода.
Упс:
function isShopOpen($day): bool
{
if ($day) {
if (is_string($day)) {
$day = strtolower($day);
if ($day === 'friday') {
return true;
} elseif ($day === 'saturday') {
return true;
} elseif ($day === 'sunday') {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
хорошо:
function isShopOpen(string $day): bool
{
if (empty($day)) {
return false;
}
$openingDays = [
'friday', 'saturday', 'sunday'
];
return in_array(strtolower($day), $openingDays, true);
}
Избегайте глубокой вложенности, возвращайтесь раньше (часть 2)
плохой:
function fibonacci(int $n)
{
if ($n < 50) {
if ($n !== 0) {
if ($n !== 1) {
return fibonacci($n - 1) + fibonacci($n - 2);
} else {
return 1;
}
} else {
return 0;
}
} else {
return 'Not supported';
}
}
хорошо:
function fibonacci(int $n): int
{
if ($n === 0 || $n === 1) {
return $n;
}
if ($n > 50) {
throw new \Exception('Not supported');
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
Избегайте бессмысленных имен переменных
Не позволяйте никому, читающему ваш код, угадать, что означают написанные вами переменные. Лучше писать четко, чем туманно.
Плохой:
$l = ['Austin', 'New York', 'San Francisco'];
for ($i = 0; $i < count($l); $i++) {
$li = $l[$i];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// 等等, `$li` 又代表什么?
dispatch($li);
}
хорошо:
$locations = ['Austin', 'New York', 'San Francisco'];
foreach ($locations as $location) {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch($location);
}
Не добавляйте ненужный контекст
Если вы уже знаете некоторую информацию из имени вашего класса и имени объекта, не повторяйте ее в имени переменной.
Плохой:
class Car
{
public $carMake;
public $carModel;
public $carColor;
//...
}
хорошо:
class Car
{
public $make;
public $model;
public $color;
//...
}
Разумное использование значений параметров по умолчанию, нет необходимости определять значения по умолчанию в методе.
фигово:
фигово,$breweryName
может бытьNULL
.
function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
// ...
}
Хорошо:
Его легче понять, чем предыдущий, но лучше контролировать значение переменной
function createMicrobrewery($name = null): void
{
$breweryName = $name ?: 'Hipster Brew Co.';
// ...
}
хорошо:
Если ваша программа поддерживает только PHP 7+, вы можете использоватьtype hintingгарантированная переменная$breweryName
нетNULL
.
function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
// ...
}
выражение
использовать тождества
фигово:
Простое сравнение преобразует строки в целые числа
$a = '42';
$b = 42;
if( $a != $b ) {
//这里始终执行不到
}
В сравненииб вернулсяFALSE
но должен вернутьсяTRUE
!
Строка '42' не равна целому числу 42
хорошо:
Проверьте тип и данные, используя оценку идентичности
$a = '42';
$b = 42;
if ($a !== $b) {
// The expression is verified
}
The comparison $a !== $b
returns TRUE
.
функция
Параметры функции (желательно меньше 2)
Чрезвычайно важно ограничить количество аргументов функции, чтобы ее было легче тестировать. Наличие более 3 необязательных параметров приводит к взрывному сочетанию роста, и у вас есть множество вариантов отдельных параметров для тестирования.
Нет идеальных параметров. 1 или 2 нормально, 3 лучше избегать. Больше нужно укреплять. Обычно, если ваша функция имеет более двух параметров, у нее слишком много работы. Если необходимо передать много данных, рекомендуется инкапсулировать высокоуровневый объект в качестве параметра.
Плохой:
function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
// ...
}
хорошо:
class MenuConfig
{
public $title;
public $body;
public $buttonText;
public $cancellable = false;
}
$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;
function createMenu(MenuConfig $config): void
{
// ...
}
Функция должна делать только одну вещь
Это, безусловно, самое важное правило в разработке программного обеспечения. Когда функция выполняет более одной функции, ее сложно реализовать, протестировать и понять. Когда вы разделяете функцию только на одну оставшуюся функцию, их можно легко реорганизовать, и ваш код будет читаться более четко. Если вы просто будете следовать этому правилу, вы опередите большинство разработчиков.
Плохой:
function emailClients(array $clients): void
{
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}
хорошо:
function emailClients(array $clients): void
{
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}
function activeClients(array $clients): array
{
return array_filter($clients, 'isClientActive');
}
function isClientActive(int $client): bool
{
$clientRecord = $db->find($client);
return $clientRecord->isActive();
}
Имя функции должно отражать то, что она сделала
Плохой:
class Email
{
//...
public function handle(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// 啥?handle处理一个消息干嘛了?是往一个文件里写吗?
$message->handle();
хорошо:
class Email
{
//...
public function send(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// 简单明了
$message->send();
В функции должен быть только один уровень абстракции
Когда у вас слишком много уровней абстракции, функции обрабатывают слишком много. Функциональность разделения необходима для улучшения возможности повторного использования и простоты использования, чтобы упростить тестирование. (Примечание переводчика: из примера кода это должно означать слишком много вложенности)
Плохой:
function parseBetterJSAlternative(string $code): void
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
// ...
}
}
$ast = [];
foreach ($tokens as $token) {
// lex...
}
foreach ($ast as $node) {
// parse...
}
}
Плохой:
Мы убираем некоторые методы из цикла, ноparseBetterJSAlternative()
Метод все еще очень сложен и не подходит для тестирования.
function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
function lexer(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
function parseBetterJSAlternative(string $code): void
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
// 解析逻辑...
}
}
хорошо:
Лучшее решение - поставитьparseBetterJSAlternative()
Удаление зависимости метода.
class Tokenizer
{
public function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}
class Lexer
{
public function lexify(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
}
class BetterJSAlternative
{
private $tokenizer;
private $lexer;
public function __construct(Tokenizer $tokenizer, Lexer $lexer)
{
$this->tokenizer = $tokenizer;
$this->lexer = $lexer;
}
public function parse(string $code): void
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// 解析逻辑...
}
}
}
Таким образом, мы можем имитировать зависимости и тестироватьBetterJSAlternative::parse()
Работает ли это так, как ожидалось.
Не используйте флаг в качестве параметра функции
Флаг говорит всем, что этот метод обрабатывает множество вещей. Как я уже говорил ранее, функция должна делать только одну вещь. Разделите код для разных флагов на несколько функций.
Плохой:
function createFile(string $name, bool $temp = false): void
{
if ($temp) {
touch('./temp/'.$name);
} else {
touch($name);
}
}
хорошо:
function createFile(string $name): void
{
touch($name);
}
function createTempFile(string $name): void
{
touch('./temp/'.$name);
}
избежать побочных эффектов
Функция, которая делает больше, чем принимает одно значение, а затем возвращает другое значение или значения, будет иметь побочные эффекты, если. Побочными эффектами могут быть запись файла, изменение какой-либо глобальной переменной или случайная передача всех ваших денег незнакомцу.
Теперь вам нужно иметь побочные эффекты в программе или ситуации, как в предыдущем примере, вам может понадобиться написать файл. То, что вы хотите сделать, это централизовать, где вы это делаете. Не используйте несколько функций и классов для записи в конкретный файл. Сделайте это с помощью одного сервиса, одного только с одним.
Суть в том, чтобы избежать распространенных ловушек, таких как совместное использование неструктурированных данных между объектами, использование изменяемых типов данных, которые можно записать во что угодно, и не сосредотачиваться на том, где возникают побочные эффекты. Если вы сделаете это, вы будете счастливее, чем большинство программистов.
Плохой:
// 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'];
хорошо:
function splitIntoFirstAndLastName(string $name): array
{
return explode(' ', $name);
}
$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);
var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];
Не пишите глобальные функции
Загрязнение глобальных переменных — плохая практика для большинства языков, потому что вы можете конфликтовать с другими библиотеками.
И человек, вызывающий ваш API, не будет знать, что он наступил на яму, пока не поймает исключение. Рассмотрим сценарий:
Если вы хотите настроить массив, вы можете написать глобальную функциюconfig()
, но он может
Конфликты с другими библиотеками, пытающимися сделать то же самое.
Плохой:
function config(): array
{
return [
'foo' => 'bar',
]
}
хорошо:
class Configuration
{
private $configuration = [];
public function __construct(array $configuration)
{
$this->configuration = $configuration;
}
public function get(string $key): ?string
{
return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
}
}
Загрузить конфигурацию и создатьConfiguration
экземпляр класса
$configuration = new Configuration([
'foo' => 'bar',
]);
Теперь вы должны использоватьConfiguration
экземпляр
Не используйте шаблон singleton
Синглтон — этоантишаблонВот объяснение: Перефразируя Брайана Баттона:
- Всегда используется как глобальный экземпляр. Обычно они используются в качестве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.
- ломатьПринцип единого ответаThey violate the single responsibility principle: by virtue of the fact that they control their own creation and lifecycle.
- Они по своей сути делают код тесно связанным.coupled. This makes faking them out under test rather difficult in many cases.
- Всегда переносите состояние на протяжении всего жизненного цикла программы. Они сохраняют состояние на протяжении всего жизненного цикла приложения.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.
Вот очень хорошее обсуждение [фундаментальной проблемы одноэлементного шаблона ((Секретарь отдела О. и every.com/2008/08/25/…) статья, естьMisko Heveryнаписано.
Плохой:
class DBConnection
{
private static $instance;
private function __construct(string $dsn)
{
// ...
}
public static function getInstance(): DBConnection
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// ...
}
$singleton = DBConnection::getInstance();
хорошо:
class DBConnection
{
public function __construct(string $dsn)
{
// ...
}
// ...
}
СоздайтеDBConnection
экземпляр класса и передатьDSNнастроить.
$connection = new DBConnection($dsn);
Теперь вы должны использовать в программеDBConnection
экземпляр
Инкапсулировать условный оператор
Плохой:
if ($article->state === 'published') {
// ...
}
хорошо:
if ($article->isPublished()) {
// ...
}
Избегайте суждений с антонимичными условиями
Плохой:
function isDOMNodeNotPresent(\DOMNode $node): bool
{
// ...
}
if (!isDOMNodeNotPresent($node))
{
// ...
}
хорошо:
function isDOMNodePresent(\DOMNode $node): bool
{
// ...
}
if (isDOMNodePresent($node)) {
// ...
}
Избегайте условного суждения
Это кажется невыполнимой задачей. Так говорят люди, когда впервые слышат это.
"нетif语句
Что еще я могу сделать? " Ответ заключается в том, что вы можете использовать полиморфизм для достижения нескольких сценариев.
такая же задача. Второй вопрос очень распространен: «Это нормально, но зачем мне это делать?»
Ответ — принцип чистого кода, который мы изучили ранее: функция должна делать только одну вещь.
когда у тебя многоif
Когда вы объявляете классы и функции, ваши функции делают несколько вещей.
Помните, делайте только одно.
Плохой:
class Airplane
{
// ...
public function getCruisingAltitude(): int
{
switch ($this->type) {
case '777':
return $this->getMaxAltitude() - $this->getPassengerCount();
case 'Air Force One':
return $this->getMaxAltitude();
case 'Cessna':
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
}
хорошо:
interface Airplane
{
// ...
public function getCruisingAltitude(): int;
}
class Boeing777 implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getPassengerCount();
}
}
class AirForceOne implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude();
}
}
class Cessna implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
Избегайте проверки типов (часть 1)
PHP слабо типизирован, а это значит, что ваши функции могут принимать аргументы любого типа. Иногда вы страдаете от этой свободы и постепенно пробуете проверку типов в своих функциях. Есть много способов избежать этого. Во-первых, это единый API.
Плохой:
function travelToTexas($vehicle): void
{
if ($vehicle instanceof Bicycle) {
$vehicle->pedalTo(new Location('texas'));
} elseif ($vehicle instanceof Car) {
$vehicle->driveTo(new Location('texas'));
}
}
хорошо:
function travelToTexas(Traveler $vehicle): void
{
$vehicle->travelTo(new Location('texas'));
}
Избегайте проверки типов (часть 2)
Если вы работаете с базовыми примитивами, такими как строки, целые числа и массивы, требуется версия PHP 7+, не требуется полиморфизм, проверка типа требуется, тогда вы должны рассмотретьобъявление типаИли строгий режим. Обеспечивает статическую типизацию на основе стандартного синтаксиса PHP. Проблема с ручной проверкой типа заключается в том, что для того, чтобы сделать это хорошо, требуется много ерунды, как будто для безопасности без ущерба для читабельности. Держите свой PHP в чистоте, пишите хорошие тесты и делайте обзоры кода. Если вы не можете, используйте объявления строгого типа PHP и строгий режим для обеспечения безопасности.
Плохой:
function combine($val1, $val2): int
{
if (!is_numeric($val1) || !is_numeric($val2)) {
throw new \Exception('Must be of type Number');
}
return $val1 + $val2;
}
хорошо:
function combine(int $val1, int $val2): int
{
return $val1 + $val2;
}
удалить код зомби
Зомби-код так же плох, как дублированный код. Нет причин хранить его в кодовой базе. Если он никогда не вызывается, удалите его! Поскольку он все еще находится в репозитории кода, он в безопасности.
Плохой:
function oldRequestModule(string $url): void
{
// ...
}
function newRequestModule(string $url): void
{
// ...
}
$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
хорошо:
function requestModule(string $url): void
{
// ...
}
$request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
Объекты и структуры данных
Использование геттеров и сеттеров
В PHP вы можете использоватьpublic
, protected
, private
для управления изменениями свойств объекта.
- Если вы хотите сделать что-то другое, кроме получения свойства объекта, вам не нужно находить и изменять каждый метод доступа к свойству в вашем коде.
- когда есть
set
Когда используется соответствующий метод атрибутов, легко увеличить проверку параметров. - Представление внутри упаковки
- использовать набори получитьлегко добавить ведение журнала и контроль ошибок, когда
- При наследовании текущего класса вы можете переопределить функцию метода по умолчанию
- get*, set* просты в использовании ленивой загрузки, когда свойства объекта извлекаются с удаленного сервера.
Кроме того, этот метод также соответствует развитию ООП.принцип открыто-закрыто
Плохой:
class BankAccount
{
public $balance = 1000;
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->balance -= 100;
хорошо:
class BankAccount
{
private $balance;
public function __construct(int $balance = 1000)
{
$this->balance = $balance;
}
public function withdraw(int $amount): void
{
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance.');
}
$this->balance -= $amount;
}
public function deposit(int $amount): void
{
$this->balance += $amount;
}
public function getBalance(): int
{
return $this->balance;
}
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->withdraw($shoesPrice);
// Get balance
$balance = $bankAccount->getBalance();
Используйте частные или защищенные переменные-члены для объектов
- правильно
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
Модификации почти опасны, потому что они доступны для подклассов, единственное различие между ними - другое расположение callables,Изменения в нем затрагивают все места, где этот класс интегрирован.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).
Поэтому используйте его только тогда, когда вам нужно контролировать доступность кода в классе.public/protected
, иначе используетсяprivate
.
Вы можете прочитать этоСообщение блога,Fabien Potencierнаписано.
Плохой:
class Employee
{
public $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->name; // Employee name: John Doe
хорошо:
class Employee
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->getName(); // Employee name: John Doe
своего рода
Используйте меньше наследования и больше композиции
Как написано Бандой четырехШаблоны проектированияКак сказал раньше,
Мы должны попытаться предпочтить композицию по наследству. Использование наследования и композиции дает много преимуществ.
Суть этого правила в том, что когда вы инстинктивно используете наследование, старайтесь думать о组合
Есть ли лучший способ смоделировать ваши потребности.
В некоторых случаях это так.
Затем вы можете подумать: «Когда мне следует использовать наследование?» Ответ зависит от вашего вопроса, и, конечно же, есть некоторые инструкции по наследованию, кроме комбинации:
- Ваше наследование выражает отношения «является», а не «имеет» (Человек -> Животное, Пользователь -> Сведения о пользователе)
- Вы можете повторно использовать код из базовых классов (люди могут двигаться как животные)
- Вы хотите внести глобальные изменения во все производные классы, изменив базовый класс (когда животные двигаются, измените их потребление энергии)
плохой:
class Employee
{
private $name;
private $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
// ...
}
// 不好,因为 Employees "有" taxdata
// 而 EmployeeTaxData 不是 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;
}
// ...
}
хорошо:
class EmployeeTaxData
{
private $ssn;
private $salary;
public function __construct(string $ssn, string $salary)
{
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
class Employee
{
private $name;
private $email;
private $taxData;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
public function setTaxData(string $ssn, string $salary)
{
$this->taxData = new EmployeeTaxData($ssn, $salary);
}
// ...
}
Избегайте согласованных интерфейсов
Свободный интерфейсэто Шаблон проектирования API, предназначенный для улучшения читаемости кода в объектно-ориентированном программировании, основанный наЦепочка методов
Сложность кода может быть уменьшена там, где есть контекст, например.PHPUnit Mock BuilderиDoctrine Query Builder, большее количество случаев повлечет за собой большие расходы:
While there can be some contexts, frequently builder objects, where this pattern reduces the verbosity of the code (for example the PHPUnit Mock Builder or the Doctrine Query Builder), more often it comes at some costs:
- сломанныйОбъект инкапсулирует
- сломанныйШаблон декоратора
- Плохо делать в тестовых компонентахmock
- Представленный diff не легко читать
Читать далееПочему когерентные интерфейсы плохи,авторMarco Pivetta.
Плохой:
class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake(string $make): self
{
$this->make = $make;
// NOTE: Returning this for chaining
return $this;
}
public function setModel(string $model): self
{
$this->model = $model;
// NOTE: Returning this for chaining
return $this;
}
public function setColor(string $color): self
{
$this->color = $color;
// NOTE: Returning this for chaining
return $this;
}
public function dump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = (new Car())
->setColor('pink')
->setMake('Ford')
->setModel('F-150')
->dump();
хорошо:
class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake(string $make): void
{
$this->make = $make;
}
public function setModel(string $model): void
{
$this->model = $model;
}
public function setColor(string $color): void
{
$this->color = $color;
}
public function dump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();
Рекомендуются заключительные занятия
Используйте как можно большеfinal
Ключевые слова:
- Предотвращение неконтролируемых цепочек наследования
- поощрятькомбинация.
- поощрятьМодель единой ответственности.
- Поощряйте разработчиков использовать ваши общедоступные методы вместо наследования классов для получения доступа к защищенным методам.
- Позволяет изменять код без нарушения работы приложений, использующих ваши классы.
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 on this topic written by Marco Pivetta (Ocramius).
Bad:
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:
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Это легко запоминающаяся аббревиатура, рекомендованная Майклом Фезерсом, которая представляет пять наиболее важных принципов проектирования объектно-ориентированного кодирования, названных Робертом Мартином.
- S: Принцип единой ответственности (SRP)
- O: открытый закрытый принцип (OCP)
- L: Принцип замещения Лисков (LSP)
- I: Принцип разделения интерфейсов (ISP)
- D: Принцип инверсии зависимостей (DIP)
Принцип единой ответственности
Single Responsibility Principle (SRP)
Как сказано в «Чистом коде», «изменение класса должно выполняться только по одной причине». Всегда легко напихать класс кучей методов, как будто мы можем только сесть на самолет Носите с собой только один чемодан (все в чемодан). Сюда Проблема в том, что концептуально такой класс не очень сплочен и оставляет много причина изменить его. Важно свести к минимуму количество изменений классов. Это связано с тем, что, когда в классе много методов, сложно модифицировать один из них. Знайте, какие зависимые модули в кодовой базе будут затронуты.
Плохой:
class UserSettings
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function changeSettings(array $settings): void
{
if ($this->verifyCredentials()) {
// ...
}
}
private function verifyCredentials(): bool
{
// ...
}
}
хорошо:
class UserAuth
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function verifyCredentials(): bool
{
// ...
}
}
class UserSettings
{
private $user;
private $auth;
public function __construct(User $user)
{
$this->user = $user;
$this->auth = new UserAuth($user);
}
public function changeSettings(array $settings): void
{
if ($this->auth->verifyCredentials()) {
// ...
}
}
}
принцип открыто-закрыто
Open/Closed Principle (OCP)
Как утверждает Бертран Мейер, «артефакты программного обеспечения (классы, модули, функции и т. Он должен быть открыт для расширения и закрыт для модификации. «Но что означает это предложение? Этот принцип в основном означает, что вы Должен позволять добавлять новые функции без изменения существующего кода.
Плохой:
abstract class Adapter
{
protected $name;
public function getName(): string
{
return $this->name;
}
}
class AjaxAdapter extends Adapter
{
public function __construct()
{
parent::__construct();
$this->name = 'ajaxAdapter';
}
}
class NodeAdapter extends Adapter
{
public function __construct()
{
parent::__construct();
$this->name = 'nodeAdapter';
}
}
class HttpRequester
{
private $adapter;
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}
public function fetch(string $url): Promise
{
$adapterName = $this->adapter->getName();
if ($adapterName === 'ajaxAdapter') {
return $this->makeAjaxCall($url);
} elseif ($adapterName === 'httpNodeAdapter') {
return $this->makeHttpCall($url);
}
}
private function makeAjaxCall(string $url): Promise
{
// request and return promise
}
private function makeHttpCall(string $url): Promise
{
// request and return promise
}
}
хорошо:
interface Adapter
{
public function request(string $url): Promise;
}
class AjaxAdapter implements Adapter
{
public function request(string $url): Promise
{
// request and return promise
}
}
class NodeAdapter implements Adapter
{
public function request(string $url): Promise
{
// request and return promise
}
}
class HttpRequester
{
private $adapter;
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}
public function fetch(string $url): Promise
{
return $this->adapter->request($url);
}
}
Принцип замены Лисков
Liskov Substitution Principle (LSP)
Это простой принцип, но в плохо понятном термине. Его формальное определение "Если S является подтипом T, то без изменения исходных свойств программы (проверить, выполнить задача и т. д.), любой объект типа T можно заменить на объект типа S (например, объект, использующий S, может быть заменен объектом T)" Это определение труднее понять :-).
Лучшее объяснение этой концепции: если у вас есть родительский класс и дочерний класс, без изменения При условии правильности исходного результата родительский класс и дочерний класс могут быть заменены местами. Это все еще звучит потрясающе Это немного сбивает с толку, поэтому давайте рассмотрим классический пример квадрат-прямоугольник. из математики Выше квадрат является типом прямоугольника, но когда ваша модель использует «is-a» через наследование отношения, это неправильно.
Плохой:
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);
}
хорошо:
Лучше рассматривать два типа квадрицепсов отдельно и заменять их более общим подтипом, который подходит для обоих типов.
Хотя квадраты и прямоугольники выглядят одинаково, они разные. Квадрат ближе к ромбу, а прямоугольник ближе к параллелограмму. Но они не подтипы. Несмотря на то, что квадраты, прямоугольники, ромбы, параллелограммы похожи, все они разные формы со своими свойствами.
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);
}
Принцип разделения интерфейса
Interface Segregation Principle (ISP)
Принцип разделения интерфейсов гласит: «Вызывающий объект не должен зависеть от интерфейсов, которые ему не нужны».
Есть яркий пример, иллюстрирующий этот принцип. Когда для класса требуется большое количество настроек, Для удобства звонящему не требуется задавать большое количество опций, так как в общем случае все они не нужны. пункт настройки. Делая настройки необязательными, мы избегаем «жирных интерфейсов».
Плохой:
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
}
}
хорошо:
Не каждый рабочий является наемным работником, но каждый наемный работник является рабочим
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
}
}
Принцип инверсии зависимости
Dependency Inversion Principle (DIP)
Этот принцип устанавливает два основных положения:
- Модули более высокого порядка не должны зависеть от модулей более низкого порядка, все они должны зависеть от абстракций.
- Абстракция не должна зависеть от реализации, реализация должна зависеть от абстракции
Сначала это кажется немного неясным, но если вы использовали PHP-фреймворк (например, Symfony), вы, вероятно, видели Внедрение зависимостей (DI), которое является реализацией этой концепции. Хотя это не совсем равные понятия, принцип инверсии зависимостей делает модули более высокого порядка Отделение от деталей реализации и создание низкоуровневых модулей. Это можно сделать с помощью внедрения зависимостей (DI). Самое большое преимущество Да, он разделяет модули. Связывание затрудняет рефакторинг, и это очень плохой шаблон разработки.
Плохой:
class Employee
{
public function work(): void
{
// ....working
}
}
class Robot extends Employee
{
public function work(): void
{
//.... working much more
}
}
class Manager
{
private $employee;
public function __construct(Employee $employee)
{
$this->employee = $employee;
}
public function manage(): void
{
$this->employee->work();
}
}
хорошо:
interface Employee
{
public function work(): void;
}
class Human implements Employee
{
public function work(): void
{
// ....working
}
}
class Robot implements Employee
{
public function work(): void
{
//.... working much more
}
}
class Manager
{
private $employee;
public function __construct(Employee $employee)
{
$this->employee = $employee;
}
public function manage(): void
{
$this->employee->work();
}
}
Не пишите повторяющийся код (DRY)
попробуй следоватьDRYв общем.
Делайте все возможное, чтобы избежать дублирования кода, это очень плохое поведение, дублирование кода Обычно это означает, что когда вам нужно изменить какую-то логику, вам нужно изменить более одного места.
Представьте, что вы управляете рестораном и ведете учет покупок и продаж вашего склада: все картофеля, лука, чеснока, перца и др. Если у вас есть несколько списков для управления записями выставления счетов, когда вы Вам нужно будет обновить все списки при приготовлении некоторых из этих видов картофеля. если у вас есть только один список Только одно место нужно обновить.
Обычно при копировании кода должно быть две или более немного отличающихся логики, большинство из них одинаковы, но из-за их различий вы должны иметь два или более изолированных, но в основном Разделите один и тот же метод, удаление повторяющегося кода означает создание функции/модуля/класса Создайте абстракцию, которая обрабатывает разницу.
Использование имеет решающее значение для абстракции, поэтому вы должны научиться соблюдатьсвоего родаГлава написание Твердые принципы, необоснованная абстракция хуже, чем дублирующий код, поэтому будьте осторожны! По словам так много, Если вы можете разработать разумную абстракцию, сделайте это! Не записывайте дублирующий код, или вы найдете В любое время вы хотите изменить часть логики, вы должны изменить несколько мест.
Плохой:
function showDeveloperList(array $developers): void
{
foreach ($developers as $developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
function showManagerList(array $managers): void
{
foreach ($managers as $manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
хорошо:
function showList(array $employees): void
{
foreach ($employees as $employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
Отлично:
Лучше сделать код компактным
function showList(array $employees): void
{
foreach ($employees as $employee) {
render([
$employee->calculateExpectedSalary(),
$employee->getExperience(),
$employee->getGithubLink()
]);
}
}