Нужно ли нам придерживаться принципа Открыто-Закрыто, если у нас хорошее тестовое покрытие?

Почему принцип Открыто-Закрыто так важен? Если у вас есть тесты, почему вы должны беспокоиться о модификации кода?

3 ответа

Если у вас есть тесты, почему вы должны беспокоиться о модификации кода?

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

class NumberOperations {
   public void add(Number a,Number b) {
      Number result = null;
      if(a instanceof Integer && b instanceof Integer) {
         Integer aInt = (Integer)a;
         Integer bInt = (Integer)a;
         result = aInt + bInt;
      } else {
          //failure/error : only integer operations supported.
      }
      return result;
   }
}

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

public void add(Number a,Number b) {
    Number result = null;
    if(a instanceof Integer && b instanceof Integer) {
        Integer aInt = (Integer)a;
        Integer bInt = (Integer)a;
        result = aInt + bInt;
    } else if(a instanceof Double && b instanceof Double) {
        Double aDecimal = (Double)a;
        Double bDecimal = (Double)b;
        result = aDecimal + bDecimal ;
    } else {
      //failure/error : only integer and double operations supported.
    }
    return result;
}

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

Если вы придерживались принципа открытого-закрытого типа, у вас могут быть подклассы IntegerOperations а также DoubleOperations простираясь от NumberOperations (который теперь может быть абстрактным), которые реализуют add метод. Код внутри первого if в add показанный выше метод может быть перемещен в add метод в IntegerOperations класс и код в else-if может быть перемещен в add метод в DoubleOperations учебный класс. Если ваш язык поддерживает дженерики, вы можете даже избавиться от проверок типов, показанных в приведенном выше примере.

Затем вы тестируете эти два новых класса по отдельности. Когда вы получите требование добавить два Strings, вы создаете новый класс с именем StringOperations, Вам не нужно беспокоиться о каких-либо нежелательных изменениях, вносимых в IntegerOperations или же DoubleOperations и, следовательно, не нужно проверять их снова. Вам нужно только проверить StringOperations учебный класс.

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

Конечно, если у вас действительно хорошие системные тесты, вы можете быть уверены, что ваш код по-прежнему работает даже с большими, принципиальными изменениями, поэтому, несмотря на то, что, по вашему мнению, некоторые принципиальные фанатики думают, этот принцип не является необходимостью - но это практичности. Вам не нужно его использовать, но, как правило, вы сами все упростите. Полезная практичность с принципом O/C заключается в возможности расширения существующего кода для соответствия новым требованиям, не рискуя нарушить существующие применения этого кода. Даже если вам не нужно тратить время на исправление существующего кода, потому что изменения не сломали его, вам все равно придется повторно его протестировать.

При рассмотрении существующих тестов (и я считаю, что это относится больше к модульным тестам, чем к системным тестам), вы должны учитывать, будут ли изменения, противоречащие закрытой части принципа o / c, нарушать или аннулировать существующие тесты. Если вы нарушаете существующие тесты со сменой интерфейса или изменением необходимых предварительных условий, вы не сможете по-настоящему использовать одни и те же тесты как в старых, так и в новых реализациях (ваши новые тесты могут основываться на старых, но не будут точно такими же). Возможно, более тревожным (просто потому, что это не станет очевидным, как в случае с неработающими тестами) является то, что изменения могут означать, что должны быть некоторые дополнительные тесты, которых еще нет - возможно, вы ввели новые особые случаи. Изменения могут иметь непреднамеренные побочные эффекты, которые не проверены.

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

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

В идеальном мире у вас были бы тесты, которые покрывали бы 100% базы кода, и вы могли бы также измерить это с помощью инструментов. Однако ни то, ни другое не является правдой. Получить покрытие 100% и убедиться, что покрытие 100% все еще работает, когда приложению исполнилось пять лет, очень сложно. У вас может не быть времени, необходимого для его достижения перед выпуском. У вас есть другие люди в команде, которые не так строги. У вас может быть просто крайний срок, который нужно соблюдать. Инструменты, которые показывают 100%, обычно понимают, что строка кода была достигнута одной ветвью выполнения, в то время как в другой, где она также может быть достигнута, не были выполнены.

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

Принципы SOLID и другие передовые практики и принципы помогают вам, так как мир не совершенен. Но, следуя принципам, таким как принцип Open/Closed, вы даете своему будущему себе гораздо больше шансов справиться с вашей кодовой базой. Эти принципы были определены опытными людьми, которые на собственном опыте узнали, что работает, а что нет.

Другие вопросы по тегам