Sunday, January 17, 2010

Качественный код и проектирование

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

Под внутренним качеством подразумевается не качество самого приложения (внешнее качество), то есть отсутствие ошибок, соответствие требованиям, простоту использования и т.д., а качество кода этого самого приложения.

Свойства качественного кода

Какими же свойствами обладает качественный код:

  • расширяемость, гибкость (extensibility, agility)
  • сопровождаемость (maintainability)
  • простота (simplicity)
  • читабельность, понятность (readability, clarity)
  • тестируемость (testability)

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

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

Итак, теперь мы лучше понимаем, что означают абстрактные слова "хороший код", "правильный код" и т.д. Это код, который обладает полезными внутренними качествами (ценностями).

Как же достичь хорошего кода?

Существует довольно много способов, и их можно условно разделить на внешние и внутренние. Внешние способы - это различные программистские практики, например, парное программирование, статический анализ кода, code review и т.д. В какой-то степени сюда можно отнести и модульное тестирование с TDD/BDD, так как они улучшают качество кода: написать модульные тесты на существующий плохой код довольно сложно, поэтому его приходится рефакторить, а при использовании TDD/BDD написать плохой код с самого начала довольно сложно, хотя и вполне реально.

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

Внешние способы достаточно результативны, однако они всегда являются лишь дополнением внутренних. Если программист не обладает навыками правильного написания кода и проектирования, парное программирование с более сильным товарищем или code review ему поможет. Но что делать, если они оба не обладают этими навыками? Поэтому для каждого программиста, кроме знания инструмента (языка программирования, технологий) важно также и умение ими пользоваться (я уже когда-то об этом писал). Это 2 разных, ортогональных друг другу направления развития.

Итак, рассмотрим один из внутренних навыков, а именно навык проектирования (design).

Зачем каждому программисту иметь навыки проектирования

Многие программисты думают, что проектирование - это какая-то специальная фаза при разработке ПО, считают ее сложной и не всегда нужной. Строят там какие-то заумные дяди какие-то UML-диаграмки и прочую чушь. Все равно ведь когда будем писать код - будем половину менять и переписывать. Но ведь на самом деле, проектирование - это не только формальная фаза процесса создания ПО (практически любого), а очень важный (я бы сказал даже необходимый) шаг в программировании. А если выкинуть название "проектирование", то это необходимый шаг в любой человеческой активности. Сейчас объясню на примере, почему. Представим себе, что перед нами стоит какая-то задача. Что мы должны сделать перед тем, как начать ее выполнять? По идее, сначала мы должны понять что нам нужно сделать. Вторым шагом нужно разобраться, как мы будем это делать. И только потом - уже собственно делать! Так и в создании ПО. Сначала мы пытаемся понять, что нужно сделать (анализируем требования, или иногда даже анализируем желания клиента и создаем требования по ним). Затем мы пытаемся разобраться как мы будем это делать (по сути, проектируем, как будет выглядеть результат, чтобы не переделывать десять раз). И только потом берем клавиатуру в зубы и идем программировать. Однако здесь важно понимать одну очень важную деталь: результат проектирования (проект) не обязательно должен быть вырезан в камне диаграммах UML и документах, а вполне может быть сделан на доске, куске бумажки или даже просто в голове, если этого достаточно. Так что отсутствие конкретной формальной фазы еще не значит, что программисты не занимаются проектированием какого-то куска кода, базы данных, UI прототипов или даже микро-архитектуры. Занимаются. Это и есть проектирование, просто менее формальное.

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

Технический долг

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

Также эта метафора объясняет, почему иногда приходится писать код быстро, не тратя времени на правильное проектирование. Как и с точки зрения финансов мы берем деньги в кредит/долг для более быстрого достижения поставленной цели, здесь мы это также делаем для более быстрого получения результата. Однако мораль сей басни такова, что не нужно забывать, что за подобные действия мы будем расплачиваться в дальнейшем.
Более подробно о техническом долге можно почитать у
Фаулера и Jeff Atwood.

Рецепты правильного проектирования

Хорошо, теперь мы знаем, что есть свойства хорошего кода (ценности), и что проектирование может нам помочь достичь их. Но как это сделать? Какие есть рецепты?

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

Из того, что связано непосредственно с проектированием, можно назвать паттерны и принципы проектирования.

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

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

Мне кажется, что ценности, принципы и паттерны можно выразить следующей иерархией (с удовольствием жду комментариев по ее улучшению):
image

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

Однако данная пирамидка совсем не обозначает, что нельзя достичь вершины, не зная те же паттерны или принципы. Очень даже можно, вот только не факт, что в полученном результате их не будет :) Да и зачем изобретать велосипед, если можно использовать наработки таких умных и известных инженеров, как Бек, Гамма, Фаулер, Лисков, и множество других. Понимание и разумное использование принципов и паттернов помогает достигнуть вершины быстрее и с меньшими трудозатратами.

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

16 comments:

  1. Интересно :)
    Вот только "ремонтопригодность" режет глаз. Имхо, сопровождаемость больше подходит.

    ReplyDelete
  2. Да, именно так! Спасибо большое! Искал нормальный перевод, но так и не подобрал :)

    ReplyDelete
  3. Спасибо за статью, давно уже хотелось почитать систематизированных сведений, кстати, вот есть еще полезный пост, с описанием нефункциональных требований: http://devprom.ru/news/Определение-нефункциональных-требований

    ReplyDelete
  4. Привет!

    "...а вот с принципами все не так просто"

    Если вдруг поможет http://blog.byndyu.ru/2009/10/solid.html

    ReplyDelete
  5. saveug, спасибо за нефункциональные требования. Но все же это больше требования к свойствам приложения, а не свойства самого кода. Эти вещи стоит различать, а они там в одну кучу собраны :)

    В английской википедии есть статья, где это хорошо разделено: Software quality.

    ReplyDelete
  6. Александр, спасибо за ссылку! Я читал эти твои статьи и мне они очень понравились :) Приятно, что происходит популяризация проектирования, причем уже не только с точки зрения паттернов, а на более высоком уровне - уровне принципов.

    Когда дойду до SOLID, обязательно сделаю ссылки на твои статьи, если ты не возражаешь :)

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

    Касательно создания софта, Денис Миллер ее описал 1 в 1 как и вы пару лет назад :)
    Посмотреть можно здесь: http://rutube.ru/tracks/1225529.html?v=f2aeceb82135c73bb4335cfa396a48a6

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

    В подтверждение два принципа иерархий гласят:
    - Изменения на верхнем уровне отражаются на всех нижних.
    - Изменения на нижних уровнях передаются наверх только как количество в качество.

    Так что можно учесть в описании.

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

    ReplyDelete
  8. Кстати, пирамида на этом не заканчивается. Там есть много интересных ещё моментов... Надо как нить развить тему :)

    ReplyDelete
  9. > На вершине пирамидки располаются наши ценности (свойства), которых мы хотим достичь.

    Ой, ещё добавлю чуток.

    Что значить достичь "гибкости" (это ценность шаблонов проектирования)? Вопрос не имеет смысла. А вот руководствоваться этой ценностью - стоит, для решения нужных задач.

    Ценность - не является достигаемой. Ценность -
    то некий критерий для принятия решений и выбора инструмента для низлежайшего уровня.

    Другой вопрос, что чем мы руководствуемся при выборе тех или инных ценностей? Ответ лежит за пределаеми этой пирамиды, но позволяет выйти на другие высшие уровни...

    ЗЫ. В моём тренинге "Шаблоны проектирования" или "Основы Аджайл" мы со слушателями до Бога добрались в повальной структуризации :)

    ReplyDelete
  10. @Александр Кондуфоров
    "Когда дойду до SOLID, обязательно сделаю ссылки на твои статьи, если ты не возражаешь :)"
    Конечно бери ;)
    Обточим эту тему со всех сторон!

    ReplyDelete
  11. Ребят, вы на меня вывалили гору информации - спасибо большое! :) Сейчас попробую разгрести.

    Денис, наверно, это все-таки правда, что идеи витают в облаках и оттуда падают в головы разным людям :) Посмотрел ваше видео - действительно точь в точь (даже удивительно, что тоже ценностями назвал верхний уровень, как и вы, а не остановился на свойствах/характеристиках - но здесь уже Стивена Кови нужно винить :)), за тем лишь исключением, что вы рассматривали пирамидку с точки зрения синхронной работы команды (что само по себе является интересным взглядом на нее), а я дошел до нее больше со стороны написания правильного кода и проектирования.

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

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

    Интересно послушать ваше мнение на этот счет, а также хотелось бы узнать, как вы дошли до пирамидки этой - где-то что-то читали или просто логическим анализом?

    Виталий, согласен с вами. Два принципа иерархий очень хорошо сформулированы. Переход количества в качество - это, конечно, еще Гегель придумал (а, может, и кто-то до него), но его приложение к нашей предметной области - это интересно :) Подумаю на эту тему...

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

    ReplyDelete
  12. В целом интересно и доступно. Не совсем точная аналогия с архитектурой зданий. Во-первых накопленный человечеством опыт по архитектуре зданий не превышает 10 веков отсилы. Во-вторых тонель по Ла-Маншем и подобные проекты как раз и не делаются по шаблонным меркам, т.к. это первый опыт - раньше никто н задавался целью построить такой тонель. Так же само и для адронного коллайдера был абсолютно другой, спец. проект. Это я к тому что маго-крупные программные проекты строятся не всегда по паттернам, а скорее ситуативно, на базе огромного опыта проектировщиков и программистов

    ReplyDelete
  13. Спасибо за поправки. Соглашусь, что в стремлении выбрать примеры позначительнее не совсем учел момент некоторого "первооткрывания". С остальным не соглашусь, поэтому небольшой оффтоп.

    10 веков - это маловато все-таки. Разве дворцы, замки и другие здания Древнего Вавилона, Египта, те же пирамиды или Александрийский маяк нельзя считать архитектурой? С другой стороны, мои 10,000 лет тоже неверны - я имел в виду в том числе и первые постройки древних людей, но сейчас понял, что мы достоверно знаем лишь про 5,000 лет (с 3,000 до н.э. - Древний Египет, Шумеры, Вавилон), до того периода люди не жили оседло, а кочевые шатры или землянки на архитектуру явно не тянут.

    ReplyDelete
  14. Тоннель под Ла-Маншем - безусловно амбициозный и первый в своем роде проект такого масштаба. В то же время, во-первых, уверен больше чем на 200%, что там было очень много предварительных чертежей, проектирования и проверок перед началом строительства, а во-вторых, при строительстве Ла-Манша был учтен опыт построек всех предыдущих подводных (и не только) тоннелей. Построить такое с нуля, без какого-то предварительного опыта и без серьезных расчетов и проектирования просто невозможно. БАК - опять же, амбициозно, круто, масштабно, но тот же опыт постройки меньших ускорителей + серьезные расчеты перед началом.

    Конечно, ПО нельзя сравнивать с архитектурой один в один (это глупо), и вы абсолютно правы, что при построении крупных программных проектов нужен "огромный опыт проектировщиков и программистов", и что эти проекты строятся не всегда по паттернам. Впрочем, я и не утверждал обратного :) Но, в отличие от архитектуры, вследствие молодости нашей индустрии этот опыт постройки крупных проектов хранится в большинстве своем в головах сотен тысяч серьезных разработчиков и архитекторов. Однако, он тоже потихоньку записывается, систематизируется и становится доступным нам с вами. Насчет паттернов - это тактические средства, которые вы используете в конкретных ситуациях, можно их использовать или не использовать - дело ваше. Принципы более высокоуровневы, но самое главное - это ваши цели, которые на вершине пирамидки, плясать нужно от них. К слову, их можно рассматривать и с точки зрения rigidity, fragility, immobility, и viscosity, описанных Uncle Bob в этой статье: http://objectmentor.com/resources/articles/Principles_and_Patterns.pdf. Жалею, что не включил их в свой недавний доклад. Ну, может, еще куда позовут :)

    ReplyDelete
  15. Если вы поинтересуетесь альтернативной историей человечества и так называемой новой хронологией которую предложили ученые Фоменко и Носовский. http://ru.wikipedia.org/wiki/Новая_хронология_(Фоменко), то вы увидите что многие факты сдвинуты в истории, то как пирамиды которые были постоены в 14-15 веках нашей эры и т.п. Очень любопытная теория, а главное правдоподобная :)

    ReplyDelete
  16. Что касается крупных проектов, то судя по высказываниям строителей которые принимали участие в мего-стройках типа БАК и тонель под ЛаМаншем, было заготовлено , построено много приспособлений которые специально были заточены под данные виды строек. Чертежи конечно на этапе проектирования имели место, куда ж без этого. Но опять же, повторюсь, ваша статья очень поучительная и во многом верная. Респект ;)

    ReplyDelete