Рефакторинг и открытый / закрытый принцип

12

Недавно я читал веб-сайт о разработке чистого кода (здесь я не размещаю ссылку, потому что она не на английском языке).

Одним из принципов, рекламируемых этим сайтом, является принцип Open Closed : каждый программный компонент должен быть открыт для расширения и закрыт для модификации. Например, когда мы реализовали и протестировали класс, мы должны только изменить его, чтобы исправить ошибки или добавить новые функциональные возможности (например, новые методы, которые не влияют на существующие). Существующая функциональность и реализация не должны быть изменены.

Я обычно применяю этот принцип, определяя интерфейс Iи соответствующий класс реализации A. Когда класс Aстал стабильным (реализован и протестирован), я обычно не слишком сильно его модифицирую (возможно, совсем не изменяю), т.е.

  1. Если появляются новые требования (например, производительность или совершенно новая реализация интерфейса), которые требуют больших изменений в коде, я пишу новую реализацию Bи продолжаю использовать Aдо тех пор, пока Bона не является зрелой. Когда Bнаступит зрелость, все, что нужно, - это изменить способ Iсоздания экземпляра.
  2. Если новые требования также предполагают изменение интерфейса, я определяю новый интерфейс I'и новую реализацию A'. Таким образом I, Aбудут заморожены и остаются реализация для производственной системы до тех пор, I'и A'не достаточно стабильна , чтобы заменить их.

Поэтому, учитывая эти наблюдения, я был немного удивлен, что веб-страница тогда предложила использовать сложные рефакторинги , «... потому что невозможно написать код непосредственно в его окончательной форме».

Нет ли противоречия / конфликта между применением открытого / закрытого принципа и предложением использования сложных рефакторингов в качестве лучшей практики? Или идея в том, что можно использовать сложные рефакторинги во время разработки класса A, но когда этот класс был успешно протестирован, его следует заморозить?

Джорджио
источник

Ответы:

9

Я считаю принцип Open-Closed целью дизайна . Если вам в конечном итоге придется нарушить его, то это означает, что ваш первоначальный дизайн провалился, что, безусловно, возможно и даже вероятно.

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

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

Скотт Уитлок
источник
Вы, конечно, не должны думать как любой принцип как цель дизайна . Это инструменты - вы не делаете программное обеспечение красивым и теоретически правильным, вы пытаетесь создать ценность для своего клиента. Это руководство , не более того.
Т. Сар
@ T.Sar Принцип - это руководство, к которому вы стремитесь, они ориентированы на удобство обслуживания и масштабируемость. Это выглядит как цель дизайна для меня. Я не вижу принципа как инструмента, как я вижу шаблон дизайна или структуру как инструмент.
Тулаинс Кордова
@ TulainsCórdova Ремонтопригодность, производительность, корректность, масштабируемость - вот цели. Принцип Открыто-Закрыто - средство для них - только один из многих. Вам не нужно подталкивать что-то к принципу открытого-закрытого, если это не применимо к нему или это отвлекает от реальных целей проекта. Вы не продаете "Открытость" клиенту. В качестве простого руководства это не лучше, чем практическое правило, от которого можно отказаться, если вы в конечном итоге найдете способ сделать свое дело более читабельным и понятным способом. Руководящие принципы - это инструменты, в конце концов, ничего больше.
Т. Сар
@ T.Sar Есть так много вещей, которые вы не можете продать клиенту ... С другой стороны, я согласен с вами в том, что нельзя делать вещи, которые умаляют цели проекта.
Тулаинс Кордова
9

Принцип Open-Closed - это скорее показатель того, насколько хорошо разработано ваше программное обеспечение ; не принцип, чтобы следовать буквально. Это также принцип, который помогает нам избежать случайного изменения существующих интерфейсов (классов и методов, которые вы вызываете, и того, как вы ожидаете, что они будут работать).

Цель состоит в том, чтобы написать качественное программное обеспечение. Одним из этих качеств является расширяемость. Это означает, что можно легко добавлять, удалять и изменять код, причем эти изменения, как правило, ограничиваются минимальным количеством существующих классов. Добавление нового кода менее рискованно, чем изменение существующего кода, поэтому в этом отношении полезно сделать Open-Closed. Но о каком коде мы говорим? Преступление нарушения OC намного меньше, когда вы можете добавлять новые методы в класс вместо необходимости изменять существующие.

OC это фрактал . Это яблоки на всех глубинах вашего дизайна. Все предполагают, что это применяется только на уровне класса. Но это одинаково применимо на уровне метода или на уровне сборки.

Слишком частое нарушение ОК на соответствующем уровне наводит на мысль, что, возможно, пришло время провести рефакторинг . «Соответствующий уровень» - это суждение, которое имеет отношение к вашему общему дизайну.

Следование Open-Closed буквально означает, что количество классов взорвется. Вы будете создавать (заглавная буква "I") интерфейсы без необходимости. В итоге вы получите кусочки функциональности, распределенные по классам, а затем вам придется написать намного больше кода, чтобы связать все это вместе. В какой-то момент вы поймете, что изменение исходного класса было бы лучше.

radarbob
источник
2
«Преступление в нарушении OC гораздо меньше, когда вы можете добавлять новые методы в класс вместо необходимости изменять существующие». Насколько я понимаю, добавление новых методов вообще не нарушает принцип OC (открытый для расширения). , Проблема заключается в изменении существующих методов, которые реализуют четко определенный интерфейс и, следовательно, уже имеют четко определенную семантику (закрыто для модификации). В принципе, рефакторинг не меняет семантику, поэтому единственный риск, который я вижу, - это появление ошибок в уже стабильном и хорошо протестированном коде.
Джорджио
1
Вот ответ CodeReview, который иллюстрирует открытый для расширения . Дизайн этого класса расширяем. Напротив, добавление метода изменяет класс.
radarbob
Добавление новых методов нарушает LSP, а не OCP.
Тулаинс Кордова
1
Добавление новых методов не нарушает LSP. Если вы добавите метод, вы ввели новый интерфейс @ TulainsCórdova
RubberDuck
6

Принцип Open-Closed, кажется, является принципом, который появился до того, как TDD стал более распространенным. Идея заключается в том, что рефакторинг кода рискованно, потому что вы можете что-то сломать, поэтому безопаснее оставить существующий код как есть и просто добавить к нему. В отсутствие тестов это имеет смысл. Недостатком этого подхода является атрофия кода. Каждый раз, когда вы расширяете класс, а не проводите его рефакторинг, вы получаете дополнительный слой. Вы просто набираете код сверху. Каждый раз, когда вы набираете больше кода, вы увеличиваете вероятность дублирования. Представить; в моей кодовой базе есть служба, которую я хочу использовать, я нахожу, что у нее нет того, что я хочу, поэтому я создаю новый класс, чтобы расширить его и включить мои новые функциональные возможности. Другой разработчик приходит позже и также хочет использовать тот же сервис. К сожалению, они не Я не понимаю, что моя расширенная версия существует. Они кодируют исходную реализацию, но им также нужна одна из функций, которые я кодировал. Вместо использования моей версии они теперь также расширяют реализацию и добавляют новую функцию. Теперь у нас есть 3 класса, оригинальная и две новые версии, которые имеют дублированные функциональные возможности. Следуйте принципу «открыто / закрыто», и это дублирование будет накапливаться в течение всего жизненного цикла проекта, приводя к ненужно сложной кодовой базе.

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

opsb
источник
1
Я не сторонник принципа открытого-закрытого или TDD (в том смысле, что я их не изобрел). Что меня удивило, так это то, что кто-то предложил принцип «открыто-закрыто» И использование рефакторинга И TDD одновременно. Мне это казалось противоречивым, и поэтому я пытался понять, как объединить все эти руководящие принципы в последовательный процесс.
Джорджио
«Идея заключается в том, что реорганизовывать код рискованно, потому что вы можете что-то сломать, поэтому безопаснее оставить существующий код как есть и просто добавить его». На самом деле я не вижу этого таким образом. Идея состоит в том, чтобы иметь небольшие автономные блоки, которые вы можете заменить или расширить (что позволяет программному обеспечению развиваться), но вам не следует прикасаться к каждому блоку после его тщательного тестирования.
Джорджио
Вы должны думать, что класс будет использоваться не только в вашей кодовой базе. Библиотека, которую вы пишете, может быть использована в других проектах. Так что OCP важен. Кроме того, новый программист, не знающий расширяющего класса с необходимой ему функциональностью, является проблемой связи / документирования, а не проблемой проектирования.
Тулаинс Кордова
@ TulainsCórdova в коде приложения это не актуально. Я бы сказал, что для библиотечного кода семантическое управление версиями лучше подходит для сообщения о критических изменениях.
opsb
1
@ TulainsCórdova со стабильностью API кода библиотеки гораздо важнее, потому что невозможно протестировать код клиента. С кодом приложения ваше тестовое покрытие немедленно сообщит вам о любых поломках. Иными словами, код приложения может вносить критические изменения без риска, тогда как библиотечный код должен управлять риском, поддерживая стабильный API и сигнализируя об ошибках, используя, например, семантическое управление версиями
opsb
6

По словам непрофессионала:

A. O / C принцип означает , что специализация должна быть сделана путем расширения, не изменяя класс для размещения для специализированных нужд.

Б. Добавление отсутствующей (не специализированной) функциональности означает, что дизайн не был завершен, и вы должны добавить его в базовый класс, очевидно, не нарушая контракт. Я думаю, что это не нарушает принцип.

C. Рефакторинг не нарушает принцип.

Когда дизайн созревает , скажем, через некоторое время в производстве:

  • Для этого должно быть очень мало причин (точка B), стремящихся к нулю с течением времени.
  • (Точка C) всегда будет возможно, хотя и более редко.
  • Все новые функциональные возможности должны быть специализацией, то есть классы должны быть расширены (унаследованы от) (точка A).
Тулаинс Кордова
источник
Принцип открытия / закрытия очень неправильно понят. Ваши очки A и B получают это точно.
gnasher729
1

Для меня принцип Open-Closed - это руководство, а не жесткое и быстрое правило.

Что касается открытой части принципа, конечные классы в Java и классы в C ++ со всеми конструкторами, объявленными как private, нарушают открытую часть принципа open-closed. Для финальных классов есть хорошие сценарии использования (примечание: твердые, а не твердые). Проектирование для расширяемости важно. Тем не менее, это требует значительного предвидения и усилий, и вы всегда обходите линию нарушения YAGNI (вам это не понадобится) и привносят в код запах спекулятивной общности. Должны ли ключевые программные компоненты быть открыты для расширения? Да. Все? Это само по себе является умозрительной общностью.

Что касается закрытой части, то при переходе с версии 2.0 на 2.1 до 2.2 на 2.3 какого-либо продукта не рекомендуется изменять поведение. Пользователям действительно не нравится, когда каждый второстепенный выпуск нарушает их собственный код. Однако по пути часто обнаруживается, что первоначальная реализация в версии 2.0 была принципиально нарушена или что внешние ограничения, ограничивающие первоначальный дизайн, больше не применяются. Вы смеете над этим и поддерживаете этот дизайн в выпуске 3.0, или вы делаете 3.0 не обратно совместимым в некотором отношении? Обратная совместимость может быть огромным ограничением. Главные границы выпуска - это место, где допустимо нарушение обратной совместимости. Вы должны знать, что это может расстроить ваших пользователей. Должно быть хорошее обоснование того, почему необходим этот разрыв с прошлым.

Дэвид Хаммен
источник
0

Рефакторинг по определению меняет структуру кода без изменения поведения. Поэтому при рефакторинге вы не добавляете новые функции.

То, что вы сделали в качестве примера для принципа Open Close, звучит нормально. Этот принцип заключается в расширении существующего кода новыми функциями.

Однако не поймите этот ответ неправильно. Я не имею в виду, что вы должны выполнять функции или выполнять рефакторинг только для больших порций данных. Наиболее распространенный способ программирования - это сделать что-то особенное, чем сразу же выполнить небольшой рефакторинг (конечно, в сочетании с тестами, чтобы убедиться, что вы не изменили поведение). Сложный рефакторинг не означает «большой» рефакторинг, это означает применение сложных и продуманных методов рефакторинга.

О ТВЕРДЫХ принципах. Это действительно хорошие рекомендации для разработки программного обеспечения, но они не являются религиозными правилами, которым нужно слепо следовать. Иногда, после добавления второй, третьей и n-й функций, вы понимаете, что ваш первоначальный дизайн, даже если он учитывает Open-Close, не учитывает другие принципы или требования к программному обеспечению. Существуют моменты в развитии дизайна и программного обеспечения, когда необходимо сделать более сложные изменения. Все дело в том, чтобы как можно скорее найти и реализовать эти проблемы и как можно лучше применять методы рефакторинга.

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

Я надеюсь, что этот ответ помог вам в вашей дилемме. Не стесняйтесь просить разъяснений, если это необходимо.

Паткос Чаба
источник
1
«Поэтому, когда вы проводите рефакторинг, вы не добавляете новые функции.», Но я могу вносить ошибки в тестируемое программное обеспечение.
Джорджио
«Иногда, много раз, после добавления второй, третьей и n-й функций, вы понимаете, что ваш первоначальный дизайн, даже если он уважает Open-Close, не уважает другие принципы или требования к программному обеспечению». начните писать новую реализацию Bи, когда она будет готова, замените старую реализацию Aновой реализацией B(это одно из применений интерфейсов). AКод может служить основой для Bкода, и тогда я могу использовать рефакторинг Bкода во время его разработки, но я думаю, что уже протестированный Aкод должен оставаться замороженным.
Джорджио
@Giorgio Когда вы проводите рефакторинг, вы можете вносить ошибки, поэтому вы пишете тесты (или еще лучше - TDD). Самый безопасный способ рефакторинга - это изменить код, когда вы знаете, что он работает. Вы знаете это, имея набор проходящих тестов. После того, как вы измените свой рабочий код, тесты все равно должны пройти, так что вы знаете, что не вносили ошибку. И помните, что тесты так же важны, как и рабочий код, поэтому вы применяете к ним то же правило, что и к рабочему коду, сохраняете их в чистоте и периодически и часто проводите рефакторинг.
Паткос Чаба
@ Джорджио Если код Bпостроен на коде Aкак эволюция A, то, когда Bон выпущен, его Aследует удалить и никогда больше не использовать. Клиенты, которые ранее использовали, Aбудут просто использовать, Bне зная об изменении, так как интерфейс Iне был изменен (может быть, здесь немного принципа подстановки Лискова? ... L из SOLID)
Patkos Csaba
Да, это то, что я имел в виду: не выбрасывайте рабочий код, пока у вас не будет действительной (хорошо проверенной) замены.
Джорджио
-1

Насколько я понимаю - если вы добавите новые методы в существующий класс, то это не сломает OCP. однако я немного запутался с добавлением новых переменных в класс. Но если вы измените существующий метод и параметры в существующем методе, то он обязательно нарушит OCP, потому что код уже протестирован и передан, если мы намеренно изменим метод [При изменении требования], тогда это будет проблемой.

Нарендер Пармар
источник