Чистый код на PHP: функции

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Это принципы разработки ПО, взятые из книги Clean Code Роберта Мартина и адаптированные для PHP. Это руководство не по стилям программирования, а по созданию читабельного, многократно используемого и пригодного для рефакторинга кода на PHP.
Не каждый из этих принципов должен строго соблюдаться, и ещё с меньшим количеством все будут согласны. Это лишь рекомендации, не более, но все они кодифицированы в многолетнем коллективном опыте автора Clean Code.
Источник: https://github.com/peter-gribanov/clean-code-php

Функции

Крайне важно ограничивать количество параметров функций, потому что это упрощает тестирование. Больше трёх аргументов ведёт к "комбинаторному взрыву", когда вам нужно протестировать кучу разных случаев применительно к каждому аргументу.

Идеальный вариант — вообще без аргументов. Один-два тоже нормально, но трёх нужно избегать. Если их получается больше, то нужно объединять, чтобы уменьшить количество. Обычно если у вас больше двух аргументов, то функция делает слишком много. В тех случаях, когда это не так, чаще всего в качестве аргумента достаточно использовать более высокоуровневый объект.

Аргументы функций: чем меньше — тем лучше

 Плохо:

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(...); // What is this? A handle for the message? Are we writing to a file now? $message->handle();

Хорошо:

class Email  {     //...      public function send(): void     {         mail($this->to, $this->subject, $this->body);     } }  $message = new Email(...); // Clear and obvious $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...         }     } }

Не используйте флаги в качестве параметров функций

Флаги говорят вашим пользователям, что функции делают больше одной вещи. А они должны делать что-то одно. Разделяйте свои функции, если они идут по разным ветвям кода в соответствии с булевой логикой.

Плохо:

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'];

Да и вообще, старайтесь не использовать global.

Не пишите в глобальные функции

Засорение глобалами — дурная привычка в любом языке, потому что вы можете конфликтовать с другой библиотекой, а пользователи вашего API не будут об этом знать, пока не получат исключение в production. Пример: вам нужен конфигурационный массив. Вы пишете глобальную функцию вроде 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 в своем приложении.

Инкапсулирование условных выражений

Инкапсуляция - это перенос некоторых данных в объект. Этот термин часто используется взаимозаменяемо с "сокрытием информации". Преимущество инкапсуляции заключается в том, что выполнение задачи происходит внутри объекта. Таким образом, это избавляет программиста от лишнего сравнения извне объекта и от создания дополнительных переменных.

Плохо:

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();     } }

Избегайте проверки типов

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')); }

Если вы работаете с базовыми примитивами (вроде строковых, целочисленных) и массивами, то не можете использовать полиморфизм. Но если кажется, что вам всё ещё нужна проверка типов и вы используете PHP 7+, то примените объявление типов или строгий режим (strict mode). Это даст вам статичную типизацию поверх стандартного 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');

Источник: m.vk.com

Комментарии: