Читаемый код или программирование как искусство. Часть 2

Продолжаем читать книгу «Читаемый код или программирование как искусство».

В прошлый раз мы обсудили 1–4 главы: названия переменных и функций, их двусмысленность, эстетику кода и простоту. В этой заметке — главы 5–7:

Глава 5. Комментарии нужны, чтобы читатель знал столько же, сколько автор

Кратко:

  • Если комментарий не несёт дополнительной информации, он не нужен;
  • Объясняйте недостатки кода, поясняйте константы;
  • Рассказывайте «зачем это», а не «что это»;
  • Хороший код лучше, чем плохой код с хорошими комментариями;
  • Думайте, как новичок.

Комментарии занимают место на экране и время, чтобы их прочесть. Не надо комментировать то, что понятно из кода.

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

// На самом деле не удаляет запись, а завершает обработку
void DeleteRegistry(RegistryKey* key);

void ReleaseRegistryHandle(RegistryKey* key);

Если за решением стоит какая-то история, о ней лучше рассказать:

// внезапно бинарное дерево в этом случае позволило ускорить алгоритм на 40%

Комментарии могут предупреждать читателя о недостатках или рассказывать, почему использовано именно это решение:

// при аргументе, большем 500, возвращает округлённое значение
// другие алгоритмы работают в два раза медленнее

У констант обычно есть история, как они появились. О ней тоже можно рассказать:

// оптимальное соотношение веса и качества
const IMAGE_QUALITY = 0.72;

Или константа может быть приближённой:

// больше всё равно никто не прочтёт
const MAX_RSS_SUBSCRIPTIONS = 1000;

Хорошая практика — думать, что потребуется объяснить новичку в проекте. Например, мысли «это связка между бизнес-логикой и БД» или «это механизм кэширования, об остальной части системы он ничего не знает» — именно то, что следует записывать.

Глава 6. Комментарии должны быть короткими

Кратко:

  • Рассказывайте на примерах;
  • Объясняйте «магические аргументы»;
  • Сокращайте.

Объяснить работу функции проще всего на примере. Здесь непонятно, уберёт ли функция только точные совпадения с chars, или просто символы, из которых chars состоит:

// Убирает сочетания 'chars' из входного 'src'.
String Strip(String src, String chars) { ... }

Так понятнее:

// Например: Strip("abba/a/ba", "ab") вернёт "/a/"
String Strip(String src, String chars) { ... }

В языках, где нельзя указывать именованные аргументы, у функций часто появляются «магические аргументы»:

connect(10, false);

Что означает 10 и false, без документации непонятно. Если название функции изменить нельзя, можно использовать комментарии для прояснения:

connect(/* timeout_ms = */ 10, /* use_encryption = */ false);

Но такой комментарий должен стоять до аргумента. Иначе будет совсем непонятно:

// «использовать или нет?..»
connect( ... , false /* use_encryption */);

// «эм, false — значит использовать?..»
connect( ... , false /* = use_encryption */);

Сокращайте комментарии и подбирайте для них точные слова.

Глава 7. Пишите так, чтобы не приходилось перечитывать

Кратко:

  • Сравнивайте изменяющееся с постоянным, а не наоборот;
  • Используйте ранний выход;
  • Аккуратнее с тернарными операторами;
  • Уменьшайте вложенность.

Порядок операндов в сравнении важен. Слева лучше ставить то, что изменяется, справа — что постоянно:

// так хорошо
if (length >= 10) {...

// так плохо
if (10 <= length) {...

Если изменяются оба значения, то справа надо ставить то, что более постоянно:

// так хорошо
while (bytes_received < bytes_expected)

// так плохо
while (bytes_expected > bytes_received)

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

if (!response) return null

try {
  response.getHeader()
  ...
}
...

Тернарные операторы — ок. Но если выражение с ним непонятное, замените на if … else:

// это ок
time_postfix = (hour >= 12) ? "pm" : "am";

// а это нет
return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent);

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

Что дальше?

В следующий раз обсудим:

  • Как разбивать большие выражения на куски;
  • Переменные, и как они влияют на читаемость;
  • Лишнюю функциональность;
  • Приём «одна задача за раз».