open atlas
↑ К треку
Паттерны и качество кода CP · 00 · 03

Чисто, а не хитро

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

CP Middle ◷ 18 min
Уровень
ОсновыJuniorMiddleSenior

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

Эта сделка и есть весь урок. Хитрый код оптимизирован под момент написания: минимум нажатий клавиш, максимум плотности, маленькое эго-удовольствие от «смотри, как компактно». Чистый код оптимизирован под следующего человека, которому это читать, и под следующее изменение, которое кому-то делать. Это разные люди с разными бюджетами — и именно второй бюджет, как этот трек тебе говорил, доминирует.

Цель

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

1

Хитрый код оптимизирован под автора; чистый код оптимизирован под следующего читателя и следующее изменение. Хитрую конструкцию оценивают по тому, насколько её мало и сколько она делает на символ. Чистую конструкцию оценивают по тому, как быстро её понимает посторонний и как безопасно он может её изменить. Эти цели реально конфликтуют: самое плотное выражение вычисления почти никогда не самое ясное. Возьмём оценку списка баллов:

// clever: one expression, three behaviours folded together
const grade = (s: number[]) =>
  s.reduce((a, x) => a + (x >= 50 ? x : 0), 0) / s.filter((x) => x >= 50).length;

Оно работает, и оно компактно. Но чтобы это прочитать, нужно дважды удержать в голове предикат фильтра, вывести, что усредняются только проходные баллы, и заметить, что массив обходится дважды. Намерение — «среднее проходных баллов» — ты реконструируешь сам, а не код заявляет. Автор сэкономил нажатия клавиш; каждый читатель платит за их расшифровку.

2

Чистая версия длиннее, но очевидна — а очевидность и есть то свойство, что снижает стоимость изменения. Разверни ту же логику, чтобы код говорил, что имеет в виду:

// clean: longer, but the intent is on the surface
function averagePassingScore(scores: number[]): number {
  const passing = scores.filter((score) => score >= PASS_MARK);
  if (passing.length === 0) return 0; // clever version divided by zero → NaN
  const total = passing.reduce((sum, score) => sum + score, 0);
  return total / passing.length;
}

Больше строк, меньше сюрпризов. Имя заявляет намерение, так что его никто не реконструирует. Защита от пустого списка видна — и именно её выписывание обнажило деление на ноль, которое хитрый однострочник прятал за своей плотностью. Стоимостью никогда не была длина; ей была непрозрачность. Чистый код тратит пару лишних строк, чтобы купить более дешёвое следующее изменение, — та же ставка, которой учил урок про стоимость изменения: заплати немного сейчас там, где это удешевит будущее.

3

Хитрость — это налог, который платит каждый будущий читатель, поэтому оправдывай плотность только измеренной причиной и комментарием. «Чисто по умолчанию» — не антипроизводительная догма; это про то, кто платит. Бесточечный конвейер или битовый трюк концентрируют экономию в одной минуте написания и размазывают стоимость по каждому чтению, ревью, отладке и онбордингу за всё время жизни файла. Эта сделка стоит того лишь когда плотность покупает что-то реальное и измеренное — отпрофилированный горячий путь, задокументированное требование к численной устойчивости, — а не туманное «это, может, быстрее». И когда ты на неё идёшь, ты оставляешь комментарий, который превращает хитрость обратно в намерение:

// HOT PATH: called per-pixel in the render loop; profiled 3.1ms → 0.4ms.
// Bit trick: (x & (x - 1)) === 0 tests power-of-two without a loop or log2.
const isPowerOfTwo = (x: number) => x > 0 && (x & (x - 1)) === 0;

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

4

Режим отказа: «чисто», прочитанное как «максимально абстрагировано», — это собственная хитрость. Противоположность хитрому однострочнику — это не пятислойная абстракция. Читатель, который услышал «чистый код» и потянулся за обобщённым Pipeline<T>, реестром стратегий и шиной событий, чтобы отформатировать одну дату, не убрал хитрость — он переместил её из лаконичности в архитектуру. И то, и другое заставляет читателя реконструировать намерение; и то, и другое повышает стоимость следующего изменения. Чисто значит очевидно, а не вычурно и не гибко-ради-самой-гибкости.

// "clever-as-abstract": indirection no requirement asked for
const formatter = FormatterFactory.create(LocaleStrategy.from(config)).build();
return formatter.apply(new DateValue(d));

// clean: obvious, names the intent, no ceremony
return d.toLocaleDateString("en-GB"); // e.g. 23/06/2026

Тест для обоих видов хитрости один и тот же: сколько времени до того, как посторонний это поймёт, и как безопасно он может это изменить? Если ответ — «ему придётся проследить цепочку — операторов или классов, — чтобы найти поведение», это хитро, каким бы ни было число строк. Senior-ход — остановиться на простейшей вещи, которая заявляет намерение.

Разбор примера

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

// clever: a reduce that hides a loop's intent + nested ternary
const totals = invoices.reduce<Record<string, number>>(
  (acc, i) =>
    i.status === "paid"
      ? { ...acc, [i.customerId]: (acc[i.customerId] ?? 0) + i.amount }
      : i.status === "void"
      ? acc
      : { ...acc, [i.customerId]: acc[i.customerId] ?? 0 },
  {},
);

Это одно выражение, поэтому оно выглядит как одна идея — но их три (пропустить void, зарегистрировать pending на нуле, просуммировать paid), и спред-внутри-reduce вдобавок втихую O(n²), потому что каждый шаг копирует весь аккумулятор. Намерение погребено под механизмом. Теперь чистое развёртывание — то же поведение, намерение на поверхности:

// clean: an explicit loop that states each rule, mutating a local map
function totalsByCustomer(invoices: Invoice[]): Map<string, number> {
  const totals = new Map<string, number>();
  for (const invoice of invoices) {
    if (invoice.status === "void") continue;           // void: ignore entirely
    const current = totals.get(invoice.customerId) ?? 0;
    const add = invoice.status === "paid" ? invoice.amount : 0; // pending: register at 0
    totals.set(invoice.customerId, current + add);
  }
  return totals;
}

Длиннее по строкам, короче по мысли. Каждое бизнес-правило сидит на своей строке с именем и комментарием, так что следующий читатель меняет одно правило, не распутывая два других, — а случайный O(n²) исчез, потому что мы мутируем один локальный Map вместо его n-кратной пересборки. Мутировать функцию-локальную коллекцию нормально; дисциплина неизменяемости — про то, чтобы не мутировать общее или входное состояние, а totals не является ни тем, ни другим. Здесь ничего не оглуплено — это то же вычисление, сказанное прямо. Вот что такое чисто: не меньше идей, просто каждая идея сделана очевидной.

Почему это работает

Зачем предпочитать более длинный код, когда «меньше кода = меньше сопровождать» — реальный принцип? Потому что «меньше кода» означает меньше концепций для понимания, а не меньше символов на экране. У плотного однострочника больше концепций на символ, чем у развёрнутого цикла, — ты просто не видишь их, пока они не укусят. Настоящий выигрыш «меньше кода» — это удаление концепции (ненужного слоя, мёртвой ветки); ложный выигрыш — сжатие тех же концепций в меньшее количество пробелов. Чистый код минимизирует концепции, которые читатель должен держать одновременно, даже когда это стоит лишних строк. Метрика — усилие читателя, никогда не число нажатий клавиш.

Частая ошибка

Самый частый способ ошибиться здесь: трактовать «чисто» как лицензию добавить абстракцию. Кто-то читает «пиши чистый код», слышит «пиши изощрённый код» и оборачивает трёхстрочную функцию в интерфейс, фабрику и объект конфигурации. Хитрость он не убрал — он переместил её из плотности в архитектуру, и читатель теперь прослеживает цепочку классов вместо разворачивания тернарника. И то, и другое прячет поведение; и то, и другое делает следующее изменение дороже. Когда ловишь себя на том, что тянешься за паттерном, спроси, требует ли его текущее требование. Если нет — абстракция и есть хитрость, а чистый ход — удалить её и написать очевидное.

Проверь себя
Викторина

Ревьюер просит тебя заменить плотный однострочник с вложенным тернарником на явный цикл, хотя цикл на шесть строк длиннее. Автор возражает, что «меньше кода — лучше сопровождаемость». По определению чистого из этого трека, кто прав и почему?

Итог

Хитрый код оптимизирован под автора — минимум нажатий клавиш, плотность, немного эго — и выставляет счёт каждому будущему читателю через время на расшифровку, риск и более медленное изменение; чистый код оптимизирован под того следующего читателя и следующее изменение, делая намерение очевидным, обычно ценой пары лишних строк, которые покупают более дешёвое будущее. Хитрость, стало быть, — это повторяющийся налог, оправданный лишь измеренной причиной — отпрофилированным горячим путём, задокументированным требованием к устойчивости — и всегда сопровождённый комментарием, который переводит трюк обратно в намерение. Ловушка — прочитать «чисто» как «максимально абстрагировано»: нагромождение слоёв, которых не требует ни одно требование, — это та же хитрость, перемещённая из лаконичности в архитектуру, и она ровно так же повышает стоимость изменения. Чисто значит очевидно, а не вычурно — а тест, здесь как и везде в этом треке, таков: во что обойдётся следующее изменение?

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.