Когда ваш код обрабатывает несколько заранее определённых опций (таких, как, например, статус чего-либо) не забывайте, что список этих опций может вырасти в будущем, так что вы (или другие разработчики) можете забыть обработать новорожденную опцию и таким образом будет порождён скрытый баг. Но мы можем избежать этой неприятности если сам код сообщит о проблеме, заставляя подумать что делать с опцией, которой раньше не было.
Для этого необходимо сделать две вещи:
1. В конструкции ветвления (switch) должны быть обработаны абсолютно все опции, существующие в системе в настоящий момент. Если какие-то опции не требуют специальной обработки, создайте для них case-пустышку с комментарием "do nothing", "irrelevant" или "not applicable" чтоб читающие код поняли, что так задумано.
2. Добавьте секцию default - её задачей будет просигнализировать об опции, у которой нет обработчика (запустить exception, высветить сообщение об ошибке - смотря как ваша система обрабатывает нештатные ситуации).
Взгляните на два следующих фрагмента. В первом примере я использую 4 статуса клиента фирмы: Active, Pending, Inactive and Deleted, но только статусы Active и Pending обрабатываются в бизнес-логике:
*** Не рекомендуется (статусы Inactive и Pending даже не упомянуты...): ***
Код: Выделить всё
switch (custStatus)
{
case CustStatus.Active:
[code fragment processing active customers]
break;
case CustStatus.Pending:
[code fragment processing pending customers]
break;
}
*** Рекомендуется: ***
Код: Выделить всё
switch (custStatus)
{
case CustStatus.Active:
[code fragment processing active customers]
break;
case CustStatus.Pending:
[code fragment processing pending customers]
break;
case CustStatus.Inactive:
case CustStatus.Deleted:
// do nothing
break;
default:
throw new Exception ("No case defined for customer status " + custStatus);
}
Так что если новый статус (например, Deceased, не про нас будь сказано...) будет добавлен в бизнес в будущем (даже через много лет!), фрагмент кода сам заставит программистов подумать как поступить. Причём это произойдёт на довольно ранней стадии разработки или юнит-теста! Но даже если ошибка выскочит в работающей системе (продакшене), это лучше, чем жить со скрытым багом (который потенциально может дорого обойтись). Может, новый статус должен быть обработан специальным образом и программисту следует решить как именно (или посоветоваться с бизнес-аналитиком). А если специальной обработки не требуется, то новый статус нужно поместить в секцию с "do nothing".
Никогда не пишите бизнес-код в секции default - она предназначена только для перехвата ошибок!
Описанный способ имеет также дополнительные преимущества:
1. Если требуется найти все места в программе, где используется некое значение (константа или enumerated), то текстовый поиск по системе найдёт ВСЕ фрагменты. Это было бы не так если б бизнес-код был в секциях default (когда название константы не упомянуто) - тогда у Вас был бы хороший шанс кое-что прошляпить...
2. Глядя на конструкцию switch программист видит полную картину, а не её фрагмент, и не должен угадывать при каких обстоятельствах программа идёт в секцию else/default. Как говорится, полуправда - хуже лжи...
Мы обсудили конструкцию switch, но та-же концепция работает и для if-ов. Чаще всего мы должны заменить if на switch чтобы предотвратить чудовищное скопление else if-ов:
*** Не рекомендуется: ***
Код: Выделить всё
if (_currEntity == Entity.Car)
{
this.Retrieve(_carId);
}
else
{
this.Retrieve(_bikeId);
}
*** Рекомендуется (if заменён на switch): ***
Код: Выделить всё
switch (_currEntity)
{
case Entity.Car:
this.Retrieve(_carId);
break;
case Entity.Bike:
this.Retrieve(_bikeId);
break;
default:
throw new Exception ("No case defined for entity " + _currEntity);
}
Допустим, в какой-то ситуации может быть два режима и нам нужно действовать по-разному в каждом из них. Само число 2 наводит на мысль, что ситуацию можно прекрасно разрулить с помощью булевой переменной: true - один режим, false - второй, вроде всё прекрасно... Проиллюстрируем ситуацию с помощью класса, экземпляр которого может быть создан как для автомобиля ("автомобильный режим"), так и для мотоцикла ("мотоциклетный режим"). На первый взгляд можно создать логический переключатель _isCarMode и инициализировать его в зависимости от режима, а затем использовать в методах класса следующим образом:
*** Не рекомендуется: ***
Код: Выделить всё
if (_isCarMode)
{
this.Retrieve(_carId);
}
else // Bike Mode
{
this.Retrieve(_bikeId);
}
Однако лучшим решением в этой ситуации будет создать набор констант (или перечисляемый тип - enum), в котором для каждого режима имеется отдельная константа, и производить явное сравнение - так, как сделано в предыдущем фрагменте "Рекомендуется". Ну, о преимуществах этого подхода вы уже знаете.
Конечно, вся эта красота может быть неприменима в определённых ситуациях. Например, если одна из 20 опций имеет нестандартную обработку, то перечисление оставшихся 19-ти может показаться обременительным и загрязняющим код (просто послать программу в else намного легче), так что нужно думать как поступить в каждом конкретном случае.