Как избежать множественных вложенных IFs
В настоящее время я пытаюсь реструктурировать свою программу, чтобы быть более OO и лучше реализовывать известные шаблоны и т.д.
У меня довольно много вложенных IF-операторов и вы хотите избавиться от них. Как я могу это сделать? Мой первый подход заключался в том, чтобы сделать это с исключениями, например,
public static Boolean MyMethod(String param) {
if (param == null)
throw new NullReferenceException("param may not be null");
if (param.Equals("none") || param.Equals("0") || param.Equals("zero"))
throw new ArgumentNullException("param may not be zero");
// Do some stuff with param
// This is not executed if param is null, as the program stops a soon
// as one of the above exceptions is thrown
}
Метод используется в основном классе приложения, например
static void Main() {
try {
Boolean test = MyClass.MyMethod(null); // Will throw an exception
} catch (Exception ex) {
MessageBox.Show(ex.Message, "Error");
}
Я думаю, что это довольно хорошо, поскольку он предотвращает вложенные инструкции и почти все действия методов хорошо расположены на одном уровне.
Как и в случае с IF-операторами, метод будет выглядеть так:
public Boolean MyMethod(String param) {
if (param != null) {
if (!param.Equals("none") && !param.Equals("0") && !param.Equals("zero")) {
// Do some stuff with param
} else {
MessageBox.Show("param may not be zero", "Error");
} else {
MessageBox.Show("param may not be null", "Error");
}
}
Что я нахожу очень, очень уродливо и трудно поддерживать.
Теперь возникает вопрос: этот подход хорош? Я знаю, это может быть субъективным, но как вы преодолеваете вложенные IFs (1 или 2 уровня не так уж плохи, но после этого становится хуже)
Ответы
Ответ 1
Это действительно зависит от их цели. В вашем первом примере инструкции if служат для обеспечения выполнения контракта, следя за тем, чтобы входной сигнал метода удовлетворял определенным требованиям. В таких случаях мой собственный код, как правило, похож на ваш код.
В случае использования блоков if для управления потоком метода (а не для выполнения контракта) иногда может быть немного сложнее. Иногда я сталкиваюсь с кодом, подобным следующему (чрезвычайно упрощенному) примеру:
private void SomeMethod()
{
if (someCondition == true)
{
DoSomething();
if (somethingElse == true)
{
DoSomethingMore();
}
}
else
{
DoSomethingElse();
}
}
В этом случае кажется, что метод имеет несколько обязанностей, поэтому в этом случае я, вероятно, решил бы разбить его на несколько методов:
private void SomeMethod()
{
if (someCondition == true)
{
DoItThisWay();
}
else
{
DoSomethingElse();
}
}
private void DoItThisWay()
{
DoSomething();
if (somethingElse == true)
{
DoSomethingMore();
}
}
Это делает каждый метод намного проще с меньшим вложенным кодом, а также может повысить читаемость, если методам присваиваются хорошие имена.
Ответ 2
Ваша проблема известна как анти-шаблон стрелки.
Существуют прагматические подходы, такие как утверждения Guard, которые вы показали в своем примере, ко всем шаблонам проектирования, избегая, если (и еще) все вместе...
Много ресурсов по их решению:
http://www.codinghorror.com/blog/2006/01/flattening-arrow-code.html
http://www.lostechies.com/blogs/chrismissal/archive/2009/05/27/anti-patterns-and-worst-practices-the-arrowhead-anti-pattern.aspx
http://elegantcode.com/2009/08/14/observations-on-the-if-statement/
Ответ 3
Вы хотите исследовать С# 4 кодовые контракты.
Часто используется шаблон шаблон спецификации DDD для абстрагирования if, если в вашем случае его, вероятно, не подходит.
Ответ 4
Я думаю, это будет полезно для вас:
http://sourcemaking.com/refactoring/replace-conditional-with-polymorphism
Ответ 5
Возможно, AspectF может помочь вам в этом случае:
public Boolean MyMethod(String param) {
try {
AspectF.Define
.ErrorMsgIfNull(param, "must be not null")
.ErrorMsgIfEquals(new string[] {"None", "Zero", "0"}, "may not be zero")
//...
// use your own "AspectFlets" you wrote
//...
.Do(() =>
{
// Do some stuff with param
// This is not executed if param is null, as the program stops a soon
// as one of the above exceptions is thrown
});
}
Если у вас есть несколько условий для встречи (или чтобы избежать), которые сделали бы уродливый блок вложенных, если этот способ факторизации кода может помочь вам сделать вещи немного более описательными.
Методы в приведенном выше коде являются только примером, который не существует, но который может быть легко реализован.
Ответ 6
Это действительно довольно широкий вопрос для ответа, поскольку он действительно будет зависеть от функциональности операторов if.
Конечно, где это возможно, я пытаюсь заменить вместо этого вложенные операторы if, но это не всегда возможно.
Проверка правильности параметра - это хороший подход, но посмотрите на то, чтобы поймать его выше в коде, то есть иметь инструкции throw, но не catch в классе, который его бросает.