Часто в процессе реализации проектов команды сталкиваются с вопросом: чему следует уделять больше внимания – выпуску новых фич или повышению качества кода? Обычно менеджеры делают выбор в пользу фич. Зачастую разработчики таким положением дел недовольны, считая, что им выделяется недостаточно времени для работы над архитектурой и качеством кода.
Закон Беттериджа гласит: «На любой заголовок, который заканчивается вопросительным знаком, можно ответить словом нет». Те, кто знаком со мной лично, знают, что я не разделяю эту мысль. Но в этой статье я хочу пойти ещё дальше и доказать, что постановка вопроса из заголовка этой статьи просто не имеет смысла. Такая постановка вопроса предполагает, что существует компромисс между затратами и качеством. И необходимо постоянно соблюдать баланс. В этой статье я докажу, что к миру разработки компьютерных систем этот компромисс не применим и, в действительности, создавать ПО высокого качества оказывается в конечном счёте дешевле.
Несмотря на то, что основная целевая аудитория статьи это разработчики, для её понимания не требуется специальных знаний. Мне бы хотелось чтобы эта статья принесла пользу всем, кто так или иначе связан с процессом разработки, а особенно менеджерам, которые формируют вектор развития продуктов.
Мы привыкли к тому что нужно выбирать между ценой и качеством
Как я писал ранее, при разработке ПО постоянно приходится делать выбор между качеством продукта и затратами на его разработку. Когда вы покупаете новый смартфон, у вас есть выбор. Заплатить больше денег и получить более быстрый процессор, больше памяти и улучшенный экран, или заплатить меньше, но пожертвовать некоторыми функциями. Из этого правила бывают исключения: иногда продукт более высокого качества стоит дешевле. А иногда люди даже не могут объективно сравнить два продукта и выбрать более качественный. Например, не замечают разницы между экранами, которые изготовлены по совершенно разным технологиям. Тем не менее, утверждение: «Высокое качество стоит дороже» – обычно справедливо.
Качество ПО это о многом
Говоря о качестве ПО, следует начать с определения критериев качества. Что такое качественное ПО? С этого момента всё несколько усложняется, ведь у любой компьютерной системы есть множество критериев, по которым можно оценивать её качество. Можно оценивать UI и UX: насколько быстро и просто пользователь может решить свою задачу? Можно оценивать надёжность: есть ли в программе баги, которые ведут к неправильному и нестабильному поведению? Ещё один критерий – это архитектура: насколько исходный код программы структурирован, насколько просто и быстро программист может найти нужный ему в данный момент участок кода?
Приведённый список критериев качества, конечно же, не полный. Но этих критериев достаточно, чтобы показать одну важную вещь. Некоторые критерии, по которым обычно оценивают качество программы, даже не видны для конечных пользователей. Заказчики могут дать обратную связь и сообщить, насколько хорошо ПО решает их бизнес-задачи. Пользователи могут пожаловаться на неудобный интерфейс. Или будут жаловаться на баги, особенно если они приводят к потере данных или к продолжительной недоступности системы. Но пользователи не в состоянии оценить архитектуру и качество кода.
Поэтому я разделяю критерии качества на две категории: внешние (например, UI/UX или наличие багов) и внутренние (архитектура). Самое важное их отличие в том, что пользователи могут оценить внешнее качество, но не могут понять, насколько хороша (или плоха) внутренняя архитектура системы.
На первый взгляд внутреннее качество не имеет значения для пользователей (но только на первый)
Если пользователи не могут оценить внутреннее качество ПО, является ли этот критерий важным? Давайте представим гипотетическую ситуацию, что две команды разработчиков, независимо друг от друга, решили создать приложение для отслеживания и прогнозирования задержек авиарейсов. Одной командой управляю я, а второй руководит Ребекка. Набор базовых функций у приложений примерно одинаковый, интерфейс у обоих приложений тоже получился достаточно удобный и продуманный, критических багов в приложениях нет. Единственное отличие заключается в том, что исходный код приложения от Ребекки чётко структурирован и организован, а код, созданный моей командой, представляет из себя беспорядочный набор классов и методов с непонятными именами и ещё более непонятной логикой того, как этот код связан между собой. Есть и ещё одно отличие: я продаю своё приложение за $6, а Ребекка продаёт почти такое же приложение за $10.
Поскольку пользователям недоступен исходный код приложений, а качество кода никак не влияет на пользовательский опыт, зачем пользователям платить лишние $4? Иными словами – зачем переплачивать за внутреннее качество, которое не имеет никакого значения для пользователей?
Если развивать эту идею ещё дальше, то можно прийти к мнению что вкладываться во внешнее качество выгоднее, чем во внутреннее. Делая выбор между двумя приложениями, пользователь может выбрать то, которое дороже, если у него более качественный и удобный интерфейс. Но пользователи не видят внутреннее устройство приложений, не говоря уже о том, чтобы пользователь мог сравнить архитектуру двух приложений. Так зачем платить больше за то, что не приносит практической пользы? И зачем разработчикам тратить время и ресурсы на повышение внутреннего качества своих программ?
Программы с высоким внутренним качеством проще расширять
Почему для программистов настолько важно чтобы код был качественный? Программисты проводят большую часть времени за его чтением и редактированием. Даже при разработке новой системы, работа почти всегда ведётся в контексте уже написанного кода. Когда программист добавляет новую фичу, сначала ему необходимо разобраться, как эта фича вписывается в существующую архитектуру приложения. Затем зачастую нужно внести изменения в архитектуру, чтобы новую фичу можно было реализовать. Часто нужно использовать структуры данных, которые уже есть в системе. Поэтому нужно понять, что эти структуры данных означают, какие между ними имеются связи и какие новые структуры данных нужно добавить для реализации фичи.
Высокое качество кода позволяет программистам быстро в нём ориентироваться. Добиться ситуации, когда код станет сложным для понимания, на самом деле очень просто. Логические условия могут переплетаться, связи между структурами данных могут быть сложными и неявными. Названия, которые Тони дал переменным и функциям 6 месяцев назад, возможно, были понятны ему, но также непонятны новому разработчику, как и мотивы побудившие Тони покинуть компанию. Разработчики обычно называют это «техническим долгом» (техдолгом), или иными словами, разницу между текущим состояние кода и идеальным состоянием, в котором он может быть.
Одним из основных преимуществ, которые даёт высокое качество кода является то, что программист может быстро понять, как работает система и внести нужные правки. Когда приложение разделено на модули, программисту не нужно изучать все 500,000 строк исходного кода и он может быстро найти нужные ему в данный момент несколько сотен строчек. Когда программисты дают понятные имена переменным, функциям и классам, можно легко понять, что делает каждый отдельный фрагмент кода без необходимости глубоко вникать в контекст. Если структуры данных в программе совпадают с терминологией из доменной области бизнеса, то программисту легко соотнести запрос на новый функционал с тем, как устроена система. Техдолг же увеличивает время, которое требуется для работы с кодом. Также возрастает вероятность ошибиться. В случае появления багов из-за плохого качества кода, потребуется дополнительное время на то, чтобы локализовать проблему и исправить её. А если баг не будет замечен сразу, то это приведёт к появлению проблем в production-коде и тому, что придётся потратить ещё больше времени на исправление этих проблем в будущем.
Каждое изменение в коде влияет на будущее продукта. Часто бывает ситуация, когда есть простой и быстрый способ реализовать новую фичу, но ценой нарушения текущей архитектуры (т.е. за счёт увеличения техдолга). Если программист выбирает этот путь, он выпускает свою фичу быстрее, но замедляет работу других разработчиков, которым придётся поддерживать этот код позднее. Если все в команде будут так поступать, то даже хорошо спроектированное приложение с хорошим кодом быстро обрастёт техдолгами, и даже для внесения небольшой правки потребуется несколько недель.
Пользователи хотят получать новые фичи как можно быстрее
Мы подходим к важному моменту, а именно: к ответу на вопрос, почему же для пользователей всё таки важно внутреннее качество ПО? Высокое внутреннее качество способствует более быстрому выпуску новых фич, потому что их проще, быстрее и дешевле делать. Наши с Ребеккой приложения выглядят сейчас почти одинаковыми, но через несколько месяцев высокое качество кода Ребекки позволит ей выпускать каждую неделю по новой фиче, а я застряну на месте, стараясь справиться с техдолгом и пытаясь запустить хотя бы одну новую фичу. Я не смогу конкурировать с Ребеккой по скорости развития, и её приложение быстро обгонит моё по функциональности. В конечном счёте пользователи удалят моё приложение и будут пользоваться приложением Ребекки, несмотря на то, что оно стоит дороже.
Визуализация влияния внутреннего качества
Главным преимуществом высокого внутреннего качества программы является уменьшение стоимости будущих изменений. Но на написание качественного кода требуется больше усилий, а это увеличивает необходимые ресурсы в краткосрочной перспективе.
На графике ниже схематично изображено, как можно представить соотношение функциональности и времени, которое требуется на его разработку. Обычно кривая выглядит примерно так:
Так выглядит процесс разработки, когда код не очень качественный. Сначала разработка идёт достаточно быстро, но затем на дальнейшее расширение функционала требуется всё больше и больше времени. В определённый момент времени, для того чтобы внести даже маленькое изменение, программисту нужно сначала изучить много сложного и запутанного кода. После того, как изменение внесено, обнаруживается, что что-то сломалось, и это ведёт к дополнительным затратам времени на тестирование и исправление ошибок.
Высокое внутреннее качество способствует повышению эффективности разработки на более поздних стадиях. Некоторым командам удаётся даже получить обратный эффект, когда каждая новая фича выпускается быстрее предыдущей за счёт того, что удаётся переиспользовать уже написанный код. Но такое происходит нечасто, потому что для этого требуется высокопрофессиональная команда с хорошей организацией работы. Но иногда такое всё-таки происходит.
Однако есть одна хитрость. На начальных стадиях разработки игнорирование качества кода оказывается более эффективно, чем следование высоким стандартам. Но когда же заканчивается этот период?
Чтобы ответить на этот вопрос, сначала нужно пояснить, что на изображениях представлены псевдографики. Не существует единственно верного способа для оценки производительности команды. Непросто понять, как плохой код влияет на конечное качество продукта (и если эта корреляция есть, то насколько она выражена). Кстати, эта проблема актуальна не только для ИТ-индустрии. Как, например, оценить качество работы юриста или доктора?
Но вернёмся к вопросу, в какой момент стоит начать задумываться о качестве кода. Опытные разработчики считают, что плохое качество кода начинает замедлять работу уже через несколько недель после начала проекта. На ранней стадии проекта на красоту архитектуры и кода можно не обращать внимания.
Также по собственному опыту могу сказать, что даже небольшие проекты получают серьёзное конкурентное преимущество, если используют в своей работе современные и эффективные практики разработки и задумываются о качестве кода.
Даже лучшие команды иногда пишут плохой код
Те, кто плохо знаком с процессом разработки, считают, что плохой код говорит о том, что команда плохо работает. Но, как показывает практика, даже самые опытные команды иногда допускают ошибки и пишут плохой код.
Чтобы наглядно это показать, хочу рассказать вам о беседе с одним из наших лучших тим-лидов. В тот момент он как раз закончил работать над проектом, который все считали очень успешным. Заказчик был в восторге от новой системы, причём, как от новых возможностей, так и от ресурсов, которые были потрачены на её разработку. Команда тоже была довольна выполненным проектом. Технический лидер команды тоже был весьма доволен результатом, но признался, что на самом деле архитектура системы получилась не такая уж удачная. Я спросил у него: «Но как же так, ты ведь один из наших лучших архитекторов?». Он ответил так, как ответил бы любой опытный архитектор: «Мы принимали хорошие решения, но только сейчас понимаем, как нужно было делать правильно».
Многие люди сравнивают создание сложных систем с проектированием небоскрёбов. Видимо, поэтому опытных разработчиков называют «архитекторами». Но в процессе создания ПО всегда есть некоторая неопределённость, нехарактерная для других областей деятельности, в которых неопределённости гораздо меньше. Типичные заказчики плохо понимают, чего они хотят от программы и начинают понимать это только в процессе работы над ней. Чаще всего, в тот момент, когда им показывают первые версии программы. Элементы, из которых создаются программы (языки программирования, библиотеки, платформы), меняются раз в несколько лет. Проводя аналогию со строительством небоскрёба, можете ли вы представить ситуацию, когда заказчик просит архитектора добавить ещё с десяток этажей и изменить планировку у нижних, при том, что половина здания уже построена? Ситуация усложняется ещё больше, когда выясняется что технологии, используемые для производства бетона, его физические свойства и характеристики обновляются каждые 2 года.
В условиях постоянных новых вызовов, роста их количества и сложности, командам постоянно приходится придумывать что-то новое. Всё чаще приходится решать задачи, которые раньше никто никогда не решал, и соответственно для них нет общеизвестного и проверенного решения. Обычно ясное понимание проблемы приходит только в момент её решения, поэтому я часто слышу мнение, что понимание того, как должна выглядеть архитектура сложной системы, приходит как минимум через год после начала работы. И даже самая профессиональная в мире команда разработчиков не сможет сделать систему идеально.
Профессиональная и организованная команда отличается от менее организованной тем, что в процессе работы над системой создаёт меньше технического долга, а также параллельно избавляется от существующего. Это помогает проекту быстро развиваться и выпускать новые фичи как можно оперативнее. Такая команда вкладывается в создание автоматизированных тестов, что помогает быстрее выявлять проблемы и тратить меньше времени на поиск и исправление багов. Участники такой команды постоянно работают над поддержанием высокого качества кода и быстро избавляются от плохого кода, пока он не начал мешать двигаться вперёд. CI-системы тоже способствуют этому, особенно в ситуации, когда множество людей параллельно работает над разными задачами. В качестве метафоры можно привести уборку на кухне после приготовления пищи. Невозможно что-то приготовить, не испачкав при этом стол, посуду и другие кухонные принадлежности. Если вы не почистите их сразу, то грязь засохнет и потом будет гораздо сложнее её отмыть. И когда в следующий раз вы захотите что-нибудь приготовить, вам будет намного сложнее это сделать потому что сначала придётся помыть гору посуды.
Исследование DevOps Research and Assessment (DORA)
Компромисс между стоимостью и качеством – не единственный в мире разработки ПО, который кажется простым на первый взгляд, но на практике всё оказывается несколько сложнее. Также широко обсуждается вопрос, что лучше выбрать – быстрые темпы разработки и релизов или более медленные темпы и тщательное тестирование. Считается, что использование второго подхода позволяет добиться более высокой стабильности продакшн-систем. Однако в исследовании DORA было доказано, что это не так.
Собрав статистику за несколько лет, исследователи выявили какие практики способствуют более высокой эффективности команд. Оказалось, что самые эффективные команды обновляют продакшн-сервера много раз в день, а выпуск кода от момента его написания до релиза занимает не более часа. Следование такому подходу позволяет выпускать изменения маленькими частями, и вероятность серьёзных поломок снижается. Команды, которые делают релизы реже, по статистике сталкиваются с большим количеством серьёзных проблем. Помимо этого команды, которые привыкли к высокому темпу, умеют быстрее восстанавливаться после сбоев. Также исследования показали, что в таких командах обычно лучше выстроены процессы и они действуют более организованно.
Поддержка систем с хорошей архитектурой стоит дешевле
Самые важные пункты из того, о чём мы говорили выше:
- Недостаточное внимание к качеству кода ведёт к накоплению технического долга
- Технический долг замедляет развитие системы
- Даже профессиональные команды иногда принимают плохие решения. Но применение современных практик и периодическое «погашение» технического долга позволяет держать его под контролем
- Поддержание высокой планки качества кода позволяет минимизировать техдолг. Это даёт возможность сосредоточиться на новых фичах и выпускать их меньшими усилиями, быстрее и дешевле
К сожалению, разработчикам обычно сложно объяснить это руководству. Я часто слышу жалобы, что руководство не даёт возможности поддерживать высокое качество кода, ограничивая время которое выделяется для работы над задачами. Отвечая на вопросы руководства, зачем тратить лишние ресурсы на красоту кода, разработчики обычно отвечают, что это является показателем высокого профессионализма. Но использование только этого аргумента подразумевает, что на поддержание высокого качества расходуются дополнительные ресурсы, которые можно было использовать на другие задачи. И это подрывает сам довод о профессионализме. Истина заключается в том, что из-за некачественной архитектуры и плохого кода, жизнь становится сложнее у всех: разработчикам сложнее с ним работать, а заказчикам он обходится дороже. При обсуждении качества кода с менеджментом, я призываю к тому чтобы рассматривать его исключительно как экономический показатель. Если программа внутри сделана качественно, то будет проще и дешевле добавлять в неё новые фичи. Это означает, что вложения в написание качественного кода, в конечном счёте, снижают общие затраты на разработку.
Это и есть истинная причина, почему вопрос из заголовка статьи просто не имеет смысла. Тратить лишние ресурсы на архитектуру и хороший код, с экономической точки зрения, в итоге, оказывается более выгодно. Компромисс между ценой и качеством, с которым мы часто сталкиваемся в обычной жизни, нельзя напрямую применить к внутреннему качеству ПО, но можно применить к внешнему качеству. Например, если речь идёт о пользовательском интерфейсе. Поскольку корреляция между стоимостью и внутренним качеством в данном случае нетипична и контринтуитивна зачастую её бывает трудно осознать (и тем более объяснить другим). Но тем не менее важно это осознать, чтобы сделать процесс разработки максимально эффективным.
У Мартина Фаулера есть ещё одна статья про технический долг. Небольшой тизер:
Дополнительное время, которое тратится на добавление новых фич, можно сравнить с процентами по банковскому кредиту. Чистка технического долга – это как выплата процентов по кредиту. Эта метафора хорошо описывает суть. Но может создаться ложное ощущение, что техническим долгом можно достаточно просто управлять. Однако в реальности это намного сложнее.