Есть ли альтернатива гипериндентированному коду? - PullRequest
8 голосов
/ 11 декабря 2008

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

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

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;
}

Ответы [ 7 ]

16 голосов
/ 11 декабря 2008

См. Код выравнивающей стрелки для получения справки.

  1. Заменить условия защитой статьи.
  2. Разложить условные блоки на отдельные функции.
  3. Конвертировать отрицательные чеки в положительные чеки.
8 голосов
/ 11 декабря 2008

Возвращение рано:

if (input == null) {
    return output;
}
7 голосов
/ 11 декабря 2008

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

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

Мне пришлось поддерживать такой код и он такой же ужасный, как фильм Чарльза Бронсонна (хотя некоторые люди любят эти фильмы)

Этот тип кода обычно исходит из процедурных языков, таких как 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/

2 голосов
/ 11 декабря 2008

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

В основном, делайте проверки последовательно и сравнивайте с ошибкой, а не с успехом. Это удаляет вложение и облегчает отслеживание (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;
}
1 голос
/ 11 декабря 2008

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

Например, вы можете сделать:

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.

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

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

1 голос
/ 11 декабря 2008

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

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 "";, которые я использовал, чтобы проиллюстрировать смысл операторов, которые бросают описательное разнообразие исключений.

0 голосов
/ 11 декабря 2008

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

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;
}
...