Приветствую вас, коллеги.
В этой публикации мы рассмотрим один из основополагающих вопросов программирования (на мой предвзятый взгляд, конечно) — проблему сложности. Я видел развитие множества разных проектов — от первых зарисовок на бумаге до выпуска новых мажорных версий через несколько лет успешной работы на рынке. И у многих проектов я наблюдал неприятную тенденцию, которую можно охарактеризовать как «постоянное усложнение».
Усложнение кода по мере развития проекта
Вначале, когда проект мал, его исходный код занимает несколько файлов и разработкой занимаются один или два человека (крупные бизнес решения с многомесячным предварительным планированием и последующим фундаментальным фейлом я оставлю для последующих публикаций). На протяжении месяцев проект бурно развивается — файлы с исходным кодом размножаются почкованием, подключаются внешние зависимости, в соответствии с меняющимися требованиями и неожиданными открытиями меняется архитектура. Часто на этом этапе к команде подключаются новые люди, которые уже не видели исходной, маленькой версии проекта и начинают работать сразу со значительной базой исходного кода. Постепенно сложность возрастает настолько, что даже отцы-основатели проекта уже не могут охватить целиком всю внутреннюю логику, фокусируясь на общей архитектуре и дистанцируясь от деталей. С возрастанием сложности разработки все чаще возникают ситуации, когда добавление тривиальной функциональности становится нетривиальной задачей — вскрываются «неочевидные» на первый взгляд свойства архитектуры, взаимодействия частей проекта, всплывают поставленные на скорую руку заплатки. И в какой-то момент времени, обычно через несколько лет, внести практически любое серьезное изменение оказывается дороже по времени, чем переписать проект с нуля.
Дешевле переписать, чем изменить
В большинстве книг и статей обычно говорят про «сложность», «технический долг«, «гибкость архитектуры» и прочие страшные слова. Но, как вы наверное уже поняли по предыдущим публикациям, я предпочитаю находить максимально простые объяснения. И желательно, чтобы они помогали ситуацию исправить, а не просто декларировали кучку трюизмов.
Итак, что же происходит? На мой взгляд, происходит следующее. Когда разработчик пишет код, он по факту конспектирует свои мысли, записывает некую картину работы программы, как он ее себе представляет. Обычно это делается небольшими мазками — программист «рисует» фрагмент, смотрит на него, запускает, отлаживает, затем вносит изменения — следующий «мазок» и так далее. Мазки накладываются один на другой, общая картина меняется, и через некоторое время получается законченное полотно. Когда этот же программист, или его коллега, хочет внести изменения в код — этот код вначале нужно прочитать и понять. И тут-то кроется засада эпических пропорций — читать код оказывается намного сложнее, чем его писать. Почему? Потому что когда мы пишем код небольшими «мазками», мы в каждый момент времени фокусируемся на одной небольшой части. А когда читаем — мы видим результирующую картину, в которой отдельные «мазки» уже слабо различимы и сливаются друг с другом.
Усугубляет ситуацию то, что картина мира каждого разработчика уникальна, и когда он пишет код — этот код следует за его мыслями и является отражением работы его мозга. Я уже говорил про отсутствие хорошей теоретической базы и образования? Написанный код, если только это не что-нибудь тривиальное, будет сильно отличаться от разработчика к разработчику — и тем самым создавать дополнительные сложности для читающего чужой код. Распространенная ситуация, когда новый разработчик, пришедший в проект, начинает критиковать существующий код — часто возникает совсем не потому, что код очень плох. А потому, что новый разработчик при попытке прочесть код, являющийся отражением мысли другого человека, испытывает серьезный дискомфорт, так как его собственная картина мира сильно отличается. Не в худшую и не лучшую сторону — просто отличается. Это как надевать чужую обувь — даже если размер примерно одинаков, радости это доставит мало.
Сложность копится незаметно
Так из небольших фрагментов образуется то, что я называю сложностью программного обеспечения — насколько тяжело разработчику его впоследствии понять и изменить. У сложности есть ряд неприятных особенностей — в частности, она накапливается крайне медленно. А человеческий разум слабо приспособлен для отслеживания медленных изменений — мы не замечаем старения близких людей, постоянного поднятия уровня воды — и, к сожалению, накопления сложности в наших программах.
С точки зрения менеджера все хорошо — задачи выполняются в срок, программа развивается. При этом он не может отследить, что каждая следующая задача занимает чуть больше времени, а некоторые требуют дополнительного времени на устранение технического долга. Зато через год наступает «прозрение», когда менеджер «неожиданно» обнаруживает, что тривиальные задачи выполняются неделями, а разработчикам все труднее и труднее предсказывать сроки.
Для борьбы со сложностью есть множество способов — как чисто программерских, к примеру, уменьшение размера коммитов, так и чисто управленческих — утренних стендапов, парного программирования и разделения знаний о проекте. Обо всем этом я расскажу в последующих публикациях. Ну а в следующем выпуске колонки вас ждет увлекательная экскурсия к корням зла — я постараюсь рассказать о том, почему сложность вообще возникает и как такое получается, что нам «тяжело» что-то понять.