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

TL; DR

Вы должны использовать комментарии в нескольких ситуациях:

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

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

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

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

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

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

  • не используйте комментарии для обсуждения - используйте слабину или другие инструменты
  • не используйте комментарии для проверки кода - есть решения получше, например Обсуждения Merge Request в gitlab, Jetbrains Upsource и т. Д.
  • не описывайте все переменные и каждый шаг вашего кода - правильно назовите свои переменные / функции / классы и используйте подсказки типов
  • не используйте комментарии для разделения кода - разделите код на несколько функций / классов / файлов
  • не описывайте каждый вариант использования и каждый шаг функции - если у вас несколько вариантов использования, напишите несколько тестов, которые будут лучшей документацией, чем большая стена текста в комментариях

Совершенно другая тема - как пользоваться комментариями. Комментарии, как и код, должны быть согласованы во всем проекте. Обсудите их со своей командой и создайте рекомендации. Решите, когда вы используете комментарии и как их форматировать. Не забывайте создавать комментарии во время кодирования, а не после. Когда вы используете какие-либо сборщики или препроцессоры, добавьте шаги с минимизацией и удалением комментариев, например. при синтаксическом разборе sass в css или при связывании js с webpack.

Комментарии как документация

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

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

В нашей компании мы планируем добавить блок комментариев к большинству классов и функций. Такие комментарии имеют форму аннотаций и краткого описания. Хотя во многих случаях достаточно прочитать код, в большинстве случаев разработчик не успевает просмотреть десятки строк кода, чтобы выяснить, для чего используется конкретный аргумент в функции. Хорошо иметь объяснение каждого аргумента в функции: что это такое и какие значения являются допустимыми. Мы также добавляем информацию о возвращаемом значении и краткое описание того, что делают функции, каковы побочные эффекты функции, какие ошибки она может вызывать и т. Д. Мы можем утверждать, что часть этого покрывается самим кодом, например. по типу подсказок, но иногда вы принимаете значения нескольких типов, возвращаете значения нескольких типов (чего не должно происходить) или выкидываете много разных исключений во многих местах кода, а комментарии помогают упорядочить эту информацию. Вот простой пример на php:

/**
* function validate if user has specific premissions
* @param User $user user for whom we check permissions
* @param string[]|Permission[] $perms names of permissions or permissions objects to validate
* @param bool $atLeastOne if true - user can have at least one permission, if false - user must have all permissions
* @return bool true if has permissions
* @throws ObjectDoesNotExistException if permission does not exist in the system
*/
function checkParmission(User $user, array $perms, bool $atLeastOne = true): bool { ... }

Для классов мы ожидаем хотя бы краткого описания и списка всех общедоступных методов и общедоступных полей. Такое краткое описание класса поможет вам использовать новые элементы и будет хорошей документацией по вашему API. Также есть много ситуаций, когда у вас есть скрытые методы или поля. Вы можете использовать магические методы, которые скрывают это перед пользователем, или вы можете создать модель для хранения данных, которые автоматически скрываются в ней из БД. Хорошим примером являются модели Laravel Eloquent. Вот простой пример Laravel Eloquent Model:

/**
* Class which represent user model
*
* @property int $id user id - autoincremental
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $email
* @property Role $role
* @property int $role_id
* @property int $status status of user: 0 - deleted; 1 - active; 2 - not verified
*
*/
class User extends Illuminate\Database\Eloquent\Model {...}

Много раз вам нужно будет создать некоторую документацию по вашему коду. Написание отдельных документов и поддержание их в актуальном состоянии - очень трудоемкая задача. Правильно закомментированный код в этом случае будет намного лучше, потому что он будет автоматически обновляться во время рефакторинга, исправления ошибок и создания новых вещей. Также будет проще пользоваться - вы сразу сможете проверить, что есть чем и как использовать, не находя нужного места в документации. Если вам нужно создавать документы, читаемые пользователем, вы можете использовать такие инструменты, как PHPDocumentor, JSDocs, DocFX или JavaDoc. Они создадут веб-страницу с описанием вашего API на основе ваших комментариев.

В контексте документации также очень важно правильно представить вашу конфигурацию, чтобы пользователи / разработчики понимали все доступные параметры конфигурации. Всегда полезно добавить хороший README, но нет лучшего решения, чем комментировать конфигурацию в примере конфигурации. Вы можете добавить все доступные параметры со значениями по умолчанию и краткими описаниями. Закомментируйте все параметры, которые не требуются, но не удаляйте их из файла примера. Я считаю, что это единственное место, где код в комментариях имеет смысл. Вот часть конфигурационного файла jest по умолчанию в качестве примера:

module.exports = {
  // All imported modules in your tests should be mocked automatically
  // automock: false,
// Stop running tests after `n` failures
  // bail: 0,
// Respect "browser" field in package.json when resolving modules
  // browser: false,
// The directory where Jest should store its cached dependency information
  // cacheDirectory: "C:\\Users\\Łukasz\\AppData\\Local\\Temp\\jest",
// Automatically clear mock calls and instances between every test
  clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
  // collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
  // collectCoverageFrom: null,
// The directory where Jest should output its coverage files
  coverageDirectory: "coverage",
...
}

Комментарии как объяснение

Мы обсуждали документацию с аннотациями, но как быть с комментариями, которые объясняют, как что-то работает или что что-то делает? Прежде всего, не описывайте, что будут делать следующие строки кода (это имеет смысл только в блоке документации в начале функции / класса). Вместо этого опишите, почему вы используете определенные значения, алгоритм или какое-то конкретное решение. Это может сэкономить вам безумное количество времени, когда вам нужно что-то изменить или вы отлаживаете код. Иногда вы используете конкретное решение, которое не является интуитивно понятным или кажется немного странным, но по какой-то конкретной причине оно должно оставаться таким, например вы пишете js для приложения, которое поддерживает старые браузеры, и вам нужно добавить небольшой взлом, чтобы он работал со старым IE. Если вы не прокомментируете это должным образом, вы потеряете время, чтобы понять или напомнить себе, почему конкретная часть была сделана таким образом. Иногда, чтобы избежать возможных ошибок, вы решаете не изменять определенную часть кода. В худшем случае, кто-то не увидит смысла конкретного решения и изменит его позже или удалит, что приведет к поломке приложения.

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

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

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

Комментарии как инструмент разработчика

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

Во-первых, вы можете использовать комментарии в инструментах разработчика. Многие IDE поддерживают подсказку кода. Большинство подсказок и автозаполнений основаны на упомянутых выше аннотациях. Кроме того, многие линтеры, анализаторы кода и инструменты тестирования используют комментарии для изменения поведения по умолчанию. Например. вы можете выбрать определенную часть кода, которую не следует тестировать или прокрашивать, вы также можете изменить правила для определенной части кода. Вот пример исключения ESLint и изменения правил:

/* eslint-disable */

thisIsNotCheckedByLint();

/* eslint-enable */
/* eslint eqeqeq: 2 */
$thisLineReturnESLintError == true;

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

class TimeReport {
  constructor(projectId) {
    this.projectId = projectId;
  }
  generateReport() {
    this.initData();
    this.analizeData();
    this.getFile();
  }
  initData() {
    //find project in DB if notexist throw ObjectNotExistsError
    //find all stages and corresponding tasks for project
    //get schedule for project
  }
  analizeData() {
    // foreach task calculate spent time and compare to estimation
    // foreach stage sort tasks by status, count them
    // foreach stage check timeline and verify if stage is closed 
    // calculate when stage ended (if ended) or can end based on task's statuses and estimations
    // sum up how many hours we are after/before schedule
  }
  getFile() {
    //get file template and propagate data
    //create PDFFile
    //encode file to base64
    //return base64
  }
}

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

Есть одна очень важная вещь об использовании комментариев в качестве инструмента - использовать их локально. Это нормально, если вы нажмете его на git для ветки WIP, но удалите их из окончательной версии вашего кода, решив проблемы. Единственное, что может остаться, - это редкие TODO. Ваш код не должен содержать никаких комментариев WIP.

Комментарии как запах кода и плохая практика

Теперь вы знаете, что можно получить от комментариев и когда их следует использовать. Когда не использовать комментарии? Существует множество ситуаций, которые вы можете встретить при проверке кода или проверке чьего-либо кода (или даже вашего собственного). Вот несколько примеров, когда вы можете использовать лучшие решения, чем комментарии:

  • Не используйте комментарии для обсуждений. Есть гораздо лучшие места для обсуждения чего-либо - организовать встречу, поговорить во время проверки кода, использовать Slack и т. Д. Если вам нужно обсудить какую-то часть кода, есть действительно хорошие инструменты, например В git lab есть хорошие инструменты CR для запросов на слияние.
  • Комментарии не являются системой контроля версий! Я много раз сталкивался со старым кодом, оставленным разработчиком в качестве комментария. Используйте систему управления версиями и нажмите несколько коммитов. Зафиксированный код не будет потерян.
  • Не делите код на разделы с помощью комментариев. Если вам нужны такие вещи, у вас плохой код. Если это файл конфигурации / перевода, разделите его на файлы меньшего размера. Если это один большой класс или функция, также разделите его на более мелкие функции / классы.
  • Не повторяйся. В этом нет смысла. Вот пример того, о чем я говорю:
//Here I set avatar for user
$user->avatar = $avatar;
  • Не объясняйте каждую переменную. Если вам нужно это сделать, измените его название. Если вам нужно объяснить конкретные строки, что они делают, выполните рефакторинг кода. Правильное название и лучший код уменьшат количество комментариев. Вот простой пример кода до рефакторинга:
const articles = (new Date(user.membershipEndDate) > new Date() && user.membershipLevel >= User.GOLD_LEVEL) ?
await db.execute('SELECT * FROM articles WHERE published = 1') //IF USER HAS ACTIVE GOLD MEMBERSHIP RETURN ALL ARTICLES
: await db.execute('SELECT * FROM articles WHERE published = 1 AND public = 1') //ELSE RETURN ONLY PUBLIC ARTICLES

И после

class User {
  ...
  get isActiveGoldMember() {
    const now = new Date();
    const membershipEnd = new Date(this.membershipEndDate);
    const isGoldMember = this.membershipLevel >= User.GOLD_LEVEL;
    return membershipEnd > now && isGoldMember;
  }
}
--------------------------------------------------------
let query = 'SELECT * FROM articles WHERE published = 1';
if (!user.isActiveGoldMember) {
  query += ' AND public = 1';
}
const articles = await db.execute(query);
  • Используйте подсказку типа для ваших методов / функций и добавьте комментарии к аннотациям, это сэкономит вам много встроенных комментариев. Если вам нужен встроенный комментарий с аннотациями, это значит, что вы сделали что-то не так. Вот пример того, о чем я говорю:
/** @var User $user **/
$user = app->auth()->user();
/** @var Company $sompany **/
$company = $user->getCompany();
  • Иногда вам нужно описать, что делает код, когда есть несколько зависимостей или сложная логика. Лучше избавиться от слабых зависимостей и подумать, не нуждается ли ваш код в рефакторинге. Если вам нужно слишком много объяснений, вам следует переписать часть кода.
  • Используйте комментарии для описания вашего API, но не описывайте все возможные варианты использования. Гораздо лучше предоставить подробные варианты использования в рамках тестов, потому что это объяснит ваш код намного лучше, чем большая текстовая стена (с абстрактными описаниями).
  • Убирайте за собой, не оставляйте комментариев разработки, которые призваны помочь вам во время разработки. Если у вас остались комментарии, скорее всего, вы не выполнили задачу или образовали технический долг.
  • Поместите весь импорт поверх вашего файла, используйте пространства имен и т. Д. Это также может сохранить некоторые комментарии.

Комментарии как тема руководства

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

  • В большинстве случаев встроенные комментарии - это запах кода.
  • Используйте блочные комментарии. Они должны быть в начале файла перед классами и функциями в качестве документации. Однострочные комментарии можно использовать для коротких тегов, таких как TODO, или простых описаний внутри функций.
  • Определите порядок аннотаций, чтобы он оставался последовательным. Для классов я начинаю с описания, затем представлены методы и поля. Для функций / методов я также начинаю с описания, затем перечисляю аргументы, возвращаемые значения и в конце все ошибки / исключения, вызванные функцией.
  • Не забывайте использовать правильные намерения. Комментарий должен иметь тот же смысл, что и код, который он комментирует. Например.
/**
 * Here is comment for Class
 */
class MyClass
{
    /**
     * Here is comment for my method
     */
    public function myMethod() {}
} 
  • Поместите комментарий перед закомментированным кодом в отдельной строке. Не добавляйте пустую строку между комментарием и комментируемым кодом. Поставьте перед комментарием. Например.:
//this is good place to comment why I use console.log()
console.log(serviceResponse)

console.log(oops)
//this is bad place to comment console.log(oops)
console.log(ouch)
  • Когда вы используете блок документов, начинайте с / **. В каждой новой строке добавляйте * в начале строки комментариев. Например.:
/**
 * This is
 * multiline
 * doc block
 *
 * @param string $text
 * @return bool
 */
  • Сделайте свой комментарий профессиональным - без глупых шуток, спорных слов и т. Д. Убедитесь, что вы используете правильное написание. Используйте один и тот же язык во всех комментариях в коде - я использую английский.
  • Не вкладывает комментарии, например:
/**
 * So you should not comment this comment
 * // but you do
 * /* And it is wrong */
 */

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

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

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

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