Skip to content

Latest commit

 

History

History
137 lines (93 loc) · 17.5 KB

chapter_5.md

File metadata and controls

137 lines (93 loc) · 17.5 KB

Глава 5. Проектировании при конструировании

Проблемы, связанные с проектированием ПО

📐 Проектирование ПО — разработка схемы преобразования спецификации (т.е. описанных требований) в готовое приложение

Хорошее проектирование полезно при работе над небольшими приложениями и просто необходимо при работе над крупными (c. 71)

Но с проектированием связаны различные проблемы:

  • Проектирование — это грязная проблема (wicked problem). Требования могут быть противоречивыми, неполными, а некоторые могут быть идентифицированы только в процессе проектирования или даже по завершению разработки (см. историю обрушения моста Tacoma Narrows). Следовательно, вероятность успешно спроектировать что-то с первого раза на практике очень невелика.
  • Проектирование — это неряшливый процесс. Он состоит из множества проб и ошибок и промежуточные результаты работы часто не похожи на что-то ясное и организованное.
  • Проектирование — это процесс поиска компромиссов и определения приоритетов. Это этап, на котором нужно выбирать между конкурирующими характеристиками проекта: быстродействие или скорость разработки, требовательность к ресурсам или простота найма сотрудников и так далее.
  • Проектирование — это недетерменированный процесс, т.е. могут существовать десятки хороших, но разных проектов одного и того же приложения.
  • Проектирование — это эвристический процесс или, если говорить проще, творческий процесс, поэтому его сложно формализовать.
  • Проектирование — это постепенный процесс. Первая версия проекта впоследствии развивается и улучшается.

Универсальных методик проектирования не существует (с. 74)

Основные концепции проектирования

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

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

Поэтому, при проектировании необходимо бороться со сложностью:

  • Разделить систему на подсистемы;
  • Грамотно определить объекты и сущности;
  • Писать программу в терминах предметной области, а не низкоуровневых деталей реализации.

При проектировании необходимо стремиться к следующим характеристикам проекта:

  • Минимальная сложность
  • Простота и удобство сопровождения
  • Слабое сопряжение (loose coupling, минимизация связей между частями программы)
  • Расширяемость (возможность улучшать систему, не нарушая основной структуры)
  • Возможность повторного использования
  • Высокий коэффициент объединения по входу (интенсивное использование вспомогательных низкоуровневых классов)
  • Низкий или средний коэффициент разветвления по входу (класс использует небольшое число других классов)
  • Портируемость
  • Минимальная, но полная функциональность
  • Стратификация (каждый уровень проекта должен быть понятен сам по себе)
  • Соответсвие стандартным методикам

Необходимо прорабатывать следующие уровни проекта:

  1. Cистема
  2. Подсистемы/пакеты и правила их взаимодействия (диаграмма взаимодействия подсистем должна быть ациклическим графом, т.е. исключать циклические взаимосвязи между подсистемами)
  3. Классы внутри подсистемы (очень важно определить детали взаимодействия каждого класса с элементами системы, т.е. интерфейс класса)
  4. Данные и методы внутри класса
  5. Методы

Практические подходы и правила проектирования (эвристические принципы)

Самый популярных подход основан на (1) определении объектов реального мира. Необходимо определить:

  • Объекты и их атрибуты
  • Действия, которые можно выполнить с объектом
  • Действия, которые этот объект может выполнять над другими объектами
  • Видимые и невидимые части объекта
  • Публичный (доступный извне) интерфейс каждого объекта

Также важно (2) определить согласованные абстракции. Абстракции дают возможность задействовать часть программы (класс/библиотеку/пакет и т.д.), игнорируя детали её реализации. Пример такой абстрации: удобный и человекочитаемый публичный интерфейс класса или библиотеки. Это очень эффективный способ борьбы со сложностью 😌.

Удачный интерфейс класса — это абстракция, позволяющая сосредоточиться на интерфейса, не беспокоясь о внутренних механизмах работы класса (с. 86).

Ещё одно правило призывает нас (3) инкапсулировать детали реализации, чтобы они не были доступны извне определённой части программы.

Абстракция говорит: «‎Вы можете рассмотреть объект с общей точки зрения». Инкапсуляция добавляет: «Более того, вы не можете рассмотреть объект с иной точки зрения» 🔐 (с. 87).

(4) Следует использовать наследование, если некоторые объекты проектируемой системы аналогичны друг другу за исключением нескольких свойств. Эти свойства или методы, могут быть определены как абстрактные на уровне класса, от которого происходит наследование, и реализованы в классах-наследниках.

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

Небольшие изменения системы могут влиять на несколько методов класса, но не должны распространяться на его интерфейс (с. 89)

(6) Нестабильные области системы должны быть изолированы в отдельных классах.

Чаще всего изменяются:

  • Бизнес-логика
  • Зависимости от оборудования/платформы
  • Ввод/вывод (например, формат входных и выходных данных в REST API)
  • Нестандартные возможности языка
  • Сложные аспекты системы
  • Переменные статуса
  • Размеры структур данных

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

Наша цель — создать классы и методы, имеющие немногочисленные, непосредственные, явные и гибкие отношения с другими классам, что ещё называют «‎слабым сопряжением» (loose coupling).

Как оценить сопряжение?

Критерий Описание
Объём Чем меньше связей, тем лучше.
Видимость Чем заметнее и понятнее связи, тем лучше.
Гибкость Чем легче изменить связь, тем лучше.

Семантическое сопряжение — самый коварный тип сопражения, когда модуль использует не «‎какой-то синтаксически элемент другого модуля, а некоторые семантические знания о внутренней работе этого модуля».

Используйте популярные шаблоны проектирования

Шаблоны проектирования — это готовые шаблоны, позволяющие решать частые проблемы разработки ПО (с. 99).

Плюсы использования шаблонов:

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

Другие эвристические принципы

  • Стремиться к максимальной связности (cohesion), чтобы все методы и фрагменты методов класса сответствовали его главной цели
  • Формируйте иерархию/иерархии вашей программы, поднимая абстракции на более высокие уровни и вытесняя детали реализации на более низкие
  • Формализуйте интерфейс класса, так как это контракт с остальными частями программы
  • Грамотно назначайте классам сферы ответсвенности
  • Проектируйте системы для удобства тестирования (но без фанатизма!)

Методики проектирования

  • Проектируйте итеративно 🔁, улучшая проект на каждом очередном шаге (практически невозможно с первого раза подготовить качественный проект)
  • Проектируйте разные части программы по отдельности, если она большая и сложная
  • Комбинируйте нисходящий ↘️ (декомпозиция программы на части до тех пор, пока следующий уровень не будет проще закодить, чем спроектировать) и восходящий ↗️ (композиция программы из низкоуровневных деталей реализации) подходы к проектированию на основе своих предпочтений и особенностей задачи

Степень детализации проекта зависит от многих вещей. Вот признаки того, что необходимо детальное проектирование: (1) проект крупный, (2) ПО будет решать отвественные задачи, (3) ПО будет использоваться длительное время (месяцы или годы).

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

  • Основные аспекты стоит фиксировать прямо в комментариях 💭 к коду
  • Результаты обсуждений и принятые решение следует сохранять в Wiki (сегодня можно использовать Notion или Evernote)
  • Также резюме обсуждений следует отправлять всем членам команды по электронной почте ✉️
  • Различные диаграммы следует сохранять в виде фото (если диаграммы были нарисованы на доске или в тетради) или использовать современные сервисы, например, Miro
  • Ещё один простой способ документирования проекта — завести карточки CRC (Class, Responsibility, Collaborator) для каждого класса