понедельник, 16 июля 2012 г.

О дизайне

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

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

Есть и другая крайность, когда команда можем потратить недели в поисках идеального решения (Святого Грааля архитектора), когда дизайн будет способен «расширяться» во всех возможных направлениях, и быть настолько «гибким», что реализовать его не будет никакой возможности.

За каждым из этих крайностей любопытно наблюдать, но только если они происходят не у тебя в команде. В большинстве же случаев разумное отношение к дизайну находится где-то посередине, когда этапы дизайна и разработки тесно связаны между собой и итеративно следуют один за другим практически непрерывно. При этом каждый раз, когда разработчик сталкивается с принятием какого-либо решения, то он старается найти компромисс среди бесконечного множества требований, которые на него давят: использовать более эффективное решение, или более расширяемое; что важнее, согласованность или наличие ломающих изменений; как быть, нарушить SRP (Single Responsibility Principle) или сделать модуль более удобным в использовании; стоит ли пожертвовать сопровождаемостью кода ради эффективности и т.п.

В результате к большинству разработчиков приходят понимание, что…

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

clip_image001

Контекст важен

Меня несколько напрягают категоричность многих авторов книг/статей или просто коллег, которые выражают свое мнение в абсолютной форме: никогда не пользуйтесь синглтонами, покрытие тестами должно быть 100%, открытые поля – всемирное зло. Проблема таких высказываний в том, что они вполне корректны в большинстве случаев, но это не значит, что этим советам следует слепо верить не задумываясь.

Да, в большинстве случаев открытые поля это и правда опасная практика, но кто мешает нам их использовать в структурах (значимых типах .NET-а) для взаимодействия с существующими системами? Да, юнит-тесты – это отличная штука, но это не значит, что без 100%-го покрытия тестами ваш проект провалится. Именно по этой причине, когда опытному программисту задается вопрос «Что лучше?», то «мудрый перец» не станет отвечать на него «в лоб», а задаст уточняющий вопрос, чтобы понять контекст решаемой задачи? Ведь то, что разумно применять в одном случае (писать юнит тесты для продакш кода), может быть совершенно не нужным в другом (а вдруг речь идет об однодневном прототипе?).

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

object[] o = new string[] {"1", "2", "3"};
o[0] = 42;

Причина появления этой возможности связана с тем, что она была с первой версии в языке Java, а при разработке языка C# в конце 90-х важность «подсадить» на новый язык существующих программистов была решающей. Именно поэтому используется привычный С-подобный синтаксис, который уже был знаком программистам С/С++ и Java, пусть у него и есть свои недостатки. Подобные решения могут казаться сомнительными сейчас, но понимание причин, побудивших к их принятию (согласованность с другими языками vs возможность ошибок времени выполнения) дают понять, почему языки или библиотеки реализованы так, а не иначе, и что влияет на их развитие.

clip_image002

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

Ну, ты же говорил?!!

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

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

Хороший дизайн

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

Отличный способ найти проблемы дизайна – это посмотреть на него со стороны самому, или попытаться объяснить его кому-то. Хороший дизайн – это дизайн, который вы сможете объяснить своему коллеге за 10 минут, не жертвуя при этом полнотой или точностью. Если же при объяснении «как это работает» приходится учитывать множество факторов, закапываться во множество деталей, и 15 раз возвращаться к одному и тому же, то с дизайном явно что-то не так. Хороший дизайн зачастую оказывается достаточно простым, с минимальным количеством хитросплетений, и минимумом лишних или неочевидных связей. Хороший дизайн, как и хорошая архитектура, борется с неотъемлемой сложностью, а не привносит дополнительную сложность, которой и так с избытком хватает в самой природе решаемой задачи.

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

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

--------------

Определение дизайна, данное в середине заметки, является вольным переводом (с некоторым дополнением) мысли Эрика Липперта, которую он выразил в одном из своих постов: Design is the art of compromising amongst various incompatible design goals.

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

Ссылки по теме

10 комментариев:

  1. Хотелось бы еще примеры хороших и интересных дизайнов. Возможно в отдельной статье.

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

    ОтветитьУдалить
  3. @Anton Norko, вот яркая иллюстрация того, о чём говорится в статье. Не бывает абстрактных «хороших дизайнов». Есть архитектурные решения, компромиссы оправданные в контексте задачи.

    ОтветитьУдалить
  4. У Воннегута есть очень хорошая фраза - "Если учёный не может объяснить восьмилетнему мальчику чем он занимается — он шарлатан". По-моему, так же с дизайном. Может, конечно, не 8-летний мальчик, но коллега понять должен. Но раз статья получилась философская, то задам вопрос :). Сереж, ты когда хочешь обсудить новый дизайн - прямо все в команде горят желанием обсуждать новый дизайн или есть предложение обсудить это попозже, потому как занят и.т.д.... Так вот, чтобы дизайн получил объективную оценку, надо чтобы команда была в этом заинтересована. А вот как это сделать - тут поле непаханное ( и в основном к программированию не относящиеся). По поводу основной мысли статьи - согласен 100%. Решение любой задачи должно отталкиваться от контекста. Получается опытный разработчик на вполне четкий вопрос должен отвечать как тот еврей из анекдота - "сколько будет 5+5? А сколько Вам надо?" И что прикольно - что это а) честно б) уточняет контекст в) говорит о квалификации разработчика.

    P.S. У Эрика ничего нет по поводу изменений во времени. Поэтому ты совсем "вольно" перевел, либо "расширил" его мысль.

    ОтветитьУдалить
  5. @Женя: а я думал, что это Эйнштейн так говорил:)

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

    Это может проходить в виде совместного анализа задачи и рисования на бумаге нового дизайна, так и более формально, когда один из ребят дизайнит в какой-нибудь туле и мы собираемся и обсуждаем гурьбой. Обычно применяется менее формальный способ:)

    При этом дизайн обычно получается более или менее объективный (во всяком случае на момент его обсуждения). Гораздо сложнее сделать так, чтобы его объективность не испортилась во время реализации, когда новые подробности не изменят его до неузнаваемости. Для этого нужна обратная связь к человеку (типа архитектору), совместно с которым шло обсуждение дизайна, чтобы можно было как понять новые изменения, так и проанализировать причины того, почему дизайн начал "плыть".

    > У Эрика ничего нет по поводу изменений во времени.

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

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

    ОтветитьУдалить
  6. >начиная от подхода, принятого на ранних этапах развития методологии XP, когда считалось, что дизайн и архитектура – это динозавры, которым нет места в динамично развивающемся мире agile разработки
    Здравствуйте.
    Откуда такая информация об изменении отношения к дизайну и архитектуре в XP в частности и в agile вообще?

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

    Поскольку agile-методологии могут доводить эту идею до абсурда, то на ранних этапах развития XP (которому в основном предшествовали более "формальные" методики разработки), бросились из одной крайности (big design upfront) в другую (никакого дизайна вообще).

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

    Немного дополнительной информации о видоизмененном взгляде XP-шников на дизайн можно посмотреть в статье Фаулера: http://martinfowler.com/articles/designDead.html

    ОтветитьУдалить
  8. Мне давно уже было интересно, есть ли положения в agile, которые его создатели со временем старались ослабить или наоборот усилить. Особенно что по этому поводу говорит Боб Мартин, много сделавший и для гибких методологий, и для дизайна.
    Насчет перегибов agile. Я увидел, как люди вообще отказываются думать и обсуждать архитектуру, решил, что риски фатальных технических долгов являются тем, с чем гибким методологиям просто приходится жить. А потом узнал, что эти люди и до своего знакомства с agile не думали об архитектуре. Думаю, когда человек уровня Кента Бека говорит, что он очень мало думает о дизайне, он все равно думает о дизайне тщательнее, чем многие программисты, заявляющие, что они-то в дизайн вкладываются как следует.
    Спасибо за ссылку. Она сняла ряд вопросов.

    ОтветитьУдалить
  9. @Сергей: @Женя: а я думал, что это Эйнштейн так говорил:)
    Это из "Колыбели для кошки", но вообще-то это говаривал Фейнман, который, в свою очередь, цитировал Резерфорда. :)
    Но я о другом. Вот эта фраза:
    "Да и само решение может влиять на задачу настолько, что исходное решение изменится до неузнаваемости."
    - это как?

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

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

    ОтветитьУдалить