10 принципов самодокументируемого кода

в 17:19, , рубрики: комментирование кода, правила хорошего кода, самодокументируемый код, Совершенный код, управление разработкой, хороший код

Привет! Сегодня я хочу поделиться советами по написанию совершенного понятного кода, взятые из книги Питера Гудлифа «Ремесло программиста // Практика написания хорошего кода».

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

1. Пишите простой код с хорошим форматированием

Формат представления оказывает огромное влияние на легкость понимания кода. Разумное представление передает структуру кода: функции, циклы и условные операторы становятся понятнее.

int fibonacci(int position)
{
     if (position < 2)
     {
         return 1;
     }
     int previousButOne = 1;
     int previous = 1;
     int answer = 2;

     for (int n = 2; n < position; ++n)
     {
         previousButOne = previous;
         previous = answer;
         answer = previous + previousButOne;
     }
     return answer;
}

  • Cтремитесь к тому, чтобы ход нормального выполнения вашего кода был очевиден. Обработка ошибок не должна отвлекать от нормальной последовательности выполнения. Условные конструкции if-then-else должны иметь единообразный порядок ветвей (например, всегда помещайте ветвь «обычного» кода перед ветвью «обработки ошибок», или наоборот).
  • Избегайте большого количества уровней вложенных операторов. В противном случае код становится сложным и требует пространных пояснений. Принято считать, что у каждой функции должна быть только одна точка выхода; это известно, как код Single Entry, Single Exit (SESE, один вход – один выход). Но обычно это ограничение затрудняет чтение кода и увеличивает количество уровней вложенности. Мне больше нравится приведенный выше вариант функции fibonacci, чем следующий вариант в стиле SESE:
    int fibonacci(int position)
    {
         int answer = 1;
         if (position >= 2)
         {
             int previousButOne = 1;
             int previous = 1;
    
             for (int n = 2; n < position; ++n)
             {
                 previousButOne = previous;
                 previous = answer;
                 answer = previous + previousButOne;
             }
         }
         return answer;
    }
    

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

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

2. Выбирайте осмысленные имена

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

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

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

3. Разбивайте код на самостоятельные функции

То, как вы разобьете код на функции и какие имена им дадите, может сделать код понятным или совершенно непостижимым.

  • Одна функция, одно действие

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

4. Выбирайте содержательные имена типов

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

  • Определяя величину, которая не будет меняться, назначьте для нее тип константа (используйте const в C).
  • Если переменная не должна принимать отрицательных значений, воспользуйтесь беззнаковым типом (при наличии его в языке).
  • Пользуйтесь перечислениями для описания связанного набора данных.
  • Правильно выбирайте тип переменных. В C/C++ записывайте размер в переменные типа size_t, а результаты арифметических операций с указателями – в переменные типа ptrdiff_t.

5. Применяйте именованные константы

Код типа if (counter == 76) вызывает недоумение. Каково волшебное значение числа 76? В чем смысл этой проверки? Практика волшебных чисел порочна. Они затеняют смысл кода. Гораздо лучше написать так:

const size_t bananas_per_cake = 76;
...
if (count == bananas_per_cake)
{
    // испечь банановый пирог
}

Если в коде часто встречается константа 76 (простите, bananas_per_cake), достигается дополнительное преимущество: когда потребуется изменить содержание бананов в пироге, достаточно модифицировать код в одном месте, а не выполнять глобальный поиск/замену числа 76, что чревато ошибками.

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

6. Выделяйте важные фрагменты кода

Старайтесь выделить важный код на фоне обычного материала. В нужном месте следует привлечь внимание читателя. Для этого есть ряд приемов. Например:

  • Разумно располагайте объявления в классе. Сначала должна идти информация об открытых объектах, поскольку именно она нужна пользователю класса. Закрытые детали реализации следует располагать в конце, поскольку они менее интересны большинству читателей.
  • По возможности скройте всю несущественную информацию. Не оставляйте ненужного мусора в глобальном пространстве имен. В C++ есть идиома pimpl, позволяющая скрыть детали реализации класса. (Meyers 97).
  • Не прячьте важный код. Не пишите в строке больше одного оператора и сделайте этот оператор простым. Язык позволяет писать очень изобретательные операторы цикла for, в которых вся логика укладывается в одной строке с помощью множества запятых, но такие операторы трудно читать. Избегайте их.
  • Ограничьте глубину вложенности условных операторов. В противном случае за нагромождением if и скобок трудно заметить обработку действительно важных случаев.

7. Объединяйте взаимосвязанные данные

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

По возможности объединяйте объекты с помощью языковых конструкций. В C++ и C# можно объединять элементы внутри одного пространства имен. В Java средством объединения служит механизм пакетов. Связанные друг с другом константы можно определить в перечислении.

8. Снабжайте файлы заголовками

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

/*********************************************************
* File: Foo.java
* Purpose: Foo class implementation
* Notice: (c) 1066 Foo industries. All rights reserved.
********************************************************/

9. Правильно обрабатывайте ошибки

Помещайте обработку всех ошибок в наиболее подходящий контекст. Если возникает проблема чтения/записи диска, ее нужно обрабатывать в том коде, который занимается доступом к диску. Для обработки этой ошибки может потребоваться сгенерировать другую ошибку (типа исключительной ситуации «не могу загрузить файл»), передав ее на более высокий уровень. Это означает, что на каждом уровне программы ошибка должна быть точным описанием проблемы в его контексте. Нет смысла обрабатывать ошибку, связанную со сбоем диска, в коде интерфейса пользователя.

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

10. Пишите осмысленные комментарии

Итак, мы попытались избежать написания комментариев с помощью других косвенных методов документирования кода. Но после того как вы приложили все усилия, чтобы написать понятный код, все остальное нужно снабдить комментариями. Чтобы код было легко понять, его нужно дополнить уместным объемом комментариев. Каким именно?

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


Уверен, что уже после внедрения в привычку нескольких этих принципов вы сделаете на одного программиста счастливее. И этим счастливым программистом будете вы. Когда? В момент возвращения к работе над своим кодом полугодовой давности.

Автор: OWIII

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js