среда, 29 июня 2011 г.

Паттерны поведения

(Эта заметка является завершением серии постов, в которую вошли «Технический долг», «Синдром рефакторинга» и «Эффект второй системы»)

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

«Технический долг», «синдром рефакторинга» и «эффект второй системы» - это типовые ситуации, с которыми периодически сталкивается команда разработчиков. И главная польза от них как раз и заключается в том, чтобы увидеть проблему и доказать ее существование нужным людям. Если вы сами поняли, что технический долг проекта слишком велик, то используя денежную метафору будет уже значительно проще доказать важность этой проблемы менеджеру или заказчику. А затем взвешенно показать ему альтернативные пути развития событий: (1) оставить все, как есть; (2) уменьшить технический долг путем разумного рефакторинга или (3) переписать все нафиг.

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

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

Управление рисками и инкапсуляция

Джон Роббинс, если вы не знали, прежде чем стать известным bugslayer-ом служил зеленым беретом в армии Соединенных Штатов; и хотя, как он сам признается, в нем сейчас сложно узнать бравого вояку, но некоторые «приемчики», почерпнутые при планировании боевых операций он научился применять и при разработке программных продуктов и управлением рисками.

Да, возможно вопросы, типа «А что будет, если Василий даст дуба до того, как он закончит фазу анализа или сбора требований?» (**) может быть чересчур жесткой и едва ли будет встречена аплодисментами коллег, но вопросы типа «А что если наше текущее решение окажется неверным?» могут быть очень полезными.

Например, вы выбираете, модель взаимодействия с базой данной, библиотеку межпроцессного взаимодействия, СУБД или библиотеку для создания интерфейса пользователя. Каждый раз, принимая подобное решение не лишне спросить себя о том, «А что будет, если это решение окажется ошибочным и нам придется от него отказаться?» Как минимизировать влияние подобной ошибки и во что выльется, если все-таки оно не взлетит?

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

Например, если вы приняли решение использовать WCF для межпроцессного взаимодействия, то это не значит, что сервисы должны торчать из всех дыр и бизнес-логика должна располагаться прямиком в классах сервисов. Или, если вы используете кастомный протокол взаимодействия, будь-то протокол работы со сторонним оборудованием, ручную реализацию RPC и т.д., то не нужно размазывать информацию об этом тонким слоем по всему приложению. Или вдруг вам еще нравится COM (хотя уже даже Дон Бокс не может на него смотреть), то не стоит каждую фигню заворачивать в СОМ объект.

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

Инкапсуляция – это не только закрытые поля класса (т.е. сокрытие данных), но это и сокрытие информации и в более широком смысле этого слова. Так что инкапсуляция использования WCF в отдельном модуле является точно такой же инкапсуляцией с архитектурной точки зрения, как и закрытое поле с типом int в вашем классе, с точки зрения дизайна.

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

Опять возвращаясь к модулю межпроцессного взаимодействия, мы можем выделить открытый интерфейс этого взаимодействия и максимально спрятать WCF в качестве детали реализации. Я нисколько не сомневаюсь, что из этой затеи на 100% все равно ничего не выйдет и согласно «закону дырявых абстракций» информация о низкоуровневых деталях реализации будет просачиваться в другие уровни или модули. Но хотя бы попытка ответить на простой вопрос «А что будет, если эта хрень не взлетит и придется нафиг отказаться от текущего решения?», должна уменьшить последствия, если вдруг эта хрень и правда не взлетит.

Одна голова хорошо, а две – мутация

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

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

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

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

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

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

Кроме того, опытный «перец» может убедить команду и принять откровенно неверное решение, просто благодаря своему опыту. Если собеседники находятся в разных весовых категориях, то более опытному человеку не составит труда переубедить собеседника в своей точке зрения, не зависимо от того, насколько она адекватна. В любом споре, типа что лучше С++ или C#, есть масса аргументов с каждой стороны и более опытный «перец» можно просто называть одни аргументы и умалчивать другие. В результате будет принято решение в интересах «перца», а не в интересах проекта.

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

Финита

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

1) Прагматизм рулит (не нужно крайностей); этот совет применим всегда и везде, и он же помогает избегать проблем, типа «синдром рефакторинга» и «эффекта второй системы»

2) Не вырубайте свои решения в камне (есть шансы, что вы где-то запороли, и тогда снова придется браться за зубило)

3) Разумно советуйтесь с разумными коллегами, ибо взгляд со стороны всегда полезен

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

(*) Помимо пользы, от паттернов проектирования может быть и вред. Бездумное и неразумное использование любого даже самого полезного инструмента или технологии приведет к «абсурду и коррупции». Сколько раз вы сталкивались с проблемой “overengineering-а”, когда для реализации простой концепции использовался десяток паттернов проектирования? В общем, это очередной пример того, что прагматизм и здравый смысл, как всегда является лучшим выбором, и паттерны проектирования – не исключение.

(**) По словам самого Джона, он задал именно этот вопрос на одном из своих первых митингов. Дословно его вопрос звучал так: «Что, если Боб умрет до того, как мы закончим фазу сбора требований?», за подробностями смело обращайтесь к оригиналу: “Debugging Applications .NET 2.0 Applications”, раздел “The "Code First, Think Later" Approach”.

(***) Я не говорю о библиотеках аля MiscUtils Джона Скита. Во-первых, его библиотека «выросла» при решении реальных задач, а во-вторых, я просто уверен, что Джон вносил в нее массу изменений на основе фидбека коллег и пользователей. Если хотите хотя бы немного узнать о сложностях проектирования крупных библиотек, полистайте “Framework Design Guidelines” Абрамса и Квалины, там есть масса интересных мыслей по этому поводу.

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

  1. Можно разъяснить, чем отличаются паттерны проектирования от архитектурных паттернов?

    ОтветитьУдалить
  2. > чем отличаются паттерны проектирования от архитектурных паттернов?

    Уровнем. SOA, клиент-сервер, DAL - это архитектурные паттерны, а синглтон и абстрактная фабрика - паттерны проектирования.

    ОтветитьУдалить
  3. Вот небольшое дополнение:

    An Architectural Pattern expresses a fundamental structural organization or schema for software systems. It provides a set of predefined subsystems, specifies their responsibilities, and includes rules and guidelines for organizing the relationships between them.

    A Design Pattern provides a scheme for refining the subsystems or components of a software system, or the relationships between them. It describes commonly recurring structure of communicating components that solves a general design problem within a particular context.

    ОтветитьУдалить
  4. Интересная статья. Но не знаю ни одного "перца", который смог бы задавить свое эго... Может быть - не повезло.

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

    Это не всегда легко, но, зачастую, возможно.

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

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