Prefer duplication over the wrong abstraction (2016) 🔥 Горячее 💬 Длинная дискуссия
Дублирование часто дешевле, чем попытка создать «правильное» обобщение. На RailsConf 2014 я говорил: «дублирование намного дешевле неправильного абстракции», что отразилось в твите «Duplication is far cheaper than the wrong abstraction» (41 shades of blue, март 2014). Когда в коде появляется повтор, разработчик вытаскивает его в метод или класс, получая новую абстракцию. Со временем к ней добавляются параметры и условия, чтобы покрыть новые требования, и код превращается в запутанный набор ветвлений. При этом сохранение такой конструкции подпитывается эффектом удержания вложенных усилий — «sunk‑cost fallacy» заставляет считать, что сложный код обязателен и важен.
Чтобы выйти из ловушки, лучше отменить прежнее решение. Нужно вернуть дублирование, заменив вынесённый метод на его тело в каждом месте вызова, а затем оставить только те части, которые действительно нужны конкретному вызывающему. Удаляя лишние ветви, вы избавляетесь от условных параметров и получаете чистый код, который легко расширять. Такой «откат» часто раскрывает, что исходная абстракция была избыточной, и позволяет заново выделить более подходящие обобщения. Главное – не позволять заложенным усилиям удерживать плохой дизайн.
Комментарии (354)
- Копирование кода иногда проще, чем вводить плохие абстракции, которые усложняют поддержку.
- Плохие абстракции добавляют скрытую сложность и приводят к самоссылкам, ухудшая читаемость.
- Правильный баланс между дублированием и абстракциями зависит от частоты изменения и контекста.
- Часто лучше откладывать рефакторинг, пока не будет ясно, что дублирование стабильно повторяется.
When did people favor composition over inheritance? 💬 Длинная дискуссия
Фраза "предпочитай композицию наследованию" стала клише, но её происхождение точно известно — это второй принцип объектно-ориентированного дизайна из книги "Design Patterns" "банды четырёх" (Gamma, Helm, Johnson, Vlissides). Оригинальная формулировка гласила: "Favor object composition over class inheritance". Авторы противопоставляли наследование ("белая коробка", где подкласс видит детали реализации) и композицию ("чёрная коробка", где объект взаимодействует только через интерфейс). Однако этот аргумент зависит от языка: в Java можно ограничить видимость для подклассов, а в Smalltalk и Python доступ к внутренностям возможен через рефлексию.
Более весомый аргумент касается гибкости: наследование статично и определяется на этапе компиляции, что затрудняет изменение, тогда как композиция динамична и позволяет заменять компоненты во время выполнения. Это меняет архитектурные зависимости — система опирается на отношения объектов в рантайме, а не на иерархию наследования. В контексте современного тренда к статической типизации и проверке компилятором, этот подход требует баланса. Интересно, что Barbara Liskov ещё в 1987 предлагала альтернативу: вместо жёсткой иерархии позволять полиморфным модулям использовать любые типы с нужными операциями, без формального отношения подтипа.
Комментарии (174)
- Inheritance can lead to complex, fragile hierarchies and "co-recursion" issues where subclass methods override parent methods in unpredictable ways.
- Composition is generally preferred for flexibility and maintainability, as it avoids tight coupling and allows "black box" reuse without exposing implementation details.
- Both approaches have valid use cases: inheritance suits "is-a" relationships with clear invariants, while composition excels at combining independent behaviors ("has-a").
- Modern best practices favor interfaces + composition to decouple polymorphism from behavior sharing, reducing brittleness compared to deep inheritance chains.
- Over-reliance on inheritance often stems from cargo-culting; it should be reserved for cases where it genuinely simplifies the design rather than complicating it.
The bloat of edge-case first libraries
Многие библиотеки в экосистеме JavaScript стали избыточно сложными из-за попыток обработать все возможные крайние случаи, даже те, что на практике почти не встречаются. Например, функция clamp, предназначенная для ограничения чисел, превращается в монстра, проверяющего строки, валидирующего типы и значения, что приводит к появлению микробиблиотек вроде is-number с 90 млн загрузок в неделю. Это результат плохого технического дизайна: вместо чёткого определения ожидаемых входных данных разработчики добавляют слои проверок для гипотетических сценариев.
Правильный подход — проектировать функции под конкретные типы данных, оставляя валидацию значений на усмотрение вызывающей стороны. Библиотеки вроде is-arrayish или pascalcase, принимающие разнородные входы, лишь увеличивают сложность и зависимости без реальной пользы. Стоит вернуться к простоте: в большинстве случаев достаточно встроенных методов языка, а специализированные решения нужны только для узких задач, а не как стандарт.
Комментарии (125)
- Обсуждение критикует избыточное использование зависимостей в JavaScript/TypeScript для простых задач, таких как проверка типов, что ведет к раздуванию экосистемы.
- Участники связывают проблему с историческими особенностями JavaScript: отсутствием строгой типизации и богатой стандартной библиотеки в прошлом.
- Поднимается вопрос о дизайне контрактов функций: следует ли валидировать входные данные внутри функции или возлагать эту ответственность на вызывающую сторону.
- Отмечается культурное различие между сообществами: в Python принята модель "согласованных взрослых", а в JS — оборонительное программирование.
- Обсуждается роль статической типизации (TypeScript) и стандартных библиотек как способа уменьшить потребность в микро-пакетах для проверок.