Здравствуйте, меня зовут Дмитрий Карловский. А вы на канале Core Dump, где мы берём различные темы из компьютерной науки и без лишней зауми раскладываем их по полочкам.

Принцип открытости/закрытости

В далёком 1988 году Бертран Мейер сформулировал свой принцип написания кода долгоживущих проектов под названием «Принцип открытости/закрытости» или OCP.

Open‑Closed Principle

Вкратце, он звучит так: «программные сущности должны быть открыты для расширения, но закрыты для изменения». И, как любой короткий принцип, он требует десятки статей для толкования. Но к чёрту всю воду, включаем нашу соковыжималку!

Виды сущностей

Принцип OCP может быть применён к разным типам сущностей, описываемых нашим кодом..

  • Функции

  • Объекты

  • Классы

  • Интерфейсы

  • Модули

  • Пакеты

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

Суть OCP

Простыми словами OCP можно объяснить так: Работает? Не трогай! Создай новую сущность. Ну ладно, баги чинить можно. Но не более!

Копипаст лучше рефакторинга!

При этом, Бертран Мейер допускал использование наследования для снижения копипаста, но это неизбежно приводит к сложным и порой абсурдным иерархиям классов. А вот популяризировавший OCP Роберт Мартин уже предлагает выделять абстракции заранее так, чтобы наследование реализации нам не требовалось. Ретроспективно это сделать, конечно, легко, но чтобы заранее ввести все абстракции на будущее, нужно выдающееся чутьё, если не сказать большего.

Путаница с расширениями

Стоит отдельно подчеркнуть неоднозначность формулировки. Может показаться, что обе половины принципа говорят об одном и том же предмете. Однако, подразумевают они на самом деле разные. Термин изменение относится к отдельной сущности — её нельзя менять. А вот термин расширение относится уже ко всему множеству сущностей — его можно лишь расширять добавлением новых сущностей.

Пунктиром на диаграмме обозначены реализации которые тем или иным способом могут быть унаследованы. Но это возможно далеко не всегда.

Иерархия типов сущностей

И тут у нас начинаются скользкие вопросы. Возьмём, такую сущность как функция. Добавить в неё новый параметр в соответствии с принципом OCP нельзя, а надо создать новую функцию. Но если эта функция не в воздухе висит, а объявлена в рамках такой сущности как класс, то его тоже менять нельзя, и надо создать новый класс. А класс лежит в неймспейсе, который находится в модуле, который собирается в пакет. А пакет в яйце, яйцо в утке, утка в зайце, заец в шоке.

Как далеко мы зайдём в этом расширении, стараясь ничего не менять?

Изменения - опасны?

Если мы изменим уже существующую, отлаженную, используемую кем‑то сущность, то можем намеренно или случайно внести в неё несовместимые изменения. Это может привести к некорректной работе зависящих от изменённой сущностей, падению сборки проекта, необходимости каскадного внесения изменений в косвенно зависящие сущности, в том числе и во внешние потребители, доступа к которым у нас может и не быть.

❌ Ошибки в поведении

❌ Ошибки при сборке

❌ Несовместимость контрактов

Проблема ли это? Если в проекте не используется тестирование, статическая типизация и прочие практики контроля качества, то любое изменение — это мина, на которую мы прыгаем, с разбега, рыбкой. Внесение изменения — 5 минут, отладка его — 5 дней. Во времена Мейера это была проблема огого!

Изменения - не опасны!

Однако, совсем другое дело, когда у нас есть современные практики контроля качества. Тогда мы не боимся вносить любые изменения, ведь уверены, что обо всех несовместимостях и дефектах мы довольно быстро узнаем благодаря автоматизированным инструментам: автотесты, авторефакторинги, автомиграции, тайпчекеры, линтеры, непрерывная интеграция и тд.

✅ Тесты

✅ Миграции

✅ Статический анализ

✅ Непрерывная интеграция

Получается, что серьёзные проблемы у нас могут быть с изменением лишь тех сущностей, на которые завязаны внешние потребители, для которых нам необходимо сохранять обратную совместимость.

Отсутствие изменений - опасно!

А вот для внутренних, контролируемых нами, потребителей проблемы возникают, наоборот, если код не менять, ведь мы плодим горы похожего, но отличающегося в мелочах, кода. Его нужно поддерживать. Он потребляет больше тех или иных ресурсов. Короче, если в коде не прибираться, то он очень быстро тухнет.

А самое печальное, что существующие закрытые к изменению сущности, зачастую оказываются несоответствующими актуальным требованиям. Так что их использование становится мало того, что бессмысленным или нежелательным, так ещё и откровенно вредным.

Например, если раньше обращение к сущности не требовало авторизации, а потом вы решили, что авторизация таки нужна. Оставлять возможность работать без авторизации было бы очень опрометчиво. Это всё равно, что раскладывать грабли рядом с кроватью: с какой бы ноги ни встал с утра — весь день будешь потирать лобные извилины. Поэтому порой обратная совместимость должна быть сломана. И это нормально.

❌ Раздутие множества сущностей

❌ Поддержка разных реализаций одной задачи

❌ Несовместимость с бизнес требованиями

Проектирование по OCP

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

✅ Гибкость и расширяемость

❌ Преждевременное усложнение

А так как программисты не славятся умением предвидеть будущее, то неизбежно одни точки расширения окажутся невостребованными, а других наоборот будет не хватать, что таки приведёт к вынужденному изменению кода.

Правильный OCP

Что ж, давайте попробуем выцепить рациональное зерно из OCP, и изменим его так, чтобы пользы он приносил больше, чем вреда. Итак..

Не ломай публичный контракт без необходимости!

В отличие от предыдущей, наша формулировка OCP мало того, что разрешает обратно совместимые изменения сущностей, так ещё и не столь строга касательно ломающих изменений.

В своей песочнице ты — царь и бог. Но создавая публичный контракт, важно думать не только о том, как им будут пользоваться, но и о том, как ты сам в дальнейшем будешь развивать проект, сохраняя обратную совместимость. А если уж придётся её ломать, то следует предоставь как минимум средства автоматического выявления несовместимостей, а как максимум — средства автоматического их разрешения.

Следовать ли OCP?

Как было показано ранее, в современных условиях следование OCP скорее вредно, чем полезно. Оно требует от программиста быть провидцем, иначе наказывает его протуханием кодовой базы. Поэтому требуется, если не полное отвержение, то хотя бы пересмотр принципа с использованием менее жёстких ограничений, разрешающих нам поддерживать проект в тонусе регулярными рефакторингами.

❌ OCP

✅ Обратная совместимость

Что ещё почитать про OCP?

В качестве десерта предлагаю вам статью Даниэля Норта, с критическим разбором принципов SOLID и OCP в частности, в противовес которому он вводит свой Принцип Снежного Кома, по которому любой ваш код рассматривается не как что‑то самоценное, а как постоянные затраты, которые всё множатся и преумножаются. Так что если есть возможность изменить код так, чтобы он стал проще, то именно так и стоит поступить. И чем раньше, тем лучше.

История возникновения CUPID / Daniel Terhorst-North

Продолжение следует..

Если данный разбор показался вам полезным, то дайте мне об этом знать посредством лайка или даже доната. А так же поделитесь ссылкой на него со своими коллегами. Особенно с теми, кто городит огород вокруг OCP.

Если же вы не согласны с каким‑либо тезисом или, наоборот, чувствуете некую недосказанность, и хотите дополнить своими идеями или иными материалами по теме, то жду ваших комментариев.

Наконец, подписывайтесь на канал, чтобы не пропустить дальнейшие разборы. Нам ещё много чего с вами нужно будет обсудить.

Лайки

Поддержка

Комментарии

Подписка

На этом пока что всё. С вами был открытый к расширению программер Дмитрий Карловский.

Актуальный оригинал на $hyoo_page.

Комментарии (14)


  1. LyuMih
    00.00.0000 00:00
    +1

    В 2 словах суть: "Не ломай публичный контракт без необходимости!"


    1. gandjustas
      00.00.0000 00:00
      +4

      "Не ломай публичный контракт" это LSP.

      OCP это "проектируй классы так, чтобы наследники не смогли сломать публичный контракт и инварианты класса"

      По сути две стороны одной медали


      1. nin-jin Автор
        00.00.0000 00:00
        -1

        Про LSP есть отдельное видео:


        1. gandjustas
          00.00.0000 00:00
          -1

          Там у вас неверное употребление понятия вариантности.


          1. nin-jin Автор
            00.00.0000 00:00
            -1

            Какое же верное?


            1. gandjustas
              00.00.0000 00:00
              -1

              В википедии инфа актуальная

              https://ru.wikipedia.org/wiki/Ковариантность_и_контравариантность_(программирование)

              Вариантность относится к обобщенным типам



          1. ApeCoder
            00.00.0000 00:00
            -1

            Уже обсуждали тут


  1. Samedi_Da_Kapa
    00.00.0000 00:00

    Сначала рекламные ссылки прикрепляли в самом конце статьи.
    Потом их стали размещать в последней трети.
    Дальше они переместились выше и стали появляться на первом экране статьи.
    Сейчас они в ленте на главной.

    Что дальше? Рекламные ссылки в названии статьи?


    1. LyuMih
      00.00.0000 00:00

      В твоей голове)

      (Нейросети скоро)


    1. nin-jin Автор
      00.00.0000 00:00

      Когда мудрец показывает на луну, дурак смотрит на его палец.


  1. madison2201
    00.00.0000 00:00
    +1

    Хотелось бы видеть примеры, хотя бы на псевдо-коде.


    1. nin-jin Автор
      00.00.0000 00:00
      -2

      Практика показывает, что когда приводишь конкретные примеры люди перестают видеть проблему в общем.


  1. aamonster
    00.00.0000 00:00
    -1

    Голосование прекрасно. Оно (и результаты, и сами вопросы) больше говорит о теме, чем статья.