Почему многие разработчики программного обеспечения нарушают принцип открытия / закрытия , изменяя многие вещи, такие как переименование функций, которые нарушают работу приложения после обновления?
Этот вопрос приходит мне в голову после быстрой и непрерывной версий в библиотеке React .
Каждый короткий период я замечаю множество изменений в синтаксисе, именах компонентов и т. Д.
Пример в следующей версии React :
Новые устаревшие предупреждения
Самое большое изменение заключается в том, что мы извлекли React.PropTypes и React.createClass в их собственные пакеты. Оба они по-прежнему доступны через основной объект React, но использование любого из них приведет к одноразовому выводу предупреждения об устаревании на консоль в режиме разработки. Это позволит в будущем оптимизировать размер кода.
Эти предупреждения не повлияют на поведение вашего приложения. Однако мы понимаем, что они могут вызвать некоторое разочарование, особенно если вы используете среду тестирования, которая рассматривает console.error как сбой.
- Считаются ли эти изменения нарушением этого принципа?
- Как новичку в React , как мне научиться этому с такими быстрыми изменениями в библиотеке (это так расстраивает)?
источник
Ответы:
ИМХО, ответ Жака Б, хотя и содержит много правды, показывает фундаментальное неправильное понимание OCP. Справедливости ради, ваш вопрос уже выражает и это недоразумение - переименование функций нарушает обратную совместимость , но не OCP. Если нарушение совместимости кажется необходимым (или поддержание двух версий одного и того же компонента, чтобы не нарушать совместимость), OCP уже был сломан раньше!
Как уже упоминал Йорг Миттаг в своих комментариях, принцип не гласит: «Вы не можете изменить поведение компонента» - он говорит, что нужно пытаться проектировать компоненты так, чтобы они были открыты для повторного использования (или расширения). несколькими способами, без необходимости модификации. Это может быть сделано путем предоставления правильных «точек расширения» или, как упоминалось @AntP, «путем разложения структуры класса / функции до точки, где каждая естественная точка расширения находится там по умолчанию». ИМХО, следование OCP не имеет ничего общего с тем, чтобы «сохранить прежнюю версию без изменений для обратной совместимости» ! Или, цитируя комментарий @DerekElkin ниже:
Хорошие программисты используют свой опыт для разработки компонентов с «правильными» точками расширения (или, что еще лучше, без необходимости использования искусственных точек расширения). Однако, чтобы сделать это правильно и без лишних усилий, вам нужно заранее знать, как могут выглядеть варианты использования вашего компонента в будущем. Даже опытные программисты не могут заглянуть в будущее и заранее знают все предстоящие требования. И именно поэтому иногда необходимо нарушать обратную совместимость - независимо от того, сколько точек расширения имеет ваш компонент или насколько хорошо он соответствует OCP в отношении определенных типов требований, всегда будет требование, которое не может быть легко реализовано без изменения компонент.
источник
Принцип открытого / закрытого типа имеет свои преимущества, но он также имеет некоторые серьезные недостатки.
Теоретически этот принцип решает проблему обратной совместимости путем создания кода, который «открыт для расширения, но закрыт для модификации». Если у класса есть некоторые новые требования, вы никогда не изменяете исходный код самого класса, а вместо этого создаете подкласс, который переопределяет только соответствующие члены, необходимые для изменения поведения. Таким образом, весь код, написанный для исходной версии класса, не затронут, поэтому вы можете быть уверены, что ваши изменения не сломали существующий код.
На самом деле вы легко в конечном итоге получаете раздувание кода и путаницу устаревших классов. Если невозможно изменить какое-либо поведение компонента с помощью расширения, необходимо предоставить новый вариант компонента с требуемым поведением и оставить прежнюю версию неизменной для обратной совместимости.
Допустим, вы обнаружили фундаментальный недостаток дизайна в базовом классе, от которого наследуется множество классов. Скажем, ошибка связана с тем, что закрытое поле имеет неправильный тип. Вы не можете это исправить, переопределив член. По сути, вы должны переопределить весь класс, что означает, что вы в конечном итоге расширяете
Object
для обеспечения альтернативного базового класса - и теперь вы также должны предоставить альтернативы всем подклассам, в результате чего получается дублированная иерархия объектов, одна дефектная иерархия, одна улучшенная , Но вы не можете удалить некорректную иерархию (поскольку удаление кода является модификацией), все будущие клиенты будут подвержены обеим иерархиям.Теперь теоретический ответ на эту проблему - «просто спроектируй это правильно с первого раза». Если код полностью декомпозирован, без каких-либо недостатков или ошибок и разработан с точками расширения, подготовленными для всех возможных будущих изменений требований, тогда вы избежите путаницы. Но на самом деле все делают ошибки, и никто не может точно предсказать будущее.
Возьмите что-то вроде .NET Framework - он по-прежнему содержит набор классов коллекций, которые были разработаны до того, как дженерики были представлены более десяти лет назад. Это, безусловно, благо для обратной совместимости (вы можете обновить фреймворк без необходимости что-либо переписывать), но это также раздувает фреймворк и предоставляет разработчикам большой набор опций, многие из которых просто устарели.
По-видимому, разработчики React чувствовали, что не стоит затрат на сложность и раздувание кода, чтобы строго следовать принципу открытого / закрытого типа.
Прагматичной альтернативой открытию / закрытию является контролируемая амортизация. Вместо того, чтобы нарушать обратную совместимость в одном выпуске, старые компоненты сохраняются для цикла выпуска, но клиенты информируются через предупреждения компилятора о том, что старый подход будет удален в более позднем выпуске. Это дает клиентам время для изменения кода. Это, кажется, подход React в этом случае.
(Моя интерпретация этого принципа основана на принципе открытого и закрытого типа Роберта К. Мартина)
источник
Я бы назвал принцип открытого / закрытого идеалом. Как и все идеалы, он мало учитывает реалии разработки программного обеспечения. Также как и все идеалы, на самом деле невозможно достичь этого на практике - кто-то просто стремится приблизиться к этому идеалу как можно лучше.
Другая сторона истории известна как Золотые наручники. Золотые наручники - это то, что вы получаете, когда слишком сильно подчиняете себя принципу «открыт / закрыт». Золотые наручники - это то, что происходит, когда ваш продукт, который никогда не нарушает обратной совместимости, не может расти, потому что было сделано слишком много ошибок в прошлом.
Известный пример этого можно найти в диспетчере памяти Windows 95. В рамках маркетинга Windows 95 было заявлено, что все приложения Windows 3.1 будут работать в Windows 95. Microsoft фактически приобрела лицензии на тысячи программ для их тестирования в Windows 95. Одним из проблемных случаев был Sim City. В Sim City действительно была ошибка, из-за которой он записывал в нераспределенную память. В Windows 3.1 без «правильного» менеджера памяти это было незначительной ошибкой. Тем не менее, в Windows 95 диспетчер памяти перехватит это и вызовет ошибку сегментации. Решение? В Windows 95, если имя вашего приложения такое
simcity.exe
, ОС фактически ослабит ограничения диспетчера памяти, чтобы предотвратить ошибку сегментации!Настоящая проблема, лежащая в основе этого идеала, заключается в ограниченности концепций продуктов и услуг. Никто на самом деле не делает ни того, ни другого. Все выстраивается где-то в серой области между ними. Если вы думаете о подходе, ориентированном на продукт, открытие / закрытие звучит как отличный идеал. Ваши продукты надежны. Однако, когда дело доходит до услуг, история меняется. Легко показать, что по принципу «открыт / закрыт» количество функций, которые должна поддерживать ваша команда, должно асимптотически приближаться к бесконечности, потому что вы никогда не сможете очистить старую функциональность. Это означает, что ваша команда разработчиков должна поддерживать все больше и больше кода каждый год. В конце концов вы достигнете предела.
Большинство программного обеспечения сегодня, особенно с открытым исходным кодом, следует общепринятой версии открытого / закрытого принципа. Очень часто можно увидеть, что открытые / закрытые следуют рабски для небольших релизов, но заброшены для крупных релизов. Например, Python 2.7 содержит много «плохих вариантов» из Python 2.0 и 2.1 дней, но Python 3.0 сместил их все. (Кроме того , переход от Windows 95. кодового в кодовую Windows NT , когда они выпустили Windows 2000 побил все виды вещей, но это же означает , что мы никогда не должны иметь дело с менеджером памяти проверяя имя приложения , чтобы решить поведение!)
источник
Ответ Дока Брауна является наиболее точным, остальные ответы иллюстрируют недопонимание открытого закрытого принципа.
Чтобы явно сформулировать недоразумение, кажется , есть мнение , что OCP означает , что вы не должны делать назад несовместимые изменения (или даже какие - либо изменения или что - то вдоль этих линий.) ОСР о проектировании компонентов , так что вам не нужно , чтобы внесите в них изменения, чтобы расширить их функциональность, независимо от того, являются ли эти изменения обратно совместимыми или нет. Помимо добавления функциональных возможностей, существует множество других причин, по которым вы можете вносить изменения в компонент независимо от того, являются ли они обратно совместимыми (например, рефакторинг или оптимизация) или обратно несовместимыми (например, не рекомендуется использовать или удалять функциональность). То, что вы можете внести эти изменения, не означает, что ваш компонент нарушил OCP (и определенно не означает, что вы нарушают OCP).
На самом деле, это вовсе не исходный код. Более абстрактное и актуальное утверждение OCP: «компонент должен допускать расширение без необходимости нарушать границы абстракции». Я бы пошел дальше и сказал бы, что более современное представление: «компонент должен обеспечивать свои границы абстракции, но допускает расширение». Даже в статье об OCP Боба Мартина, когда он «описывает» «закрытый для модификации» как «исходный код нерушим», он позже начинает говорить об инкапсуляции, которая не имеет ничего общего с модификацией исходного кода и всем, что связано с абстракцией границы.
Таким образом, ошибочная предпосылка в этом вопросе заключается в том, что OCP - это (задумано как) руководство по развитию кодовой базы. У OCP обычно лозунг: «компонент должен быть открыт для расширений и закрыт для изменений потребителями». По сути, если потребитель компонента хочет добавить функциональность к компоненту, он должен иметь возможность расширить старый компонент на новый с дополнительными функциями, но он не должен иметь возможность изменять старый компонент.
OCP ничего не говорит о том, что создатель компонента изменил или удалил функциональность. OCP не поддерживает вечную совместимость с ошибками . Вы, как создатель, не нарушаете OCP, изменяя или даже удаляя компонент. Вы, или, вернее, написанные вами компоненты, нарушаете OCP, если единственный способ, которым потребители могут добавить функциональность к вашим компонентам, - это изменить его, например, с помощью патчей для обезьян.или иметь доступ к исходному коду и перекомпилировать. Во многих случаях ни один из этих вариантов для потребителя не означает, что если ваш компонент не «открыт для расширения», им не повезло. Они просто не могут использовать ваш компонент для своих нужд. OCP утверждает, что не ставит потребителей вашей библиотеки в это положение, по крайней мере, в отношении некоторого идентифицируемого класса «расширений». Даже когда могут быть внесены изменения в исходный код или даже в первичную копию исходного кода, лучше всего «притворяться», что вы не можете его изменить, поскольку это может привести к многочисленным негативным последствиям.
Итак, чтобы ответить на ваши вопросы: Нет, это не нарушения OCP. Никакие изменения, вносимые автором, не могут быть нарушением OCP, поскольку OCP не является прототипом изменений. Изменения, однако, могут создавать нарушения OCP, и они могут быть мотивированы сбоями OCP в предыдущих версиях кодовой базы. OCP - это свойство определенного куска кода, а не эволюционная история кодовой базы.
Напротив, обратная совместимость является свойством изменения кода. Нет смысла говорить, что какой-то кусок кода обратно совместим. Имеет смысл говорить о обратной совместимости некоторого кода по отношению к более старому коду. Следовательно, никогда не имеет смысла говорить о том, что первый фрагмент некоторого кода обратно совместим или нет. Первый фрагмент кода может удовлетворять или не удовлетворять OCP, и в целом мы можем определить, удовлетворяет ли какой-то код OCP, не обращаясь к каким-либо историческим версиям кода.
Что касается вашего последнего вопроса, то он, возможно, не является темой для StackExchange в целом, поскольку он в первую очередь основан на мнениях, но если не считать его, можно приветствовать технологию и, в частности, JavaScript, где в последние несколько лет описываемое вами явление называлось усталостью JavaScript . (Не стесняйтесь в Google, чтобы найти множество других статей, некоторые сатирические, говорящие об этом с разных точек зрения.)
источник
private
или нет. Если автор делаетprivate
методpublic
позже, это не значит, что они нарушили контроль доступа, (1/2)private
раньше. «Удаление опубликованного компонента - явное изменение», - это не секвитур. Либо компоненты новой версии удовлетворяют требованиям OCP, либо нет, вам не нужна история кодовой базы, чтобы определить это. По твоей логике я никогда не смог бы написать код, который удовлетворял бы OCP. Вы связываете обратную совместимость, свойство изменений кода, с OCP, свойство кода. Ваш комментарий имеет столько же смысла, сколько говорит, что быстрая сортировка не имеет обратной совместимости. (2/2)