Словарик философствующего информатика/Несвязность и закон Деметры
- По материалам книги «Программист-прагматик» Эндрю Ханта и Дэвида Томаса.
В разделе Ортогональность мы высказали предположение, что выгодно писать «скромные» программы. Но эта «скромность» работает в двух направлениях: не раскрывайте себя перед другими и не общайтесь со слишком многими людьми. Шпионы, диссиденты, революционеры и им подобные часто организованы в небольшие группы, называемые ячейками. Хотя отдельные личности в каждой ячейке могут знать друг о друге, они не знают ничего об участниках других ячеек. Если одна ячейка раскрыта, то никакое количество «сыворотки правды» неспособно выбить из ее участников информацию об их сподвижниках вне пределов ячейки. Устранение взаимодействий между ячейками убережет всех. Мы полагаем, что этот принцип хорошо бы применить и к написанию программ. Разбейте вашу программу на ячейки (модули) и ограничьте взаимодействие между ними. Если один модуль находится под угрозой и должен быть заменен, то другие модули должны быть способны продолжить работу.
Сведение связанности к минимуму
правитьЧто произойдёт, если появятся модули, которые знают друг о друге? В принципе ничего — вы не должны впадать в паранойю, как шпионы или диссиденты. Однако, следует внимательно следить за тем, со сколькими другими модулями вы взаимодействуете. Это важнее, чем то, каким образом вы пришли к взаимодействию с ними. Предположим, вы занимаетесь перепланировкой своего дома или строите дом с нуля. Обычная организация включает «генерального подрядчика». Вы нанимаете подрядчика для выполнения работ, но подрядчик выполняет или не выполняет эти работы сам; работа может быть предложена разнообразным субподрядчикам. Но, будучи клиентом, вы не имеете дело с субподрядчиками напрямую, генеральный подрядчик берет от вашего имени эту головную боль на себя. Нам бы хотелось воспользоваться той же моделью в программном обеспечении. Когда мы запрашиваем у объекта определённую услугу, то мы хотим, чтобы эта услуга оказывалась от нашего имени. Мы не хотим, чтобы данный объект предоставлял нам еще какой-то объект, подготовленный третьей стороной, с которым нам придется иметь дело для получения необходимой услуги. Предположим, что вы пишете класс, генерирующий график по данным научного прибора. Научные приборы рассеяны по всему миру, каждый объект-прибор содержит объект-местоположение, который дает информацию о его расположении и часовом поясе. Вы хотите, чтобы ваши пользователи могли выбирать прибор и наносить его данные на график с отметкой часового пояса. Вы можете записать
public void plotDate(Date aDate, Selection aSelection) {
TimeZone tz = aSelection.getRecorder().getLocation().getTimeZone();
...
}
Но теперь подпрограмма построения графика без особой надобности связана с тремя классами — Selection
, Recorder
и Location
. Этот стиль программирования резко увеличивает число классов, от которых зависит наш класс. Почему это плохо? Потому что при этом увеличивается риск того, что внесение несвязанного изменения в другой части системы затронет вашу программу. Например, если сотрудник по имени Фред вносит изменение в класс Location так, что он непосредственно более не содержит TimeZone, то вам придется внести изменения и в свою программу. Вместо того, чтобы продираться через иерархию самостоятельно, просто спросите напрямую о том, что вам нужно:
public void plotDate(Date aDate, TimeZone aTz) { ... }
plotDate(someDate, someSelection.getTimeZone());
Мы добавили метод к классу Selection
, чтобы получить часовой пояс от своего имени; подпрограмме построения графика неважно, передается ли часовой пояс непосредственно из класса Recorder
, от некоего объекта, содержащегося в Recorder
, или же класс Selection
сам составляет другой часовой пояс. В свою очередь, подпрограмма выбора должна запросить прибор о его часовом поясе, оставив прибору право получить его значение из содержащегося в нем объекта Location
. Непосредственное пересечение отношений между объектами может быстро привести к комбинаторному взрыву отношений зависимости. Признаки этого явления можно наблюдать в ряде случаев:
- В крупномасштабных проектах на языках С или C++ , где команда компоновки процедуры тестирования длиннее, чем сама программа тестирования.
- «Простые» изменения в одном модуле, распространяющиеся в системе через модули, не имеющие связей.
- Разработчики, которые боятся изменить программу, поскольку они не уверены, как и на чем скажется это изменение. Системы, в которых имеется большое число ненужных зависимостей, отличаются большой сложностью (и высокими затратами) при сопровождении и в большинстве случае весьма нестабильны. Для того чтобы поддерживать число зависимостей на минимальном уровне, мы воспользуемся законом Деметры при проектировании методов и функций.
Закон Деметры для функций
правитьЗакон Деметры для функций свести к минимуму связывание между модулями в любой программе. Он пытается удержать вас от проникновения в объект для получения доступа к методам третьего объекта. Создавая «скромную» программу, в которой закон Деметры соблюдается в максимально возможной степени, мы можем добиться цели, выраженной в следующей подсказке:
Минимизируйте связывание между модулями
А не все ли равно?
правитьОказывает ли следование закону Деметры (каким бы хорошим он не был с точки зрения теории) реальную помощь в создании программ, более простых в сопровождении? Исследования показали, что классы в языке C++ с большими совокупностями откликов менее ошибкоустойчивы, чем классы с небольшими совокупностями (совокупность откликов представляет собой число функций, непосредственно вызываемых методами конкретного класса). Поскольку следование закону Деметры уменьшает размер совокупности отклика в вызывающем отклике, то классы, спроектированные данным образом, также будут менее склонны к наличию ошибок. Использование закона Деметры сделает вашу программу более адаптируемой и устойчивой, но не бесплатно: будучи «генеральным подрядчиком», ваша программа должна непосредственно делегировать полномочия и управлять всеми существующими субподрядчиками, не привлекая к этому клиентов вашего модуля. На практике это означает, что вы будете создавать большое количество методов-оболочек, которые просто направляют запрос далее к делегату. Эти методы-оболочки влекут за собой расходы во время исполнения и накладные расходы дискового пространства, которые могут оказаться весьма значительными, а для некоторых приложений даже запредельными. Как и при использовании любой методики, вы должны взвесить все «за» и «против» для конкретного приложения. В проекте схемы базы данных обычной практикой является «денормализация» схемы для улучшения производительности: нарушение правил нормализации в обмен на скорость выполнения. Подобного же компромисса можно достичь и в этом случае. На самом деле, соблюдая закон Деметры и плотно связывая несколько модулей, вы можете получить существенный выигрыш в производительности. Ваша конструкция работает прекрасно, пока она известна и приемлема для этих связываемых модулей.
Физическая несвязанность
правитьВ данном разделе мы много говорим о сохранении логической несвязанности между элементами проектируемой системы. Однако существует взаимозависимость другого рода, которая становится весьма существенной с увеличением масштаба систем. В своей книге «Large-Scale C++ Software Design» Джон Лакос обращается к вопросам, касающимся отношений между файлами, каталогами и библиотеками, составляющими систему. Игнорирование этих проблем физического проектирования в крупномасштабных проектах приводит, помимо прочих проблем, к тому, что цикл сборки может растягиваться на несколько дней, а процедуры модульного тестирования могут сорвать сроки готовности всей системы. Г-н Лакос приводит убедительные доказательства того, что логическое и физическое проектирование должно осуществляться в тандеме и что устранение повреждений в большом фрагменте программы, нанесенных ему циклическими зависимостями, представляется чрезвычайно трудным делом. Мы рекомендуем вам прочесть эту книгу, если вы участвуете в разработке крупномасштабных проектов, даже если вы осуществляете реализацию на языке, отличном от C++. В противном случае вы можете оказаться на пути к хрупкому, негибкому будущему. Или вообще оказаться без будущего.