После изменения внутреннего алгоритма модуля приходится менять его потребителей. Какой дефект границы это показывает?
Это показывает, что граница модуля раскрывает потребителям детали реализации. Контракт модуля недостаточно изолирует изменяемые решения, поэтому изменение внутри распространяется наружу и создаёт лишнюю связанность.
Сокрытие информации появилось как практический способ ограничить влияние изменений в больших программных системах. Идея состоит в том, чтобы модуль скрывал решения, которые могут измениться, и предоставлял другим частям системы только устойчивый контракт.
Без такой изоляции структура программы начинает отражать внутреннее устройство компонентов. Любое изменение алгоритма, хранения данных или внутреннего процесса тогда требует синхронных изменений во множестве потребителей.
Граница модуля дефектна, если потребитель зависит не от результата операции, а от того, как модуль этот результат получает. Примерами являются доступ к внутренним структурам данных, зависимость от порядка внутренних шагов или необходимость знать технические детали хранения.
Последствия — рост сцепления, увеличение области регрессионного тестирования и замедление эволюции системы. Даже небольшое изменение может потребовать выпуска нескольких компонентов, согласования их версий и временной поддержки старого поведения.
Нужно отделить публичный контракт от реализации. Контракт должен описывать доступную потребителю возможность: входные данные, результат, существенные ошибки и гарантии поведения. Он не должен требовать знания внутренних структур, алгоритмов или последовательности вспомогательных операций.
Хорошая граница скрывает наиболее изменчивые решения. Например, модуль может сам выбирать способ поиска, хранения или кэширования, а потребителю возвращать только устойчивое представление результата.
Важно не путать сокрытие информации с механическим уменьшением числа методов. Слишком общий контракт может скрыть детали формально, но оставить неясные семантические зависимости или заставить потребителей воспроизводить внутренние правила самостоятельно.
Контракт должен быть достаточно узким, чтобы не раскрывать лишнее, но достаточно выразительным, чтобы потребителям не приходилось обходить модуль. Если обходы становятся необходимыми, это сигнал, что граница выбрана неправильно или контракт неполон.
Компромисс состоит в том, что абстракция имеет стоимость: её нужно спроектировать, документировать и тестировать. Кроме того, стабильный контракт иногда ограничивает оптимизацию реализации или требует адаптеров при миграции старого интерфейса.
Проверять качество границы следует изменением реализации мысленно или на практике. Если можно заменить алгоритм, формат хранения или внутреннюю последовательность действий без изменения потребителей и их ожиданий, граница, вероятно, выполняет свою функцию.
В системе расчёта тарифов потребители напрямую использовали структуру правил, хранившуюся в модуле тарифов. После перехода с последовательного перебора правил на индексированную структуру пришлось изменить несколько сервисов: они знали поля правил, порядок их обработки и специальные значения.
Рассматривались два варианта. Первый — сохранить общую структуру и добавить новые поля: это требовало меньше начальной работы, но продолжало раскрывать реализацию и увеличивало будущую связанность. Второй — предоставить операцию расчёта тарифа с устойчивыми входными данными и результатом, скрыв хранение и выбор правил; этот вариант потребовал адаптеров и переноса части логики.
Выбрали второй вариант, потому что алгоритмы расчёта и структура индексов менялись чаще, чем смысл операции для потребителей. После миграции внутренний индекс заменили без изменений в сервисах, а контракт дополнительно закрепили тестами поведения. Цена решения — необходимость явно согласовать ошибки и правила версионирования результата.
1. Достаточно ли спрятать поля объекта, чтобы граница стала хорошей?
Нет. Скрытие полей защищает только от одного вида прямой зависимости. Если публичные операции требуют от потребителя соблюдать внутренний порядок вызовов, интерпретировать технические коды или самостоятельно собирать внутренний объект, детали реализации всё равно просачиваются через поведение интерфейса.
Нужно оценивать не только видимость данных, но и семантическую зависимость. Потребитель должен зависеть от гарантированного результата и правил контракта, а не от случайного текущего устройства модуля.
2. Всегда ли изменение потребителя означает плохую границу модуля?
Нет. Потребитель должен измениться, если изменился его бизнес-смысл или действительно изменился публичный контракт. Дефект проявляется тогда, когда потребителя заставляет меняться только внутренняя замена алгоритма, хранения или вспомогательной структуры.
Полезно разделять изменения контракта и изменения реализации. Если изменение контракта необходимо, его следует сделать явным, документировать и при необходимости поддержать переходный период, а не маскировать несовместимость случайными техническими обходами.
3. Может ли слишком стабильный контракт ухудшить архитектуру?
Да. Попытка никогда не менять контракт может привести к чрезмерно универсальному интерфейсу, множеству необязательных параметров или неясным флагам. Такой контракт формально скрывает реализацию, но становится сложным, плохо проверяемым и начинает отражать сразу несколько разных сценариев.
Граница должна быть стабильной не за счёт накопления исключений, а за счёт ясной ответственности модуля. Если требования устойчиво расходятся, лучше пересмотреть границу или выделить отдельные контракты, чем сохранять один универсальный интерфейс любой ценой.