Как мне переписать очень большой составной оператор if в C #? - PullRequest
23 голосов
/ 08 июня 2009

В моем коде C # у меня есть выражение if, которое началось достаточно невинно:

if((something == -1) && (somethingelse == -1) && (etc == -1)) {
    // ...
}

Это растет. Я думаю, что сейчас в нем должно быть 20 статей.

Как должен Я справлюсь с этим?

Ответы [ 18 ]

28 голосов
/ 08 июня 2009

Используйте ворота там, где это возможно.

оператор if

if(bailIfIEqualZero != 0 && 
   !string.IsNullOrEmpty(shouldNeverBeEmpty) &&
   betterNotBeNull != null &&
   !betterNotBeNull.RunAwayIfTrue &&
   //yadda

Реорганизованная версия

if(bailIfIEqualZero == 0)
  return;

if(string.IsNullOrEmpty(shouldNeverBeEmpty))
  return;

if(betterNotBeNull == null || betterNotBeNull.RunAwayIfTrue)
  return;

//yadda
19 голосов
/ 08 июня 2009

Разложите его в функцию и сделайте каждое условие охранной оговоркой:

int maybe_do_something(...) {
    if(something != -1)
        return 0;
    if(somethingelse != -1)
        return 0;
    if(etc != -1)
        return 0;
    do_something();
    return 1;
}
16 голосов
/ 08 июня 2009

Предполагая, что все эти условия действительно необходимы, вы можете объединить условия в один или несколько uber-логических значений или вызовов функций для улучшения читабельности.

например.,

bool TeamAIsGoForLaunch = BobSaysGo && BillSaysGo;
bool TeamBIsGoForLaunch = JillSaysGo && JackSaysGo;

if (TeamAIsGoForLaunch && TeamBIsGoForLaunch && TeamC.isGoForLaunch())
9 голосов
/ 08 июня 2009

Одна вещь, которую следует учитывать, это , почему у вас так много предложений. Точно так же, как операторы SWITCH часто указывают, что вы должны перемещать варианты выбора в подклассы, большая сложная цепочка операторов IF может указывать на то, что вы объединяете слишком много концепций (и, следовательно, решений) в одном месте.

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

Помимо этого, существовали дополнительные правила «исключений», основанные на состоянии клиента, конкретных настройках продукта и т. Д. Это могло бы быть разработано в сложной цепочке операторов IF, но вместо этого мы сделали управляемые данными приложения.

Поместив всю эту информацию в таблицы, мы затем смогли выполнить правила данных. Сначала срабатывают общие правила оптовика, а затем срабатывают любые правила переопределения. Затем, имея базовую комиссию оптовика, мы заставим оптовика распространять общие правила розничного предприятия, а затем исключения из них.

Это превратило огромный шарик логики в простой четырехэтапный процесс, при котором каждый шаг просто выполнял запрос к базе данных, чтобы найти правильное правило для применения.

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

8 голосов
/ 08 июня 2009

Переведите его в функцию.

bool Check()
{
  return (something == -1) && (somethingelse == -1) && (etc == -1);
}

Кроме того, вы можете создать более читаемый код / ​​логику в своей функции проверки.

6 голосов
/ 08 июня 2009

Есть много способов справиться с этим, но позвольте мне выбрать несколько.

Во-первых, есть случай, когда все критерии (все AND-критерии в вашем операторе if) + код, который нужно выполнить, если все они истинны, являются одноразовыми.

В этом случае используйте ваш код. Возможно, вы захотите сделать то, что несколько других уже предложили, переписать, чтобы использовать код типа Guard-clause.

Другими словами, вместо этого:

if (a && b && c && d && ......)
    DoSomething();

... вы переписываете что-то похожее на это:

if (!a) return;
if (!b) return;
if (!c) return;
if (!d) return;
if (!...) return;
DoSomething();

Почему? Потому что, как только вы начинаете вводить OR-критерии в микс, становится трудно читать код и выяснять, что произойдет. В приведенном выше коде вы разделяете критерии для каждого оператора AND (&&), и, таким образом, код становится проще для чтения. По сути, вы переписываете код, говоря «если и то, и то, или другое, и третье, или что-то другое, то делаете что-то« чтобы быть », если это, затем выходите; если это другое; затем выходите; если некоторые другое дело, затем выход; если ничего из вышеперечисленного, сделать что-то ".

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

Например, что легче читать, это?

if (a && b && c && d && e && f && (h || i) && (j || k) || l)

или это:

if (CanAccessStream() && CanWriteToStream())

при условии, что все эти буквы можно разделить на эти два критерия.

В этом случае я бы взял некоторые критерии и включил в эти методы и выбрал подходящее название для критериев.

Третий вариант - это когда критерии различаются в нескольких местах кода, но фактический код для выполнения остается тем же.

В этом случае я бы переписал так, чтобы вы сгруппировали критерии и наслоили методы так, чтобы вызов одного метода проверил некоторые критерии, а затем вызвал другой метод, который проверит некоторые другие критерии и т. Д.

Например, вы могли бы написать это:

if (stream != null && buffer != null && inBuffer > 0 && stream.CanWrite)
  stream.Write(buffer, 0, inBuffer);
else
    throw new InvalidOperationException();

или вы могли бы написать это:

if (inBuffer > 0)
{
    Debug.Assert(buffer != null);
    WriteToStream(buffer, inBuffer);
}

...

private void WriteToStream(Byte[] buffer, Int32 count)
{
    if (stream.CanWrite)
        stream.Write(buffer, 0, count);
    else
        throw new InvalidOperationException();
}

Я бы сказал, что второй способ легче читать и более пригоден для повторного использования, чем первый.

4 голосов
/ 08 июня 2009

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

if(something != -1)
    return MyEnum.Something;
if(somethingelse != -1)
    return MyEnum.SomethingElse;
if(etc != -1)
    return MyEnum.SomethingElseEntirely;
return MyEnum.None;
4 голосов
/ 08 июня 2009

Выполните агрегатные операции со списком ваших значений.

if (new[] { something, somethingelse, ... }.All(x => x == -1)) {
}

* Редактировать: Предоставление данных дополнительной строки:

var Data = new[] { something, somethingelse, ... };
if (Data.All(x => x == -1)) {
}
3 голосов
/ 08 июня 2009

Похоже, у вас есть 3 части информации, которые вместе представляют определенное состояние в вашем приложении. Вместо того, чтобы включать эти 3 части состояния, почему бы не создать значение, которое инкапсулирует их? Затем вы можете использовать иерархию объектов или делегата во время создания, чтобы связать действие, которое вы пытаетесь выполнить.

3 голосов
/ 08 июня 2009

Вы также можете разделить состояние на класс.

class mystate
{
    int something;
    int somethingelse;
    int etc;

    bool abletodostuff()
    {
        return (something == -1) && (somethingelse == -1) && (etc == -1);
    }
}
...