Чисто, а не хитро
Хитрый код оптимизирован под автора — плотность и эго; чистый — под следующего читателя и следующее изменение. Хитрость это налог, который платит каждый будущий читатель, оправданный лишь измеренной причиной на горячем пути. Чисто значит очевидно, а не вычурно.
В кодовой базе ты находишь однострочник, который сворачивает валидацию, преобразование и агрегацию в один плотный reduce с вложенным тернарником внутри. Он бесспорно впечатляет — чтобы такое написать, нужно настоящее мастерство, и он работает. Потом на эту самую строку прилетает баг-репорт, и ты тратишь сорок минут, мысленно разворачивая её, прежде чем вообще получится локализовать дефект. Хитрость, на которую автор потратил одну приятную минуту, теперь стоит каждому отладчику и ревьюеру десяти.
Эта сделка и есть весь урок. Хитрый код оптимизирован под момент написания: минимум нажатий клавиш, максимум плотности, маленькое эго-удовольствие от «смотри, как компактно». Чистый код оптимизирован под следующего человека, которому это читать, и под следующее изменение, которое кому-то делать. Это разные люди с разными бюджетами — и именно второй бюджет, как этот трек тебе говорил, доминирует.
После этого урока ты умеешь отличать «хитрое» (плотное, льстящее автору, тяжёлое для изменения) от «чистого» (очевидное, служащее читателю, дешёвое для изменения); объяснять, почему хитрость — это повторяющийся налог на каждого будущего читателя, а не разовый выигрыш; оправдывать плотность только измеренной причиной вроде горячего пути плюс комментарием; и распознавать режим отказа, в котором «чисто» прочитано как «максимально абстрагировано» — что есть та же хитрость, надевшая другую шляпу.
Хитрый код оптимизирован под автора; чистый код оптимизирован под следующего читателя и следующее изменение. Хитрую конструкцию оценивают по тому, насколько её мало и сколько она делает на символ. Чистую конструкцию оценивают по тому, как быстро её понимает посторонний и как безопасно он может её изменить. Эти цели реально конфликтуют: самое плотное выражение вычисления почти никогда не самое ясное. Возьмём оценку списка баллов:
// 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;Оно работает, и оно компактно. Но чтобы это прочитать, нужно дважды удержать в голове предикат фильтра, вывести, что усредняются только проходные баллы, и заметить, что массив обходится дважды. Намерение — «среднее проходных баллов» — ты реконструируешь сам, а не код заявляет. Автор сэкономил нажатия клавиш; каждый читатель платит за их расшифровку.
Чистая версия длиннее, но очевидна — а очевидность и есть то свойство, что снижает стоимость изменения. Разверни ту же логику, чтобы код говорил, что имеет в виду:
// 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;
}Больше строк, меньше сюрпризов. Имя заявляет намерение, так что его никто не реконструирует. Защита от пустого списка видна — и именно её выписывание обнажило деление на ноль, которое хитрый однострочник прятал за своей плотностью. Стоимостью никогда не была длина; ей была непрозрачность. Чистый код тратит пару лишних строк, чтобы купить более дешёвое следующее изменение, — та же ставка, которой учил урок про стоимость изменения: заплати немного сейчас там, где это удешевит будущее.
Хитрость — это налог, который платит каждый будущий читатель, поэтому оправдывай плотность только измеренной причиной и комментарием. «Чисто по умолчанию» — не антипроизводительная догма; это про то, кто платит. Бесточечный конвейер или битовый трюк концентрируют экономию в одной минуте написания и размазывают стоимость по каждому чтению, ревью, отладке и онбордингу за всё время жизни файла. Эта сделка стоит того лишь когда плотность покупает что-то реальное и измеренное — отпрофилированный горячий путь, задокументированное требование к численной устойчивости, — а не туманное «это, может, быстрее». И когда ты на неё идёшь, ты оставляешь комментарий, который превращает хитрость обратно в намерение:
// 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;Комментарий — это квитанция об уплате налога. Он называет измеренную причину и переводит трюк, так что следующий читатель платит один раз (прочитать комментарий) вместо каждого раза (реверс-инжиниринг битов). Плотность без такой квитанции — это налог, который ты наложил на других, чтобы польстить себе.
Режим отказа: «чисто», прочитанное как «максимально абстрагировано», — это собственная хитрость. Противоположность хитрому однострочнику — это не пятислойная абстракция. Читатель, который услышал «чистый код» и потянулся за обобщённым 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-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.