- PVSM.RU - https://www.pvsm.ru -

23 рекомендации для читабельного кода

23 рекомендации для читабельного кода - 1

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

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

Ниже приведены 23 принципа, которые помогут написать более читабельный код. Это длинная статья, поэтому не стесняйтесь перейти к любой части:

Содержание

1. Сначала определить проблему [1]
2. Выбрать правильный инструмент [2]
3. Главное — простота [3]
4. Функции, классы и компоненты должны иметь чёткое назначение [4]
5. Придумывать имена трудно, но это важно [5]
6. Не дублируйте код [6]
7. Удалите мёртвый код, не оставляйте его в комментариях [7]
8. Постоянные значения должны быть в статических константах или перечисляемых типах [8]
9. Предпочитайте внутренние функции, а не кастомные решения [9]
10. Читайте руководства для конкретного языка [10]
11. Избегайте нескольких вложенных блоков [11]
12. Суть не в лаконичности [12]
13. Изучите шаблоны проектирования и когда их избегать [13]
14. Разделите классы на хранение и обработку данных [14]
15. Исправляйте корень проблемы [15]
16. Скрытая ловушка абстракций [16]
17. Приложение не подчиняется правилам реального мира [17]
18. По возможности типизируйте переменные, даже если это не требуется [18]
19. Пишите тесты [19]
20. Проводите статический анализ [20]
21. Код-ревью с человеком [21]
22. Комментарии [22]
23. Документация [23]
Вывод [24]

1. Сначала определить проблему

Независимо от того, исправляете вы ошибку, добавляете функцию или разрабатываете приложение, вы по сути решаете чью-то проблему. Нужно чётко понимать, какую проблему вы решаете своим шаблоном проектирования, рефакторингом, внешними зависимостями, базами данных и всем остальным, на что вы тратите драгоценное время.

Ваш фрагмент кода — тоже потенциальная проблема. Даже самый красивый код. Единственный раз, когда код перестаёт быть проблемой — когда проект закончен и больше не поддерживается. Почему проблемой? Потому что кому-то придётся его читать, понимать, исправлять, расширять или даже полностью удалять реализованную функцию.

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

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

Есть и другие типы проблем. Допустим, нужно реализовать фильтруемый список записей. Данные хранятся в БД со сложными связями между записями. Анализ задания клиента показал, что для такой фильтрации данных из-за структуры БД придётся потратить около 20 часов на создание сложных SQL-запросов с множеством операций соединения и внутренних запросов. Почему бы не объяснить, что другое решение, пусть и неполное, займёт всего 1 час? Может оказаться, что дополнительная функция не стоит столько потраченного времени и денег.

2. Выбрать правильный инструмент

Возможно, этот модный язык, ваш любимый фреймворк или новый движок базы данных — не самый лучший инструмент для решения проблемы. В серьёзном проекте не полагайтесь только на один инструмент, о котором вы слышали много хорошего. Это рецепт катастрофы. Если нужны отношения между данными в базе, то желание поучиться MongoDB приведёт к печальному финалу. Да, всё можно реализовать, но часто приходится использовать обходные пути, а это дополнительный код и неоптимальные решения. Конечно, гвоздь забивается и деревянной доской, но быстрый поиск в Google наведёт на молоток. Возможно, со времени прошлого поиска решения уже появилась новая нейросеть, которая автоматически выполнит задачу вместо вас.

3. Главное — простота

Возможно, вы слышали выражение «Преждевременная оптимизация — корень всего зла» [25]. Тут есть доля правды. Лучше выбрать простое решение, пока вы на 100% не уверены, что оно не сработает. Не просто предполагаете, но уже опробовали или заранее рассчитали — и точно уверены, что не сработает. Какова бы ни была причина для выбора более сложного решения — будь то скорость выполнения, экономия RAM, расширяемость, отсутствие зависимостей или другие причины — оно может сильно повлиять на читабельность кода. Не усложняйте без необходимости. Если только вы не знаете более эффективное решение, которое точно не повлияет на читабельность или требования по времени разработки.

По той же причине не нужно использовать все новые возможности языка, если они не приносят пользы вам и вашей команде. Новое — не значит лучшее. Если не уверены, то перед рефакторингом вернитесь к первому пункту [1] и подумайте, какую проблему вы пытаетесь решить. Оператор for не устарел только из-за того, что в JavaScript появилось слишком много новых способов написания циклов.

4. Функции, классы и компоненты должны иметь чёткое назначение

Знаете принципы SOLID [26]? Похоже, они неплохо подходят для разработки универсальных библиотек, но хотя я пару раз их использовал и видел несколько реализаций в рабочих проектах, но мне кажется, что они слишком запутаны и сложны.

Разделите код на функции, каждая из которых выполняет одно действие. Например, возьмём кнопку. Кнопка может быть классом, который объединяет все функциональные возможности кнопки. Вы можете реализовать кнопку с одной функцией для рисования на экране, с другой — для выделения при наведении курсора мыши, одну для вызова и ещё одну для анимации по щелчку. Можно и дальше делить класс. Если нужно рассчитать положение прямоугольной кнопки по разрешению экрана, не используйте функцию draw. Для рисования кнопки реализуйте другой класс, его могут использовать и другие элементы GUI.

Следовать этому принципу просто. Всякий раз, когда возникает мысль «Это необязательно сюда вставлять» — вы можете переместить данный код в другую функцию и оставить коллегам-разработчикам больше информации в названии функции и комментариях.

Рассмотрим примеры кода с одинаковой функциональностью. Какой быстрее прочитать и понять его действия?

// C++
if (currentDistance < radius2) {
    // Это вид игрока
    if (!isLight) {
        // Если освещение тайла около 30% или больше или расстояние от игрока 1, то тайл должен быть видим.
        if (hasInfravision || map.getLight(mapPosition) > 0.29f || ASEngine::vmath::distance(center, mapPosition) == 1) {
            map.toggleVisible(true, mapPosition);
        }
    }
    // Это для вычислений света
    else {
        ASEngine::ivec3 region = World::inst().map.currentPosition;
        ASEngine::ivec2 pos = mapPosition;
        if (mapPosition.x > 63) {
            pos.x -= 64;
            region.x += 1;
        }
        else if (mapPosition.x < 0) {
            pos.x += 64;
            region.x -= 1;
        }
        if (mapPosition.y > 63) {
            pos.y -= 64;
            region.y += 1;
        }
        else if (mapPosition.y < 0) {
            pos.y += 64;
            region.y -= 1;
        }
        map.changeLight(pos, region, 1.0f - static_cast(currentDistance) / static_cast(radius2));
    }
}

// C++
if (currentDistance < radius2) {
    // Это вид игрока
    if (!isLight) {
        this->markVisibleTile(hasInfravision, map, center, mapPosition);
    }
    // Это для вычислений света
    else {
        ASEngine::ivec3 region = World::inst().map.currentPosition;
        ASEngine::ivec2 pos = map.getRelativePosition(mapPosition, region);
        map.changeLight(pos, region, 1.0f - static_cast(currentDistance) / static_cast(radius2));
    }
}

5. Придумывать имена трудно, но это важно

Названия должны хорошо различаться и давать общее представление о том, что делает переменная или функция. Поскольку с этим кодом работает вся команда, важно соответствовать соглашениям, выбранным для проекта. Даже если лично вы с ними не согласны. Если каждый запрос на запись в базе данных начинается со слова “find”, например, “findUser”, то ваши коллеги могут запутаться, если вы вдруг назовёте функцию “getUserProfile”, потому что вы к ней привыкли. По возможности группируйте названия. Например, если есть много классов для проверки ввода, то суффикс “Validator” сразу покажет назначение класса.

Выберите какой-нибудь тип записи и соблюдайте его. Всё становится запутанным, если в разных файлах одного проекта встречются camelCase, snake_case, kebab-case и beercase.

6. Не дублируйте код

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

7. Удалите мёртвый код, не оставляйте его в комментариях

Код в комментариях сбивает с толку. Кто-то временно его убрал? Это важно? Когда его прокомментировали? Он мёртв, избавьте его от страданий. Просто уберите. Понимаю, что вы не решаетесь удалить код, потому что всё может пойти плохо — и тогда вы просто раскомментируете его. Вы можете быть очень привязаны к нему, ибо потратили время и энергию на его написание. Или вы думаете, что он может «скоро» понадобиться. Все эти проблем решает система управления версиями. Если код понадобится, просто зайдите в историю git [27]. Убирайте за собой!

8. Постоянные значения должны быть в статических константах или перечисляемых типах

Вы используете строки или целые числа для определения типов объектов? Например, у пользователя может быть роль «админ» или «гость». Как вы проверите, что у него роль «админ»?

if ($user->role == "admin") { 
// user is an admin
}

Это совсем не классно. Прежде всего, если имя admin изменится, вам придётся менять его во всём приложении. Говорите, такое редко случается, мол, в современных IDE массовую замену сделать несложно? Это правда. Другая причина — отсутствие автодополнения, и из-за этого повышается вероятность ошибки. Её может быть трудно искать.

Определив глобальные константы или перечисляемые типы в зависимости от языка, вы получите автозаполнение и сможете изменить значение в одном месте, если это когда-нибудь понадобится. Не нужно даже помнить, какое значение скрыто за константой, вы просто позволяете IDE выполнять магию автозаполнения.

// PHP
const ROLE_ADMIN = "admin";

if ($user->role == ROLE_ADMIN) { 
// user is an admin
}

// C++
enum class Role { GUEST, ADMIN }; // Можно сопоставить такой enum со строкой, но это не требуется.

if (user.role == Role.ADMIN) { 
// user is an admin
}

Это не просто типы ваших объектов. В PHP в качестве имён полей можно указать массивы со строками. В сложных структурах легко сделать опечатку, поэтому объекты предпочтительнее. Старайтесь избегать кодирования строками — и вы уменьшите количество опечаток и увеличите скорость работы благодаря автозаполнению.

9. Предпочитайте внутренние функции, а не кастомные решения

Если в языке или платформе есть встроенное решение проблемы, используйте его. Всегда можно быстро погуглить функцию, даже если она используется нечасто. Вероятно, на поиск кастомного решения уйдёт больше времени. Если вы обнаружили у себя в коде фрагмент, который делает то же самое, что и внутренняя функция, просто быстро замените, не оставляйте его. Удалённый код перестаёт быть проблемой, так что удаление кода — это здорово!

10. Читайте руководства для конкретного языка

Если вы пишете на PHP, то должны знать PSR [28]. Для JavaScript есть приличное руководство от Airbnb [29]. Для C++ есть руководство от Google [30] и основные рекомендации [31] Бьёрна Страуструпа, создателя C++. В других языках могут быть свои рекомендации по качеству кода, или вы даже можете придумать собственные стандарты для своей команды. Важно всем придерживаться выбранного ориентира для проекта [32], каким бы он ни был. Это предотвращает проблемы из-за того, что люди с разным уникальным опытом делают то, к чему привыкли.

11. Избегайте нескольких вложенных блоков

Просто сравните два блока кода:

void ProgressEffects::progressPoison(Entity entity, std::shared_ptr<Effects> effects)
{
    float currentTime = DayNightCycle::inst().getCurrentTime();
    if (effects->lastPoisonTick > 0.0f && currentTime > effects->lastPoisonTick + 1.0f) {
        if (effects->poison.second > currentTime) {
            std::shared_ptr<Equipment> eq = nullptr;
            int poisonResitance = 0;
            if (this->manager.entityHasComponent(entity, ComponentType::EQUIPMENT)) {
                eq = this->manager.getComponent<Equipment>(entity);
                for (size_t i = 0; i < EQUIP_SLOT_NUM; i++) {
                    if (eq->wearing[i] != invalidEntity && this->manager.entityHasComponent(eq->wearing[i], ComponentType::ARMOR)) {
                        std::shared_ptr<Armor> armor = this->manager.getComponent<Armor>(eq->wearing[i]);
                        poisonResitance += armor->poison;
                    }
                }
            }
            int damage = effects->poison.first - poisonResitance;
            if (damage < 1) damage = 1;
            std::shared_ptr<Health> health = this->manager.getComponent<Health>(entity);
            health->health -= damage;
        } else {
            effects->poison.second = -1.0f;
        }
    }
}

void ProgressEffects::progressPoison(Entity entity, std::shared_ptr effects)
{
    float currentTime = DayNightCycle::inst().getCurrentTime();
    if (effects->lastPoisonTick < 0.0f || currentTime < effects->lastPoisonTick + 1.0f) return;
    if (effects->poison.second <= currentTime) {
        effects->poison.second = -1.0f;
        return;
    }
    
    int poisonResitance = this->calculatePoisonResistance(entity);
    int damage = effects->poison.first - poisonResitance;
    if (damage < 1) damage = 1;
    std::shared_ptr health = this->manager.getComponent(entity);
    health->health -= damage;
}

Второй гораздо легче читается, правда? Если возможно, старайтесь избегать вложения друг в друга блоков if и циклов. Распространённый трюк — инвертировать инструкцию if и заранее выйти из функции.

12. Суть не в лаконичности

Часто говорят, что чем меньше кода — тем лучше выполняется задача. Некоторые даже одержимы количеством добавляемых и удаляемых строк — и подсчитывают их как мерило производительности. Это можно для упрощения, но не в ущерб читабельности. Всё можно ужать в одну строку, но обычно её гораздо труднее понять, чем несколько простых строк по одной команде.

В некоторых языках есть сокращённый вариант if, например:

$variable == $x ? $y : $z; // if ($variable == x) { $result = $y; } else { $result = $z; }

Это хороший вариант, если без крайностей:

$variable == $x ? ($x == $y ? array_merge($x, $y, $z) : $x) : $y; // Что за ересь?!

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

$result = $y;
if ($variable == $x && $x == $y) $result = array_merge($x, $y, $z);
else if ($variable == $x) $result = $x;

Эти три строки занимают больше места на экране, но анализ данных ускоряется.

13. Изучите шаблоны проектирования и когда их избегать

Есть много популярных шаблонов проектирования [33]. Следует иметь в виду: хотя эти шаблоны решают определённые проблемы в приложении, их полезность зависит от множества факторов, таких как размер проекта, количество людей, работающих над ним, временны́е (стоимостные) ограничения или требуемая сложность решения. Некоторые шаблоны вроде синглтонов называют «антипаттернами»: если в одних случаях они иногда помогают, то в других возникают проблемы.

Просто убедитесь, что понимаете цену введения дополнительной сложности для вашего конкретного решения. Возможно, для связи между компонентами в простой системе вместо шаблона наблюдателя [34] подойдёт простое решение из несколько булевых выражений? Тратить время на реализацию выбранного шаблона оправдано в более крупных и сложных приложениях.

14. Разделите классы на хранение и обработку данных

Класс-хранитель (data holder) хранит некоторые данные в своих внутренних структурах. Когда нужно, доступ к ним осуществляется через геттеры и сеттеры, но этот класс не манипулирует данными, если только они не должны изменяться при хранении или при доступе.

Очень хороший пример — архитектурный шаблон Entity Component System [35], в котором компоненты содержат только данные, а системы управляют и обрабатывают их. Другой вариант — шаблон репозитория, связанный с внешней БД, где класс Model представляет данные из БД в структурах, зависящих от языка, а класс Repository синхронизирует данные с БД, либо сохраняя изменения в Model, либо извлекая их.

Такое разделение упрощает понимание разных частей приложения. Рассмотрим пример репозитория. Если вы хотите вывести список данных, хранящихся в наборе «моделей», то нужно ли вам знать, откуда они получены? Как хранятся в БД и как сопоставлены со структурами языка? Ответ на оба вопроса отрицательный. Пропускаете модели через существующие методы репозитория и концентрируетесь только на своей задаче, которая отображает данные.

Как насчёт примера Entity Component System [35], если нужно реализовать системы, которые обрабатывают действия, воспроизводят анимацию, звук, оценивают ущерб и так далее? Вам не нужно знать, почему сработало действие. Не имеет значения, инициировал его скрипт ИИ или игрок нажал сочетание клавиш. Нужно только заметить, что данные в компоненте изменились, что указывает на необходимость обработки определённым образом.

15. Исправляйте корень проблемы

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

Но сначала попробуйте вернуться на несколько шагов назад. Откуда берутся эти данные и как они используются? Может, их можно получить в более удобном для обработки формате из внешнего источника или изменить их сразу при получении? Исправив проблему в корне, вы можете исправить ту же проблему в нескольких местах и в будущих функциях или изменениях. Всегда старайтесь упростить хранение данных для удобства доступа, как только получаете их. Это особенно важно при получении данных из внешнего источника. При их получении от пользователей приложения или внешнего API следует отсеять лишнее и немедленно реорганизовать всё остальное.

16. Скрытая ловушка абстракций

Почему мы пишем абстрактные, общие решения? Чтобы легко расширять приложения, адаптироваться к новым требованиям и повторно использовать код.

Но с точки зрения удобочитаемости абстракции зачастую дорого обходятся. Самый высокий уровень абстракции — когда реализация полностью скрыта. Вы можете настроить обработку данных, но у вас нет контроля над деталями: как данные будут храниться в БД, насколько эффективна обработка, какая информация записывается в журнал и многое другое. Аргумент в пользу такого решения: если новый источник обрабатывается так же, как и текущий, то легко просто бросить его в библиотеку и указать место хранения. Вы по сути жертвуете контролем ради скорости реализации.

Когда возникнет непредвиденная проблема, будет очень трудно понять все обширные абстрактные идеи. Поэтому по возможности лучше не скрывать детали реализации. Сохранение контроля над кодовой базой обеспечивает лучшую гибкость. Не пишите общее решение простых проблем только потому, что его «можно» расширить в будущем. Такое редко происходит, а при необходимости решение можно переписать.

Рассмотрим на примере. Вы создаёте класс из 10−15 строк читабельного кода, который импортирует данные из CSV-файла [36] и помещает их в базу данных. Зачем создавать два класса и обобщать решение, чтобы оно потенциально могло быть расширено в будущем на импорт XLS или XML, если сейчас даже нет намёка, что это понадобится для вашего приложения? Зачем вызывать внешнюю библиотеку на 5000 строк кода, которая не нужна в данный момент для решения вопроса?

Редко возникает необходимость в обобщении места хранения данных. Сколько раз за карьеру вы меняли движок БД? За последние 10 лет я только один раз столкнулся с проблемой, которую решили таким образом. Создание абстрактных решений дорого обходится и очень часто это не нужно, если только вы не создаёте библиотеку, которая одновременно обслуживает огромное разнообразие проектов.

С другой стороны, если вы точно знаете, что сразу нужен импорт из XLS и CSV, то общее решение — вполне жизнеспособный вариант. Не имеет особой разницы, напишете вы общее решение сразу или потом, когда требования приложения изменятся. Но намного проще иметь сразу ясное и простое решение, а потом его заменить.

17. Приложение не подчиняется правилам реального мира

Во время реализации ООП-парадигмы для приложения у меня возник интересный спор о моделировании «реального мира». Допустим, нужно обработать много данных для рекламной системы. У нас два лога: в первом информация о показах рекламы, во втором — о кликах. Во втором логе есть все данные из первого лога плюс несколько дополнительных полей.

В реальном мире просмотр и клик рассматриваются как отдельные, но похожие действия. Поэтому путём моделирования реального мира создаём основной класс “Log”, который расширяется на классы “ClickLog” и “EmissionLog”:

struct Log {
    int x;
    int y;
    int z;
}
struct EmissionLog : public Log {}
struct ClickLog : public Log {
    float q;
}

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

В отличие от реального мира, в нашем приложении ClickLog — расширение EmissionLog. Этот журнал обрабатывается теми же классами, что и EmissionLog. Если вы расширите логи показов на логи кликов, то вы сообщаете коллегам: все события для показов работают и с кликами без необходимости уведомлять в приложении обо всех возможных обработчиках логов.

struct EmissionLog {
    int x;
    int y;
    int z;
}
struct ClickLog : public EmissionLog {
    float q;
}

18. По возможности типизируйте переменные, даже если это не требуется

Можете пропустить этот раздел, если пишете только на языках со статической типизацией. В динамически типизированных языках, таких как PHP или JavaScript, может быть трудно понять функцию кода, не посмотрев содержимое переменных. По той же причине код становится непредсказуемым, если одна переменная в зависимости от условий может оказаться объектом, массивом или нулевым объектом. Чем меньше типов переменных в параметрах функции — тем лучше. В PHP с версии 7 доступны типизированные аргументы и возвращаемые типы, и можно выбрать TypeScript вместо чистого JavaScript. Это улучшит удобочитаемость кода и предотвратит глупые ошибки.

По возможности запрещайте нулевые значения. Null — это мерзость. Приходится осуществлять специальную проверку на нуль, чтобы избежать фатальных ошибок, в это ненужный код. В JavaScript ситуация ещё хуже с его null и undefined. Отметьте для коллег переменные, которые могут принимать значение null:

// PHP >= 7.1
function get(?int count): array { 
    //... 
}

// Typescript
interface IUser = {
    name?: string; // name field might not be available
    type: number;
}

19. Пишите тесты

С годами приходит опыт, и если нам удалось избежать выгорания, то мы приобретаем навык проверки даже сложных функций в уме. На этом этапе циклы TDD [37] кажутся немного пустой тратой времени. Рекомендуется писать интеграционные тесты: они проверяют, что вся функция целиком работает как надо — вполне вероятно, что там найдутся какие-то небольшие ошибки, а проверка занимает миллисекунды.

Если у вас не хватает опыта работы с языком или библиотекой и вы часто пробуете разные подходы, то написание тестов [37] сильно поможет. Оно делит работу на управляемые куски. Интеграционные тесты быстро объясняют, какие проблемы решает код, предоставляя информацию быстрее, чем универсальная реализация. Простое объяснение «такие входные данные предусматривают такую выдачу» может ускорить процесс понимания приложения.

20. Проводите статический анализ

Есть множество инструментов с открытым исходным кодом для статического анализа. Продвинутые IDE часто позволяют осуществлять её в реальном режиме времени. В среде Docker некоторые процессы можно автоматизировать на каждом коммите.

Надёжные варианты для PHP:

  • Copy / Paste detector [38].
  • PHP Mess Detector [39] — проверяет потенциальные баги и запутанные фрагменты.
  • PHP Code Sniffer [40] — проверка на соответствие стандартам.
  • PHPMetrics [41] — инструмент статического анализа c панелью инструментов и диаграммами.

JavaScript:

  • JsHint [42] / JsLint [43] — обнаружение ошибок и потенциальных проблем, инструмент можно интегрировать в IDE для анализа в реальном времени.
  • Plato [44] — инструмент визуализации исходного кода и сложности.

C++:

  • Cppcheck [45] — находит баги и неопределённое поведение.
  • OClint [46] — улучшает качество кода.

Разные языки:

  • pmd [47] — поиск запутанных фрагментов кода.

21. Код-ревью с человеком

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

Код-ревью не всегда легко провести — это зависит от команды, но выгоды могут оказаться невероятными. Самое большое и чистое приложение, в разработке которого я принимал участие, сопровождалось очень тщательными код-ревью.

22. Комментарии

Возможно, вы заметили, что мне нравятся простые правила кодирования, поэтому их легко соблюдать всем коллегам. То же самое и с комментариями.

Я считаю, что комментарии должны сопровождать каждую функцию, включая конструкторы, каждое свойство класса, статическую константу и каждый класс. Это вопрос дисциплины. Если пропустить и не прокомментировать какие-то «очевидные» фрагменты, то лень в итоге победит.

О чём бы вы ни думали при реализации функции (только связанное с работой!), запишите это в комментариях. Особенно запишите, как всё работает, как используется класс, какова цель этого нумерованного класса и так далее. Цель очень важна, поскольку её трудно объяснить одним лишь правильным именованием, если кто-то не знает соглашения.

Я понимаю что название класса “InjectorToken” говорит само за себя. Честно говоря, это отличное название. Но при рассмотрении этого класса я хочу знать, для чего этот токен, что он делает, как его использовать и что это за инжектор. Было бы прекрасно прочитать это в комментариях, чтобы не приходилось искать в коде приложения, верно?

23. Документация

Да, конечно, я тоже ненавижу писать документацию. Если вы напишете в комментариях всё, что знаете, то теоретически документацию можно сгенерировать автоматически. Тем не менее, документация позволяет быстро найти важную информацию о работе приложения.

Для автоматической генерации документации можно использовать Doxygen [48].

Вывод

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

Выберите путь, который подходит именно вам и вашей команде, и придерживайтесь его. И если вдруг у вас появится настроение для экспериментов, попробуйте что-нибудь из того, что упомянуто в этой статье. Надеюсь, это улучшит качество вашей работы. Спасибо за чтение и не забудьте поделиться информацией, если вы нашли её интересной!

Автор: m1rko

Источник [49]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/293054

Ссылки в тексте:

[1] 1. Сначала определить проблему: #1

[2] 2. Выбрать правильный инструмент: #2

[3] 3. Главное — простота: #3

[4] 4. Функции, классы и компоненты должны иметь чёткое назначение: #4

[5] 5. Придумывать имена трудно, но это важно: #5

[6] 6. Не дублируйте код: #6

[7] 7. Удалите мёртвый код, не оставляйте его в комментариях: #7

[8] 8. Постоянные значения должны быть в статических константах или перечисляемых типах: #8

[9] 9. Предпочитайте внутренние функции, а не кастомные решения: #9

[10] 10. Читайте руководства для конкретного языка: #10

[11] 11. Избегайте нескольких вложенных блоков: #11

[12] 12. Суть не в лаконичности: #12

[13] 13. Изучите шаблоны проектирования и когда их избегать: #13

[14] 14. Разделите классы на хранение и обработку данных: #14

[15] 15. Исправляйте корень проблемы: #15

[16] 16. Скрытая ловушка абстракций: #16

[17] 17. Приложение не подчиняется правилам реального мира: #17

[18] 18. По возможности типизируйте переменные, даже если это не требуется: #18

[19] 19. Пишите тесты: #19

[20] 20. Проводите статический анализ: #20

[21] 21. Код-ревью с человеком: #21

[22] 22. Комментарии: #22

[23] 23. Документация: #23

[24] Вывод: #24

[25] «Преждевременная оптимизация — корень всего зла»: http://wiki.c2.com/?PrematureOptimization

[26] SOLID: https://en.wikipedia.org/wiki/SOLID

[27] историю git: https://git-scm.com/book/en/v2/Git-Basics-Viewing-the-Commit-History

[28] PSR: https://www.php-fig.org/psr/

[29] руководство от Airbnb: https://github.com/airbnb/javascript

[30] руководство от Google: https://google.github.io/styleguide/cppguide.html

[31] основные рекомендации: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md

[32] всем придерживаться выбранного ориентира для проекта: https://alemil.com/guidelines-for-writing-readable-code#guide20

[33] шаблонов проектирования: https://en.wikipedia.org/wiki/Software_design_pattern

[34] шаблона наблюдателя: https://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B1%D0%BB%D1%8E%D0%B4%D0%B0%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)

[35] Entity Component System: https://alemil.com/entity-component-system

[36] импортирует данные из CSV-файла: https://alemil.com/laravel-tdd-workflow

[37] циклы TDD: https://alemil.com/test-driven-development

[38] Copy / Paste detector: https://github.com/sebastianbergmann/phpcpd

[39] PHP Mess Detector: https://phpmd.org

[40] PHP Code Sniffer: https://github.com/squizlabs/PHP_CodeSniffer

[41] PHPMetrics: https://github.com/phpmetrics/PhpMetrics

[42] JsHint: https://github.com/jshint/jshint

[43] JsLint: https://github.com/douglascrockford/JSLint

[44] Plato: https://github.com/es-analysis/plato

[45] Cppcheck: http://cppcheck.sourceforge.net/

[46] OClint: https://github.com/oclint/oclint

[47] pmd: https://github.com/pmd/pmd

[48] Doxygen: http://www.stack.nl/~dimitri/doxygen/index.html

[49] Источник: https://habr.com/post/423691/?utm_campaign=423691