How to prevent the arrowhead anti-pattern

后端 未结 13 1309
南旧
南旧 2020-12-05 06:37

I\'m a bit confused about how to best refactor my code into something more readable.

Consider this piece of code:

var foo = getfoo();
if(foo!=null)
{         


        
相关标签:
13条回答
  • 2020-12-05 06:55

    I'd go for the multiple return statements. This makes the code easy to read and understand.

    Don't use goto for obvious reasons.

    Don't use exceptions because the check you are doing isn't exceptional, it's something you can expect so you should just take that into account. Programming against exceptions is also an anti-pattern.

    0 讨论(0)
  • 2020-12-05 06:58

    You can chain expressions. An assignment returns the assigned value, so you can check its result. Also, you can use the assigned variable in the next expression.

    As soon as an expression returns false, the others are not longer executed, because the entire expression would return false already (because of the and operation).

    So, something like this should work:

    Foo foo; Bar bar; Moo moo; Cow cow;
    
    if( (foo = getfoo()) != null &&
        (bar = getbar(foo)) != null &&
        (moo = getmoo(bar)) != null &&
        (cow = getcow(moo)) != null )
    {
      ..
    }
    
    0 讨论(0)
  • 2020-12-05 07:02

    An alternative is to use a "fake" single loop for controlling program flow. I can't say I'd recommend it, but it's definitely better looking and more readable than arrowhead.

    Adding a "stage", "phase" or sth like that variable may simplify debugging and/or error handling.

    int stage = 0;
    do { // for break only, possibly with no indent
    
    var foo = getfoo();
    if(foo==null) break;
    
    stage = 1;
    var bar = getbar(foo);
    if(bar==null) break;
    
    stage = 2;
    var moo = getmoo(bar);
    if(moo==null) break;
    
    stage = 3;
    var cow = getcow(moo);
    
    return 0; // end of non-erroreous program flow
    }  while (0); // make sure to leave an appropriate comment about the "fake" while
    
    // free resources if necessary
    // leave an error message
    ERR("error during stage %d", stage);
    
    //return a proper error (based on stage?)
    return ERROR;
    
    0 讨论(0)
  • 2020-12-05 07:02
    try
    {
      if (getcow(getmoo(getbar(getfoo()))) == null)
      {
        throw new NullPointerException();
      }
    catch(NullPointerException ex)
    {
      return; //or whatever you want to do when something is null
    }
    
    //... rest of the method
    

    This keeps the main logic of the method uncluttered, and has just one exceptional return. Its disadvantages are that it can be slow if the get* methods are slow, and that it is difficult to tell in a debugger which method returned the null value.

    0 讨论(0)
  • 2020-12-05 07:04

    Consider inverting the null checks to:

    var foo = getfoo();
    if (foo == null)
    {
        return;
    }
    var bar = getbar(foo);
    if (bar == null)
    {
        return;
    }
    ...etc
    
    0 讨论(0)
  • 2020-12-05 07:04

    This is one of few scenarios where it is perfectly acceptable (if not desirable) to use goto.

    In functions like this, there will often be resources that are allocated or state changes that are made mid-way which need to be undone before the function exits.

    The usual problem with return-based solutions (e.g., rexcfnghk's and Gerrie Schenck's) is that you need to remember to undo those state changes before every return. This leads to code duplication and opens the door to subtle errors, especially in larger functions. Do not do this.


    CERT actually recommends a structural approach based on goto.

    In particular, note their example code taken from copy_process in kernel/fork.c of the Linux kernel. A simplified version of the concept is as follows:

        if (!modify_state1(true))
            goto cleanup_none;
        if (!modify_state2(true))
            goto cleanup_state1;
        if (!modify_state3(true))
            goto cleanup_state2;
    
        // ...
    
    cleanup_state3:
        modify_state3(false);
    cleanup_state2:
        modify_state2(false);
    cleanup_state1:
        modify_state1(false);
    cleanup_none:
        return;
    

    Essentially, this is just a more readable version of the "arrowhead" code that doesn't use unnecessary indentation or duplicate code. This concept can easily be extended to whatever best suits your situation.


    As a final note, especially regarding CERT's first compliant example, I just want to add that, whenever possible, it is simpler to design your code so that the cleanup can be handled all at once. That way, you can write code like this:

        FILE *f1 = null;
        FILE *f2 = null;
        void *mem = null;
    
        if ((f1 = fopen(FILE1, "r")) == null)
            goto cleanup;
        if ((f2 = fopen(FILE2, "r")) == null)
            goto cleanup;
        if ((mem = malloc(OBJSIZE)) == null)
            goto cleanup;
    
        // ...
    
    cleanup:
        free(mem); // These functions gracefully exit given null input
        close(f2);
        close(f1);
        return;
    
    0 讨论(0)
提交回复
热议问题