Данная статья представляет собой перевод материала OCP против ЯГНИ .
В этом посте я бы хотел осветить тему OCP и YAGNI — противоречие между принципом открытости/закрытости и принципом «вам это не понадобится».
ОКП
Начнем с того, что вспомним, что такое OCP. Принцип открытости/закрытости гласит, что: Программные объекты (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для модификации.
Впервые он был представлен Бертраном Мейером в его канонической книге « Проектирование объектно-ориентированного программного обеспечения «С тех пор его популяризировал Боб Мартин, когда он представил принципы ТВЕРДЫЙ .
Официальное определение довольно расплывчато и не помогает нам понять основной смысл.
Итак, давайте углубимся в этот принцип.
В настоящее время существует две интерпретации этого принципа: Бертран Мейер и Боб Мартин.
Интерпретация Боба Мартина сводится к тому, чтобы избежать волновых эффектов.
То есть, когда вы меняете часть кода, вам не нужно вносить изменения во всю кодовую базу, чтобы учесть это изменение.
В идеале вы должны иметь возможность добавлять новые функции, ничего не меняя в существующем коде.
Принцип рекомендует закрыть исходный модуль (класс, метод и т. д.) на модификацию и вместо этого открыть в нем точку расширения.
Эта точка расширения позволит вам внедрить новую функциональность без изменения существующей базы кода.
Обычно это реализуется с помощью полиморфизма.
Например, следующий фрагмент кода нарушает версию OCP Боба Мартина:
Здесь, чтобы ввести новую фигуру, нужно будет изменить метод Рисовать .public void Draw(Shape shape) { switch (shape.Type) { case ShapeType.Circle: DrawCircle(shape); break; case ShapeType.Square: DrawSquare(shape); break; default: throw new ArgumentOutOfRangeException(); } }
Можно сказать, что добавление новой формы будет распространяться на существующую кодовую базу в том смысле, что вам нужно будет изменить приведенный выше оператор.
выключатель .
Чтобы исправить это, вы можете создать абстрактный класс.
Форма а затем переместите логику рисования в ее подклассы: public abstract class Shape
{
public abstract void Draw();
}
public class Circle : Shape
{
public override void Draw()
{
/* … */
}
}
/* etc. */
Теперь, если вам нужно добавить новую фигуру, вы просто создаете подкласс и переопределяете метод. Рисовать .
Используя терминологию OCP, вы закрыли класс.
Форма на модификацию и открыл в нем точку расширения.
Эту точку расширения можно использовать для создания новых функций без изменения существующего кода.
Первоначальное намерение Бертрана Мейера, лежащее в основе этого принципа, было иным.
В то время как интерпретация Боба Мартина направлена на сокращение количества изменений, Бертран Мейер говорит об обратной совместимости.
Когда имеется несколько взаимозависимых модулей, каждый из которых разработан отдельной командой программистов, вам нужно сделать некоторые вещи, чтобы они заработали.
Вы не можете просто изменить свой модуль.
Вы должны учитывать его клиентов.
Например, если у вас есть библиотека, предоставляющая метод CreateCustomer(string email)
В него нельзя в любой момент добавить новый обязательный параметр, например: CreateCustomer(string email, string bankAccountNumber)
Это было бы кардинальным изменением для клиентского кода, который уже привязан к исходной версии метода.
По сути, это проблема, которую Бертран Мейер пытался решить с помощью принципа OCP. Во время разработки ваш модуль открыт для модификаций, поскольку никто еще не занимался его разработкой.
Но как только вы его опубликуете, вам нужно будет прекратить вносить какие-либо изменения и закрыть его API, чтобы он всегда оставался совместимым с существующими клиентами.
Если вам нужно внести изменения после публикации, вы можете сделать это, создав новый модуль.
Обратите внимание, что Бертран Мейер говорит здесь конкретно об API, а не о фактической реализации модуля.
Вы все равно можете изменить реализацию, если это не повлияет на API модуля.
Другими словами, исправления ошибок и изменения, которые не нарушают обратную совместимость, приемлемы, но изменение сигнатур методов и требование новых предварительных условий — нет. Вот полный список того, что такое API:
- Сигнатура метода: имя, параметры, возвращаемое значение.
- Предварительные условия: список требований, которым должны соответствовать клиенты, прежде чем они смогут использовать метод. Примером такого предварительного условия может быть требование создания строкового параметра.
электронная почта в некотором роде.
- Постусловия: список гарантий, которые предоставляет модуль.
Примером может служить обещание отправить поздравительное электронное письмо вновь созданному клиенту.
- Инварианты: список условий, которые всегда должны быть истинными.
Единственным типом совместимости, который был актуален в то время, когда Мейер писал об этом принципе, была бинарная совместимость.
Это происходит, когда у вас две библиотеки и одной из них нужно использовать другую без перекомпиляции и без внесения изменений.
Однако этот тип совместимости все еще может применяться сегодня.
Все вопросы, связанные с управлением версиями веб-API, по сути, связаны с принципом OCP Мейера, применяемым к крупномасштабным проектам.
Если у вас есть Микросервис 1, который зависит от Микросервиса 2, вы не можете внести критические изменения в Микросервис 2, для таких изменений его API должен быть закрыт. Но вы все равно можете создать новую версию и предоставить существующим клиентам выбор: остаться со старой версией или перейти на новую.
Еще один важный момент, который следует отметить, заключается в том, что версия OCP Мейера имеет смысл только в контексте нескольких команд разработчиков, где каждый модуль разрабатывается разными командами.
В типичной корпоративной программной среде, где вы являетесь одновременно автором и клиентом написанного вами кода, нет необходимости придерживаться таких сложных методов.
Вам не нужно закрывать свой код, поскольку у вас есть все необходимое для исправления любых критических изменений, которые вы можете внести.
Только когда вы опубликуете свой модуль/библиотеку/сервис и сделаете его доступным для других команд, вам следует фактически закрыть его API. В противном случае нарушение изменений не является проблемой.
Более подробно эту тему автор затронул в одной из своих предыдущих статей: Общая библиотека и корпоративная разработкаИтак, две разновидности OCP, несмотря на одинаковое название, различаются по своему смыслу.
Это важно для нашего обсуждения противостояния OCP и YAGNI. Код, который был указан ранее: public void Draw(Shape shape)
{
switch (shape.Type)
{
case ShapeType.Circle:
DrawCircle(shape);
break;
case ShapeType.Square:
DrawSquare(shape);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
нарушает интерпретацию OCP Мартина, но не противоречит интерпретации Мейера.
Это связано с тем, что добавление новых фигур не требует от нас изменения метода API. Рисовать .
Все существующие клиенты останутся без изменений, кардинальных изменений не будет. В этом смысле интерпретация Боба Мартина шире.
Его цель — сократить количество изменений в целом, чтобы иметь возможность расширить поведение программного обеспечения, внося небольшие изменения или вообще не внося их в исходный код. Интерпретация Бертрана Мейера направлена только на сокращение критических изменений: изменений, которые могут вызвать проблемы, когда несколько команд работают вместе.
Ягни
YAGNI означает «Вам это не понадобится» и, по сути, означает, что вам не следует тратить время на функциональность, которая вам не нужна прямо сейчас.Вам не следует разрабатывать эту функциональность, а также изменять существующий код для обеспечения будущих функций.
Вот два основных момента, которые объясняют, почему это хорошая идея:
- Требования бизнеса постоянно меняются.
Если вы тратите время на функцию, которая сейчас не нужна бизнесу, вы крадете время у функций, которые ему нужны прямо сейчас.
Более того, когда бизнесу наконец-то понадобится разработанный функционал, взгляд на него, скорее всего, изменится и вам все равно придется вносить в него изменения.
Это занятие расточительно и приводит к чистому убытку, поскольку выгоднее было бы просто реализовать функцию с нуля, когда возникнет реальная необходимость.
- Ваш код — это не актив, а пассив.
Предпочтительно иметь меньше кода, поскольку любой дополнительный код увеличивает затраты на обслуживание.
Введение кода «на всякий случай», когда в этом нет необходимости, увеличивает общую стоимость поддержки всей кодовой базы.
Помните, что вам нужно будет провести рефакторинг этой дополнительной части, исправить ошибки (если они возникнут), покрыть тестами и так далее.
Предпочтительно отложить внедрение новых функций до как можно более поздней стадии проекта.
Вы можете сломать YAGNI, если разрабатываете функциональность, которую сложно изменить в будущем.
К ним относятся клиентские API, сторонние библиотеки, фундаментальные архитектурные решения, пользовательские интерфейсы (UI) (их может быть сложно изменить, поскольку пользователи не хотят принимать новый внешний вид).
В таких ситуациях стоит потратить некоторое время, чтобы попытаться предсказать, как решения, которые вы принимаете сейчас, повлияют на будущую функциональность.
Например, рекомендуется заранее инвестировать в правильную систему управления версиями веб-API, поскольку после ее публикации изменить ее будет невозможно.
Аналогично, метод или класс, ориентированный на потребителя, в общедоступной библиотеке должен оставаться там для обеспечения обратной совместимости, даже если вы решите, что он больше не нужен.
Такие вещи сложно изменить.
Другими словами, если решение, которое вы собираетесь принять, станет чем-то высеченным в камне, YAGNI не будет применяться.
В этом случае нужно учитывать возможные будущие требования.
Однако рекомендуется принимать как можно меньше таких решений.
По крайней мере, постарайтесь сохранить их на более поздний этап.
Таким образом вы сможете собрать больше информации о реальных потребностях бизнеса.
Также имейте в виду, что большинство решений, которые вы принимаете, не являются одними из них, их можно довольно легко изменить.
YAGNI применяется к большей части кода, который мы пишем каждый день.
OCP против ЯГНИ
Обратите внимание, что YAGNI не только рекомендует не реализовывать неиспользуемые функции, но и запрещает изменять существующие функции для размещения возможных новых функций в будущем.В этом противоречие.
Такой «учёт возможных новых функций в будущем» — это именно то, что предлагает версия OCP Боба Мартина.
Давайте еще раз посмотрим на метод Рисовать : public void Draw(Shape shape)
{
switch (shape.Type)
{
case ShapeType.Circle:
DrawCircle(shape);
break;
case ShapeType.Square:
DrawSquare(shape);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
С одной стороны у нас есть ЯГНИ, где написано, что этот оператор выключатель приемлемо, если полученный код прост и удобен для понимания и сопровождения.
С другой стороны, у нас есть OCP Боба Мартина, в котором написано, что нам нужно иметь возможность расширять его без изменения исходного кода, то есть без изменения самого оператора.
выключатель .
Какой из этих принципов имеет более высокий приоритет? Чтобы ответить на этот вопрос, вернемся назад. Обратите внимание, что речь идет о споре между YAGNI и OCP Боба Мартина, а не о его версии Бертрана Мейера.
Это потому, что ЯГНИ последнему не противоречит, они в принципе о разных вещах говорят. Что касается версии Боба Мартина, то ее можно рассматривать с двух разных точек зрения.
Первый — когда вы одновременно являетесь автором и клиентом написанного кода.
Именно в такой ситуации оказывается большинство разработчиков корпоративных приложений.
Вторая — когда вам необходимо опубликовать свой код для внешнего использования.
Типичными примерами являются пакет или платформа NuGet. YAGNI превосходит OCP, когда у вас есть полный контроль над использованием кода.
Почему? Потому что ЯГНИ вместе с ЦЕЛОВАТЬ , является самый важный принцип в разработке программного обеспечения .
Следование этому должно быть главным приоритетом любого программного проекта.
Это также имеет смысл, если вы присмотритесь к OCP Боба Мартина.
Почему вам следует добавлять точки расширения в свой код преждевременно, даже если это вносит чрезмерную сложность? Действительно ли стоит затраченных усилий и дополнительных затрат на замену простого оператора? выключатель в отдельную иерархию классов? Конечно, нет. Гораздо лучше закладывать эти точки расширения постфактум, когда у вас уже есть полная картина и когда вы видите, что оператор выключатель стал слишком раздутым.
В этом случае вы можете применить рефакторинг и извлечь эту иерархию классов.
Но не раньше, чем необходимость в этом станет очевидной.
Теперь другая ситуация, когда вы не контролируете использование вашего кода.
В данном случае, как говорилось ранее, YAGNI неприменим, поскольку стоимость изменения уже реализованного функционала слишком высока.
Вы не можете так легко реорганизовать свой код, потому что вы не единственный его потребитель.
В этой ситуации вам действительно необходимо выявить потенциальные точки вариаций и создать вокруг них интерфейс, который позволит потребителям расширять ваши классы, а не изменять их.
Итак, в приведенном выше примере кода, если вы ожидаете, что Рисовать будет методом, ориентированным на потребителя, и если вы хотите предоставить средства для его расширения, рекомендуется заранее заменить его базовым классом.
Форма и позвольте вашим потребителям создавать свои собственные формы.
Версия OCP Боба Мартина имеет гораздо больше смысла, если поместить ее в рамки исходной точки зрения Бертрана Мейера.
Точки расширения следует включать только в том случае, если вам необходимо в той или иной форме предоставить свой код для внешнего использования.
В противном случае придерживайтесь YAGNI и не добавляйте дополнительную гибкость, если это действительно не необходимо.
Краткое содержание
Существует две интерпретации принципа открытости/закрытости:- Оригинальная интерпретация Бертрана Мейера касается обратной совместимости.
Вам необходимо закрыть API вашего модуля/библиотеки/сервиса, если он предназначен для внешнего использования.
- Идея Боба Мартина состоит в том, чтобы избежать волнового эффекта: вы должны иметь возможность расширить поведение программного обеспечения, незначительно изменяя исходный код или не изменяя его вообще.
Это достигается путем добавления точек расширения в базу кода.
YAGNI противоречит версии OCP Боба Мартина.
Противоречие разрешается, если противопоставить OCP Боба Мартина исходной точке зрения Мейера.
То есть, если вы применяете этот принцип только тогда, когда ваш код используется внешними командами.
YAGNI превосходит OCP Боба Мартина, когда вы являетесь единственным потребителем своего кода (разработка корпоративного программного обеспечения).
OCP Боба Мартина превосходит YAGNI, когда вы не единственный потребитель вашего кода.
Теги: #программирование #Системный анализ и проектирование #ООП #проектирование #проектирование и рефакторинг #рефакторинг #Идеальный код #yagni #ocp
-
Php-Дайджест № 162 (1–12 Августа 2019 Г.)
19 Oct, 24 -
Goquests На Kickstarter
19 Oct, 24 -
Робот Имитирует Поведение Лабораторной Крысы
19 Oct, 24 -
Понимание C Путем Изучения Ассемблера
19 Oct, 24