Перейти к содержанию

Codeconventions

Правила написания кода (Code Conventions) в компании "Программный Центр"

Code Conventions — это правила, которые нужно соблюдать при написании любого кода. Мы различаем Code Style и Code Conventions. Для нас Code Style — это внешний вид кода. То есть расстановка отступов, запятых, скобок и прочего. А Code Conventions — это смысловое содержание кода. Правильные алгоритмы действий, правильные по смыслу названия переменных и методов, правильная композиция кода. Соблюдение Code Style легко проверяется автоматикой. А вот проверить соблюдение Code Conventions в большинстве случаев может только человек.

Ценности

Главная цель Code Conventios — сохранение низкой стоимости разработки и поддержки кода на длинной дистанции.

Основные ценности, помогающие достичь этой цели:

  • Читаемость

    Код должен легко читаться, а не легко записываться. Это значит, что такие вещи как синтаксический сахар (если он направлен на ускорение записи, а не дальнейшего чтения кода) вредны. Не нужно экономить переменные, буквы для их названий и так далее.

  • Вандалоустойчивость

    Код надо писать так, чтобы у разработчика, который с ним будет работать, было как можно меньше возможности внести ошибку.

  • Поддержание наименьшей энтропии

    Энтропия — это количество информации, из которой состоит проект (информационная емкость проекта). Код проекта должен выполнять продуктовые требования с сохранением наименьшей возможной энтропии.

Принципы

Принципы — это способы соблюдения описанных выше ценностей. Они чуть более детальны, содержат основные методологии разработки и подходы, которыми мы руководствуемся.

Код должен быть:

  • Понятным, явным. Явное лучше, чем неявное.
  • Удобным для использования сейчас.
  • Удобным для использования в будущем.
  • Должен стремиться к соблюдению принципов KISS, SOLID, DRY, GRASP.
  • Код должен обладать низкой связанностью и высокой связностью (подробно это описано в GRASP). Любая часть системы должна иметь изолированную логику и при надобности внешний интерфейс, который позволяет с этой логикой работать. Любая внутренняя часть должна иметь возможность быть измененной без какого-либо ущерба внешним системам.
  • Код должен быть таким, чтобы его можно было автоматически отрефакторить в IDE (например, Find usages и Rename в PHPStorm). То есть должен быть слинкован типизацией и PHPDoc'ами.
  • В БД не должны храниться части кода (даже названия классов, переменных и констант), так как это делает невозможным автоматический рефакторинг.
  • Последовательным. Код должен читаться сверху вниз. Читающий не должен держать что-то в уме, возвращаться назад и интерпретировать код иначе. Например, надо избегать обратных циклов do {} while ();
  • Должен иметь минимальную цикломатическую сложность.

Общие правила

  • Запрещен неиспользуемый код

    Если код можно убрать, и работа системы от этого не изменится, его быть не должно.

    Плохо:

    if (false) 
    {
        legacyMethodCall();
    }
    // ...
    $legacyCondition = true;
    if ($legacyCondition) 
    {
        finalizeData($data);
    }
    

    Хорошо:

    // ...
    finalizeData($data);
    

  • Название переменных должно соответствовать содержанию

    Нельзя писать короткие названия, например $c. Нельзя назвать переменную $day и хранить в ней массив статистических данных за этот день.

  • Часто упоминаемые объекты именуются одинаково во всем проекте

    Плохо:

    $customer = new User();
    $client = new User();
    $object = new User();
    

    Хорошо:

    $user = new User();
    

  • Признак объекта добавляется к названию

    Если это отфильтрованный по какому-то признаку заказ, то признак добавляется к названию. Например, $unpaidOrder.

  • Переменные, отражающие свойства объекта, должны включать название объекта

    Плохо:

    $project = new Project();
    $name = $project->name;
    $project = $project->name;
    

    Хорошо:

    $project = new Project();
    $projectName = $project->name;
    

  • Переменные по возможности должны называться на корректном английском

    Плохо:

    $usersStored = [];
    

    Хорошо:

    $storedUsers = [];
    

    Исключение: сгруппированные по некому признаку поля или константы. В этом случае можно использовать префикс.

  • К переменной нельзя обращаться по ссылке (через &)

    Амперсанды могут использоваться только как логические или битовые операторы.

    Плохо:

    function removePrefix(string &$name) 
    {
        // ...
    }
    

    Хорошо:

    function removePrefix(string $name): string 
    {
        // ...
        return $result;
    }
    

  • Переменные и свойства объекта должны являться существительными и называться так, чтобы они правильно читались при использовании, а не при инициализации

    Плохо:

    $object->expire_at
    $object->setExpireAt($date);
    $object->getExpireAt();
    

    Хорошо:

    $object->expiration_date;
    $object->setExpirationDate($date);
    $object->getExpirationDate();
    

  • В названии переменной не должно быть указания типа

    Нельзя писать $projectsArray, надо писать просто $projects. Это же касается и форматов (JSON, XML и т.п.), и любой другой не относящейся к предметной области информации.

    Плохо:

    $projectsList = $repository->loadProjects();
    $projectsListIds = $utils->extractField('id', $projectsList);
    

    Хорошо:

    $projects = $repository->loadProjects();
    $projectsIds = $utils->extractField('id', $projects);
    

  • Нельзя изменять переменные, которые передаются в метод на вход

    Исключение — если эта переменная объект.

    Плохо:

    function parseText(string $text) 
    {
        $text = trim($text);
        // ...
    }
    

    Хорошо:

    function parseText(string $text) 
    {
        $trimmedText = trim($text);
        // ...
    }
    

Нельзя нескольким переменным присваивать одно и то же значение

Плохо:
```
function loadUsers(array $ids) 
{
    $usersIds = $ids;
    // ...
}
```
  • Оператор clone должен использоваться только в тех случаях, когда без него не обойтись

    Также можно использовать clone, если без него код серьёзно усложнится, а с ним станет понятным и очевидным. Простой пример — клонирование объектов DateTime. Или использование клонирования для сравнения двух версий объекта: старой и новой.

  • Запрещено использовать результат операции присваивания

    Плохо:

    $foo = $bar = strlen($someVar);
    // ...
    $this->_callSomeFunc($bar = strlen($foo));
    // ...
    if (strlen($foo = json_encode($bar)) > 100) 
    {
       // ...
    }
    

    Хорошо:

    $bar = strlen($someVar);
    $foo = $bar;
    // ...
    $bar = strlen($foo);
    $this->_callSomeFunc($bar);
    // ...
    $foo = json_encode($bar);
    if (strlen($foo) > 100) 
    {
        // ...
    }
    

  • У свойства сущности должно быть одно назначение

    Запрещено использовать одно свойство для разных целей в зависимости от ситуации. В таком случае нужно создать дополнительное свойство.

Логические переменные и методы

  • Названия boolean методов и переменных должны содержать глагол is, has или can

    Переменные правильно называть, описывая их содержимое, а методы — задавая вопрос. Если переменная содержит свойство объекта, следуем правилу признак объекта добавляется к названию.

    Плохо:

    $isUserValid = $user->valid();
    $isProjectAnalytics = $accessManager->getProjectAccess($project, 'analytics');
    
    Хорошо:
    $userIsValid = $user->isValid();
    $projectCanAccessAnalytics = $accessManager->canProjectAccess($project, 'analytics');
    

    Такое именование позволяет легче читать условия:

    // if user is valid, then do something
    if ($userIsValid) 
    {
        // do something
    }
    

  • Запрещены отрицательные логические названия

    Плохо:

    if ($project->isInvalid()) 
    {
        // ...
    }
    if ($project->isNotValid()) 
    {
        // ...
    }
    if ($accessManager->isAccessDenied()) 
    {
        // ...
    }
    

    Хорошо:

    if (!$project->isValid()) 
    {
        // ...
    }
    if (!$accessManager->isAccessAllowed()) 
    {
        // ...
    }
    if (!$accessManager->canAccess()) 
    {
        // ...
    }
    

  • Не используйте boolean переменные (флаги) как параметры функции

    Флаг в качестве параметра это признак того, что функция делает больше одной вещи, нарушая принцип единственной ответственности (Single Responsibility Principle или SRP). Избавляйтесь от них, выделяя код внутри логических блоков в отдельные ветви выполнения.

    Плохо:

    function someMethod() 
    {
        // ...
        $projectNotificationIsEnabled = $notificationManager->isProjectNotificationEnabled($project);
        storeUser($user, $projectNotificationIsEnabled);
    }
    
    function storeUser(User $user, bool $isNotificationEnabled) 
    {
        // ...
        if ($isNotificationEnabled) {
            notify('new user');
        }
    }
    

    Хорошо:

    function someMethod() 
    {
        // ...
        storeUser($user);
        if ($notificationManager->isProjectNotificationEnabled($project)) {
            notify('new user');
        }
    }
    
    function storeUser(User $user) 
    {
        // ...
    }
    

Работа с массивами

  • Для конкатенации массивов запрещено использовать оператор +

    Обратите внимание, что array_merge все числовые ключи приводит к int, даже если они записаны строкой.

    Плохо:

    return $initialData + $loadedData;
    

    Хорошо:

    namespace Service;
    
    class ArrayUtils {
    
        public function mergeArrays(array $array1, array $array2): array {
            return array_merge($array1, $array2);
        }
    }
    
    public function someMethod() {
        return $this->_arrayUtils->mergeArrays($initialData, $loadedData);
    }
    

  • Для проверки наличия ключа в ассоциативном массиве используем array_key_exists, а не isset

    isset проверяет не ключ на его наличие, а значение этого ключа, если он есть. Это разные методы с разным поведением и назначением. Если вы хотите проверить значение ключа, то делайте это явно. Сначала явно проверьте наличие ключа через array_key_exists и обработайте ситуацию его отсутствия, затем приступайте к работе со значением.

    Плохо:

    function getProjectKey(array $requestData) 
    {
        return isset($requestData['project_key']) ? $requestData['project_key'] : null;
    }
    

    Хорошо:

    function getProjectKey(array $requestData) 
    {
        if (!array_key_exists('project_key', $requestData)) 
        {
            return null;
        }
        return $requestData['project_key'];
    }
    

Работа со строками

  • Строки обрамляются одинарными кавычками

    Двойные кавычки используются только, если:

    • Внутри строки должны быть одинарные кавычки
    • Внутри строки используется подстановка переменных
    • Внутри строки используются спец. символы \n, \r, \t и т.д.

    Плохо:

    $string = "Some string";
    $string = 'Some \'string\'';
    $string = "\t".'Some string'."\n";
    

    Хорошо:

    $string = 'Some string';
    $string = "Some 'string'";
    $string = "\tSome string\n";
    

  • Вместо лишней конкатенации используем подстановку переменных в двойных кавычках

    Плохо:

    $string = 'Object with type "' . $object->type() . '" has been removed';
    

    Хорошо:

    $string = "Object with type \"{$object->type()}\" has been removed";
    

Работа с датами

  • Дата всегда должна быть представлена DateTime, интервал как DateInterval

    Исключение, если в метод нужно передать объект \Bitrix\Main\Type\DateTime.

    Плохо:

    $date = $request->get('date');
    $interval = 86400*30;
    loadSomeData($date, $interval);
    

    Хорошо:

    $date = $this->_dateService->instance($request->get('date'));
    $interval = new \DateInterval('P30D');
    loadSomeData($date, $interval);
    

Работа с пространствами имён

  • Все пространства имён должны быть подключены через use в начале файла. В самом коде не должно быть обратного слеша перед названием пространства имён

    Плохо:

    $object = new \Some\Object();
    

    Хорошо:

    use Some;
    $object = new Some\Object();
    

  • Нельзя подключать несколько классов из одного пространства имён через use

    Плохо:

    use Entity\User;
    use Entity\Project;
    
    $user = new User();
    $project = new Project();
    

    Хорошо:

    use Entity;
    
    $user = new Entity\User();
    $project = new Entity\Project();
    

Работа с методами

  • Должна быть использована максимально возможная типизация для вашей версии PHP. Все параметры и их типы должны быть описаны в объявлении метода либо в PHPDoc. Возвращаемое значение тоже

    Плохо:

    /**
     * @param $id
     * @param $name
     * @param $tags
     * @return mixed
     */
    function storeUser($id, $name, $tags = []) 
    {
        // ...
    }
    

    Хорошо:

    // в PHP 7.1 тип элементов массива в объявлении метода указать нельзя, поэтому добавляем PHPDoc
    /**
     * @param int $id
     * @param string $name
     * @param string[] $tags
     * @return User
     */
    function storeUser(int $id, string $name, array $tags = []): User 
    {
        // ...
    }
    

  • Все возможные типы должны быть определены в PHPDoc

    Наибольшую пользу это приносит при работе с массивами:

    Плохо:

    /**
     * @param array $users
     * @param mixed $project
     * @param int $timestamp
     * @return mixed
     */ 
    public function someMethod($users, $project, $timestmap) 
    {
        foreach ($users as $user) 
        {
            // IDE не сможет определить тип $user
        }
        // ...
    }
    

    Хорошо:

    /**
     * @param Users[] $users
     * @param Project $project
     * @param int $timestamp
     * @return Foo
     */
    public function someMethod(array $users, Project $project, int $timestmap): Foo 
    {
        foreach ($users as $user) 
        {
            // подсказки IDE и рефакторинг работают корректно
        }
        // ...
    }
    

  • В PHPDoc у аргументов не надо указывать null

    Плохо:

    /**
    * @param string|null $sortBy
    * @return \DateTimeZone[]
    */
    public function getTimeZonesList($sortBy = null) 
    {
        // ...
    }
    

    Хорошо:

    /**
    * @param string $sortBy
    * @return \DateTimeZone[]
    */
    public function getTimeZonesList($sortBy = null) 
    {
        // ...
    }
    

  • Название метода должно начинаться с глагола и соответствовать правилам именования переменных

    Плохо:

    public function items() 
    {
        // ...
    }
    public function convertedDataObject(array $data) 
    {
        // ...
    }
    

    Хорошо:

    public function loadItems() 
    {
        // ...
    }
    public function convertDataToObject(array $data) 
    {
        // ...
    }
    

  • Все методы класса по умолчанию должны быть private

    Если метод используется наследниками класса, то он объявляется protected. Если используется сторонними классами, тогда public.

  • Параметры в методах должны следовать в следующем порядке: обязательныечасто используемыередко используемые

    Нужно соблюдать читаемость при написании вызова.

    Плохо:

    public function method($required, $practicallyUnused = 5, $often = [], $lessOften = null)
    public function filter($value, $name, $operator) // ...$service->filter(15, "id", "=")
    

    Хорошо:

    public function method($required, $often = [], $lessOften = null, $practicallyUnused = 5)
    public function filter($name, $operator, $value) // ...$service->filter("id", "=", 15)
    

  • Если переменные, объявленные на вход к методу, могут быть null, они должны явно обозначаться как таковые

    Хорошо:

    public function someMethod(string $projectName = null) 
    {
        // ...
    }
    

Возврат результата работы метода

  • Метод всегда должен возвращать только одну структуру данных (или null) или ничего не возвращать

    Метод не может в разных ситуациях возвращать разные типы данных.

    Плохо:

    function loadUser() 
    {
        if ($someCondition) 
        {
            return ['id' => 1];
        }
        return new User();
    }
    

    Хорошо:

    function loadUser(): User 
    {
        if ($someCondition) 
        {
            $user = new User();
            $user->id = 1;
            return $user;
        }
        return new User();
    }
    

  • В больших методах возвращаемая переменная должна называться $result

    Если у вас большой метод (больше 15 строк), возвращаемая переменная должна называться $result, если с ней могут происходить изменения в середине работы метода. В любом месте в методе должно быть понятно, где вы оперируете результатом, а где локальными переменными.

    Плохо:

    function loadUsers(): array 
    {
        $users = [];
        // ... много кода, изменяющего переменную $users
        return $users;
    }
    

Хорошо:

function loadUsers(): array 
{
    $result = [];
    // ... много кода, изменяющего переменную $result
    return $result;
}

Работа с классами

  • Трейты имеют постфикс Trait

    Исключение, если trait является хелпером

    Хорошо:

    trait AjaxResponseTrait 
    {
        // ...
    }
    

  • Интерфейсы имеют постфикс Interface

    Хорошо:

    interface ApplicationInterface 
    {
        // ...
    }
    

  • Абстрактные классы имеют префикс Abstract

    Хорошо:

    abstract class AbstractApplication 
    {
        // ...
    }
    

  • Все свойства и константы класса по умолчанию должны быть private

    Если свойство используется наследниками класса, то оно объявляется protected. Если используется сторонними классами, тогда public.

Работа с объектами

  • Все объекты должны быть неизменяемыми (immutable), если от них не требуется обратного

    Плохо:

    class SomeObject 
    {
        /**
         * @var int
         */
        public $id;
    }
    

    Хорошо:

    class SomeObject 
    {
        /**
         * @var int
        */ 
        private $_id;
    
        public function __construct(int $id) 
        {
            $this->_id = $id;
        }
    
        public function id(): int 
        {
            return $this->_id;
        }
    }
    


  • Статические вызовы можно делать только у самого класса. У экземпляра можно обращаться только к его свойствам и методам

    Плохо:

    $type = $user::TYPE;
    

    Хорошо:

    $type = User::TYPE;
    

К началу