15 сентября 2011 г.

Упаковка по функциональности, а не по слоям

Перевод статьи «Package by feature, not layer».

Первый вопрос при проектировании нового приложения – «Как упаковать его по пакетам?». Для обычных приложений существует два ответа на этот вопрос.

Упаковка по функциональности (Package-by-feature)

Паттерн Package-by-feature подразумевает использование пакетов для отражения фунциональности. Предпологается, что все классы, относящиеся к некоторой функциональности (и только они) упаковываются в один каталог/пакет. В результате мы получаем пакеты с высокой степенью связанности и модульности с одной стороны, и минимальной связью с остальными пакетами с другой. Элементы приложения, которые тесно взаимодействуют друг с другом, находятся в непосредственной близости и не расползаются по всему приложению. В некоторых случаях, удаление части фунционала сводится только к одной операции – удалению каталога. (Операция удаления может быть хорошим показателем степени модульности элемента приложения: максимальная модульность возможна только в том случае, если элемент может быть удален используя только одну операцию.)

В паттерне package-by-feature имена пактеов отражают основные, высокоуровненвые аспекты приложения. К примеру, ПО для аптечных киосков может состоять из следующих пакетов:
  • com.app.doctor
  • com.app.drug
  • com.app.patient
  • com.app.presription
  • com.app.report
  • com.app.security
  • com.app.webmaster
  • com.app.util
  • и т.п.
Внутри пакета все (или почти все) сущности связаны специфической функциональностью. К примеру, пакет com.app.doctor может содержать следующие сущности:
  • DoctorAction.java – обработчик или контроллер объекта
  • Doctor.java – модель объекта
  • DoctorDAO.java – класс для доступа к данным
  • Сущности для работы с БД
  • Элементы пользовательского инерфейса (возможно JSP для web-приложений)
Стоит отметить, что пакет может содержать не только Java-код, но и другие файлы. На самом деле, для того чтобы паттер package-by-feature действительно работал так, как предпологается, все элементы, относящиеся к данной функциональности – от пользовательсного интерфейса до Java-кода и сущностей для работы с БД – должны находиться в одном каталоге и относиться только к одной определенной функциональности (и только к ней).

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

Упаковка по слоям (Package-by-layer)

Ещё один вариант упаковки – упаковка по слоям. Паттерн package-by-layer предпологает, что пакеты отражают высокоуровненые «слои» приложения, например:
•    com.app.action
•    com.app.model
•    com.app.dao
•    com.app.util

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

Рекомендация: используйте упаковку по функциональности

Для большинства приложений паттерн package-by-feature предпочтительнее, потому что даёт:

1. Большую модульность
Как отмечалось выше, только упаковка по функциональности даёт высокую степень модульности, с высокой связанностью и низкой корреляцией между пакетами

2. Простую навигация по коду
Программисты, которые будут сопровождать приложение, не будут долго искать элементы функциональности, потому что они все будут лежать в одном каталоге. Некоторые инструменты, поддерживающие package-by-layer паттерн используют специальные соглашения об именовании чтобы облегчить навигацию, но package-by-feature делает таке соглашения ненужными, в первую очередь благодаря остутствию самой необходимости навигации между каталогами.

3. Более высокий уровень абстракции
Поддерживание высокого уроян абстракции – один из основопологающих принципов хорошего программирования. Он позволяет рассматривать проблему с точки зрения фундаментальных сервисов, а не реализации. В результате более высокого уровня абстракции приложение становиться самодокументируемым: общий размер приложения связан с количество пакетов, а базовый функционал – с именами пакетов. Фундаментальным недостатком паттерна package-by-layer является то, что он ставит детали реализации на самый высокий уровень абстракции.

4. Разделение функциональности и слоёв
Паттерн package-by-feature поддерживает идею разделения слоёв приложения, однако это разделение реализовано в использовании разных классов. С другой стороны, паттерн package-by-layer реализует это разделение используя разделение и по классами, и по пакетам, что не выглядит необходимым или целесообразным.

5. Минимизация области видимости
Минимизация области видимости – другой главный принцип эффективной разработки. Паттерн package-by-feature позволяет некоторым классам изменить их область видимости с public до package-private. Это значительное изменение, которое уменьшит «волновой эффект» (ripple effect). Паттерн package-by-layer, наоборот, не позволяет использовать package-private видимость и требует объявить элемент public. Это фундаментальный изъян, который не позволяет минимизировать волновой эффект с помощью сокрытия реализации.

6. Перспектуву для расширения
В паттерне package-by-feature колличество классов в каждом пакете ограничено количеством элементов, необходимых для реализации функциональности. Если пакет становится слишком большим, он может быть просто разбит на два или более пакета. С другой стороны, паттерн package-by-layer предпологает монолитную архитектуру. По мере увеличения приложения количество пакетов остается постоянным, в то время как количество классов в пакетах без конца увеличивается.

Если вам нужны еще доказательства, рассмотрим следующее.

Структура каталогов основопологающая для вашего кода

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

(Кристофер Александер – архитектор. Не работав программистом, он повлиял на множество людей, занимающихся программированием. Его рання книга «A Pattern Language» была главным вдохновением для развития паттернов проектирования. Он много размышлял о том, как создавать прекрасные вещи и это в должной мере отразилось в широко используемых программных конструкциях.)

В интервью для радио CBC, Александер рассказал следующую историю: «Я работал с одним из моих студентов. У него были сложности с проектированием чего-то. Он просто не знал как вообще взяться за дело. Так что я сел возле него, и сказал: Послушай, обрисуй самые важные вещи. Пойми их. Запомни. Потрать время. Не торопись. Подумай о них некоторое время. Когда ты чувствуешь, что нашел самую главную вещь,когда у тебя нет сомнения, что это действительно она, тогда вперёд – делай её. Когда ты закончишь делать самую главную вещь, спроси себя может ли она быть сделана еще лучше. Отбрось ерунду, просто подумай, может ли она быть еще лучше или нет. Когда это будет сделано и ты чувствуешь, что лучше ты эту вещь уже не сделаешь, тогда ищи следующую самую важную вещь».

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

Сравните разные реакции программистов, которые столкнулись с разной структурой каталогов. При использовании паттерна package-by-feature программист может подумать что-то вроде:
•    «Понятно. Это список всех высокоуровненвых функций приложение в одном месте. Прикольно.»
•    «Посмотрим. Интересно где же этот элемент находится... О, вот же он. И все, что мне еще нужно лежит тут же, все в одном месте. Прекрасно.»


При использовании паттерна package-by-layer программист может подумать следующее:
•    «Эти каталоги не говорят мне ничего. Сколько функционала в этом приложении? Без понятия. Оно выглядит точно так же, как и все остальные. Вообще без различий. Чудно. Понеслась...»
•    «Хм. Интересно, где находится этот элемент... Наверное его части разбросаны по всему приложению во всех этих каталогах.  У меня есть всё что мне необходимо? Позже увидим.»
•    «Интересно, соблюдается ли все еще соглашение об именовании. Если нет, мне нужно будет поискать в другом каталоге.»
•    «Ух ты, только посмотрите на размер этого каталога... офигеть.»


Паттерн package-by-layer не эффективен и в других областях

По аналогии, вы можете убедиться, что паттерн package-by-layer приводит к плохим результатам. К пример, представьте машину. На самом верхнем уровне, «реализация» машины подразделяется на (упаковка по функциональности):
  • Безопасность
  • Двигатель
  • Управление
  • Система топлива
  • и т.п.
Теперь представьте машину, чья «реализация» определена низко-уровневыми категориями (упаковка по слоям):
  • Электроника
  • Механика
  • Гидравлика
В случае проблемы с трансмиссией, к примеру, вам нужно исправлять все эти три компонента. Это значит постоянное перемещение от одной части машины к совершенно другой. Тем временем в этих разных частях вы бы могли заметить элементы, которые не имеют ничего общего с проблемой, которую вы пытаетесь решить. Но они постоянно будут вставать у вас на пути, всегда и везде отвлекая вас от реальной задачи. Не было бы проще хранить все что нужно (и не более) в одном месте?

Еще пример, рассмотрим крупную организацию, состоящую из подразделений (package-by-feature):
  • Отдел по работе с клиентами
  • Отдел по работе с персоналом
  • Бухгалтрерия
  • и т.п.
При использовании паттерна package-by-layer основные подразделения были бы похожы на:
  • Руководители
  • Менеджеры
  • Исполнители
Теперь представьте этот аппарат физически разделенный по этим трём категориям. Каждый менеджер находится, к примеру, рядом с другими менеджерами, а не с исполнителями, с которыми он работает. Было бы это эффективным? Нет.

Так в чем же различие при разработке ПО? Похоже паттерн package-by-layer – это просто плохая традиция, которую давно пора нарушить.

1 комментарий:

  1. Отличная статья!! Как раз сейчас работаю на большом проекте со структурой каталогов по слоям. Это ужас!
    Чтобы исправить какую-то функциональность в компоненте нужно облазить и найти составные части разбросанные по разным слоям.

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

Примечание. Отправлять комментарии могут только участники этого блога.