Чтобы написать действующую программу, не нужно много знать и уметь. Дети в школе делают это постоянно. Юноши и девушки в колледжах начинают свой бизнес, вырастающий до стоимости в миллиарды долларов, написав несколько строк кода на PHP или Ruby. Начинающие программисты в офисах по всему миру перелопачивают горы требований, хранящихся в гигантских баг-трекерах, и вносят исправления, чтобы заставить свои системы «работать». Возможно, они пишут не самый лучший код, но он работает. Он работает, потому что заставить что-то работать – один раз – не очень сложно.
Создать программу, которая будет работать правильно, – совсем другое дело. Написать правильную программу сложно. Для этого необходимы знания и умения, которые молодые программисты еще не успели приобрести. А чтобы приобрести их, требуется мыслить и анализировать, на что у многих программистов просто нет времени. Это требует такой самодисциплины и организованности, которые не снились большинству программистов. А для этого нужно испытывать страсть к профессии и желание стать профессионалом.
Но когда создается правильный программный код, происходит что-то необычное: вам не требуются толпы программистов для поддержки его работоспособности. Вам не нужна объемная документация с требованиями и гигантские баг-трекеры. Вам не нужны огромные опенспейсы, работающие круглые сутки без выходных.
Правильный программный код не требует больших трудозатрат на свое создание и сопровождение. Изменения вносятся легко и быстро. Ошибки немногочисленны. Трудозатраты минимальны, а функциональность и гибкость – максимальны.
Да, такое представление кажется утопическим. Но я видел это воочию. Я участвовал в проектах, где дизайн и архитектура системы упрощали их создание и сопровождение. У меня есть опыт работы в проектах, потребовавших меньшего числа участников, чем предполагалось. Мне довелось работать над системами, в которых ошибки встречались удивительно редко. Я видел, какой невероятный эффект оказывает хорошая архитектура на систему, проект и коллектив разработчиков. Я был на земле обетованной.
Но я не прошу верить мне на слово. Оглянитесь на свой опыт. Случалось ли у вас что-то противоположное? Доводилось ли вам работать над системами с настолько запутанными и тесными связями, что любое изменение, независимо от сложности, требовало недель труда и было сопряжено с огромным риском? Испытывали ли вы сопротивление плохого кода и неудачного дизайна? Приходилось ли вам видеть, как дизайн системы оказывал отрицательное влияние на моральный дух команды, доверие клиентов и терпение руководителей? Доводилось ли вам оказываться в ситуации, когда отделы, подразделения и целые компании становились жертвами неудачной архитектуры их программного обеспечения? Были ли в аду программирования?
Я был, и многие из нас были там. В нашей среде чаще встречается опыт борьбы с плохим дизайном, чем получение удовольствия от воплощения хорошо продуманной архитектуры.
За долгие годы вокруг понятий «дизайн» и «архитектура» накопилось много путаницы. Что такое дизайн? Что такое архитектура? Чем они различаются?
Одна из целей этой книги – устранить весь этот беспорядок и определить раз и навсегда, что такое дизайн и архитектура. Прежде всего, я утверждаю, что между этими понятиями нет никакой разницы. Вообще никакой.
Слово «архитектура» часто используется в контексте общих рассуждений, когда не затрагиваются низкоуровневые детали, а слово «дизайн» обычно подразумевает организацию и решения на более низком уровне. Но такое разделение бессмысленно, когда речь идет о том, что делает настоящий архитектор.
Возьмем для примера архитектора, спроектировавшего мой новый дом. Этот дом имеет архитектуру? Конечно! А в чем она выражается? Ну… это форма дома, внешний вид, уступы, а также расположение комнат и организация пространства внутри. Но когда я рассматривал чертежи, созданные архитектором, я увидел на них массу деталей. Я увидел расположение всех розеток, выключателей и светильников. Я увидел, какие выключатели будут управлять теми или иными светильниками. Я увидел, где будет находиться узел отопления, а также местоположение и размеры водонагревательного котла и насоса. Я увидел подробное описание, как должны конструироваться стены, крыша и фундамент.
Проще говоря, я увидел все мелкие детали, поддерживающие все высокоуровневые решения. Я также увидел, что низкоуровневые детали и высокоуровневые решения вместе составляют дизайн дома.
То же относится к архитектуре программного обеспечения. Низкоуровневые детали и высокоуровневая структура являются частями одного целого. Они образуют сплошное полотно, определяющее форму системы. Одно без другого невозможно; нет никакой четкой линии, которая разделяла бы их. Есть просто совокупность решений разного уровня детализации.
В чем состоит цель таких решений, цель хорошего дизайна программного обеспечения? Главная цель – не что иное, как мое утопическое описание:
Цель архитектуры программного обеспечения – уменьшить человеческие трудозатраты на создание и сопровождение системы.
Мерой качества дизайна может служить простая мера трудозатрат, необходимых для удовлетворения потребностей клиента. Если трудозатраты невелики и остаются небольшими в течение эксплуатации системы, система имеет хороший дизайн. Если трудозатраты увеличиваются с выходом каждой новой версии, система имеет плохой дизайн. Вот так все просто.
В качестве примера рассмотрим результаты исследований из практики. Они основаны на реальных данных, предоставленных реальной компанией, пожелавшей не разглашать своего названия.
Сначала рассмотрим график роста численности инженерно-технического персонала. Вы наверняка согласитесь, что тенденция впечатляет. Рост численности, как показано на рис. 1.1, должен служить признаком успешного развития компании!
Рис. 1.1. Рост численности инженерно-технического персонала. Воспроизводится с разрешения автора презентации Джейсона Гормана (Jason Gorman)
Теперь взгляните на график продуктивности компании за тот же период, измеряемой в количестве строк кода (рис. 1.2).
Рис. 1.2. Продуктивность за тот же период
Очевидно, что здесь что-то не так. Даже при том, что выпуск каждой версии поддерживается все большим количеством разработчиков, похоже, что количество строк кода приближается к своему пределу.
А теперь взгляните на по-настоящему удручающий график: на рис. 1.3 показан рост стоимости строки кода с течением времени.
Рис. 1.3. Изменение стоимости строки кода с течением времени
Эта тенденция говорит о нежизнеспособности. Какой бы рентабельной ни была компания в настоящее время, растущие накладные расходы поглотят прибыль и приведут ее к застою, если не к краху.
Чем обусловлено такое значительное изменение продуктивности? Почему строка кода в версии 8 продукта стоит в 40 раз дороже, чем в версии 1?
Причины неприятностей перед вашими глазами. Когда системы создаются второпях, когда увеличение штата программистов – единственный способ продолжать выпускать новые версии и когда чистоте кода или дизайну уделяется минимум внимания или не уделяется вообще, можно даже не сомневаться, что такая тенденция рано или поздно приведет к краху.
На рис. 1.4 показано, как выглядит эта тенденция применительно к продуктивности разработчиков. Сначала разработчики показываю продуктивность, близкую к 100 %, но с выходом каждой новой версии она падает. Начиная с четвертой версии, как нетрудно заметить, их продуктивность приближается к нижнему пределу – к нулю.
Рис. 1.4. Изменение продуктивности с выпуском новых версий
С точки зрения разработчиков, такая ситуация выглядит очень удручающе, потому все они продолжают трудиться с полной отдачей сил. Никто не отлынивает от работы.
И все же, несмотря на сверхурочный труд и самоотверженность, они просто не могут произвести больше. Все их усилия теперь направлены не на реализацию новых функций, а на борьбу с беспорядком. Большую часть времени они заняты тем, что переносят беспорядок из одного места в другое, раз за разом, чтобы получить возможность добавить еще одну мелочь.
Если вы думаете, что ситуация скверная, взгляните на нее с точки зрения руководства! На рис. 1.5 изображен график изменения месячного фонда оплаты труда разработчиков за тот же период.
Рис. 1.5. Изменение фонда оплаты труда разработчиков с выпуском новых версий
Когда была выпущена версия 1, месячный фонд оплаты труда составлял несколько сотен тысяч долларов. К выпуску второй версии фонд увеличился еще на несколько сотен тысяч. К выпуску восьмой версии месячный фонд оплаты труда составил 20 миллионов долларов и тенденция к увеличению сохраняется.
Этот график выглядит не просто скверно, он пугает. Очевидно, что происходит что-то ужасное. Одна надежда, что рост доходов опережает рост затрат, а значит, оправдывает расходы. Но с любой точки зрения эта кривая вызывает беспокойство.
А теперь сравните кривую на рис. 1.5 с графиком роста количества строк кода от версии к версии на рис. 1.2. Сначала, всего за несколько сотен тысяч долларов в месяц, удалось реализовать огромный объем функциональности, а за 20 миллионов в последнюю версию почти ничего не было добавлено! Любой менеджер, взглянув на эти два графика, придет к выводу, что необходимо что-то предпринять, чтобы предотвратить крах.
Но что можно предпринять? Что пошло не так? Что вызвало такое невероятное снижение продуктивности? Что могут сделать руководители, кроме как топнуть ногой и излить свой гнев на разработчиков?
Примерно 2600 лет тому назад Эзоп сочинил басню о Зайце и Черепахе. Мораль той басни можно выразить по-разному:
• «медленный и постоянный побеждает в гонке»;
• «в гонке не всегда побеждает быстрейший, а в битве – сильнейший»;
• «чем больше спешишь, тем меньше успеваешь».
Притча подчеркивает глупость самонадеянности. Заяц был настолько уверен в своей скорости, что не отнесся всерьез к состязанию, решил вздремнуть и проспал, когда Черепаха пересекла финишную черту.
Современные разработчики также участвуют в похожей гонке и проявляют похожую самонадеянность. О нет, они не спят, нет. Многие современные разработчики работают как проклятые. Но часть их мозга действительно спит – та часть, которая знает, что хороший, чистый, хорошо проработанный код играет немаловажную роль.
Эти разработчики верят в известную ложь: «Мы сможем навести порядок потом, нам бы только выйти на рынок!» В результате порядок так и не наводится, потому что давление конкуренции на рынке никогда не ослабевает. Выход на рынок означает, что теперь у вас на хвосте висят конкуренты и вы должны стремиться оставаться впереди них и бежать вперед изо всех сил.
Поэтому разработчики никогда не переключают режим работы. Они не могут вернуться и навести порядок, потому что должны реализовать следующую новую функцию, а потом еще одну, и еще, и еще. В результате беспорядок нарастает, а продуктивность стремится к своему пределу около нуля.
Так же как Заяц был излишне уверен в своей скорости, многие разработчики излишне уверены в своей способности оставаться продуктивными. Но ползучий беспорядок в коде, иссушающий их продуктивность, никогда не спит и не никогда бездействует. Если впустить его, он уменьшит производительность до нуля за считаные месяцы.
Самая большая ложь, в которую верят многие разработчики, – что грязный код поможет им быстро выйти на рынок, но в действительности он затормозит их движение в долгосрочной перспективе. Разработчики, уверовавшие в эту ложь, проявляют самонадеянность Зайца, полагая, что в будущем смогут перейти от создания беспорядка к наведению порядка, но они допускают простую ошибку. Дело в том, что создание беспорядка всегда оказывается медленнее, чем неуклонное соблюдение чистоты, независимо от выбранного масштаба времени.
Рассмотрим на рис. 1.6 результаты показательного эксперимента, проводившегося Джейсоном Горманом в течение шести дней. Каждый день он писал от начала до конца простую программу преобразования целых чисел из десятичной системы счисления в римскую. Работа считалась законченной, когда программа успешно проходила предопределенный комплект приемочных тестов. Каждый день на решение поставленной задачи затрачивалось чуть меньше 30 минут. В первый, второй и третий дни Джейсон использовал
Рис. 1.6. Время на выполнение итерации с использованием и без использования методики TDD
хорошо известную методику разработки через тестирование (Test-Driven Development; TDD). В остальные дни он писал код, не ограничивая себя рамками этой методики.
Прежде всего обратите внимание на кривую обучения, заметную на рис. 1.6. Каждый раз на решение задачи затрачивалось меньше времени. Отметьте также, что в дни, когда применялась методика TDD, упражнение выполнялось примерно на 10 % быстрее, чем в дни без применения TDD, и что даже худший результат, полученный с TDD, оказался лучше самого лучшего результата, полученного без TDD.
Кто-то, взглянув на этот результат, может посчитать его удивительным. Но для тех, кто не поддался обману самонадеянности Зайца, результат будет вполне ожидаемым, потому что они знают простую истину разработки программного обеспечения:
Поспешай не торопясь.
И она же является ответом на дилемму, стоящую перед руководством. Единственный способ обратить вспять снижение продуктивности и увеличение стоимости – заставить разработчиков перестать думать как самонадеянный Заяц и начать нести ответственность за беспорядок, который они учинили.
Разработчики могут подумать, что проблему можно исправить, только начав все с самого начала и перепроектировав всю систему целиком, – но это в них говорит все тот же Заяц. Та же самонадеянность, которая прежде уже привела к беспорядку, теперь снова говорит им, что они смогут построить лучшую систему, если только вновь вступят в гонку. Однако в действительности все не так радужно:
Самонадеянность, управляющая перепроектированием, приведет к тому же беспорядку, что и прежде.
Любой организации, занимающейся разработкой, лучше всего избегать самонадеянных решений и с самого начала со всей серьезностью отнестись к качеству архитектуры ее продукта.
Серьезное отношение к архитектуре программного обеспечения подразумевает знание о том, что такое хорошая архитектура. Чтобы создать систему, дизайн и архитектура которой способствуют уменьшению трудозатрат и увеличению продуктивности, нужно знать, какие элементы архитектуры ведут к этому.
Именно об этом рассказывается в данной книге. В ней рассказывается, как выглядит добротная, чистая архитектура и дизайн, чтобы разработчики могли создавать системы, способные приносить прибыль долгое время.
Всякая программная система имеет две разные ценности: поведение и структуру. Разработчики отвечают за высокий уровень обеих этих ценностей. Но, к сожалению, они часто сосредоточиваются на чем-то одном, забывая про другое. Хуже того, они нередко сосредоточиваются на меньшей из двух ценностей, что в конечном итоге обесценивает систему.
Первая ценность программного обеспечения – его поведение. Программистов нанимают на работу, чтобы они заставили компьютеры экономить деньги или приносить прибыль заинтересованной стороне. Для этого мы помогаем заинтересованным сторонам разработать функциональную спецификацию или документ с требованиями. Затем пишем код, заставляющий компьютеры заинтересованных сторон удовлетворять этим требованиям.
Когда компьютер нарушает требования, программисты вынимают свои отладчики и исправляют проблему.
Многие программисты полагают, что этим их работа ограничивается. Они уверены, что их задача – заставлять компьютеры соответствовать требованиям и исправлять ошибки. Они жестоко ошибаются.
Вторая ценность программного обеспечения заключена в самом названии «программное обеспечение». Слово «обеспечение» означает «продукт»; а слово «программное»… Как раз в нем и заключается вторая ценность.
Идея программного обеспечения состоит в том, чтобы дать простую возможность изменять поведение компьютеров. Поведение компьютеров в некоторых пределах можно также изменять посредством аппаратного обеспечения, но этот путь намного сложнее.
Для достижения этой цели программное обеспечение должно быть податливым – то есть должна быть возможность легко изменить его. Когда заинтересованные стороны меняют свое мнение о некоторой особенности, приведение ее в соответствие с пожеланиями заинтересованной стороны должно быть простой задачей. Сложность в таких случаях должна быть пропорциональна лишь масштабу изменения, но никак не его форме.
Именно эта разница между масштабом и формой часто является причиной роста стоимости разработки программного обеспечения. Именно по этой причине стоимость растет пропорционально объему требуемых изменений. Именно поэтому стоимость разработки в первый год существенно ниже, чем во второй, а во второй год ниже, чем в третий.
С точки зрения заинтересованных сторон они просто формируют поток изменений примерно одинакового масштаба. С точки зрения разработчиков, заинтересованные стороны формируют поток фрагментов, которые они должны встраивать в мозаику со все возрастающей сложностью. Каждый новый запрос сложнее предыдущего, потому что форма системы не соответствует форме запроса.
Я использовал здесь слово «форма» не в традиционном его понимании, но, как мне кажется, такая метафора вполне уместна. У разработчиков программного обеспечения часто складывается ощущение, что их заставляют затыкать круглые отверстия квадратными пробками.
Проблема, конечно же, кроется в архитектуре системы. Чем чаще архитектура отдает предпочтение какой-то одной форме, тем выше вероятность, что встраивание новых особенностей в эту структуру будет даваться все сложнее и сложнее. Поэтому архитектуры должны быть максимально независимыми от формы.
Функциональность или архитектура? Что более ценно? Что важнее – правильная работа системы или простота ее изменения?
Если задать этот вопрос руководителю предприятия, он наверняка ответит, что важнее правильная работа. Разработчики часто соглашаются с этим мнением. Но оно ошибочно. Я могу доказать ошибочность этого взгляда простым логическим инструментом исследования экстремумов.
Если правильно работающая программа не допускает возможности ее изменения, она перестанет работать правильно, когда изменятся требования, и вы не сможете заставить ее работать правильно. То есть программа станет бесполезной.
Если программа работает неправильно, но легко поддается изменению, вы сможете заставить работать ее правильно и поддерживать ее работоспособность по мере изменения требований. То есть программа постоянно будет оставаться полезной.
Эти аргументы могут показаться вам неубедительными. В конце концов, нет таких программ, которые нельзя изменить. Однако есть системы, изменить которые практически невозможно, потому что стоимость изменений превысит получаемые выгоды. Многие системы достигают такого состояния в некоторых своих особенностях или конфигурациях.
Если спросить руководителя, хотел бы он иметь возможность вносить изменения, он ответит, что безусловно хотел бы, но тут же может уточнить, что работоспособность в данный момент для него важнее гибкости в будущем. Однако если руководитель потребует внести изменения, а ваши затраты на эти изменения окажутся несоизмеримо высокими, он почти наверняка будет негодовать оттого, что вы довели систему до такого состояния, когда стоимость изменений превышает разумные пределы.
Рассмотрим матрицу президента Дуайта Дэвида Эйзенхауэра для определения приоритета между важностью и срочностью (рис. 2.1). Об этой матрице Эйзенхауэр говорил так:
У меня есть два вида дел, срочные и важные. Срочные дела, как правило, не самые важные, а важные – не самые срочные[9].
Рис. 2.1. Матрица Эйзенхауэра
Это старое изречение несет много истины. Срочное действительно редко бывает важным, а важное – срочным.
Первая ценность программного обеспечения – поведение – это нечто срочное, но не всегда важное.
Вторая ценность – архитектура – нечто важное, но не всегда срочное.
Конечно, имеются также задачи важные и срочные одновременно и задачи не важные и не срочные. Все эти четыре вида задач можно расставить по приоритетам.
1. Срочные и важные.
2. Не срочные и важные.
3. Срочные и не важные.
4. Не срочные и не важные.
Обратите внимание, что архитектура кода – важная задача – оказывается в двух верхних позициях в этом списке, тогда как поведение кода занимает первую и третью позиции.
Руководители и разработчики часто допускают ошибку, поднимая пункт 3 до уровня пункта 1. Иными словами, они неправильно отделяют срочные и не важные задачи от задач, которые по-настоящему являются срочными и важными. Эта ошибочность суждений приводит к игнорированию важности архитектуры системы и уделению чрезмерного внимания не важному поведению.
Разработчики программного обеспечения оказываются перед проблемой, обусловленной неспособностью руководителей оценить важность архитектуры. Но именно для ее решения они и были наняты. Поэтому разработчики должны всякий раз подчеркивать приоритет важности архитектуры перед срочностью поведения.
Эта обязанность означает постоянную готовность к битве – возможно, в данном случае лучше использовать слово «борьба». Честно говоря, подобная ситуация распространена практически повсеместно. Команда разработчиков должна бороться за то, что, по их мнению, лучше для компании, так же как команда управленцев, команда маркетинга, команда продаж и команда эксплуатации. Это всегда борьба.
Эффективные команды разработчиков часто выходят победителями в этой борьбе. Они открыто и на равных вступают в конфликт со всеми другими заинтересованными сторонами. Помните: как разработчик программного обеспечения вы тоже являетесь заинтересованной стороной. У вас есть свой интерес в программном обеспечении, который вы должны защищать. Это часть вашей роли и ваших обязанностей. И одна из основных причин, почему вас наняли.
Важность этой задачи удваивается, если вы выступаете в роли архитектора программного обеспечения. Архитекторы, в силу своих профессиональных обязанностей, больше сосредоточены на структуре системы, чем на конкретных ее особенностях и функциях. Архитекторы создают архитектуру, помогающую быстрее и проще создавать эти особенности и функции, изменять их и дополнять.
Просто помните, что если поместить архитектуру на последнее место, разработка системы будет обходиться все дороже, и в конце концов внесение изменений в такую систему или в отдельные ее части станет практически невозможным. Если это случилось, значит, команда разработчиков сражалась недостаточно стойко за то, что они считали необходимым.