За 5 лет работы я заметил, что разработчики, особенно опытные, считают, что идеальный код вообще не требует комментариев. Но они также почти уверены в том, что отсутствие комментариев в коде означает, что он идеален.

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

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

1. Исправление странных ошибок или крайних случаев

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

fun processPayment(amount: Double) {
// Решение известной проблемы в PaymentLibrary v2.3.
// Библиотека не может обработать сумму, равную $1000, из-за внутренней ошибки.
// Дополнительные сведения см. в тикете #123 (или добавьте ссылку на Jira).
if (amount == 1000.0) {
// Временное изменение суммы для обхода ошибки
processPayment(999.99)
processPayment(0.01)
} else {
// Нормальная обработка
PaymentLibrary.process(amount)
}
}

// или
fun doSomething() {
// Устранение проблемы X в Android SDK версии 29, когда функция XYZ не работает.
if (androidSdkVersion == 29) {
applyWorkaroundForSDK29()
}
}

2. Объяснение бизнес-логики

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

fun handleUserLeavingChannel(userId: String, channelId: String) {
if (isLastAdmin(userId, channel)) {
// Если пользователь является последним администратором, то перед уходом он должен назначить другого пользователя администратором.
// Это очень важно для обеспечения надлежащего модерирования и управления каналом.
throw AssignAdminRequiredException("You must assign a new admin before leaving as you are the last admin.")
}

removeUserFromChannel(userId, channel)
}

3. Предоставление обзора кода с примерами

Сложные алгоритмы, такие как вычисления или анимация, трудно воспринимать на глаз, не взяв в руки ручку. Поэтому полезно будет добавлять к ним объяснения и/или примеры.

// Длина дуги - это угол, помноженный на радиус
// Угол (в радианах) - это длина, деленная на радиус
// Для расчета минимального угла длина должна быть такой же, как ширина штриха
val strokeCapOffset = (180.0 / PI).toFloat() * (strokeWidth / (CircularIndicatorDiameter / 2)) / 2f

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

4. Объяснение того, что делает код, вместо использования подфункций

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

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

В этом случае стоит написать комментарий, объясняющий, что делает тот или иной фрагмент кода, и читатель, которому он неинтересен, сможет пропустить его.

Вот пример из библиотеки Google androidx.lifecycle.SavedStateHandle:

private val savedStateProvider =
SavedStateRegistry.SavedStateProvider {
// Получение сохраненного состояния из каждого SavedStateProvider, зарегистрированного с этим
// SavedStateHandle, путем итерации по копии для избежания повторого входа.
val map = savedStateProviders.toMap()
for ((key, value) in map) {
val savedState = value.saveState()
set(key, savedState)
}
// Преобразование Map текущих значений в Bundle
val keySet: Set<String> = regular.keys
val keys: ArrayList<String> = ArrayList(keySet.size)
val value: ArrayList<Any?> = ArrayList(keys.size)
for (key in keySet) {
keys.add(key)
value.add(regular[key])
}
bundleOf(KEYS to keys, VALUES to value)
}

Заметьте, можно было бы использовать разделение на подфункции, например так:

SavedStateRegistry.SavedStateProvider {
saveStateFromProviders()
convertToBundle()
}

private fun saveStateFromProviders() {
val map = savedStateProviders.toMap()
for ((key, value) in map) {
val savedState = value.saveState()
set(key, savedState)
}
}

private fun convertToBundle(): Bundle {
val keySet: Set<String> = regular.keys
val keys: ArrayList<String> = ArrayList(keySet.size)
val values: ArrayList<Any?> = ArrayList(keys.size)
for (key in keySet) {
keys.add(key)
values.add(regular[key])
}
return bundleOf(KEYS to keys, VALUES to values)
}

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

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

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

5. Комментарии TODO

Комментарии TODO (от англ. to do  —  надо сделать) помогают отметить работу, которую предстоит выполнить позже, например когда вы пишете код вместе с кем-то и должны передать ему задачу.

fun fetchUsers() {
val users = fetchUsersUseCase()
...

// TODO: показать пользователей в UI
}

Однако в большинстве случаев лучше завершить все задачи до слияния кода. Если не хватает какой-то важной функции, создайте тикет или задачу в Jira.

// TODO: Реализовать механизм кэширования для повышения производительности
fun fetchData() {
// ...
}

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


Важные замечания

  1. Хорошие комментарии не оправдывают непонятный код.
  2. Не пишите комментарии к очевидному коду.
  3. Делайте комментарии короткими и описательными.
  4. Поддерживайте комментарии в актуальном состоянии или удаляйте их.

Заключение

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

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи ilyas ipek: Best Use Cases for Code Comments

Предыдущая статьяСовременный подход к разработке Angular
Следующая статьяКак создать на Python скринер акций и выполнить анализ настроений на основе ИИ