Single-Entry, Single-Exit, Should It Still Be Applicable In Object-oriented Languages?

Before the modern high-level languages Edsger Dijkstra came up with “Structured Programming”.  This programming methodology relied on the programmer to form and enforce most of the structure of the program–manually keeping sub-structures and logic separate from one another to promote maintainability and easy of understanding, among other things.  Think assembly language with a linear collection of instructions and jumps and then the only concept of a method or function is how the rest of the logic jumps to that block of code.

This concept of delineating functions hinged on a single entry, i.e. from point A to point B only one point is actually jumped to from “external” code.  This single entry concept usually included a single exit, to ease the delineation of a “function”.  This is known as the single-entry, single-exit methodology (SESE).

It’s hard to think of multiple entry points with modern high-level languages what with object-orientation and abstraction and encapsulation; but it’s easy to see multiple exits from a method.  For example:

        public static int CountCommas(string text)

        {

            if (String.IsNullOrEmpty(text))

            {

                return 0;

            }

            if (text.Length == 0)

            {

                return 0;

            }

 

            int index = 0;

            int result = 0;

            while (index > 0)

            {

                index = text.IndexOf(‘,’, index);

                if (index > 0)

                {

                    result++;

                }

            }

            return result;

        }

Structured programming (at least SESE) suggests writing the method like this instead:

        public static int CountCommas(string text)

        {

            int result = 0;

            if (!String.IsNullOrEmpty(text))

            {

                if (text.Length > 0)

                {

                    int index = 0;

                    while (index > 0)

                    {

                        index = text.IndexOf(‘,’, index);

                        if (index > 0)

                        {

                            result++;

                        }

                    }

                }

            }

 

            return result;

        }


This concept may have made for more readable code when Dijkstra first cemented the concept in the late 60′s early 70′s; but in Object-Oriented languages I believe it’s less readable.  For one thing, it’s difficult to shoe-horn SESE with other language concepts like exceptions:

        public static int CountCommas(string text)

        {

            Exception exception = null;

            int result = 0;

            if (!String.IsNullOrEmpty(text))

            {

                if (text.Length > 0)

                {

                    int index = 0;

                    while (index > 0)

                    {

                        index = text.IndexOf(‘,’, index);

                        if (index > 0)

                        {

                            result++;

                        }

                    }

                }

                else

                {

                    exception = new ArgumentException(“argument of zero length”, “text”);

                }

            }

            else

            {

                exception = new ArgumentNullException(“text”);

            }

 

            if (exception != null)

            {

                throw exception;

            }

 

            return result;

        }



And this technically still violates SESE since we exit via return or via throw, although they have close proximity.

I believe the above example is hard to read and hard to maintain.  I would abandon the SESE trappings of structured programming in favour of:

 

        public static int CountCommas(string text)

        {

            if (String.IsNullOrEmpty(text))

            {

                throw new ArgumentNullException(“text”);

            }

            if (text.Length == 0)

            {

                throw new ArgumentException(“argument of zero length”, “text”);

            }

 

            int index = 0;

            int result = 0;

            while (index > 0)

            {

                index = text.IndexOf(‘,’, index);

                if (index > 0)

                {

                    result++;

                }

            }

            return result;

        }


In high-level languages that do have concepts like functions, subroutines, or methods, the “Single Entry” aspect of SESE is moot, evolving to the concept of “Single Exit” or the “Single Point of Exit From Methods Principle”.  This seems like a Cargo Cult to me–separating the part of a concept that is no longer obviously archaic in the hopes of getting the same result in a different context.


Interestingly, as I was writing this, Patrick Smacchia posted to his blog about NDepend and Nesting Depth–which basically details metrics that show the SESE implementations I show above would actually have higher nesting depths than the non-SESE implementations and thus be more complex, less readable, and less testable.


 What are your thoughts?  Do you generally follow Single Point of Exit From Methods Principle?  If you do, do you ignore it for exceptions?

kick it on DotNetKicks.com

33 thoughts on “Single-Entry, Single-Exit, Should It Still Be Applicable In Object-oriented Languages?”

  1. While I agree with you, I still think that a single exit point is a rule that we should remember, but add to that rules the exceptions like the ones you outline above. It makes sense to do your parameter checking at the beginning of the method and throw/return then. Other cases such as a long switch where each case returns is another good example (winproc message handling for example.)

    I think it is important to remember the spirit of the rule though. Return statements nested deep within the logic are easily missed and increase complexity. Maybe the rule should be, which style produces the least complex code in this situation? The only problem with that is that managers can’t write that into their coding standards and point to it as a hard and fast rule ;)

  2. If SESE means increasing your cyclomatic complexity and decreasing mantainability, I’d suggest that SESE go BYEBYE.

    Actually, I’d suggest it GTFOGTFO, but that’s kinda rude.

    Personally, when I see more than three opening curly brackets in a row within a single method I get out the rubber mallet. Somebody’s going to get their fingers broke.

  3. Even the “Single Entry” idea is getting to be increasingly dubious with modern languages.

    Specifically many modern languages support coroutines, which eliminates the single entry concept.

    In C#, we see support for a limited subset of coroutines through the yield construct. Developers new to “yield” (and often new to coroutines) get very frustrated due to the interleaving of method calls. It’s the breaking of “single entry” that is doing them in.

    I also agree with you that multiple exits often produce far clearer code, than a single exit.

    As code becomes increasingly nested, complexity (to us humans) increases.

    To some degree, a language like F# cleans up your examples, because you can decorate the method to do the argument checking by contract. The overall point though is very valid!

  4. @Chris: True; although at the language level the single-entry is still maintained; it’s under the covers that co-routines is implemented. Yield, because of this, is very confusing to many people. yield is another construct that makes writing a method with a single-exit very difficult.

  5. @Troy: Fail Fast seems a bit orthogonal, I’m not comparing exceptions versus error codes (but prefer exceptions to error codes). You could still fail fast with structured programming. The proximity of the failure and when the method returns is different.

  6. One very strong argument for multiple returns is that a return makes it immediately obvious that this is the result that will be returned. Setting a result value can be changed later, meaning you have to read the rest of the code to know if that value is altered.

    To counter another point, how is it any easier to find deeply nested assignments to result than to find deeply nested returns. Multple returns often allow you to simplify nesting.

    The only rule you need here is: do what makes the code more readable.

  7. @Peter

    “Fail Fast” is more than exceptions vs. error codes.

    My point was the tenet of “Immediate and VISIBLE Failure”.

    In certain situations, choosing to respect this tenet of “fail fast” readability would indeed result in multiple returns.

    Granted, I’m not implying that error handling is the ONLY cause of multiple returns, however, that seems to be the main focus of your post, hence my take on the comprehensive meaning of “Fail Fast”

  8. Isn’t it faster if you use a code like this ???

    public static int CountCommas(string text, char separator)
    {
    Exception exception = null;
    int result = 0;
    if (!String.IsNullOrEmpty(text))
    {
    if (text.Length > 0)
    {
    string[] text_count=text.Split(separator);
    result=text_count.length;
    }
    else
    {
    exception = new ArgumentException(“argument of zero length”, “text”);
    }
    }
    else
    {
    exception = new ArgumentNullException(“text”);
    }

    if (exception != null)
    {
    throw exception;
    }
    return result;
    }

  9. I am not following the Single Exit mantra because it leads to multi level nested statements and in case that level>3 that is very hard to read

    Instead of SESE mantra, I prefer Fowler “Guard clause” style which IMHO increase code readibility

  10. Just for the ones that don’t know, checking text.Length is included in IsNullOrEmpty():

    public static bool IsNullOrEmpty(string value)
    {
    if (value != null)
    {
    return (value.Length == 0);
    }
    return true;
    }

  11. I often use a flag to keep track whether operations are successful or not. This can give us SESE approach without too deep nested structures. As exceptions should be exceptional, I’d rather throw them right away in the beginning of a method, just like the last example has done.

    The final solution takes a shortcut. If the business case would require the method to handle null (not likely) and String.Empty objects (more likely), more checks are required in code.

    Here is my take for comma counter with flag variable.

    public static int CountCommas(string text){
    // Exceptions would be thrown right here. Maybe for null parameter?

    // Non-exceptional code starts here.
    bool success;
    int result = 0;
    success = !String.IsNullOrEmpty(text) ? true : false;

    if(success){
    success = text.Length > 0 ? true : false;
    }

    if(success){
    int index = 0;
    while (index > 0){
    index = text.IndexOf(‘,’, index);
    if (index > 0){
    ++result;
    }
    }
    }
    return result;
    }

  12. I agree with the idea of multiple exit points. Your example shows how much cleaner the code can look (by the way, the length==0 check is superflouous, String.IsNullOrEmpty already checks that).

    However, when you’re dealing with developers who aren’t exactly the brightest of the bunch, on large projects, you end up with massive multi-thousand-line functions that have dozens of exit points, and it becomes an absolute maintenance nightmare. However, this is more a problem with huge functions than it is with the multi-exit principle.

    So yeah, I completely agree that SEME is a good idea, but you just need to make sure your team isn’t dumb enough to make functions more than 2-3 dozen lines long.

  13. @Troy: I choose a academic example that was supposed to exemplify multiple returns. A caveat of “pay no attention to the logic” should have been added :-). It’s an academic example that shows two different ways of implementing some logic (one where null/zero-length strings are not errors, and one where they are–and generate exceptions).

  14. I still use the single exit strategy. I sometimes wince when I am reading someone elses method and halfway through I see return statement.

    I like the ability to set a break point at the very end of a function and let the code run to see what i’m going to get while still being in the context of that method.

    Theres something reassuring about knowing all code paths will leave my function at 1 spot and 1 spot only. That way if I later find myself needing to .ToUpper() my string result or something, I only need to do it in 1 spot.

    And as far as exceptions go, I throw them at the point they need to be thrown rather than holding onto them.

  15. @Dave: multiple return statements in a method does not preclude being able to set a break-point at the end of a method to break when the method is complete. “return” is just a language syntax detail; pretty much every language must generate prolog and epilog code for methods, meaning it does implement an single exit; but that’s an implementation detail of the language. SESE was created before these high-level languages started doing this, and is useful is that particular context. But, in higher-level languages this “sense of security” makes you limit what you can do with evolving capabilities of the language. Point in case are exceptions, it makes no sense for them to follow single-exit; which means you now have conflicting (confusing, hard-to-maintain, hard to test, etc.) styles.

  16. I would submit that throwing exceptions whenever they need to be thrown (throughout the method when they are encountered) and having at most one return statement in each function doesn’t have to be conflicting.

    For example, look at the last chunk of code you wrote. That is the one I prefer. Maybe this means I’m not strict SESE? If so thats fine. Being strict anything is limiting.

    What if you want to add some form of tracing at the end of a method to log the result?

    What if there is some extra work you need to do on the return value before it leaves the method?

    If I am stuck with debugging 1 of 2 existing projects… One with all methods having (potentially) multiple exit points anywhere in the method. Versus another with all methods having exactly 1 exit point. I know which one I’m choosing.

    That said, I do recognize the issue of deeper nesting and that IS a problem.

  17. @Dave: multiple exceptions in a method is multiple exits, not single exit. The argument for a single exit with regard to return statements is easier to take than a single throw per method; but they’re tantamount the same thing.

  18. Like pretty much every programming concept/idiom/pattern ever conceived, its not a black and white issue. Religiously adhering to or ignoring SSEE will not make more readable code. Single-exit can create deep nesting or excessive guard clauses; multiple-exit can result in code that is hard to trace by visual inspection. Both of these are undesirable; the key is finding an appropriate balance between the two by understanding the pros and cons of each. As always, there is no silver bullet.

  19. There is no rule in software development, which cannot be broken, when there is a pressing need to do so. For some rules this justification is harder (rarer) to find, for some rules it is easier to find. IMHO the SESE rule is one of the weakest rules, i.e. it is very easy to find examples where it makes sense to break SESE in many real world examples. Personally I prefer to avoid long if/else if constructs and replace them with many similar looking code blocks:
    if(statement1)
    {
    do stuff
    return
    }
    if(statement2)
    {
    so stuff2
    return;
    }
    If ‘statements’ and ‘do stuffs’ are similar in size and function this is very easy to read and understand. If there are only two or three such blocks I usually don’t bother and let them as they are. If there are more it is easy to see where to refactor and to apply a behavioral pattern like state, strategy or command. This very often leads back to a heeded SESE rule.

  20. The preconditions should not have to be checked. As they are assumed to be valid.

    int CountCommas(string text)
    {
    // pre: text is a string

    // we add overhead to check if the text is a string (which is a bit of misplaced check imho)
    if (!(text is string))
    {
    // maybe this is only possible if text == null but then, how would we know if this holds in the near future
    // maybe in the near future we can also call CountCommas(infinitestringofcommas); where infinitestringofcommas is a new type
    // so we cannot anticipate on the text being null
    throw new ArgumentException(“text is not a valid string”);
    }

    // lets count comma’s
    int N = text.Length;
    int numberOfCommas = 0;
    for (int n = 0; n != N; n++)
    {
    if (text[n] == ‘,’)
    {
    numberOfCommas++;
    }
    }

    // one exit
    return numberOfCommas;
    }

  21. A method all on it’s own as shown as an example can’t really assume anything; which is why the example checks parameters. It’s advisable for any publicly available method to always check it’s arguments.

    What if(!(text is string)) actually compiles to is:

    if(text == null)

    Replacing that with String.IsNullOrEmpty avoids having to begin a for loop with no iterations. So, we’re back to what I have (minus the extraneous Length check, that someone else pointed out).

    But, for some algorithms you don’t want to throw an exception. Let’s say we did throw an exception if we passed an invalid string (in your example). What would it mean to an algorithm that needs the count of commas in a string? If it doesn’t pass a valid string, would it continue processing any differently if it caught the exception? I.e. it’s likely just going to assume in the light of an InvalidArgumentException that what it passed obviously doesn’t have an commas in it and process as such.

    But, the point isn’t to write a perfect CountCommas method, it’s just an academic example showing the difference between SESE and non-SESE styles.

  22. I view “return” and “exception” differently. A return is the path back from a method, while an exception is…well, an exception. I will aim towards having a single return, and an exception whereever it is logical.

    I generally dispise multiple return statements. I can’t recall having a real problem (either authoring, or reading others’ code) with overly deep nesting, and I think this can be addressed with other coding practices and guidlines.

    If you have four nestings or four return statements, your method is likely not refactored to begin with.

    All this being said, I think the most important and unbreakable rule is that all the rules can be broken if done with thought and reason. I have seen a few cases where there have been multiple returns and have thought, “Ok…that makes sense…”

    Now, let’s get to a real topic of contention: ternary operation!!

  23. We don’t have to go overboard with the “single exit” rule but I have seen code that doesn’t even attempt to reduce the number of exits. For instance the following code taken from one of the posts above…

    public static bool IsNullOrEmpty(string value)
    {
    if (value != null)
    {
    return (value.Length == 0);
    }

    return true;
    }

    … can be rewritten as follows (by putting short-circuit booleans to good use):

    public static bool IsNullOrEmpty(string value)
    {
    return value == null || value.Length == 0;
    }

    See how the implementation matches the method name.

    I sincerely believe that structured programing is an art that vastly reduces code complexity and those that don’t believe in it don’t know what they are missing. With the exception of exceptions/gaurds/assertions, it’s easy enough to write code with a single exit. Nesting can be minimized by breaking down large methods into smaller ones. If a method is over a page long, it’s probably too long.

  24. I always thought the big benefit of SESE was that it allowed you to do some things at the top of the method (set up data structures, validate args, etc.) or at the end of the method (cleanup, etc.) and be sure that those things would actually be executed.

    OO doesn’t have anything to do with this. Exceptions do foil it, as many have pointed out.

    But every modern language I’m familiar with has some way of automating back-out/cleanup code. C++ stack-allocated object destructors, most other languages’ “finally” constructs, etc. And the single-entry thing is just assumed these days (coroutines are not an exception to this; a C# iterator method is conceptually a single method with a single entrypoint – the fact that the compiler breaks it up is an implementation detail. You still get to do some initialization at the top, and any cleanup can be put in finally blocks that will run with the enumerator is disposed.)

    I’ve found that most people who try to religiously follow the SESE ideal don’t end up writing the kind of code people have been posting here (with lots of nice nested ifs and such.) What they do is write a bunch of “goto CLEANUP;” statements, and then do their cleanup at the end. This is an abomination in a world that has “finally” or its equivalents.

  25. In most OO languages you can allocate data as needed, not like most procedural languages where you have to allocate data at the top of a function.

    If you get away from that procedural method of allocation, SESE becomes very difficult–time better spent writing value-added code.

  26. SESE taken to an extreme is bad, but undisciplined use of break and return is equally bad. Exception handling is a good example of when SESE can and should be abandoned. The fact that we can identify examples where SESE isn’t appropriate does not imply that the idea as a whole is bad and should be abandoned.

    Others have commented on what they see as the horrors of nested control structures. Still others have commented on what they see as the horrors of introducing flags. Consider that there are benefits as well as drawbacks. With nesting, the reader has an immediate understanding of the conditions under which the nested code segment is executing. The nesting itself provides modular contextual information that helps the developer understand the code (without having to study the _entire_ function).

    In terms of break and return statements in loops, consider this:

    while () {
    ….
    }

    What can we assume about the state of the program after the while loop? If we blindly accept return and break statements inside the loop, we can’t assume anything without a careful examination of the body of the loop. If we avoid breaks and returns, we can assume is false. Again, this can provide useful context in terms of modular reasoning about the function.

    SESE is a good and reasonable conceptual baseline. It should not be followed too rigidly, but it should not be outright abandoned.

  27. public static int CountCommas(string text)
    {
    int result = 0;
    Exception exception = null;
    if (text == null)
    exception = new ArgumentNullException(“text”);
    else if (text.Length == 0)
    exception = new ArgumentException(“argument of zero length”, “text”);
    if (exception == null)
    {
    result = text.Split(‘,’).Length – 1;
    }
    else
    {
    throw exception; // if you must
    }
    return result;
    }

    /*
    personally, i’d just
    return (text + “”).Split(‘,’).Length – 1;
    since null has zero commas
    */

  28. I consider multiple returns within a method to be a design smell that begs for refactoring into smaller, self-documenting cohesive chunks e.g.

    (avoid statics, they are not thread safe unless you include synchronization mechanisms)

    public int CountCommas(string text)
    {
    int result = CountTextDelimeterTokens(‘,’, text);

    return result;
    }

    public int CountTextDelimeterTokens(char delimeter, string text)
    {
    int result = 0;

    if (DelimitedTextIsValid(text))
    {
    result = text.Split(delimeter).Length -1;
    }

    return result;
    }

    public bool DelimitedTextIsValid(string text)
    {

    bool result = !String.IsNullOrWhiteSpace(text);

    return result;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>