Наверняка многие слышали о принципе инверсии зависимостей, букве D из аббревиатуры SOLID. Изначально этот принцип был описан Бобом Мартином еще в 1996 году в статье для C++ Report. Затем эта же статья в расширенном виде вошла в книги «дядюшки» Боба “Agile Software Development, Principles, Patterns and Practices” и затем в “Agile Principles, Patterns and Practices in C#”.
В исходной статье Боба Мартина есть 3 части: философские размышления о хорошем и плохом дизайне, описание принципа инверсии зависимости и «простой пример» с лампочками и кнопками. Вторая часть статьи весьма известна, последняя – малоинтересна, а вот первую часть, ИМХО, незаслуженно обходят вниманием.
Мне кажется, что именно понимание качества дизайна и причин его «загнивания» являются самыми важными качествами для любого программиста. Как и в большинстве других случаев, самое главное идентифицировать проблему, а уж решить ее потом будет делом техники.
Поскольку я нигде не увидел русскоязычную версию первой части статьи (а в приведенных выше книгах в этом разделе слишком много лишних деталей), то я решил ее здесь опубликовать, с некоторыми последующими комментариями.
Итак, перед вами перевод раздела статьи “Dependency Inversion Principle” под название “Что пошло не так с ПО?» (What goes wrong with software?).
Что пошло не так с ПО?
У большинства из нас был печальный опыт работы с фрагментами системы, у которых был «плохой дизайн». Более того, у некоторых из нас был еще более печальный опыт, осознания того, что именно они были авторами систем с «плохим дизайном». Так что же приводит к плохому дизайну?
Большинство разработчиков не стремятся к «плохому дизайну», при этом для многих систем наступает момент, когда начинают говорить, что ее дизайн прогнил. Почему так происходит? Был ли дизайн плохим с самого начала или дизайн загнил с течением времени? Корнем этой проблемы является отсутствие хорошего определения «плохого» дизайна.
Определение «плохого дизайна»
Проводили ли вы инспекцию дизайна со своим коллегой, которым вы особенно гордились? При этом ваш коллега говорил вам насмешливо что-то типа: «Мда… А почему ты реализовал это именно так?» Это очень часто происходило со мной, и я видел, как это происходило со многими другими разработчиками. Конечно же, двое несогласных коллег никогда не используют одинаковые критерии для определения «плохого дизайна». Самым распространенным критерием, с которым я сталкивался, был синдром АВЯБСЭНТ или «А вот я бы сделал это не так» (TNTWIWHDI, That’s not the way I would have done it).
Но существует набор критериев, с которым, как мне кажется, согласились бы все разработчики. Любой кусок кода, который удовлетворяет своим требованиям, но все же, проявляет одну (или несколько) характеристик, обладает плохим дизайном.
- Его тяжело изменить, поскольку любое изменение влияет на слишком большое количество других частей системы. (Жесткость, Rigidity).
- При внесении изменений неожиданно ломаются другие части системы. (Хрупкость, Fragility).
- Код тяжело использовать повторно в другом приложении, поскольку его слишком тяжело «выпутать» из текущего приложения. (Неподвижность, Immobility).
Более того, будет очень сложно найти кусок системы, который не содержит ни одной из этих характеристик (т.е. является гибким, надежным и повторноиспользуемым), отвечает требованием, и при этом дизайн которого плохой. Таким образом, мы можем использовать эти три характеристики для однозначного определения, является ли дизайн «плохим» или «хорошим».
Причины «плохого дизайна»
Что делает дизайн жестким, хрупким и неподвижным? Взаимозависимость модулей.
Дизайн является жестким (rigid), если его нельзя с легкостью изменить. Эта жесткость связана с тем, что единственное изменение куска кода в переплетенной системе приводит к каскадным изменениям в зависимых модулях. Когда количество каскадных изменений не может быть предсказано проектировщиком или разработчиком, то оценить влияние такого изменения невозможно. Это делает стоимость изменений непредсказуемой; менеджеры, столкнувшиеся с такой неопределенностью, неохотно соглашаются на внесение изменений, таким образом, дизайн официально становится жестким.
Хрупкость (fragility) – это склонность системы к поломкам во множестве мест после единственного изменения. Обычно новые проблемы происходят в местах, концептуально не связанных с местом изменений. Такая хрупкость серьезно подрывает веру в дизайн и сопровождение системы. Пользователи и менеджеры не могут предсказать качества их продукта. Простые изменения в одной части приложения приводят к ошибкам в других, совершенно несвязанных частях. Исправление этих ошибок приводит к еще большему количеству проблем, и процесс сопровождения превращается в известного пса, гоняющегося за собственным хвостом.
Дизайн является неподвижным (immobile), когда нужные части системы сильно завязаны на другие нежелательные подробности. Чтобы представить проектировщику, насколько легко использовать существующий дизайн повторно, достаточно подумать о том, насколько просто его будет использовать в новом приложении. Если дизайн является сильносвязанным, то этот проектировщик ужаснется количеством работы, необходимой для отделения требуемых частей системы от ненужных подробностей. В большинстве случаев, такой дизайн не является повторно используемым, поскольку стоимость его отделения превышает разработку его с нуля.
---------------------------------------------------------
Сегодня все описанное здесь может показаться откровенным баяном, и так оно и есть, ведь оригинал статьи, чей перевод вы только что прочитали, написан 16 (!) лет назад. Тем не менее она остается весьма актуальной и сегодня.
За это время многое изменилось, и теперь мы все реже сталкиваемся с «кривым» структурным дизайном, стараясь сделать из него объектно-ориентированную конфетку. Вместо этого, мы все чаще и чаще сталкиваемся с ОО-дизайном, который является жестким, хрупким и неподвижным, и думаем, где же мы сбились со своего пути. Мы выучили десятки принципов разработки, но стали забывать, ради чего мы их хотели использовать.
Здесь подняты очень хорошие вопросы. Чем опасны хрупкие и жесткие системы? Да тем, что процесс управления подобным проектом становится непредсказуемым и, по сути, неуправляемым. Как менеджер может давать, или не давать добро на добавление некоторой фичи, если он не знает, сколько на самом деле на это потребуется времени? А как выплачивать тот самый технический долг, когда при его выплате мы огребем, причем понять сколько именно огребем мы не можем, пока не огребем?
Проблема с повторным использованием тоже очень актуальна. Постоянные читатели должны помнить, что в моем понимании юнит-тесты служат не только для проверки некоторых предположений относительно тестируемого модуля, но и для определения степени его связанности и могут служить показателем повторного использования. Мысли у Боба Мартина аналогичны (хоть и в более широком контексте): для того, чтобы понять, будет ли использоваться ваш код повторно нужно, чтобы трудозатраты на его повторное использование были меньшими, чем стоимость разработки с нуля. В противном случае никто с этим делом не будет даже заморачиваться.
Использование принципов и паттернов проектирования служат одной цели – сделать наш дизайн хорошим. Если их использование не дает вам никакой выгоды (или наоборот, нарушает принципы «хорошего дизайна»), значит что-то в консерватории не то и, возможно, инструмент начали использоваться не по назначению.
> Проводили ли вы инспекцию дизайна со своим коллегой, которым вы особенно гордились?
ОтветитьУдалитьЯ очень горжусь своими коллегами)))
Да, неоднозначненько вышло. Подумаю, как перефразировать.
ОтветитьУдалитьДа, и гордиться коллегами - это и правда хорошо, но хорошо еще и дизайном своим городиться:))
ОтветитьУдалитьПроводили ли вы со своим коллегой инспекцию дизайна, которым вы особенно гордились?
ОтветитьУдалитьВот и все )))
Проблема старого дизайна это - проблема старого человека, если можно так метафорично выразиться :). Если дизайн не омолаживать, не лечить, то в конце концов, это будет неподвижный монстр, малейшее телодвижение которого приводит, к тому, что у него то тут не работает, то там что-то отваливается.
ОтветитьУдалитьА теперь подробнее про косметологию в программировании, гг... :) Продукт со временем эволюционирует, в него добавляется новый функционал и адаптируется/изменяется старый. В первую очередь меняется код. И если всегда меняется только код, то скоро "пациент будет скорее мертв, чем жив". Т.к. за изменениями кода по необходимости должны следовать изменения в дизайне, и может в архитектуре.
Текущее состояние каждой абстракции продукта (код, дизайн, архитектура) должны соответствовать идеям и обязанностям, которые были на этот уровень возложены. Когда текущее состояние уровня абстракции не соответствует, тем идеям и обязанностям, которые были заложены, то на этом уровне необходимо сделать изменения. Иначе возникает "Технический долг".
Возникновение "технического долга" происходить из-за того, что менеджмент компании не знает внутреннего состояния продукта и процесса разработки софта. Из двух предложенных программистами вариантов: "быстрый " и "правильный", руководство очень редко выбирает "правильный" и чаще выбирает "быстрый", потому что работает сейчас, а внутреннее качество нас не особо интересует.
Так вот костыли плохого дизайна, прежде всего растут от корявого руководства с рукавами в районе пояса, а не от плохих программистов. При том что последних, можно отсеять при приеме, как-то научить или отстранить от изменений в дизайне.
@Слава: либо мне всегда везло с менеджерами, либо с красноречием. Я обычно работаю людьми, с логическим мышлением, которым вполне реально обосновать свою точку зрения.
ОтветитьУдалитьМенеджеры в этом плане - не исключение (с заказчиками обычно сложнее, а вот с менеджерами ни разу проблем не было).
К сожалению, загнивание может происходить незаметно и не только из-за близаруких менеджеров с руками не оттуда (кстати, непонятно, причем здесь руким менедежров). Изначальные ошибки, оптимизм разработчиков, которые не хотят/боятся признаться, что нужно больше времени на фичу. Ну и банальное не лучшее использование техник дает о себе знать.
Сейчас появилось огромное количество плохого ОО дизайна: все на интерфейсов, якобы тестируется отлично, но с кучей "неявной" связности между разными кусками системы.
В общем, я бы в самую последнюю очередь пенял на менеджеров, которые в аутсорсе далеко не такую роль играют, как следует. Да и все модные "гибкие" методологии тоже играют не последнюю роль в отодвигании роли менеджера на второй план.
Я бы тоже хотел сказать, что во всех боках систем виноват кто-то другой, но, к сожалению, это не так. Виноваты мы, а не менеджеры или заказчики.
"К сожалению, загнивание может происходить незаметно и не только из-за близаруких менеджеров с руками не оттуда (кстати, непонятно, причем здесь руким менедежров). Изначальные ошибки, оптимизм разработчиков, которые не хотят/боятся признаться, что нужно больше времени на фичу. Ну и банальное не лучшее использование техник дает о себе знать."
Удалить-- покажусь "евангелистом", но скажу - "загнивание" ОЧЕНЬ ЧАСТО проистекает из желания НЕ СЛОМАТЬ ("дабы чего не вышло"), а оно в свою очередь проистекает из отсутствия покрытия и тестов...
И тут - ДА - "повод задуматься" и "работать над собой", если что-то не так, то может "стоит написать тест" :-)
Я понимаю, что - БАНАЛЕН... Но это - РАБОТАЕТ :-)
"Я бы тоже хотел сказать, что во всех боках систем виноват кто-то другой, но, к сожалению, это не так. Виноваты мы, а не менеджеры или заказчики."
Удалить-- по моему опыту - 60 на 40 :-)
Этот комментарий был удален автором.
ОтветитьУдалитьВот тут я с тобой кардинально не согласен. Думаю что большинство программистов, преднамеренно не хотят ухудшать продукт. Из-за недостатка опыта, понимания текущего кода может возникнуть кривое решение. Но вина в этом не программиста, а того кто за ним не досмотрел.
ОтветитьУдалитьПо своей сути программист это исполнитель, не более. Над ним уже архитекторы, ПМы, продукт оунеры, бизнес драйверы и т.д. Если на этом уровне процесс взаимодействия упорядочен, то как правило с кодом там все хорошо.
Я все таки думаю, что плохой дизайн является косвенным последствием плохого стиля управления, недостаток контроля, спешка и т.д.
Решают это тем, что пытаются сменить программиста, а не поменять процесс управления.
@Слава: я не говорю о том,что вина лежит только лишь на разработчике, но и винить кого-то другого я бы не стал.
ОтветитьУдалитьЯ предпочитаю искать проблему в себе, а не перкладывать ее на кого-то.
Вот, например, по поводу "быстро" или качественно: если ко мне подойдут и спросят, как делать, быстро или долго? Я спрошу: А что стоит на кону? Или же, если менеджер будет продавливать "быстрый" вариант, то я объясню ему, к чему это придет и вернусь к этому вопросу, *когда* к этому все придет.
Тут два момента: не нужно прогибаться - разработчикам перед лидами, лидам - перед менеджерами, менеджерами - перед заказчиками. Гибкость - хорошее дело, а вот тупой прогиб - плохое. Можно говорить, что причины прогибов в плохом менеджменте, и зачастую это действительно так.
Второй момент в том, что в нашей индустрии довольно много адекватных людей (или мне так везет). Если ты сможешь обосновать свою позицию, то в 9 случаях из 10 к тебе прислушаются. И есть все шансы, что именно твое личное воздействие приведет к изменению процессов.
"Я предпочитаю искать проблему в себе, а не перкладывать ее на кого-то."
Удалить-- это - ПРАВДА, но "не вся" :-)
"Сегодня все описанное здесь может показаться откровенным баяном, и так оно и есть, ведь оригинал статьи, чей перевод вы только что прочитали, написан 16 (!) лет назад. Тем не менее она остается весьма актуальной и сегодня."
ОтветитьУдалить-- а вот мне "баяном" - не кажется.. я 18-ть лет на одном и том же месте работаю.. так что я могу "оценить ретроспективу".. :-)
Сергей, объясните пожалуйста, в чем разница между дизайном и архитектурой?
ОтветитьУдалитьЕсли можно на примере.
Разница в уровне абстракции. Дизайн - это структура приложения на низком уровне: уровне классов, модулей и их взаимоотношений. Архитектура - это структура системы на более высоком уровне: на уровне подсистем или целых приложений.
УдалитьТак, например, выбор базы данных - архитектурное решение. Выделение слоя доступа к данным - архитектурное решение. А выделение базового класса - это уже решение уровня дизайна. Вопрос: передавать ли интерфейс или использовать данным классом в поле конкретный экземпляр - это тоже вопрос дизайна.
Вы эти определения сами придумали?
УдалитьВы понимаете, что в ваших же определениях противоречия? Модуль и есть подсистема. Тогда ваши определения Дизайн и Архитектуры пересекаются.
Удалить@Deja: во-первых, я не приводил определений, для этого википедия есть. Я здесь привел мое понимание этих понятий.
УдалитьВо-вторых, с чего вы взяли, что модуль и есть подсистема и что в нашей индустрии есть четкое и непротиворечивое понимание того, что такое модуль?
У Мейера, например, модуль есть синоним класса (см. раздел 2.2. его книги Object-Oriented Software Construction). У Фаулера - другое определение модуля (см. перевод моего поста про определение юнит-теста).
Но ни у кого из них не проводится параллель между модулем и подсистемой.