В кругу разработчиков бытует присказка: «Код больше читают, чем пишут». Но ведь этим дело не ограничивается. Код просматривают чаще, чем читают.
Мы больше читаем код, чем пишем его, потому что на сопровождение кода времени уходит больше, чем на программирование с нуля. Впереди у написанной строчки кода долгая и трудная жизнь. Код пишешь единожды, а потом многократно возвращаешься к нему, исправляя баги, добавляя фичи, перерабатывая его структуру. Для этих задач надо менять код. А чтобы поменять код, надо его прочесть и понять. Причём не только вам: над кодом будут работать и другие программисты, с другими целями и при других условиях.

Тихон Джелвис
Ведущий специалист по искусственному интеллекту в Target
На десять строк, которые нужно тщательно пересмотреть, приходится сотня-другая, которую вы не затрагиваете напрямую. Приходится продираться сквозь базу кода к нужному разделу и отслеживать окружающий контекст.
Глубоко погружаться в связанные друг с другом части кода необязательно. Вам просто нужно понимать, для чего они нужны. Для этого не надо углубляться в код — ни у кого нет времени или «оперативной памяти» держать в голове всю кодовую базу. Чтобы уловить суть, достаточно просто бегло просмотреть код.
Код просматривают чаще, чем читают.
Каждый раз, когда вы пишете код, вы прочитываете его несколько раз. Аналогичным образом вы просматриваете несколько раз каждый фрагмент кода, который прочитали. Поэтому писать код, который достаточно бегло просмотреть — не менее важно, чем писать код, который в принципе можно прочитать.
Для этого важно думать о «форме» кода, чтобы по структуре кода можно было быстро понять, для чего он. Когда ты заботишься о форме кода, ты делаешь так, что связанный код выглядит как связанный, а несвязанный код выглядит как несвязанный независимо от их реализации. То, как выглядит ваш код — ключевой момент, который управляет вниманием читателя, не принуждая его углубляться в детали кода.
Про многословие
Многословные идентификаторы снижают ясность формы кода. Это не значит, что нельзя использовать многословные имена в своём коде. Однако стоит подходить к делу со вкусом и сдержанностью. Если идентификатор не рядом, — скажем, это функция от логически удалённого модуля, — описательное имя может перевесить недостатки многословия.
В качестве наглядного примера давайте сравним три версии одной и той же логики. Вот код Java, в котором используется BigDecimal:
final BigDecimal result =
a.multiply(x.pow(2)).plus(b.multiply(x.plus(c)));
В Java нет перегрузки операторов. Но если бы была, код выглядел бы вот так:
final BigDecimal result = a * (x * x) + b * x + c;
При этом в обоих случаях реализуется одна и та же математическая формула:
Это так же просто, как вычисление значений многочлена, но это непонятно с ходу в первой версии кода: чтобы разобраться, что к чему, нужно внимательно прочитать последовательность вызовов именованных методов. Версия с перегруженными операторами читается легче, но даже она всё ещё требует большего внимания, чем математическая нотация (когда используются математические символы и правила для представления и выполнения вычислений в коде). Первый сниппет можно сравнить с чтением параграфа, второй — с чтением короткого предложения, а математическая нотация похожа на чтение одного слова.
Благодаря математической записи мы можем сразу же определить, что перед нами многочлен — просто по его форме. Информация, которая нас интересует в многочленах — коэффициенты членов; сложение и умножение больше похожи на подробности реализации.
В математической нотации это выражается так: визуально группируется информация по каждому члену и применяется оператор сложения (+), чтобы всё объединить. Да, именно сложение делает многочлен многочленом. Но как только мы понимаем, на что смотрим, этот знак отходит на второй план.
Про контекст
Люди хорошо считывают контекст. Мы можем наблюдать это повсеместно: в зависимости от контекста одно и то же слово может иметь разное, а иногда кардинально разное значение. Это настолько естественно, что как только люди привыкают к этому, то перестают это замечать. Когда вы в последний раз ловили себя на мысли, что красное вино на самом деле бордовое? Или что для программистов «поток» в многопоточном приложении и «поток» операций в рабочем цикле — это не одно и то же?
Полисемия или многозначность затрудняет изучение языка. Во многом потому, что носители языка даже не замечают, что используют её в речи. Как только осваиваешься в языке — будь то иностранный язык или какой-то жаргон, принятый в определённой социальной группе, — начинаешь органично воспринимать значение слов в зависимости от контекста.
Почему вообще в языке существует полисемия? Потому что воспринимать контекст проще, чем общаться многословными и подробными фразами.
Эту особенность языка можно использовать и в программировании. Если я работаю над модулем, реализующим HTTP-клиента для Stripe, я вполне могу использовать get
в значении «отправить в Stripe аутентифицированный запрос HTTP GET». Но в более широком и менее специфичном для Stripe контексте я бы написал Stripe.get
.
По этой причине мне так нравится уточнённый импорт в языках вроде Хаскель (Haskell). Мой проверенный метод: если я поймал себя на использовании одинакового суффикса или префикса для ряда идентификаторов (getHttps
, postHttps
и подобных), возможно, их стоит сгруппировать в модуль для уточнённого импорта. В модуле эти идентификаторы будут просто get
и post
, но пользователи смогут импортировать их как Https.get
и Https.post
, если это уместнее в их контексте.
Про промежуточный код
Часть вашего кода — это «мясо» или логика, на которой держится всё, что вы делаете. Остальной код больше похож на водопроводные трубы — код, который нужен, чтобы всё работало, но не так важен в каждом конкретном случае. Иногда исправление бага напрямую зависит от того, как конкретное значение конфигурации попадает к вам в функцию, но чаще всего вам гораздо важнее, что эта функция делает.
Чтобы быстро понимать код и хорошо в нём ориентироваться, принципиально важно отличать промежуточный код от основной логики. При беглом просмотре базы кода на промежуточный код можно вообще не обращать внимания. По моему наблюдению, именно там гнездятся всякие «противоречивые» характеристики языка, такие как макросы, перегруженные операторы и абстракции управления.
Например, применение инфиксных (бинарных) операторов для компонентов промежуточного кода визуально отличает такой код-прокладку от «нормальных» идентификаторов и заодно придаёт коду некоторую визуальную структуру, осуществляя группировку в регулярных выражениях.
Через какое-то время, когда вы бегло просматриваете код, вы перестаёте замечать операторов промежуточного кода. Немного приглядевшись, вы начнёте видеть сходства в коде, который в разных контекстах делает одно и то же. Рассмотрим для примера Applicative
— аппликативные функторы в Хаскеле. Они позволяют применить функцию, упакованную в контекст, к значению, упакованному в контекст (например, Maybe
и IO
):
f a b c -- обычное применение функции
f <$> a <*> b <*> c -- с Applicative
А ещё этот стиль кода наглядно демонстрирует, как операторы естественным образом группируют код:
f (a + b) (c * d)
f <$> a + b <*> c * d
Для непосвящённого человека аппликативная нотация (способ записи элементов кода с применением аппликативных функторов) может ассоциироваться с какими-то помехами в коде — по правде говоря, это неидеальный пример чистого промежуточного кода. Но такое ощущение пропадёт, как только вы лучше освоитесь в Хаскеле. И промежуточный код вы перестанете замечать — прямо как в каноничной сцене из «Матрицы».

Про содержательную информацию
На мой взгляд, полезно задумываться о минимальном объёме информации, который должно содержать выражение. Например, для многочлена от одной переменной принципиальное значение несут коэффициенты. С точки зрения содержательной информации знать остальное необязательно.
Нам всё ещё может понадобиться дополнительный код — для соединения с другими частями системы, для структуры или просто в качестве детали реализации. Но при проектировании API важно отличать основную информацию, которую несёт код, от всего остального. «Всё остальное» может быть полезным для организации кода, а может оказаться совершенно необоснованным шаблонным кодом.
Я руководствуюсь следующим правилом: стараюсь исключать всё больше ненужного кода по мере того, как всё чаще повторяю определённый тип выражения. В крайнем случае, если у вас куча фрагментов повторяющегося кода, самым читабельным вариантом станет табличная разметка.
Про математическую нотацию
Математическая нотация имеет более длинную историю развития, чем языки программирования, и является отличным примером записи, которая схватывается на лету.
Сравним два способа написания одного и того же выражения:

Интегральная запись может быть незнакома новичку, но она удивительно эффективна. Можно сразу сказать, что представляет собой выражение, благодаря знаку интеграла и типичной для уравнений схеме. Любое интегральное выражение составлено по определённой схеме — этот принцип я и имею в виду, когда говорю о «форме кода».
Быстро вычленить части уравнения, чтобы понять, что к чему, — это довольно легко: пределы отличаются от самого уравнения и от переменной интегрирования (то есть dx
). Сам многочлен продолжает ту же тему: +
как оператор придаёт многочлену структуру, подчёркивая его природу как совокупности членов. В каком-то смысле +
— это просто вспомогательный элемент, который отходит на второй план, чтобы мы могли определить содержание конкретно этого многочлена (а именно, коэффициенты и степень).
Однако предложение, в котором содержится информация о многочлене, может прочитать любой человек, даже если он незнаком с обозначением интегралов. Но у текста есть один большой недостаток — его приходится читать. Причём каждый раз. Нам приходится читать текст слово в слово, чтобы понять, что он описывает интеграл, а также какие пределы и функция в него интегрированы. Без внимательного чтения мы даже не можем сказать, что это интеграл от многочлена.
Если бы вокруг этого предложения были другие, мы бы не смогли отличить его от остального текста. Читая документацию, я часто пропускаю объяснения и смотрю на цифры и уравнения, пока не нахожу, что мне нужно, — без специальных обозначений и визуальной структуры я бы не смог этого сделать.
Именно поэтому я не поклонник API и предметно-ориентированных языков, которые пытаются имитировать текст на естественном языке. Этот стиль ассоциируется у меня с Ruby и CoffeeScript, но он восходит ещё к SQL, если не раньше. Читать DSL слово за словом, как и описанную текстом математическую запись — последнее, что я хочу делать в своей жизни.
Про привычку писать лёгкий код
Хороший код — это проблема человеческого фактора. Хочется понимать, как люди взаимодействуют с кодом: как они его читают, пишут, просматривают, переделывают и ориентируются в нём, — чтобы писать и выстраивать максимально простой и удобный код.
Умение писать код, который легко просматривать — важный и полезный навык. Такой код кажется более лёгким. Звучит неоднозначно, но такой подход действительно сокращает затраты времени, сил и внимания на работу с кодовой базой.
Я очень ценю код, который можно бегло просмотреть, и всегда придерживаюсь этого принципа в своей работе над кодом. Как только я осознал для себя всю важность такого подхода, нужная привычка писать лёгкий код выработалась сама собой, и теперь я об этом даже не задумываюсь.
Чтобы расти, нужно выйти из привычной зоны и сделать шаг к переменам. Можно изучить новое, начав с бесплатных занятий:
курс-симулятор «Системный аналитик: первые шаги к профессии»;
гайд «Как применять нейросети в работе»;
курс «Специалист по информационной безопасности: старт карьеры»;
Или открыть перспективы и получить повышение с профессиональным обучением:
магистратура «Прикладной искусственный интеллект» с УрФУ;
курс для действующих IT-специалистов «DevOps-инженер»;
магистратура «Инженерия машинного обучения» с УрФУ;
профессия «Дата-инженер» с Yandex Cloud.
Комментарии (2)
nv13
10.07.2025 21:40final
BigDecimal result = a
(x
x) + b * x + c;
А вот интересно про С++) перезагрузили операторы
auto
result = a
(x
x) + b * x + c;
float z = result / 2;
Если у BigDecimal тоже что то перезагружено, то даже компилятор может промолчать, нет? А потом ищи пропавшие мильоны)
Мне кажется, что если разработчик заморочился BigDecimal, то это уже не совсем имплементация, а часть алгоритма, поэтому скрывать её в контексте такое себе. И искать этот контекст может быть труднее, чем разобраться в более громоздкой записи
gev
Прочитал это, думаю, это вам, нужно на Haskell посмотреть!
Читаю дальше:
Да ладно! ;)