Если у меня есть полный набор модульных тестов для приложения, должен ли я по-прежнему применять принцип Open / Closed (OCP)? - PullRequest
0 голосов
/ 13 сентября 2009

В статье Википедии об OCP написано (выделено мной):

... принцип открытого / закрытого состояния гласит: «программные объекты (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации» ... Это особенно ценно в производственной среде, где изменяется к исходному коду может потребовать проверки кода, модульные тесты и другие подобные процедуры , чтобы квалифицировать его для использования в продукте: выполнение кода принцип не меняется при его расширении и поэтому не требует таких усилий .

Итак, правильно ли я читаю, что OCP будет полезен, если есть нет автоматического модульного тестирования, но не обязательно, если равен ? Или статья в Википедии неправильная?

Ответы [ 8 ]

5 голосов
/ 13 сентября 2009

Единичные тесты по определению !, о поведении внутри a юнит (как правило, один класс): чтобы выполнить их правильно, вы стараетесь изо всех сил изолировать тестируемое устройство от его взаимодействия с другими устройствами (например, с помощью насмешек, внедрения зависимостей и т. д.).

OCP относится к поведению в единицах («программных объектах»): если объект A использует объект B, он может расширить его, но не может его изменить. (Я думаю, что акцент в статье в Википедии исключительно на изменениях исходного кода неуместен: проблема относится ко всем изменениям, независимо от того, получены ли они с помощью изменений исходного кода или другими средствами времени выполнения).

Если A изменил B в процессе его использования, то на несвязанную сущность C, которая также использует B, впоследствии может быть оказано неблагоприятное воздействие. В этом случае правильные юнит-тесты обычно НЕ улавливают поломку, потому что она не ограничивается юнитом: это зависит от тонкой, специфической последовательности взаимодействий между юнитами, при этом A использует B и , тогда C также пытается используйте B. Интеграционные, регрессионные или приемочные тесты МОГУТ его поймать, но вы никогда не можете положиться на такие тесты, обеспечивающие идеальное покрытие возможных путей кода (это достаточно сложно даже в модульных тестах, чтобы обеспечить идеальное покрытие внутри одного объекта / объекта! 1017 *

Я думаю, что в некотором смысле наиболее яркой иллюстрацией этого является противоречивая практика исправления обезьян , разрешенная в динамических языках и популярная в некоторых сообществах практиков таких языков (не всех! -). Исправление обезьян (MP) - это изменение поведения объекта во время выполнения без изменения его исходного кода, поэтому оно показывает, почему я думаю, что вы не можете объяснить OCP исключительно в терминах изменений исходного кода.

MP хорошо показывает пример, который я только что привел. Модульные тесты для A и C могут каждый проходить с летающими цветами (даже если они оба используют реальный класс B вместо того, чтобы издеваться над ним), потому что каждый блок, по сути, работает нормально; даже если вы тестируете ОБА (так что это уже далеко от тестирования UNIT), но бывает так, что вы тестируете С до А, все выглядит нормально. Но, скажем, A обезьяна исправляет B, устанавливая метод B.foo, чтобы он возвращал 23 (как нужно A) вместо 45 (как документально поставляет B, а C полагается). Теперь это нарушает OCP: B должен быть закрыт для модификации, но A не соблюдает это условие и язык его не применяет. Затем, если A использует (и модифицирует) B, а затем очередь C, C работает в состоянии, в котором он никогда не тестировался - то, где B.foo, недокументировано и удивительно, возвращает 23 (тогда как всегда вернул 45 за все время тестирования ...! -).

Единственная проблема с использованием MP в качестве канонического примера нарушения OCP заключается в том, что оно может породить ложное чувство безопасности среди пользователей языков, которые явно не допускают MP; на самом деле, через файлы конфигурации и опции, базы данных (где каждая реализация SQL допускает ALTER TABLE и т. п .;-), удаленное взаимодействие и т. д. и т. д., каждый достаточно большой и сложный проект должен следить за нарушениями OCP, даже если бы это было написано на Eiffel или Haskell (и тем более, если якобы «статический» язык фактически позволяет программистам вставлять в память все, что они хотят, до тех пор, пока у них есть правильные заклинания приведения, как это делают C и C ++ - теперь ЭТО такую ​​вещь, которую вы определенно хотите отловить в обзорах кода; -).

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

Единственной нишей, в которой я видел «немодифицируемое после выпуска», широко применяемое, являются интерфейсы для моделей компонентов, таких как старый добрый COM от Microsoft - ни один опубликованный COM-интерфейс никогда не может изменяться (так что в итоге вы получите IWhateverEx IWhatever2, IWhateverEx2 и т. П., Когда исправления интерфейса оказываются необходимыми - никогда не меняется на исходный IWhatever! -).

Даже в этом случае гарантированная неизменность применяется только к интерфейсам - реализациям этих интерфейсов всегда разрешено исправлять ошибки, настраивать оптимизацию производительности и т. П. (" сделайте все правильно в первый раз «просто не работает при разработке ПО: если вы можете выпускать программное обеспечение только тогда, когда на 100% уверены, что оно имеет 0 ошибок и максимально возможную и необходимую производительность на каждой платформе, на которой оно когда-либо будет использоваться, вы» никогда не выпускал ничего, соревнование съело бы твой обед, и ты обанкротился бы ;-). Опять же, для исправления и оптимизации таких ошибок, как обычно, нужны обзоры кода, тесты и т. Д.

Я полагаю, что спор в вашей команде вызван не исправлениями ошибок (кто-то спорит за запрет этих ? -) или даже оптимизацией производительности, а скорее вопросом о том, где разместить новые функции - Должны ли мы добавить новый метод foo в существующий класс A или, скорее, расширить A в B и добавить foo только в B, чтобы A оставался «закрытым для модификации»? Модульные тесты сами по себе еще не отвечают на этот вопрос, поскольку они могут не использовать все существующие варианты использования A (A может быть отключено, чтобы изолировать другую сущность, когда эта сущность будет проверена ...), поэтому вам нужно пойти на один уровень глубже и посмотреть, что foo точно означает , или может быть , делая.

Если foo является просто средством доступа и никогда не изменяет экземпляр A, для которого он вызывается, то добавление его явно безопасно; если foo может изменить состояние экземпляра и последующее поведение, наблюдаемое другими существующими методами, , тогда у вас есть проблема. Если вы уважаете OCP и поместите foo в отдельный подкласс, ваши изменения будут очень безопасными и рутинными; если вам нужна простота ввода foo прямо в A, , тогда вам делать нужны подробные обзоры кода, легкие тесты "парной интеграции компонентов", проверяющие все варианты использования A , и так далее. Это не ограничивает ваше архитектурное решение, но оно четко указывает на различные затраты, связанные с любым выбором, поэтому вы можете планировать, оценивать и расставлять приоритеты соответствующим образом.

Дикта и принципы Мейера не являются Священной Книгой, но с должным критическим отношением их очень стоит изучить и обдумать в свете ваших конкретных, конкретных обстоятельств, поэтому я благодарю вас за это в этом случае. ! -)

3 голосов
/ 13 сентября 2009

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

Если единственные пользователи - это вы и ваша собака, конечно, вы можете изменить мужество вне класса, оставаясь при этом очень эффективными и без проблем.

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

  • рискует что-то сломать для кого-то
  • пусть расширяют его
  • раздувает дизайн, расширяя его самостоятельно

выберите лучшее для вашего случая использования.

Как всегда, понимание контекста и компромиссов - вот что делает инженерию интересной. Знайте, когда выбрать правильный инструмент. Бывают случаи, когда OCP не применяется, но это не отменяет его полезность, если вы рассмотрели его и отклонили, потому что это не относится к вашему контексту для A и B.

1 голос
/ 08 октября 2010

Хорошие принципы проектирования (такие как OCP) не дают шансов на хорошие процессы разработки (такие как модульные тесты и TDD). Они дополняют друг друга.

Статья в Википедии, IMO, предполагает , что вы всегда , используя процессы хорошего качества, такие как модульные тесты и обзоры кода (в XP это означает TDD и парное программирование), даже при использовании OCP. Далее говорится, что с OCP вы лучше контролируете область своих изменений , что приводит к меньшим усилиям в этих процессах качества.

0 голосов
/ 14 сентября 2009

В этой статье из C2 Wiki обсуждается напряжение между OCP и XP.

Исходя из некоторых комментариев в этой статье и этой академической диссертации (раздел B.2), ответ на вопрос "с модульными тестами, должен ли OCP все еще применяться?" будет выглядеть как нет .

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

Agile-разработка (с XP или просто TDD), с другой стороны, направлена ​​на воздействие изменений через эволюционный дизайн («Охватить изменения», кто-нибудь?), А не на попытки заранее разработать абстракции. И, как мы все знаем, предварительный дизайн практически не работает на практике.

0 голосов
/ 13 сентября 2009

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

0 голосов
/ 13 сентября 2009

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

Этот вид дизайна можно увидеть во многих местах: (N) перехватчики Hibernate, шаблоны данных WPF и т. Д. И т. Д.:)

0 голосов
/ 13 сентября 2009

Как и большинство идей, выдвинутых Бертраном Мейером, принцип Open / Closed в значительной степени просто ошибочен. Если вашей системе требуются некоторые новые функции, и эта функциональность принадлежит существующему классу, измените этот класс. Не кладите это куда-нибудь еще, чтобы удовлетворить произвольный закон.

0 голосов
/ 13 сентября 2009

Я думаю, что это все еще ценный принцип, даже если у вас есть автоматическое модульное тестирование.

...