инструкции по переводу
Эта статья основана на php-cpmверсия янвэйцзеизclean-code-phpПереводите и синхронизируйте большие объемы оригинального контента.
Исходный текст часто обновляется, и мой метод перевода заключается в использовании инструмента сравнения текста для сравнения строк за строками. Приоритет отдается обеспечению актуальности текстового контента, а затем постепенно улучшается качество перевода.
Если вы столкнетесь с различными проблемами, такими как недействительные ссылки, устаревшее содержание, неправильная терминология и другие ошибки перевода в процессе чтения, вы можете активно отправлять PR.
Хотя многие разработчики все еще используют PHP5, для работы большинства примеров в этой статье требуется PHP 7.1+.
содержание
- вводить
-
Переменная
- Используйте буквальные имена переменных
- Используйте одно и то же имя переменной для одного и того же объекта
- Используйте удобные для поиска имена (часть 1)
- Используйте удобные для поиска имена (часть 2)
- Используйте понятные переменные
- Используйте понятные переменные
- Избегайте глубокой вложенности, возвращайтесь раньше (часть 1)
- Избегайте глубокой вложенности, возвращайтесь раньше (часть 2)
- Избегайте бессмысленных имен переменных
- Не добавляйте ненужный контекст
- Разумное использование значений параметров по умолчанию, нет необходимости определять значения по умолчанию в методе.
-
функция
- Параметры функции (желательно меньше 2)
- Функция должна делать только одну вещь
- Имена функций должны быть осмысленными глаголами (или указывать, что делать)
- В функции должен быть только один уровень абстракции
- Не используйте флаг в качестве параметра функции
- избежать побочных эффектов
- Не пишите глобальные функции
- Не используйте рисунок Singleton
- Инкапсулировать условный оператор
- Избегайте суждений с антонимичными условиями
- Избегайте условного суждения
- Избегайте проверки типов (часть 1)
- Избегайте проверки типов (часть 2)
- удалить код зомби
- Объекты и структуры данных
- своего рода
- Принципы SOLID для классов SOLID
- Не пишите повторяющийся код (DRY)
- перевести
вводить
Эта статья относится к книге Роберта С. Мартина.Clean CodeПринципы инженера-программиста, изложенные в книге, применимы и к PHP. Это не руководство по стилю. Это руководство по разработке удобочитаемого, многократно используемого и реконфигурируемого программного обеспечения PHP.
Не все приведенные здесь принципы должны соблюдаться, и даже некоторые из них являются общепризнанными. Это всего лишь рекомендации, но ониClean CodeАвтор подвел итог годам.
Эта статья подлежитclean-code-javascriptвдохновение
Переменная
Используйте буквальные имена переменных
Плохой:
$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);
}
Избегайте глубокой вложенности, возвращайтесь раньше (часть 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) {
return 0;
}
if ($n === 1) {
return 1;
}
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;
//...
}
Разумное использование значений параметров по умолчанию, нет необходимости определять значения по умолчанию в методе.
фигово:
This is not good because $breweryName
can be NULL
.
function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
// ...
}
Хорошо:
This opinion is more understandable than the previous version, but it better controls the value of the variable.
function createMicrobrewery($name = null): void
{
$breweryName = $name ?: 'Hipster Brew Co.';
// ...
}
хорошо:
If you support only PHP 7+, then you can use type hinting and be sure that the $breweryName
will not be NULL
.
function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
// ...
}
функция
Параметры функции (желательно меньше 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) {
// parse...
}
}
хорошо:
Лучшее решениеparseBetterJSAlternative()
Зависимое удаление метода.
class Tokenizer
{
public function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}
class Lexer
{
public function lexify(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
}
class BetterJSAlternative
{
private $tokenizer;
private $lexer;
public function __construct(Tokenizer $tokenizer, Lexer $lexer)
{
$this->tokenizer = $tokenizer;
$this->lexer = $lexer;
}
public function parse(string $code): void
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// parse...
}
}
}
Таким образом, мы можем имитировать зависимости и тестировать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;
}
}
Load configuration and create instance of Configuration
class
$configuration = new Configuration([
'foo' => 'bar',
]);
And now you must use instance of Configuration
in your application.
Не используйте шаблон singleton
Синглтон — этоантишаблон. Paraphrased from Brian Button:
- They are generally used as a global instance, why is that so bad? Because you hide the dependencies of your application in your code, instead of exposing them through the interfaces. Making something global to avoid passing it around is a code smell.
- They violate the single responsibility principle: by virtue of the fact that they control their own creation and lifecycle.
- They inherently cause code to be tightly coupled. This makes faking them out under test rather difficult in many cases.
- They carry state around for the lifetime of the application. Another hit to testing since you can end up with a situation where tests need to be ordered which is a big no for unit tests. Why? Because each unit test should be independent from the other.
There is also very good thoughts by Misko Hevery about the root of problem.
Плохой:
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)
{
// ...
}
// ...
}
Create instance of DBConnection
class and configure it with DSN.
$connection = new DBConnection($dsn);
And now you must use instance of DBConnection
in your application.
Инкапсулировать условный оператор
Плохой:
if ($article->state === 'published') {
// ...
}
хорошо:
if ($article->isPublished()) {
// ...
}
Избегайте суждений с антонимичными условиями
Плохой:
function isDOMNodeNotPresent(\DOMNode $node): bool
{
// ...
}
if (!isDOMNodeNotPresent($node))
{
// ...
}
хорошо:
function isDOMNodePresent(\DOMNode $node): bool
{
// ...
}
if (isDOMNodePresent($node)) {
// ...
}
Избегайте условного суждения
Это кажется невыполнимой задачей. Так говорят люди, когда впервые слышат это. "нетif语句
Я могу Zuosa? «Ответ заключается в том, что вы можете использовать для достижения одинаковой задачи полиморфного разнообразия сценариев. Вторая проблема очень распространена, - вы можете сделать это, но почему я это делаю? «Ответ перед нами, изучил принцип чистого кода: функция должна делать только одну вещь, когда у вас есть много соотношения.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');
Объекты и структуры данных
использовать getters 和 setters
Вы можете использовать метод в PHPpublic
, 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 withdrawBalance(int $amount): void
{
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance.');
}
$this->balance -= $amount;
}
public function depositBalance(int $amount): void
{
$this->balance += $amount;
}
public function getBalance(): int
{
return $this->balance;
}
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->withdrawBalance($shoesPrice);
// Get balance
$balance = $bankAccount->getBalance();
Свойства объекта в основном ограничены частными/защищенными
Упс:
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, предназначенный для улучшения читаемости кода в объектно-ориентированном программировании на основеЦепочка методов
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:
- Breaks Encapsulation
- Breaks Decorators
- Is harder to mock in a test suite
- Makes diffs of commits harder to read
For more informations you can read the full blog post on this topic written by Marco Pivetta.
Плохой:
class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake(string $make): self
{
$this->make = $make;
// NOTE: Returning this for chaining
return $this;
}
public function setModel(string $model): self
{
$this->model = $model;
// NOTE: Returning this for chaining
return $this;
}
public function setColor(string $color): self
{
$this->color = $color;
// NOTE: Returning this for chaining
return $this;
}
public function dump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = (new Car())
->setColor('pink')
->setMake('Ford')
->setModel('F-150')
->dump();
хорошо:
class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake(string $make): void
{
$this->make = $make;
}
public function setModel(string $model): void
{
$this->model = $model;
}
public function setColor(string $color): void
{
$this->color = $color;
}
public function dump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();
SOLID
SOLIDЭто легко запоминающаяся аббревиатура, рекомендованная Майклом Фезерсом, которая представляет пять наиболее важных принципов проектирования объектно-ориентированного кодирования, названных Робертом Мартином.
- S: Принцип единой ответственности (SRP)
- O: открытый закрытый принцип (OCP)
- L: Принцип замены Ривиса (LSP)
- I: Принцип разделения интерфейсов (ISP)
- D: Принцип инверсии зависимостей (DIP)
Принцип единой ответственности Принцип единой ответственности (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()) {
// ...
}
}
}
принцип открыто-закрыто Открытый/закрытый принцип (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);
}
}
Принцип замены Лисков Принцип замещения Лисков (LSP)
Это простой принцип, но в плохо понятном термине. Его формальное определение таково: «Если S является подтипом T, то любой объект типа T может быть заменен объектом типа S без изменения исходных свойств программы (проверка, выполнение задач и т. д.). Объекты, использующие S, могут быть заменяет объекты T)" Это определение труднее понять :-).
Лучшее объяснение этой концепции таково: если у вас есть родительский класс и дочерний класс, родительский класс и дочерний класс можно поменять местами без изменения правильности исходного результата. Это все еще звучит запутанно, поэтому давайте рассмотрим классический пример квадрат-прямоугольник. Математически квадрат — это своего рода прямоугольник, но когда ваша модель использует отношение «есть-а» посредством наследования, это неверно.
Плохой:
class Rectangle
{
protected $width = 0;
protected $height = 0;
public function render(int $area): void
{
// ...
}
public function setWidth(int $width): void
{
$this->width = $width;
}
public function setHeight(int $height): void
{
$this->height = $height;
}
public function getArea(): int
{
return $this->width * $this->height;
}
}
class Square extends Rectangle
{
public function setWidth(int $width): void
{
$this->width = $this->height = $width;
}
public function setHeight(int $height): void
{
$this->width = $this->height = $height;
}
}
function renderLargeRectangles(Rectangle $rectangles): void
{
foreach ($rectangles as $rectangle) {
$rectangle->setWidth(4);
$rectangle->setHeight(5);
$area = $rectangle->getArea(); // BAD: Will return 25 for Square. Should be 20.
$rectangle->render($area);
}
}
$rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles($rectangles);
хорошо:
abstract class Shape
{
protected $width = 0;
protected $height = 0;
abstract public function getArea(): int;
public function render(int $area): void
{
// ...
}
}
class Rectangle extends Shape
{
public function setWidth(int $width): void
{
$this->width = $width;
}
public function setHeight(int $height): void
{
$this->height = $height;
}
public function getArea(): int
{
return $this->width * $this->height;
}
}
class Square extends Shape
{
private $length = 0;
public function setLength(int $length): void
{
$this->length = $length;
}
public function getArea(): int
{
return pow($this->length, 2);
}
}
function renderLargeRectangles(Shape $rectangles): void
{
foreach ($rectangles as $rectangle) {
if ($rectangle instanceof Square) {
$rectangle->setLength(5);
} elseif ($rectangle instanceof Rectangle) {
$rectangle->setWidth(4);
$rectangle->setHeight(5);
}
$area = $rectangle->getArea();
$rectangle->render($area);
}
}
$shapes = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles($shapes);
Принцип разделения интерфейса Принцип разделения интерфейсов (ISP)
Принцип разделения интерфейсов гласит: «Вызывающий объект не должен зависеть от интерфейсов, которые ему не нужны».
Есть яркий пример, иллюстрирующий этот принцип. Когда для класса требуется большое количество настроек, вызывающему объекту не требуется устанавливать большое количество параметров для удобства, так как ему обычно не нужны все настройки. Делая настройки необязательными, мы избегаем «жирных интерфейсов».
Плохой:
interface Employee
{
public function work(): void;
public function eat(): void;
}
class Human implements Employee
{
public function work(): void
{
// ....working
}
public function eat(): void
{
// ...... eating in lunch break
}
}
class Robot implements Employee
{
public function work(): void
{
//.... working much more
}
public function eat(): void
{
//.... robot can't eat, but it must implement this method
}
}
хорошо:
Не каждый рабочий является наемным работником, но каждый наемный работник является рабочим
interface Workable
{
public function work(): void;
}
interface Feedable
{
public function eat(): void;
}
interface Employee extends Feedable, Workable
{
}
class Human implements Employee
{
public function work(): void
{
// ....working
}
public function eat(): void
{
//.... eating in lunch break
}
}
// robot can only work
class Robot implements Workable
{
public function work(): void
{
// ....working
}
}
Принцип инверсии зависимости Принцип инверсии зависимостей (DIP)
Этот принцип устанавливает два основных положения:
- Модули более высокого порядка не должны зависеть от модулей более низкого порядка, все они должны зависеть от абстракций.
- Абстракция не должна зависеть от реализации, реализация должна зависеть от абстракции
Сначала эта строка может показаться немного неясной, но если вы использовали 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в общем.
Делайте все возможное, чтобы избежать копирования кода, это очень плохое поведение, код репликации обычно означает, что вам нужно изменить более одной вещи, когда вам нужно изменить некоторую логику.
Представьте, что вы управляете рестораном и ведете учет покупок и продаж вашего склада: весь картофель, лук, чеснок, перец и т. д. Если у вас есть несколько списков для управления выставлением счетов, вам нужно будет обновить все списки, когда вы будете готовить картофель. Если у вас есть только один список, необходимо обновить только одно место.
Обычно, когда вы копируете код, у вас должно быть две или более слегка различающихся логики, большинство из них одинаковы, но из-за их различий у вас должно быть два или более изолированных, но в основном одинаковых метода. Удаление дублирующегося кода означает создание абстракции с функцией /module/class, который обрабатывает разницу.
Правильная абстракция имеет решающее значение, поэтому вы должны научиться соблюдатьClassesВ главе, раскрывающей принципы SOLID, необоснованная абстракция хуже, чем дублирование кода, так что будьте осторожны! Сказав все это, если вы можете разработать разумную абстракцию, реализуйте ее! Не повторяйте, иначе вы обнаружите, что каждый раз, когда вы хотите изменить часть логики, вам приходится менять несколько мест.
Плохой:
function showDeveloperList(array $developers): void
{
foreach ($developers as $developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
function showManagerList(array $managers): void
{
foreach ($managers as $manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
хорошо:
function showList(array $employees): void
{
foreach ($employees as $employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
Отлично:
It is better to use a compact version of the code.
function showList(array $employees): void
{
foreach ($employees as $employee) {
render([
$employee->calculateExpectedSalary(),
$employee->getExperience(),
$employee->getGithubLink()
]);
}
}
перевести
Перевод на другие языки:
- Chinese:
- Russian:
- Portuguese:
- Thai: