Используйте цикл while, чтобы избежать глубоко вложенных операторов if в java

Привет, я написал небольшую функцию, например

public void foo(MyClassA paraA) {
    if (paraA == null) return;
    MyClassB paraB = doSomeStuff(paraA);
    if (paraB == null) return;
    MyClassC paraC = doMoreStuff(paraB);
    if (paraC == null) return;
    ....
}

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

public void foo(MyClassA paraA) {
    if (paraA == null) {doLog(); return;}
    MyClassB paraB = doSomeStuff(paraA);
    if (paraB == null) {doLog(); return;}
    MyClassC paraC = doMoreStuff(paraB);
    if (paraC == null) {doLog(); return;}
    ....
}

Приведенное выше также чисто и легко читается, но мне приходится пару раз повторять doLog(). Поэтому я снова меняю на

public void foo(MyClassA paraA) {
    if (paraA != null) {
        MyClassB paraB = doSomeStuff(paraA);
        if (paraB != null) {
            MyClassC paraC = doMoreStuff(paraB);
            if (paraC != null) {
                ....
                return;
            }
        }
    }
    doLog();
}

Вышеупомянутый вызов doLog() только один раз, но я закончил с некоторыми глубоко вложенными операторами if, которые очень уродливы и трудно читаемы. Итак, как мне сохранить ту же чистоту, что и раньше, и использовать doLog() только один раз? Обратите внимание, что возврат чего-то другого вместо void для foo() не допускается. И я также читал, что использование try/catch вместо нулевой проверки является антишаблоном.

Если я попытаюсь, я хочу написать что-то вроде

public void foo(MyClassA paraA) {
    while(true) {
        if (paraA == null) break;
        MyClassB paraB = doSomeStuff(paraA);
        if (paraB == null) break;
        MyClassC paraC = doMoreStuff(paraB);
        if (paraC == null) break;
        ....
        return;
    }
    doLog();
}

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


person user1589188    schedule 13.02.2015    source источник
comment
Что делает doLog? Можете ли вы изменить его, чтобы принимать параметры?   -  person shree.pat18    schedule 13.02.2015
comment
@shree.pat18 не задумывался об этом, почему? Может быть, вы можете дать свои предложения как с параметром, так и без него, спасибо.   -  person user1589188    schedule 13.02.2015
comment
Если бы вы могли добавлять строку каждый раз, когда одна из переменных имеет значение null, а затем передавать полученную строку в doLog один раз в конце, вам не пришлось бы делать к ней повторные вызовы.   -  person shree.pat18    schedule 13.02.2015
comment
@ shree.pat18 да, это можно сделать, но это не связано с моим вопросом. О каком шаблоне вы говорите, который вызывает doLog только один раз? Если вы имеете в виду вложенное если, я против этого. Если вы имеете в виду мою версию цикла while, у меня есть вопрос, использующий ли цикл while анти-шаблон.   -  person user1589188    schedule 13.02.2015
comment
из-за возвращения; он не дойдет до метода dolog(), надеюсь, вас это устраивает.   -  person someone    schedule 13.02.2015
comment
Я думаю, вы могли бы добавить paraA, paraB, paraN в массив. Затем переберите массив (проверьте на нуль). Если его значение равно null, создайте экземпляр значения в index[n] и передайте его в допустимых параметрах.   -  person benscabbia    schedule 13.02.2015
comment
@user1589188 user1589188 Плохо - я пропустил твою часть о быстром сбое.   -  person shree.pat18    schedule 13.02.2015


Ответы (3)


В Java есть изящная конструкция break с меткой, которая может вам помочь.

public void foo(MyClassA paraA) {
    block: {
        if (paraA == null) { break block; }
        MyClassB paraB = doSomeStuff(paraA);
        if (paraB == null) { break block; }
        MyClassC paraC = doMoreStuff(paraB);
        if (paraC == null) { break block; }
        ...
        return;
    }

    doLog();
}

Если бы вы лучше использовали полиморфизм, вы могли бы сделать это:

public void foo(MyInterface para) {
    while (para != null) {
        para = para.doStuff();
    }
    doLog();
}

Если вы абсолютно не можете использовать такой полиморфизм, используйте диспетчер.

Но я видел это раньше, и это похоже на конечный автомат. Дайте поиску «конечный автомат java enum». У меня такое ощущение, что вы на самом деле пытаетесь это сделать.

person David Ehrmann    schedule 13.02.2015
comment
Я не знаю, можно ли использовать break, не находясь внутри цикла for или while! Это интересно, позвольте мне попробовать это. Если это сработает, скорее всего, я выберу ваш ответ. - person user1589188; 13.02.2015
comment
См. JLS #14.15. . - person user207421; 13.02.2015
comment
@user1589188 user1589188 Спасибо, но, пожалуйста, подумайте о полиморфном подходе для этого, даже если вы в конечном итоге его не используете. Это хороший образец, чтобы знать. - person David Ehrmann; 13.02.2015
comment
Ага, пробовал, работает. Единственный оставшийся вопрос: я чувствую, что эта маркировка похожа на goto. Разве это не очередной антипаттерн? Не могу выполнить полиморфинг doStuff(), так как мне нужно передать некоторые параметры на другом этапе. - person user1589188; 13.02.2015
comment
На самом деле это ничем не отличается от множественных возвратов из одной функции... что немного похоже на goto, но злоупотребления goto были намного хуже, чем когда-либо были множественные возвраты, break и continue. Определенно есть люди, которым не нравится многократное возвращение, но я бы сказал, что на данный момент это скорее личное предпочтение. - person David Ehrmann; 13.02.2015
comment
Что, если бы вы создали конечный автомат с изменяемым контекстом, который изменяется на каждой итерации? - person David Ehrmann; 13.02.2015

Вы думаете, что это чисто

public void foo(MyClassA paraA) {

    MyClassB paraB = paraA != null?doSomeStuff(paraA):null;
    MyClassC paraC = paraB != null?doMoreStuff(paraB):null;

     if (paraC != null) {
         ....

     }

     doLog();
}
person someone    schedule 13.02.2015
comment
Еще лучше, поставьте нулевую проверку в doMoreStuff - person Cole Johnson; 13.02.2015
comment
Это чисто, но не выходит из строя быстро. Как только вы узнаете, что paraA имеет значение null, вы должны перейти к doLog() напрямую, поэтому ваш вариант не оптимален, поскольку вам нужно выполнить еще несколько проверок условий, прежде чем достичь doLog() - person user1589188; 13.02.2015
comment
Нулевые проверки выполняются настолько быстро, насколько это возможно. Оставьте это горячей точке, чтобы оптимизировать их с помощью jmp. - person Has QUIT--Anony-Mousse; 14.02.2015

ИМХО, ваш второй фрагмент кода - это то, что вы должны делать.

Не пытайтесь сделать свой код коротким. Это антипаттерн.

if (a==null) {
  log("Failed in step a");
  return;
}
B b = a.doSomething();

очень быстро читается и понимает. Вы ничего не экономите, сжимая этот код. Нуль. Нада. Оставьте это Hotspot VM и сосредоточьтесь на том, чтобы сделать код понятным. "если ноль, то возврат в журнал" — это классический, хорошо понятный и общепринятый шаблон.

Стало популярным пытаться сделать код «читабельным» с помощью таких лямбда-антипаттернов:

B b = ifNullLog(a, () -> a.doSomething())

куда

T ifNullLog(Object guard, Function<T> func) {
  if (guard == null) { doLog(); return null; }
  return func.run();
}

но ИМХО это полный антипаттерн. На самом деле рекомендуется даже требовать фигурные скобки для каждого if, else, for, while, чтобы упростить вставку такого оператора журнала, не рискуя сломать код.

Код, как ваш первый фрагмент:

if (a == null) return;

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

if (a==null)
  return;
  return;
B b = a.doSomething();

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

Также допустимо использовать коды возврата. Просто не делайте успех по умолчанию (см. Heartbleed снова).

Code c = execute(a);
if (c != Code.SUCCESS) {
  doLog(c);
  return;
}

куда

Code execute(A a) {
  if (a == null) { return Code.FAILED_A_NULL; }
  B b = a.doSomething();
  if (b == null) { return Code.FAILED_B_NULL; }
  ...
  return Code.SUCCESS;
}

Классический вариант использования «возврата», еще один хороший паттерн.

person Has QUIT--Anony-Mousse    schedule 14.02.2015