Есть ли альтернатива коду с гиперотступами?

Я часто сталкиваюсь с кодом, который должен выполнять множество проверок и в конечном итоге получает отступ как минимум на пять или шесть уровней, прежде чем что-либо делать. Мне интересно, какие существуют альтернативы.

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

public String myFunc(SomeClass input)
{
    Object output = null;

    if (input != null)
    {
        SomeClass2 obj2 = input.getSomeClass2();
        if (obj2 != null)
        {
            SomeClass3 obj3 = obj2.getSomeClass3();
            if (obj3 != null && !BAD_OBJECT.equals(obj3.getSomeProperty()))
            {
                SomeClass4 = obj3.getSomeClass4();
                if (obj4 != null)
                {
                    int myVal = obj4.getSomeValue();
                    if (BAD_VALUE != myVal)
                    {
                        String message = this.getMessage(myVal);
                        if (MIN_VALUE <= message.length() &&
                           message.length() <= MAX_VALUE)
                        {
                            //now actually do stuff!
                            message = result_of_stuff_actually_done;
                        }
                    }
                }
            }
        }
    }
    return output;
}
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
0
532
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Для получения справки см. Код стрелки сглаживания.

  1. Replace conditions with guard clauses.
  2. Decompose conditional blocks into seperate functions.
  3. Convert negative checks into positive checks.

Вернуться раньше:

if (input == null) {
    return output;
}

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

public String myFunc(SomeClass input)
{
    Object output = null;

    if (input == null) return "";

    SomeClass2 obj2 = input.getSomeClass2();
    if (obj2 == null) return "";

    SomeClass3 obj3 = obj2.getSomeClass3();
    if (obj3 == null || BAD_OBJECT.equals(obj3.getSomeProperty()))
    {
        return "";
    }

    SomeClass4 = obj3.getSomeClass4();
    if (obj4 == null) return "";

    int myVal = obj4.getSomeValue();
    if (BAD_VALUE == myVal) return "";

    String message = this.getMessage(myVal);
    if (MIN_VALUE <= message.length() &&
           message.length() <= MAX_VALUE)
    {
         //now actually do stuff!
         message = result_of_stuff_actually_done;
    }

    return output;
}

Однако измените все операторы return "";, которые я использовал для иллюстрации точки, на операторы, которые вызывают описательную разновидность Exception.

Да, вы можете удалить отступы следующим образом:

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

public String myFunc(SomeClass input)
{
    Object output = null;

    if (input == null)
    {
        return null;
    }

    SomeClass2 obj2 = input.getSomeClass2();
    if (obj2 == null)
    { 
        return null;
    }

    SomeClass3 obj3 = obj2.getSomeClass3();
    if (obj3 == null || BAD_OBJECT.equals(obj3.getSomeProperty()))
    {
        return null;
    }

    SomeClass4 = obj3.getSomeClass4();
    if (obj4 == null)
    {
        return null;
    }
    int myVal = obj4.getSomeValue();
    if (BAD_VALUE == myVal)
    {
        return null;
    }
    String message = this.getMessage(myVal);
    if (MIN_VALUE <= message.length() &&
       message.length() <= MAX_VALUE)
    {
        //now actually do stuff!
        message = result_of_stuff_actually_done;
    }
    return output;
}

Почему вы остановились на тесте if? Его также можно превратить в скорейшее возвращение, применив закон Де Моргана, чтобы перевернуть смысл составного теста.

Hudson 11.12.2008 20:07

Вы предполагаете, что я знал, что такое закон Де Моргана :) На самом деле, я оставил его таким, каким он был исключительно из-за личных предпочтений. В моей голове было больше смысла читать это таким образом.

Andrew Rollings 11.12.2008 20:18

Если вам не нужно останавливать процесс, не вставляйте.

Например, вы можете:

if (input == null && input.getSomeClass2() == null && ...)
    return null;

// Do what you want.

Предполагая, что вы используете такой язык, как Java, который упорядочивает условные выражения.

В качестве альтернативы вы можете:

if (input == null && input.getSomeClass2() == null)
    return null;

SomeClass2 obj2 = input.getSomeClass2();
if (obj2 == null)
    return null;

...

// Do what you want.

Для более сложных случаев.

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

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

public String myFunc(SomeClass input)
{
    Object output = null;

    if (inputIsValid(input))
    {
      //now actually do stuff!
      message = result_of_stuff_actually_done;
    } 

    return output;
}


private bool inputIsValid(SomeClass input)
{

    // *****************************************
    // convert these to guard style if you like   
    // ***************************************** 
    if (input != null)
    {
        SomeClass2 obj2 = input.getSomeClass2();
        if (obj2 != null)
        {
            SomeClass3 obj3 = obj2.getSomeClass3();
            if (obj3 != null && !BAD_OBJECT.equals(obj3.getSomeProperty()))
            {
                SomeClass4 = obj3.getSomeClass4();
                if (obj4 != null)
                {
                    int myVal = obj4.getSomeValue();
                    if (BAD_VALUE != myVal)
                    {
                        String message = this.getMessage(myVal);
                        if (MIN_VALUE <= message.length() &&
                           message.length() <= MAX_VALUE)
                        {
                            return true;
                        }
                    }
                }
            }
        }
    }
    return false;
}

Да есть альтернатива.

И, пожалуйста, никогда не пишите так (если вы не поддерживаете свой собственный код)

Мне приходилось поддерживать такой код, и он такой же ужасный, как фильм Charles_Bronsonn (хотя некоторым нравятся эти фильмы)

Этот вид кода обычно исходит из процедурных языков, таких как C (процедурный C: P) В любом случае.

Это стало причиной того, что ObjectOrientedProgrammng стало мейнстримом. Он позволяет создавать объекты и добавлять к ним состояние. Создавайте операции с этим состоянием. Они не только собственники.

Я знаю, что вы придумали этот сценарий, но в большинстве случаев все эти условия - бизнес правила!!. В большинстве случаев эти правила МЕНЯЮТСЯ, и если первоначальный разработчик больше не работает (или прошло уже несколько месяцев), не будет реального способа изменить этот код. Правила неудобно читать. И от этого возникает много боли.

Что ты можешь сделать?

1.) Сохраняйте состояние объекта ВНУТРИ объекта, используя переменные-члены частный (атрибуты AKA, свойства, переменные экземпляров и т. д.)

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

3.) Создайте методы, определяющие состояние. Это то, что они называют самодокументирующийся код

Так что вместо

// validates the user has amount
if ( amount > other && that != var || startsAligned() != false  ) {
}

Создать метод

if ( isValidAmount() ) {
}

private boolean isValidAmount() {
   return ( amount > other && that != var || startsAligned() != false  );
}

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

Итак, как бы это выглядело при таком подходе?

Нравится.

// these are business rules
// then it should be clear that those rules are
// and what they do.

// internal state of the object.
private SomeClass2 obj2;
private SomeClass3 obj3;
private SomeClass4 obj4;

//public String myFunc( SomeClass input ) {
public String myComplicatedValidation( SomeClass input ) {
    this.input = input;
    if ( isValidInput() && 
        isRuleTwoReady() &&
        isRuleTreeDifferentOf( BAD_OBJECT ) &&
        isRuleFourDifferentOf( BAD_VALUE ) && 
        isMessageLengthInRenge( MIN_VALUE , MAX_VALUE ) ) { 
                message = resultOfStuffActuallyDone();
    }
}

// These method names are self explaining what they do.
private final boolean  isValidInput() {
    return  this.input != null;
}
private final boolean isRuleTwoReady() {
    obj2 = input.getSomeClass2();
    return obj2 != null ;
}
private final boolean isRuleTreeDifferentOf( Object badObject ) {
    obj3 = obj2.getSomeClass3();
    return obj3 != null && !badObject.equals( obj3.getSomeProperty() );
}
private final boolean isRuleFourDifferentOf( int badValue ) {
    obj4 = obj3.getSomeClass4();
    return obj4 != null && obj4.getSomeValue() != badValue;
}
private final boolean isMessageLengthInRenge( int min, int max ) {
    String message = getMessage( obj4.getSomeValue() );
    int length = message.length();
    return length >= min && length <= max;
}

Я знаю, похоже, больше кода. Но подумайте об этом. Правила почти удобочитаемы

    if ( isValidInput() && 
        isRuleTwoReady() &&
        isRuleTreeDifferentOf( BAD_OBJECT ) &&
        isRuleFourDifferentOf( BAD_VALUE ) && 
        isMessageLengthInRenge( MIN_VALUE , MAX_VALUE ) ) { 
                message = resultOfStuffActuallyDone();
    }

Можно почти прочитать как

if is valid input 
and rule two is ready 
and rule three is not BAD OBJECT 
and rule four is no BAD_VALUE 
and the message length is in range

И если правила будут небольшими, кодировщик может очень легко понять их и не бояться что-то затормозить.

Подробнее об этом можно прочитать по адресу: http://www.refactoring.com/.

Другие вопросы по теме