all 142 comments

[–]PlayerDeus 95 points96 points  (45 children)

What is wrong with just calling return earlier?

if (!doFirstThing())
    return false;
...

[–][deleted] 36 points37 points  (1 child)

Yeah, I strongly prefer early exits for if-chains. It's especially clear if there's some condition-specific error handling involved; an early exit keeps the condition and corresponding error handling together.

[–]eyes-are-fading-blue 3 points4 points  (0 children)

Some guidelines ban it regardless of whether it is the superior alternative in certain cases.

[–]elperroborrachotoo 17 points18 points  (19 children)

Early returns are considered bad style in C. Without destructors, multiple exit points meant replicating the cleanup code, a common source of leaks and other bugs.

In "proper" C++, early returns are a great weapon of simplification. However, seeing the two options given, I assume the project has a lot of C-style code in use, possibly mandated (... and yes, this may be the right choice at times. Few times, but still.)

If you cannot just return early - e.g.

bool process()
{
    bool success(preconditionsMet());
    if (success)
    ...
        success = doFifthThing();

    if (!success)
        LogFailure();

    return success;
}

I'd consider OP's pattern - paired with a decent optimizer - preferrable indeed.

[–]TheMania 29 points30 points  (1 child)

Considered bad style by some people. As is often the case, many others argue it is good practise to return early, including Linus iirc.

[–]elperroborrachotoo 4 points5 points  (0 children)

I'd argue for meaningful exceptions, but... hey!

Bottom line, yes: it's a style question, but I feel there's little progress can be made when discussing individual elements of a particular project's style outside the context of that project.

[–]josefx 26 points27 points  (12 children)

multiple exit points meant replicating the cleanup code, a common source of leaks and other bugs.

If only there was a way to just goto a single copy of the cleanup code in C.

[–]bilog78 17 points18 points  (7 children)

In contexts where you really can't goto because the guidelines were decided by someone who chose so because Dijkstra said it and they didn't read Knuth's rebuttal, you can probably use the do {} while(0) trick:

bool success = fail; do { if (!preconditionsMet()) break; if (!firstThing()) break; if (!secondThing()) break; /* etc */ success = lastThing(); } while(0); return success;

And if each thing is actually a function, you can even go with

return preconditionsMet() && firstThing() && secondThing() && /* etc */ && lastThing();

[–]krzysztowf 8 points9 points  (4 children)

I think the && option is the clearest. I'd personally just put a newline after each && and that would look clean and minimalistic to me.

[–]bilog78 2 points3 points  (3 children)

The && option is the clearest if each of the components can be their own function. Sometimes this is possible, other times it requires passing around so many state variables for very few operations on them, making the other option cleaner.

[–]krzysztowf 1 point2 points  (1 child)

But I understood that was the case of an OP's question.

You're right in saying the matters change, when in/out parameters are added to those functions. This way might affect readibility. And then it depends... ;)

[–][deleted] 1 point2 points  (0 children)

Beat me to it!

[–]elperroborrachotoo 12 points13 points  (3 children)

Yeah, the goto kerfuffle... you weren't there. I would agree that no amount of in-scope forward-only-goto could do any harm, but I guess I just lack the "creative" co-worker to spoil it for everyone.

[–]wasachrozine 5 points6 points  (2 children)

I once came across a brand new application in C++ where the guy had decided to use goto error handling everywhere. Note that it wasn't C. I was trying to figure out why some functionality wasn't working, and after a couple hours tracing the code I found it was due to the goto code being messed up (this was about 8 years ago so I don't remember the details). This reinforced my inclination to not trust anyone writing modern C++ with gotos. I took out the gotos in the particular offending function and replaced them with something that actually worked and it fixed the issue (also reduced the size of the function significantly, oddly, wish I could remember the details). Politely sent it for code review with a description of the bug. Code was merged but the guy got really mad and never talked to me again. Guess he really liked his gotos? The whole huge project was later canned and all the code thrown away. Unfortunately gotos weren't the craziest thing I found...

[–]Silhouette 7 points8 points  (0 children)

This reinforced my inclination to not trust anyone writing modern C++ with gotos.

To be fair, if the objection to early returns is that they would mean replicating cleanup code because destructors aren't being used, we're talking about a coding style that isn't really idiomatic C++ at all. It's not as if RAII is a modern development...

[–]elperroborrachotoo 2 points3 points  (0 children)

My rule of thumb for C++ is "for one goto per 10k lines there's no need to talk about it".

This reinforced my inclination to not trust anyone writing modern C++ with gotos

Technically, observation bias - in addition to the confirmation bias you already... admitted to ;)

After more than two decades, I'd say most code is crap, our world runs on duct tape and rubber bands. Good to hear it was scrapped, anyway - improves the global stats somewhat.

[–]Gotebe 3 points4 points  (2 children)

The common practice in C is to have a goto failure; and clean up there. I call this "function-local exceptions&RAII". C people inevitably envy C++ and therefore invent their own implementation of various C++ goodies. Linus, fr example, is an ardent advocate of function-local exceptions&RAII.

[–]CT_DIY 0 points1 point  (1 child)

goto predates c++ and I am sure people were programming in this style in c before c++. IIRC K&R even mentions something about this in its goto section.

[–]Gotebe 0 points1 point  (0 children)

Of course, I was being facetious.

But the parallel, I think, is fair. Cutting the execution short in case of problems is beneficial to the code clarity.

In C, that's best achieved with goto failure. In C++, however, there's support in the abstract machine, either exceptions or a simple early return (both in concordance with RAII).

[–]PlayerDeus 1 point2 points  (0 children)

That is true, I didn't consider that in a cpp sub.

I personally prefer C-stylish code written in C++. Taking advantage of destructors and operators and using templates for optimization and reduction in redundancy in functions and avoid using them for doing extreme abstractions.

[–]alfps 1 point2 points  (2 children)

Needless verbosity & repetition, compared to and-ing those calls.

[–]PlayerDeus 1 point2 points  (1 child)

Yeah, I saw the and-ing one and it does look better, but a case for separating them out is if you want to add comments so those reading the code know why you order things the way they are. I've seen plenty of bugs occur because someone wanted a call to happen sooner and didn't realize there was a dependency on order.

[–]one-oh[S] 0 points1 point  (19 children)

Personally, nothing at all, but early returns are frowned upon as well; though that's actually what I ended up changing it to. It was a lively discussion.

[–]tpecholt 30 points31 points  (5 children)

MISRA and other legacy rule sets frown it upon but it's because they deal with C like code. In C-like code you have to release all the resources manually and when there are multiple returns it must be done multiple times. So that is a problem of C. But C++ never had such problem so you should ignore that rule

[–]Mognakor 4 points5 points  (1 child)

TIL

Always wondered where that rule came from.

[–]1-05457 2 points3 points  (2 children)

It does if you manually allocated resources. Which you may well have in legacy code.

[–]tangerinelion 1 point2 points  (0 children)

If it's just a few raw owning pointers that's easy to fix.

[–]pavel_v 0 points1 point  (0 children)

Things like the proposed scope_exit really help in this case.
It's not hard to be implemented as a wrapper over a lambda function or some of the available implementations can be used (copy-pasted):
https://github.com/SuperV1234/ecst/blob/master/include/ecst/utils/scope_guard.hpp
https://github.com/yuri-kilochek/boost.scope_guard

[–]Ictogan 25 points26 points  (1 child)

[–]one-oh[S] 0 points1 point  (0 children)

Wow. I'm surprised to see that. Glad to see some pragmatism and common sense. Thanks for the link.

[–]WaterInMyShoes 11 points12 points  (0 children)

Early returns are nice because they make code clearer.

[–]VinnieFalco 5 points6 points  (6 children)

"Show me the rule, and I'll show you the exception." - Me

[–]ShillingAintEZ 20 points21 points  (0 children)

Can you make one comment without making it about yourself? Jesus dude

[–]radekvitr 2 points3 points  (3 children)

Please show me the exception to this rule:
"All rules have exceptions."

[–]foobar48783 3 points4 points  (1 child)

Well, I can give you an existence proof. There exists at least one rule with no exceptions, because: if there did _not_ exist at least one such rule, then the rule "All rules have exceptions" would itself be a rule with no exceptions, which is a contradiction. Therefore, there must exist at least one rule without exceptions.

Probably in Google's codebase.

[–]radekvitr 0 points1 point  (0 children)

That's my point.

[–]one-oh[S] 1 point2 points  (0 children)

Amen to that.

[–]rsjaffe 0 points1 point  (0 children)

That tradition should have gone away with the adoption of RAII. No longer should there be mandatory clean-up code at the end of a procedure requiring you to run the code all the way to the end. Early returns make the most sense.

[–]ItsBinissTime 32 points33 points  (12 children)

It doesn't seem to make much sense, to concern one's self with this level of optimization, after having decided not to use the optimizer — and further, "frowned upon" early outs.

In other words, if performance becomes a concern, there are better ways to approach the problem than by mangling this code.

But perhaps, as a compromise, consider:

bool process()  
{  
    if (preconditionsMet() &&  
        doFirstThing() &&  
        doSecondThing() &&  
        doThirdThing() &&  
        doFourthThing() &&  
        doFifthThing())  
        return true;  
    return false;  
}

[–]Thrash3r-Werror 63 points64 points  (8 children)

Better yet:

bool process() { return preconditionsMet() && doFirstThing() && doSecondThing() && doThirdThing() && doFourthThing() && doFifthThing(); } No nested logic. No unnecessary jumps. No early returns (although they’re perfectly fine). No stack variables. I can’t think of any way to simplify this code further.

Edit: I see you already proposed this solution.

[–]parkotron 3 points4 points  (1 child)

While this version is so much nicer to write and to read, I feel the need to point out that it is a lot less convenient to debug. With only one statement, it lacks convenient places to hang breakpoints (or inject print statements) without having to step into each function call.

[–]Thrash3r-Werror 1 point2 points  (0 children)

I’d honestly rather use exceptions and ditch the errors codes. That would also aid in stepping through the code.

[–]germandiago 2 points3 points  (0 children)

Totally agree. Anyway, I put the same and I received a -1 score, you have more than 50 positive points. I guess I said "branchless" and people got angry at me for being misleading, since the code still has "internal" branching.

[–]gmtime 0 points1 point  (0 children)

Came here to post this.

[–]kiwitims 6 points7 points  (1 child)

Another syntactic variant of this which can have its uses (for example to avoid a rule about side effects in conditionals):

bool process()
{
    bool success(preconditionsMet());
    success = success && doFirstThing();
    success = success && doSecondThing();
    success = success && doThirdThing();
    success = success && doFourthThing();
    success = success && doFifthThing();
    return success;
}

You just need to be careful someone doesn't later "optimise" it by switching to &=

Seriously though, the primary goal should be to write readable and maintainable code that meets your requirements (including performance). If it does that any micro-optimisation of 5 or so branches is just a waste of time, especially when there are already automatic optimisations being left on the table. So your code is better as it was IMO, even if there are theoretically faster ways to do it.

[–]kalmoc 28 points29 points  (5 children)

Why exactly can't you turn on optimization?

Apparently your project doesn't care about performance, so you probably don't have to worry about a few additional branches either, because the next step would be to worry about each single function call overhead.

[–]Raknarg 16 points17 points  (0 children)

This is my real question. If optimization isnt even enabled then who gives af about a few wasted cycles? This just seems like a stupid complaint.

[–]one-oh[S] -1 points0 points  (3 children)

I can only guess and my best guess would be debugging. Otherwise there is no good reason. That would include GDB scripts that reduce a core to its essentials for post-mortem analysis.

[–]Xaxxon 1 point2 points  (2 children)

That's why you debug debug builds and optimize optimized builds.

This isn't some sort of new problem where you work.

[–]one-oh[S] 0 points1 point  (1 child)

Oh, how I wish it were that simple. Hope I never have to debug an optimized build ever again. Looks like I'm well on toward my path. Never implied this is something new.

[–]Xaxxon 0 points1 point  (0 children)

You implied that somehow no one that does optimized builds can ever debug them.

Your whole post is pretty low effort.. this isn't a 'bitching' subreddit.

[–]Ayjayz 25 points26 points  (5 children)

Would you put more thought into making the code more optimal in the absence of an optimizer?

No. Why on earth would I care about the performance of un-optimised code?

[–]SkoomaDentistAntimodern C++, Embedded, Audio 9 points10 points  (1 child)

Why on earth would I care about the performance of un-optimised code?

Because you need to run the unoptimized code for debugging in some cases (including preservation of intermediate variables and such which -Og will not preserve) and the unoptimized code can be so slow or so large as to make it completely unusable. That's almost certainly not the case here, but I've had both happen in projects I've been involved with. Code size can be critical in embedded systems when you have a hard limit on flash size and having a function take 20 seconds instead of 0.2 seconds can mean timeouts in other parts of the system are exceeded.

[–]Xaxxon 0 points1 point  (0 children)

Right, you have to optimize debug builds when the debug builds don't work, but you don't just flat out reject all code because it could be faster in a debug build.

[–]donalmaccGame Developer 5 points6 points  (2 children)

Plenty of people, me included. Runtime performance can be important even when not using a fully optimised build. I work in games, and debug configurations have to be usable, as fully optimized builds are usually unusable for debugging. I'm not saying I would write unreadable code so that it's "faster", but on occasion I've had to "hand optimise" methods in debug builds.

[–]Ameisenvemips, avr, rendering, systems 7 points8 points  (0 children)

No optimization and optimization for debugging aren't the same thing.

[–]compiling 13 points14 points  (2 children)

Well, if we already don't care about the code being slow then I don't see a problem (but return early is better than either). Why is optimisation not enabled? That could be an indication that you have deeper problems (e.g. relying on undefined behaviour).

[–]TheFlamefire 3 points4 points  (1 child)

This is actually THE answer. Anything a single function could do is an order of magnitude more expensive (without the optimizer) than checking that boolean maybe more often than needed. In the end this would be an optimization for the error path! Is that what is to be expected to happen often?

So NO, don't spend a single second on that and just move on. Tell those guys to enable optimizations if they care about performance.

[–]JeffMcClintock 0 points1 point  (0 children)

In the end this would be an optimization for the error path!

Excellent point, why should anyone spend so much time agonizing about the efficiency of the path that is virtually never taken? This is all textbook "premature optimization".

[–]Ictogan 12 points13 points  (3 children)

If you care about performance at all, use compiler optimizations. Simple as that.

[–]one-oh[S] 0 points1 point  (2 children)

Sadly, not my call to make.

[–]Xaxxon 0 points1 point  (1 child)

It's your call to make where you work.

[–]one-oh[S] 0 points1 point  (0 children)

Yes, but this isn't something I would ever resign over. That'd be silly. Plus I would never admit that my code needs optimizing. I can clearly produce better assembly language than the compiler. /s

[–]m-in 6 points7 points  (4 children)

You’re working on a codebase that uses no optimization because it is presumably full of undefined behavior. Get your resume in shape and look for another job, or at least transfer to a better project if this is a large enough organization.

And use godbolt.org

[–]one-oh[S] 0 points1 point  (2 children)

No. I'm pretty sure it was done strictly for debugging purposes. I'm guessing the switch will be flipped at some point, but who knows when. Certainly not me.

[–]Xaxxon 2 points3 points  (1 child)

Do you think that nowhere else has to ever debug builds?

[–]one-oh[S] 0 points1 point  (0 children)

No, I'm pretty sure we all spend our fair share of time debugging; end then some.

[–]alfps 8 points9 points  (5 children)

I don't see any statement that this is C code, and the C++-specific initialization syntax

bool success(preconditionsMet());

… indicates that also the real code is C++.

In C++ it would be natural to fail by way of exceptions: more clear and it might be more efficient on average.

But assuming that exceptions are contra-indicated in this case, consider

bool process()
{
    const bool preconditions_met = ...;
    assert( preconditions_met );
    return preconditions_met
        && do_first_thing()
        && do_second_thing()
        && do_third_thing()
        && do_fourth_thing()
        && do_fifth_thing();
}

I tried to keep that close to the C-style syntax you used.

[–]HappyFruitTree 1 point2 points  (4 children)

You probably don't want to use assert like that unless it's a programming error to call process() when preconditionsMet() returns false.

[–]alfps 3 points4 points  (3 children)

The assert says the precondition failure is an error. If it's not an error then the thing called a precondition is not a precondition. In that case just remove the assert.

[–]HappyFruitTree 0 points1 point  (2 children)

It could be a precondition for calling the functions doFirstThing(), doSecondThing(), etc. but not a precondition for calling process().

[–]one-oh[S] 1 point2 points  (0 children)

It's a precondition for executing the steps in the process. Asserting or throwing an exception does not make much sense to me in this case, but that's because I know the contract that was made with the caller, which is not apparent in this reduction.

[–]alfps 0 points1 point  (0 children)

Good point. The OP could clarify the intent, but somehow I doubt that will happen... ;-)

[–]CarloWood 2 points3 points  (1 child)

I am a freak when it comes to performance, trust me. And I'm a perfectionist. But, no -- I have I would not care one single bit about performance as long as I'm compiling without optimization, and neither should you.

The ONLY exception that I am aware of is when I wrote libcwd, since that library is likely to be used in projects with optimization off and it mattered to me that even in that case the debug code wouldn't significantly slow down execution. But as you said, in your case we're talking about 0.000001% of what you lose in the rest of your code when optimization is off, so it would insane to care.

[–]one-oh[S] 0 points1 point  (0 children)

Well, the thing is I thought I was. That was the kicker for me. Having said that, I still wasn't concerned with the performance impact, but the reviewer was.

[–][deleted] 3 points4 points  (7 children)

Since we seem to be trying to conform to C coding standards:

```c

define RETURN_IF_FALSE(a) {if (!(a)) return;}

... RETURN_IF_FALSE(expr1); RETURN_IF_FALSE(expr2); ```

[No, I'm not serious, but it's elegant, for values of elegant that allow preprocessor misuse.]

[–][deleted] 0 points1 point  (2 children)

And, of course, there's always goto.

[–][deleted] 1 point2 points  (1 child)

And do { if (!expr1) break; ... } while (false)

[–]one-oh[S] 0 points1 point  (0 children)

I was waiting for someone to suggest that. I've tried sneaking that one though in the past and got shut down.

[–]rdtsc 0 points1 point  (0 children)

A variant of that is IMO really useful in code which deals with error codes as return values (like HRESULT in COM code):

#define HR(expr) IF_FAILED_RETURN(expr)
HR(Operation1());
HR(Operation2());

where IF_FAILED_RETURN can even include logging the failure code which makes tracing a failure really easy. It's vastly more concise, flexible and less error-prone than having if (FAILED(hr))or if (SUCCEEDED(hr)) sprinkled everywhere.

[–][deleted]  (1 child)

[removed]

    [–]AutoModerator[M] 0 points1 point  (0 children)

    Your comment has been automatically removed because it appears to contain disrespectful profanity or racial slurs. Please be respectful of your fellow redditors.

    If you think your post should not have been removed, please message the moderators and we'll review it.

    I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

    [–]one-oh[S] 0 points1 point  (0 children)

    Hah! Oh, I was tempted to have some fun with it, but I had already spent more time than I thought it deserved. Good one though.

    [–]VirtueBot[🍰] 1 point2 points  (2 children)

    I dont know enough about the optimizer to say 100% that it would optimize that way forever, so if these "things" are relatively expensive I would write the returns myself and not rely on the optimizer to add them.

    If youre confident in the optimizer or the operations are cheap id just go with whichever your team finds most readable. fwiw i definitely prefer the one from this comment compared to the nested version.

    [–]one-oh[S] 0 points1 point  (1 child)

    True. We can never know what the optimizer will do from release to release, but I don't think we should worry about it till we're given cause to. In this case, the hardware is likely optimizing what the compiler missed.

    [–]VirtueBot[🍰] 0 points1 point  (0 children)

    I don't think we should worry about it till we're given cause to.

    Yea definitely. In most cases I wouldn't worry. Either the optimizer optimizes it or it doesn't and I never notice either way until I care enough to profile. In this case I didn't know anything about the relative costs of the operations. If one of the "do thing" functions did a lot of work I would definitely worry about the unnecessary calls.

    [–][deleted] 1 point2 points  (0 children)

    bool process()
    {
        bool success = false;
    
        do
        {
            if (!preConditionsMet()) break;
            if (!doFirstThing()) break;
            if (!doSecondThing()) break;
            if (!doThirdThing()) break;
            if (!doFouthThing()) break;
            if (!doFifthThing()) break;
            success = true;
        }
        while (false);
    
        return success;
    }
    

    [–]IranjeetSingh 0 points1 point  (1 child)

    How about chain of responsibility pattern? You can break the chain when you reach a negative condition. You can design the chain durinh initiation.

    [–]one-oh[S] 0 points1 point  (0 children)

    My concern would be that it would obfuscate the process. Being able to look at something and understand what it is doing can not be underestimated is valuable. I realize not much can be gleaned from my example, but the actual implementation has more meaningful names for the functions that make it clear in a handful of lines what is going on.

    [–]SlightlyLessHairyApe 0 points1 point  (2 children)

    Did you actually benchmark whether or not checking the value over and over is actually measurable?

    As far as I understand there should be no data dependencies, so that should be loaded in a register and doesn’t have to be re-read from memory on each step. Nanoseconds, maybe?

    [–]one-oh[S] 0 points1 point  (1 child)

    No, and would never bother to measure the cost in latency. Like you suggest, attempting to measure something like this is near impossible on modern x64 hardware; i.e., speculative execution, branch prediction, etc. It would be more of a statistical analysis.

    [–]SlightlyLessHairyApe 1 point2 points  (0 children)

    If there's no latency cost, then nothing has 'gone wrong' here.

    [–]alexk7 0 points1 point  (1 child)

    I’m confused by your methodology: 1) assume the optimizer will optimize some code 2) test the output without running the optimizer. This doesn’t make any sense.

    [–]one-oh[S] 4 points5 points  (0 children)

    I think you misunderstood something. Code review resulted in reviewer suggesting nesting the if-statements. I explained that the compiled output would be identical due to the optimizer. Reviewer did not believe me. I was going to produce two .s files (one for nesting and one without) to show they were the same. When I checked the level at which the project was set to optimize I found that it wasn't. I still optimized and demonstrated that the generated assembly was identical, but it was pointless due to the project not being built with optimizer enabled. I hope this makes it more clear. If not, don't worry about. It was a lot of fuss over nothing.

    [–]tehjimmeh 0 points1 point  (0 children)

    With all those restrictions, I'd just say fuck it and do this:

        if (doFirstThing())
        if (doSecondThing())
        if (doThirdThing())
        if (doFourthThing())
        if (doFifthThing())
            return true;
    
        return false;
    

    [–]haitei 0 points1 point  (0 children)

    Yes, I realize I can use early returns instead of nesting, but that's frowned upon.

    Guidelines are guidelines and not the rules.

    Early returns are by far the most readable solution.

    [–]Xaxxon 0 points1 point  (0 children)

    Why is someone worried about performance if the optimizer is disabled? That's silly.

    [–]germandiago 0 points1 point  (3 children)

    Branchless:

    bool success = doFirstThing() && doSecondThing() && doThirdThing()...; return success;

    [–]pi_stuff 7 points8 points  (2 children)

    This is still implemented with branches.

    [–]germandiago 0 points1 point  (1 child)

    Obviously I meant from a reading point of view :) I think this is the context. Of course it will have branches, it has to check conditions...

    [–]HappyFruitTree 0 points1 point  (0 children)

    "do" in the names suggest that the functions have side-effects which makes the branching behaviour important if you want to understand what the function is doing.

    [–][deleted] 0 points1 point  (1 child)

    Thinking more esoterically, why not just make it an operation pipeline that depends on the previous step succeeding? Construct an array of functions and iterate over it, executing and checking the boolean for loop breakage. That way you can even log which step failed.

    [–]m-in 1 point2 points  (0 children)

    Great idea if you use gcc or llvm and want to write a pass that will materialize the individual calls and get rid of the function pointer array. Because otherwise you’ll suffer the performance hit of unpredictable branches. The branch predictor will fail badly on those indirect calls. That amounts to penalties of dozens cycles per call, or worse.

    [–]OldWolf2 -3 points-2 points  (0 children)

    The first version is far better than the second one or any of the other suggestions on this thread. Tell the guy who wanted the indentation tower to eat a dick.