Мне трудно описать эту проблему. Может быть, поэтому мне трудно найти хорошее решение (слова просто не сотрудничают). Позвольте мне объяснить через код:
// original code
enum Fruit
{
Apple,
Orange,
Banana,
}
...
Fruit fruit = acquireFruit();
if (fruit != Fruit.Orange && fruit != Fruit.Banana)
coreFruit();
else
pealFruit();
eatFruit();
Теперь притворимся, что годы развития идут с этими тремя типами. Различные разновидности вышеуказанной логики распространяются в хранимых процедурах, пакетах служб SSIS, приложениях Windows, веб-приложениях, приложениях Java, сценариях Perl и т. Д. ...
Наконец:
// new code
enum Fruit
{
Apple,
Orange,
Banana,
Grape,
}
В большинстве случаев "система" работает нормально, пока не будет использован виноград. Затем части системы действуют неадекватно, очищая и / или отбирая виноград, когда он не нужен или нежелателен.
Каких правил вы придерживаетесь, чтобы избежать этих беспорядков? Я предпочитаю, чтобы старый код генерировал исключение, если он не был реорганизован для рассмотрения новых перечислений.
Я сделал снимок в темноте:
# 1 Избегайте «Не в логике», как это
// select fruit that needs to be cored
select Fruit from FruitBasket where FruitType not in(Orange, Banana)
# 2 При необходимости используйте тщательно сконструированные методы NotIn ()
internal static class EnumSafetyExtensions
{
/* By adding enums to these methods, you certify that 1.) ALL the logic inside this assembly is aware of the
* new enum value and 2.) ALL the new scenarios introduced with this new enum have been accounted for.
* Adding new enums to an IsNot() method without without carefully examining every reference will result in failure. */
public static bool IsNot(this SalesOrderType target, params SalesOrderType[] setb)
{
// SetA = known values - SetB
List<SalesOrderType> seta = new List<SalesOrderType>
{
SalesOrderType.Allowance,
SalesOrderType.NonAllowance,
SalesOrderType.CompanyOrder,
SalesOrderType.PersonalPurchase,
SalesOrderType.Allotment,
};
setb.ForEach(o => seta.Remove(o));
// if target is in SetA, target is not in SetB
if (seta.Contains(target))
return true;
// if target is in SetB, target is not not in SetB
if (setb.Contains(target))
return false;
// if the target is not in seta (the considered values minus the query values) and the target isn't in setb
// (the query values), then we've got a problem. We've encountered a value that this assembly does not support.
throw new InvalidOperationException("Unconsidered Value detected: SalesOrderType." + target.ToString());
}
}
Теперь я могу безопасно использовать код, подобный этому:
bool needsCoring = fruit.IsNot(Fruit.Orange, Fruit.Banana);
Если этот код будет распространен по всей системе, исключения будут выброшены, когда Виноградная Лета прибудет в город (qa поймает их всех).
Так или иначе, это план. Кажется, что проблема должна быть очень распространенной, но я не могу найти что-либо в Google (возможно, по моей вине).
Как вы все справляетесь с этим?
UPDATE:
Я чувствую, что ответом на эту проблему является создание механизма «лови все остальное», который останавливает обработку и предупреждает тестировщиков и разработчиков о том факте, что новое перечисление требует рассмотрения. "switch ... default" отлично подходит, если он у вас есть.
Если в C # нет переключателя ... по умолчанию, мы можем исправить приведенный выше код следующим образом:
Fruit fruit = acquireFruit();
if (fruit != Fruit.Orange && fruit != Fruit.Banana)
coreFruit();
else if(fruit == Fruit.Apple)
pealFruit();
else
throw new NotSupportedException("Unknown Fruit:" + fruit)
eatFruit();
ПРЕДУПРЕЖДЕНИЕ:
Вы действительно не должны использовать ни один из указанных выше псевдокодов. Он может (?) Компилироваться или даже работать, но на самом деле это ужасный код. Я видел много хороших решений в этой теме, если вы ищете подход на основе ООП. Хорошее решение, конечно, помещает все переключения и проверки в централизованный метод (заводской метод - это то, что меня поражает). Кроме того, потребуется также сверка кода.