Всем привет! У нас уже был перевод предыдущей статьи Дэна — CUPID; её чтение не обязательно, но полезно, потому что многие тезисы здесь перекликаются с CUPID.

Дэниел Тергорст-Норт

Cоздатель Behaviour-Driven Development и идеи Deliberate Discovery. Он утверждает, что «лучших практик не существует» и каждое решение несёт альтернативные издержки.

Вы можете накормить волков и сохранить овец, если сделаете все правильно. 

‘Мы можем сделать это быстро и заплатить за спешку потом, или мы можем сделать все тщательно и заплатить прямо сейчас.’ Такова фундаментальная дихотомия в разработке программного обеспечения, противоречие между «перфекционизмом» и «прагматизмом», но я не думаю, что мы вообще должны идти здесь на какой‑то компромисс.

Мой друг, работающий техническим директором, использует метафору о прокладывании двух тропинок через гору. Тропинка слева быстрая, но грязная, ее расчистили с помощью мачете и грубой силы, и вы не ждете, что кто-то последует за вами по этой тропинке, вы просто прорубаете себе путь — но ваш прогресс при этом становится довольно быстрым. Тропа справа шире, чище, вымощена камнями и более прочна, но, чтобы построить ее, нужно время. Левая тропинка полезна, если мы хотим провести быструю разведку и понять, что нас ждет впереди. Правая — эта та тропа, по которой вы поведете других, точно зная, что здесь безопасно.  

Мой друг живет в постоянном напряжении, находясь между людьми, которые выбирают левую тропу, которая также является самым быстрым способом заработать деньги (а он работает в торговой фирме, так что этот аргумент имеет немалый вес), и людьми, которые выбирают правую тропу и хотят создать устойчивый, надежный продукт.  

Эта ситуация описывает многие организации, где я работал. Напряжение в отношениях с обеими сторонами основано на убежденности, что делать иначе значит делать неправильно. Сторонники правой тропы беспокоятся, что те, кто предпочитают левую тропу, накапливают технический долг, который рано или поздно вернется и отомстит им, оправдывая такой подход «прагматизмом». Люди, предпочитающие левую тропу, думают, что те, кто ходят по правой — «перфекционисты», которые занимаются оверинженерингом и наводят ненужный блеск (и многие бизнес менеджеры склоняются к тому, чтобы с ними согласиться).

Я заявляю, что существует и тропа, идущая посередине, о которой ни одна из сторон не догадывается. Я называю этот путь «Лучшей простой системой на сегодня» (Best Simple System for Now, BSSN). Мне потребовалось много времени, чтобы сформулировать эту идею, которая «пряталась на видном месте» много лет. Существует несколько идей, от которых я отталкивался, которые предполагают ее наличие или как минимум намекают на нее, о чем я собираюсь здесь упомянуть, но я никогда не встречал ее четкой формулировки. Поэтому вот она.  

Характеристики лучшей простой системы на сегодня

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

Каждая часть в формулировке «Лучшая простая система на сегодня» выбрана осознанно, и все они усиливают друг друга. Любое отклонение от любой из них означает, что это уже не лучшая простая система на сегодня, а что-то более слабое. Давайте разберемся с каждой из этих частей.  

на Сегодня (for Now)

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

Inserting image..., Picture
оригинальная картинка автора статьи

Люди фундаментально ленивы. Это не обзывательство, а жизненно важный эволюционно выработанный признак. Мы полагаемся на наше инстинктивное и автоматическое подсознательное стремление освобождать место под активную сознательную обработку. В своей работе, «Думать быстро и медленно», Даниэль Канеман вводит понятия «Система 1» и «Система 2», соответственно. Сопоставление паттернов относится к подсознательным активностям Системы 1; у нас это хорошо получается, это естественно для нас. «Увидеть, что там на самом деле» сложнее, это требует сознательного мышления и усилий.

Мы никогда не уходим более чем на один маленький шажок от «Это движок для создания правил!» или от «Это машина состояний!», поэтому наш эффективный инженерный мозг решает сэкономить немного времени — и переделок — и ухватиться за предпочитаемое нами решение сразу. Частью причин такого выбора является наше знание о том, что каждое изменение имеет свою цену, так что сделать несколько малых шагов всегда будет менее эффективно, чем сделать один большой шаг, про который мы «знаем», что он нам понадобится. Это может звучать разумно, но есть серьезные коммерческие и операционные причины не делать так, к чему я перейду позже.

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

Простая (Simple)

Система не должна предвосхищать будущее ни в какой форме. Это противоречит всем советам, которые я когда-либо получал — или давал — как программист!  

Когда я разрабатываю архитектуру какого-то продукта, две версии меня обычно находятся в конфликте между собой. «Умная» версия меня — та, у которой есть десятилетия опыта и большое эго — знает, что эта штука будет меняться впоследствии, так что мы должны сделать ее интерфейсом. Или что мы почти наверняка будем иметь дело с вот таким объемом данных, поэтому надо сделать многопоточную версию алгоритма обработки, вместо того, чтобы сделать обычный последовательный алгоритм. Более скромная версия меня знает, что я буду близок к правде, но все же неправ по поводу всех моих предсказаний, так же, как я был близок, но неправ много раз прежде, поэтому лучше придержать все эти подходы на настоящее время. Более скромная версия обычно проигрывает.

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

Увидев и поработав за эти годы со многими кодовыми базами, я уверен в том, что это случается и со всеми остальными. Большинство кода, с которым я работаю, содержит интерфейсы и другие заделы, которым несколько десятилетий от роду, и которыми никто никогда не воспользовался; выборы алгоритмов и технологий, которые фантастически подошли бы для 20000 одновременных пользователей, в то время как обслуживается только 12 человек; хуки и расширения для “масштабирования” и “гибкости” за гранью самых невероятных мечтаний, возможно, введенные в надежде, что это будет их magnum opus система, которая определит их карьеру. И, конечно же, этот код полон трудно уловимых багов, вызываемых взаимодействием между всеми этими сложными решениями.  

Совершенство достигается не тогда, когда больше нечего добавить, а тогда, когда больше нечего убрать. 

— Антуан де Сент-Экзюпери

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

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

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

Напротив, я испытал на себе закон Галла1 большое количество раз, от словаря enterprise данных до решений с обобщенным алгоритмом и до конфигурируемых через графический интерфейс движков создания правил, и каждый раз путь до неизбежной неудачи неизменно составлял 3-5 лет.

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

— Джон Галл  

Лучшая (Best)

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

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

Когда Уорд Каннингем говорил о «простейшей вещи, которая сможет работать», он имел в виду именно это. Он не имел в виду платоновский идеал простоты, в том смысле, что малейший шаг, который он сможет сделать, продвинет его понимание вперед. В очень милом интервью, которое он дал Биллу Веннерсу в 2004-м году, он указывает на то, что ’сам по себе акт ее написания помог организовать наши мысли’

Лучший код показывает ваши намерения; он обеспечивает баланс между связыванием (cohesion) и сцеплением (coupling), облегчая навигацию по коду, его анализ и внесение в него изменений.  

Если появляется дублирование или, что хуже, почти дублирование, идей внутри кода, тогда это не лучший код. С другой стороны, если код помешан на идее DRY (do not repeat yourself), прибегает к сцеплению только потому, что код в двух местах выглядел похоже, тогда это тоже не лучший код. Если в коде отсутствуют термины домена, такие как front и centre, а маппинг осуществляется напрямую на сценарий использования из реального мира, то это не лучший код.  

Я писал о том, что я считаю хорошим кодом, приятным кодом, и я сформулировал эти принципы как CUPID

  • Composable (пригодный для композиций): хорошо сочетается с другим кодом (другими подсистемами одной системы); содержит как можно меньше зависимостей  

  • Unix philosophy (философия Unix): делает одну и только одну вещь, очевидным и понятным образом  

  • Predictable (предсказуемый): в своем поведении, observability, характеристиках рантайма, режимах сбоев; также в том, что случится, когда вы его измените 

  • Idiomatic (идиоматический): использует паттерны и идиомы, знакомые разработчику, опытному в этой технологии  

  • Domain-based (базируется на домене): показывает свое предназначение в наименованиях, поведении и структуре кода  

Важно отметить, что вы можете писать приятный код так же быстро — или даже быстрее — чем нарубленный топором “прагматичный” код. CUPID строится на идее Ричарда Гэбриэла об обитаемости кода, понятии, которое он определяет как ощущение комфорта и уверенности в себе при работе с кодовой базой.  

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

Вы часто слышите о техниках экстремального программирования, таких как парное программирование или test-driven development, упоминаемых как необходимое условие для хорошего кода, но не позволяйте фанатикам или идеологиям столкнуть вас с пути. Эти техники — всего лишь инструменты; не являющиеся ни необходимыми, ни достаточными, но зачастую они способны помочь.  

В то время, как я сам являюсь преданным фанатом этих техник, мне случалось работать с хорошо структурированным, демонстрирующим предназначение, легко читаемым кодом в огромных кодовых базах на  C и C++, где эти техники не использовались. С другой стороны, я пострадал от строго test-driven кода, где не было никакой связанности, где было плохое понимание домена, и который редактировался через боль, мне приходилось продираться через него, он выглядел, как эквивалент двойной записи в бухгалтерии. Да что там, я и сам поучаствовал в написании такого кода. Теперь это заставляет меня извиваться от негодования.


Аргументы против BSSN

Теперь давайте посмотрим на некоторые аргументы против написания лучших простых систем на сегодня. 

Это лишь чрезмерные затраты на прототип 

В чем смысл инвестировать а такой код, делая его “лучшим” кодом, если мы собираемся выбросить его, как только он выполнит свою задачу?   

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

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

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

До того, как его купила Meta, WhatsApp масштабировался до более чем 500 миллионов активных пользователей с кодовой базой на Erlang, которую написали и поддерживали около 13 инженеров. Подобным образом, реляционная база данных SQLite является одной из самых широко используемых программ в истории, она инсталлирована практически на любой браузер, десктопный компьютер, ноутбук, мобильное устройство, сервер или другой хост. Ее основная команда состоит из трех человек, и они не принимают чужие Merge Request-ы. 

Она неполна

Если вы знаете, что должен делать ваш продукт, почему ваши пользователи должны терпеть недопеченную версию? 

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

Первый iPhone, выпущенный в 2007-м году, был 2G устройством в 3G мире. Он не достиг паритета по поддержке сетей с другими смартфонами еще в течение года, и еще год прошел, прежде чем они ввели функцию копирования и вставки между мессенджерами и электронной почтой! Тем не менее, iPhone украл рынок у таких “непотопляемых” конкурентов, как Nokia и BlackBerry, а потом тихонечко добавил эти возможности к более поздним версиям. 

Некоторые продукты выделяются на рынке за счет небольших, но сфокусированных подмножеств функций. Google Docs делает только малую долю того, что может делать Microsoft Word, и это так и задумано. То же самое верно для Google Sheets и Excel. Google Workspace стало конкурентом Microsoft Office, потому что это малое подмножество покрывает большинство сценариев использования для людей большую часть времени, и при этом дает преимущество в плане того, что вам не надо инсталлировать программу, обновлять ее, устанавливать патчи, обеспечивать безопасность и проводить аудиты, за исключением самого браузера. Это делает его приобретение и управление жизненным циклом привлекательным для крупных предприятий, тогда как его изучение становится более простым для новых пользователей.

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

Ричард Гэбриэл — автор идеи обитаемого кода — описывает эту идею как «худшее — это лучшее». В начале 1990-х он с разочарованием, но без удивления, открыл для себя, что язык C стал буквально вездесущим, а Lisp не стал, хотя с его точки зрения он был лучше. Почему люди предпочли незаконченный, лишенный единообразия, чрезмерно сложный язык C простому, корректному, единообразному и законченному языку Lisp? Он затем пускается в весьма забавную обличительную тираду о том, что C и Unix являются идеальными компьютерными вирусами, и о том, почему «худшее — это лучшее»: поставка незаконченного продукта и дополнение его в ходе дальнейших итераций лучше, чем ожидание полной готовности, даже если итерации никогда не сделают версию «законченной».

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

Она неэффективна

Зачем постоянно рефакторить и перерабатывать код? Почему не сделать сразу как надо?  

Когда вы принимаете решение потратить время и деньги на разработку продукта, вы принимаете решение на базе инвестиционных рисков. Деньги, которые вы ожидаете заработать, должны превосходить количество денег, которое вы собираетесь потратить на достаточное количество, чтобы вам было комфортно начинать процесс. Это называется “возврат инвестиций” (Return on Investment, ROI) или возврат капитала (Return on Capital, ROC). Чем меньше мы потратим на создание продукта, тем больше выгоды мы получим, если предположить, что доход останется тем же. Так почему же мы хотим все время перерабатывать продукт? Ведь это наверняка уменьшит нашу прибыль?  

Ответ состоит в том, чтобы посмотреть, когда вы заработаете эти деньги. В терминологии финансов, деньги сегодня стоят больше, чем деньги завтра, или, точнее, деньги завтра несут в себе большие риски, чем деньги сегодня. Чем сильнее мы отсрочим выплату, тем больше неуверенность. Деньги, которые вы тратите на создание чего-то, что, как вы надеетесь, принесет выгоду в будущем, называется ожидаемым риском (Value at Risk, VAR)

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

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

Если вы будете принимать во внимание VAR, это даст вам возврат капитала с поправкой на риск (Risk-Adjusted Return on Capital, RAROC). По сути, вы масштабируете инвестиции в соответствии с той суммой, которой рискуете, что доказывает, что более рискованное расписание поставок является худшей инвестицией. Эту концепцию мне впервые показал мой бывший коллега, гуру по торговле и рискам Крис Маттс

Если кратко, поставки итерациями имеют тенденцию давать худший ROI — как только вы включите в расчеты все эти релизы и постоянную работу по планированию, как и весь этот постоянно перелопачиваемый код — но лучший RAROC, а это именно то, что имеет значение. 

В своей основополагающей книге, “Принципы рабочего процесса разработки продукта” (The Principles of Product Development Flow) Дональд Райнертсен предлагает нам дополнительный взгляд на эту проблему. Представьте, что вы могли бы щелкнуть пальцами, и все нужды клиента были бы удовлетворены, а все его проблемы решены. Вы сделали бы его счастливым сразу и он заплатил бы вам сразу. Теперь подумайте о каждом часе, каждом дне, каждой неделе, каждом месяце, который требуется для прихода к решению на виду у клиента, прежде чем вы сможете удовлетворить эту потребность, и клиент решит купить ваш продукт. Это все мертвые деньги, которые вы никогда не получите. Это называется «стоимость задержки» (Cost of Delay).

Здесь же рядом стоит и упущенная выгода (Opportunity Cost), что является ценой наиболее дорогостоящей работы, которую вы могли бы выполнять вместо этой. Если ваша команда тратит месяцы на что‑то, она в то же время не делает что‑то другое!

Райнертсен исследует экономику разработки продукта и показывает, как цена задержки и упущенная выгода могут во много раз превосходить «затраты на усилия», такие как темп расходования средств (burn rate) на проекте, зарплаты или покупка лицензий. Мы зацикливаемся на последних, в то время как большие расходы прячутся на видном месте. Все наши метрики проекта концентрируются на активностях и трудозатратах — использование, продуктивность, расписания — вместо того, чтобы обратить внимание на время выполнения или пропускную способность. Райнертсен советует нам «измерять единицы работы, а не работников».

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

Почему мы так не делаем?

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

Одна из первопроходцев программирования, Грейс Хоппер, описывала отношение к делу по принципу «Мы всегда делали так» как наиболее опасное. Мы должны бросать ему вызов везде, где мы его находим.

В своей работе «Семь привычек высокоэффективных людей» (Seven Habits of Highly Effective People) Стивен Ковей определяет «мышление дефицита» (scarcity mindset) как веру в то, что жизнь является игрой на нулевые ставки, где все решается прямым обменом. Это является нашим состоянием по умолчанию. По контрасту, он предлагает принять «мышление изобилия» (abundance mindset), при котором мы ищем обоюдной выгоды в любой ситуации. Один сражается за «свою долю пирога»; другой пытается понять, как сделать пирог больше.

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

Кент Бек советует нам «сделать изменения простыми, затем сделать простое изменение». Я верю, что нам нужна дисциплина, привычки, смелость и смирение, чтобы сохранять наш код простым, чтобы его было легко изменить. Нам не надо предсказывать будущее, если мы можем легко к нему приспособиться.

Дисциплина

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

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

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

Привычки

Чтобы создать и поддерживать «лучшую простую систему на сегодня», не нужны никакие тайные или глубокие познания. Нужна лишь ежедневная преданность идее и хорошие привычки. Для этого требуются усилия и осознанная практика, призванная обучить себя не смотреть на общий случай, не бежать в первую очередь за библиотекой, «потому что она добавит всего одну зависимость», не абстрагировать код раньше времени и не применять принцип DRY к коду без нужды «просто потому что», не делать чрезмерных инвестиций в компонент, потому что у нас есть обязательные для всех стандарты по коду с произвольно введенными правилами, например, по проценту покрытия тестами, возвращаться назад и стабилизировать компонент, который вы написали как набросок, чтобы посмотреть, будет ли он работать, и он заработал, а также удалять компоненты, которые не заработали в наброске.

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

Смелость

Как такое вообще может заработать? И если это хорошая идея, почему все остальные так не делают? Прыгать в неизвестность сложно. Смелость — это не отсутствие страха, а способность принять решение идти вперед вопреки ему. Я поощряю принцип «поверь мне один раз». Если мы пробуем этот подход, и он не работает, мы всегда можем вернуться назад и попробовать что‑то другое. Но если сработает… ну разве это не круто?

Смирение

Брюс Ли говорил своим студентам, чтобы они «были как вода». Если вы нальете воду в чашку, она примет форму чашки. Если она течет в реку, она принимает форму реки. Гибкость приходит не от предвосхищения, а от простоты. Нет ничего проще, чем текущая вода!

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

С чего начать?

Лучший способ принять BSSN — это Просто Начать. Как и с любым новым навыком, у вас сначала все будет получаться не так, вы будете ошибаться в ту или другую сторону, пока не освоитесь. Будьте добры к себе и дайте себе немного времени. Выход из своей зоны комфорта — это необходимый шаг к изучению чего-то нового, но на этой дороге могут быть кочки.  

То, что я познал с наибольшим трудом — и мне до сих пор трудно — это увидеть ту часть, которая называется «на сегодня». Будет возникать искушение задать интерфейс или выделить «переиспользуемый» компонент (просто на всякий случай, потому что я умный!) или, наоборот, не доработать по инжинирингу, потому что я недооценил, как быстро эти микро‑инвестиции в качество и усилия окупятся (к тому же мне не нужны эти тесты или этот единообразный доменный язык, ведь я умный!)

Хорошая новость состоит в том, что чем больше вы практикуетесь в этих привычках, тем проще становится построить лучшую простую систему на сегодня. Я могу использовать TDD почти так же быстро, как «просто писать код», и результат всегда лучше. С другой стороны, я могу использовать подход «написать — прогнать — пронаблюдать» в командной строке (это называется REPL или Read‑Eval‑Print loop), пока я делаю наброски для чего‑либо. До тех пор, пока я получаю обратную связь, мне не важно, повторяемая ли это обратная связь или автоматизированная.

В качестве примера REPL, я недавно писал некоторые кастомизированные фикстуры для PyTest — тестировать фреймворк для тестирования сложно! — и для некоторых из них понадобилось создавать и манипулировать временными файлами и директориями. Пока я делал наброски и изучал вопрос, как я хочу, чтобы мои фикстуры работали, было намного проще запустить несколько команд tree и grep, чем писать тесты для проверки того, куда записывались файлы. Плюс, такие виды тестов приводят к «бухгалтерии с двойной записью», воспроизводя реализацию того, что они тестируют, весьма хрупкие и не очень полезные конструкции. Если PyTest поменяет свой подход к созданию временных файлов, мне не придется об этом беспокоиться, при условии, что я получу требуемое поведение.

Над чем вы работаете прямо сейчас? Как вы могли бы переработать какие‑то части своей работы в лучшую простую систему на сегодня? Начните с этого и посмотрите, что получится. Если это сложно, это будет индикатором того, где вам придется приложить определенные усилия, чтобы «изменения стали простыми». Не пытайтесь сделать сразу все. Начните с чего‑то, что приведет к быстрой отдаче, чтобы вы смогли выстроить доверительные отношения с вашими товарищами по команде и с собой по поводу того, как это работает и чем отличается от привычных способов.

Приведу пару примеров. 

Пример: JSON библиотека

Один из моих клиентов, работающий в торговле, хотел получить от меня совет по поводу того, какую JSON библиотеку им следует выбрать для критического для бизнеса торгового приложения, написанного на Java. Два из их синьорных инженеров сцепились между собой, отстаивая разные решения и не показывая никаких признаков готовности пойти на компромисс. Они приводили в качестве аргументов различные характеристики: масштабируемость, устойчивость, транзитивные зависимости, поддержка сообщества и т. д. Они хотели получить мнение внешнего консультанта в качестве решающего голоса.

Вместо того, чтобы выбрать, какую сторону поддержать, я спросил, сколько разных типов они планируют посылать «по проводу». После некоторых дискуссий выяснилось, что этих типов девять. Девять типов. Я предположил, что им не нужна была «JSON библиотека» с ее глюками в рантайме, транзитивными зависимостями и сложностью. Возможно, им просто необходимо было сериализовать и десериализовать девять типов объектов.

В качестве эксперимента мы определили интерфейс, который реализовывался бы в каждом типе T

interface Jsonable<T> { 
    String toJson(T obj); 
} 

И мы потребовали, чтобы они содержали статический метод следующего вида (другие языки могли бы сделать этот пэйринг более симметричным): 

class SomeT { 
  public static SomeT fromJson(String json) { 
    // ... 
  } 
} 

Они реализовали эти интерфейсы с тестами в девяти местах. Девять пар однострочных методов, никаких зависимостей, скорость молнии, минимальная поверхность для атаки; лучшая простая система на сегодня. Если бы они добавили десятый тип, они б точно знали, как расширить это решение и как в точности оно будет себя вести. Если бы они захотели изменить формат для любого из типов в любое время, они могли бы сделать это тривиальным образом. И так далее.  

Пример: XML стример

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

Это были дни расцвета Enterprise Java, когда мир работал на XML, и вы использовали XML в XML ваш XML, или другой ваш XML

Все что мы хотели — это представить вот такой объект: 

class Boat { 
  String name; 
  int length; 
  boolean isSubmersible; 
} 

// ...

new Boat("Boaty McBoatface", 5, true); 

в таком виде: 

<Boat> 
  <name>Boaty McBoatface</name> 
  <length>5</length> 
  <isSubmersible>true</isSubmersible> 
</Boat> 

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

Как один из ранних адептов open source ПО, он засабмитил свою библиотеку, называемую XStream, на хостинговый сайт OSS, древнего предвестника GitHub. Они отказались ее принимать на том основании, что она была настолько простой, что не стоила места на хостинге. Более 20 лет спустя XStream все еще сильна и ее можно найти практически везде, где есть Java, включая Международную космическую станцию! И она до сих пор проста и очевидна.

Эти примеры не означают, что BSSN обязана быть разработана дома и не поощряют культуру «изобретено не здесь». Касательно библиотеки XStream, мой приятель был рад построить вокруг нее сообщество, так что ему больше не надо беспокоиться о ней!

Эти примеры говорят нам, что необходимо «смотреть на то, что есть на самом деле» и решать вопросы только этого сценария использования. Если библиотека от третьей стороны или инструмент являются лучшим решением на сегодня, затем вам надлежит их использовать. Но необходим целостный взгляд на стоимость ее внедрения, обучения использованию, странности, транзитивные зависимости, обходные пути для ее ограничений, когда они идет вразрез с вашими целями, и на стоимость и усилия, которые потребуются для удаления ее в будущем, а не только на кратковременное удобство, а именно на возможность не писать несколько строк кода. И если вы все-таки решите написать что-то сами, пишите только то, что вам нужно на данный момент. 

Много букв, не читал

Вам не придется выбирать между наведением блеска, что подается как мастерство или перфекционизм и срезанием углов, что подается как прагматизм или реализм. Вы можете совместить качество первого со скоростью и фокусом последнего. Я называю это «Лучшая простая система на сегодня».

Вы можете сделать выбор в пользу совестливого развития кодовой базы, чтобы в любой момент времени она была «настолько простой, насколько возможно, но не проще», имея при этом приятные CUPID характеристики, состояла из малых компонентов, которые общаются друг с другом как ячейки, передающие сообщения, как Алан Кей однажды назвал объектно‑ориентированное программирование. Не то, чтобы ООП было единственным способом, или даже наилучшим способом достижения такой цели. Это лишь пример того, что я называю «архитектурой заменяемых компонентов» (replaceable component architecture).

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

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


[¹] Как подметил эксперт по сложности Дэйв Сноуден, «закон» Галла применим только в некоторых контекстах; статья в Википедии, на которую я ссылаюсь, описывает это как универсальное правило. Это наблюдение кажется валидным для многих крупных организаций, где я работаю. Я также считаю его удобным инструментом для того, чтобы помочь людям понять эволюционную архитектуру, а не чувствовать себя обязанными покрыть всю базу a priori.

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

Комментарии (6)


  1. DenSigma
    10.07.2025 10:54

    Автор только что придумал вещь, которая в промышленном производстве существует уже три столетия - чертеж.


    1. KartonDev Автор
      10.07.2025 10:54

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


  1. ant1free2e
    10.07.2025 10:54

    XStream - это как раз пример общего решения, "простая система на сегодня" выковыривала бы данные поиском по подстроке или что-то в таком роде.

    Если совсем пренебрегать DRY, SOLID, OOP и ориентироваться только на скорость деливеринга, такие системы очень быстро превращаются в очень сложные для разработчиков любого уровня и погруженности в систему и ее домен. Именно с этим в первую очередь связанно стремление оверинжинирить на будущее


  1. ris58h
    10.07.2025 10:54

    минимальная поверхность для атаки; лучшая простая система на сегодня.

    Потом оказывается что забыли экранирование кавычек сделать (или ещё что-нибудь не самое очевидное из JSON), и поэтому прод прилёг и надо срочно чинить.

    XStream

    Отличный пример во что превращается простая система со временем. Не отдал бы в open-source - внутри конторы жил бы свой кривой велосипед.


    1. KartonDev Автор
      10.07.2025 10:54

      минимальная поверхность для атаки; лучшая простая система на сегодня.

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

      > XStream - тем не менее одна из самых популярных библиотек для Xml. В целом я бы лучше пример привел - IDEA-ная библиотека для работы с XML. Она вообще очень маленькая. Просто, понятно. Не всегда удобно, но понятно)

      Тут в пример с упрощением фичей можно привести в пример GSON который умер и овер-хед Jackson. Рабочий вариант который покрывает все фичи, проще и понятнее против усложненного продукта в котором стоит разобраться только при прочтении мануала о полях, политиках маршал-анпаршал и те. И это простые примеры - написание бибиотеки - всегда конечный (почти) процесс. В то время как написание проектов / продукто для User End пользования почти всегда завязан на продолжительную подджержку. Тут, как мне кажется - правила, которые говорит автор - начинают играть новыми красками. Например, заказчик хочет, чтобы БП делал тоже, что по тз - но по соверменному с какой то микро-фичей, которая особо не играет функциональной роли - но несет существенную нагрузку на кодовую базу или что хуже - разработчики пытаются напилить на кодовую базу миллионы абстракций на будущее, которые надо будет меинтейнить в будущем.

      tl;dr;

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


  1. vfadeev_sam
    10.07.2025 10:54

    Нравится мне Дэниел умением порефлексировать на тему своего жизненного опыта и синтезировать что-то полезное. Сразу видно - человек деньги зарабатывает и думает о том как эффективно использовать свое время на проекте