Грань между DRY и Single Responsibility
На одном из моих последних проектов мы с коллегами делали UI-кит на Jetpack Compose. Идея была правильная: вынести повторяющиеся элементы в переиспользуемые компоненты - кнопки, text fields, карточки, тулбары. Чтобы не копировать один и тот же код по всему приложению.
Так у нас появилась условная PrimaryButton.
Сначала это была простая кнопка: текст, единый стиль, состояние enabled/disabled. Потом понадобился loading. Потом иконка слева. Потом иконка справа. Потом другой размер. Потом особое поведение для формы. Потом другой padding для одного экрана. Потом отдельная логика для экрана оплаты. Потом еще один флаг, чтобы “не создавать новую кнопку”.
В какой-то момент PrimaryButton перестала быть кнопкой. Она стала компонентом, который пытался знать обо всех сценариях приложения. И вот здесь DRY начал работать против нас.
Проблема была не в самом переиспользовании, а в том, что мы пытались убрать любое визуальное дублирование. Но в итоге смешали кучу ответстыенностей в одном компоненте. Формально мы избавились от дублирования кода. Фактически получили компонент, который становился сложнее при каждом новом кейсе.
После этого я стал иначе смотреть на DRY. Это не про избегание “дублирования строчки кода”. Это про дублирование ответственности и бизнес-правил. А UI иногда нормально дублировать, если сохраняет простоту компонента.
В Compose очень легко уйти в универсальные компоненты с кучей параметров. Иногда лучше создать еще один маленький компонент, чем добавить десятый boolean-флаг в существующий.
DRY не должен побеждать Single Responsibility.