News for hackers. Stuff that matters.
Re:Avoid Else, Return Early (2013) by weinzierl on Monday March 26, 2018 @10:14AM (#16679027)
> Programmers with lots of hours of maintaining code eventually evolve to return early, [..]

I agree with all your other points and I‘d even agree if you wrote return early makes code more readable but not that it‘s something experienced programmers do.

Programming is still - to some degree - resource management. Inexperienced devs often miss that fact, because they are focused on memory management and believe they can rely on the garbage collector for clean-up. In real projects you quickly end up managing file descriptors, sockets, handles, GUI resources, etc. Early return is terrible for that, because your coding style starts to depend on the kind of resource to manage.

That‘s why the more experienced folks end up writing single exit point code sooner or later.

EDIT: I‘m not arguing that early return and proper resource management are incompatible, just that most experienced programmers have often been burnt enought to avoid it.

Re:Avoid Else, Return Early (2013) by 3pt14159 on Monday March 26, 2018 @10:25AM (#16679122)
Oh common. This might be true for a very specific type of codebase, but it isn't true for programming in general. If I'm coding a library of standard statistical functions I'm exiting early. If I'm coding a Rails webapp I'm exiting early. If I'm coding a shell script, even one used on tens of thousands of servers, I'm exiting early.

The resource almost all programmers are managing is the resource of human time. Human time to code the project, human time to review the code, human time to alter the code after the fact, human time to port the code to different environments. If you're writing code to run AI at Google then fine, you're managing resources at a level where performance starts to hit limits that warrant a style change, but generally speaking you know that ahead of time and you choose tools to handle a project of the right scope, but generally speaking exiting early isn't impacting your code performance. Big O mistakes are.

Re:Avoid Else, Return Early (2013) by jjnoakes on Monday March 26, 2018 @11:42AM (#16679687)
Don't forget the human time to debug the code (especially the rare or only-happens-under-load issues, which can be related to resource exhaustion, among other things).

Also, some of your examples (like altering or reviewing code) can become easier and faster if resource management is a first-class citizen in your code base or language (i.e. is either more explicit in the code, and you know to look for it and manage it, or it is somehow taken care of automatically by your language, so you would have a hard time forgetting it, even if it is handled implicitly).

Re:Avoid Else, Return Early (2013) by 3pt14159 on Monday March 26, 2018 @02:01PM (#16680998)
Yeah that is true.
Re:Avoid Else, Return Early (2013) by bottled_poe on Monday March 26, 2018 @10:36AM (#16679185)
I guess I would agree with the concept of managing “human time”, but I believe you are focusing on the wrong human. The developer will certainly spend a lot of time writing and maintaining a code base, but that’s minuscule compared to the time spent by the end user. What software design principles will save the user the most “human time”?
Re:Avoid Else, Return Early (2013) by weinzierl on Monday March 26, 2018 @10:43AM (#16679228)
Cleaning up resources after use is not a question of performance, it is a question of correctness.
Re:Avoid Else, Return Early (2013) by 3pt14159 on Monday March 26, 2018 @10:46AM (#16679257)
Choosing an environment that requires you to clean up resources instead of handling it for you is mostly a question of performance.
Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @10:52AM (#16679312)
Not really, inlined RAII is just as fast as doing it yourself.
Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @10:20AM (#16679070)
That's one of the reasons I love RAII; all resources, memory and otherwise, are treated the same.
Re:Avoid Else, Return Early (2013) by weinzierl on Monday March 26, 2018 @10:39AM (#16679210)
That‘s what I was thinking when writing my edit. The somewhat ironic thing is that C++ people could use early exits safely, but often don’t, while Java folks typically write early exit code when they really shouldn’t.
Re:Avoid Else, Return Early (2013) by jasonmaydie on Monday March 26, 2018 @10:22AM (#16679093)
it's not just about in app resources. This could also affect external resources.. like not cleaning up temporary files because a future maintainer returned early and left some critical data behind.
Re:Avoid Else, Return Early (2013) by klmr on Monday March 26, 2018 @10:27AM (#16679132)
RAII can handle such resources, and good use of RAII does handle such resources. Languages without time-deterministic GC even tend to add extra language constructs to aid this, such as `using` in C# and Java (7?) with its new `try` block.
Re:Avoid Else, Return Early (2013) by jstimpfle on Monday March 26, 2018 @11:32AM (#16679617)
Downside is, now your cleanup code is spilled all over the codebase. And where it's implemented it lacks often the necessary context (from the location of use). So, it's often wrong or incomplete, or suffers from the type-for-single-usage syndrome.

Yes, RAII works for the casual std::vector, but it's not a maintainable solution for general resources.

Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @11:57AM (#16679805)
It's right next to the create code, not 'spilled all over the codebase'. And if you really want different behavior on destruction (which is _really_ rare IMO), you can pass a destruction strategy at object construction. If you don't know how you want to destroy something when you construct it, you need to take a step back and reexamine your architecture.
Re:Avoid Else, Return Early (2013) by Too on Monday March 26, 2018 @12:10PM (#16679920)
True, for those who don't agree, read about rule-of-zero for some expansions on this. If you have to write the same type of clean up code in multiple destructors you are doing it wrong, wrap that resource in a class that has the responsibility of managing that type of resource and forget about the destructors elsewhere.
Re:Avoid Else, Return Early (2013) by jstimpfle on Monday March 26, 2018 @04:29PM (#16682265)
And the create code is spilled over the codebase, just as well :-). It's not where actual work is done. This approach of encapsulating functionality in a class may work in some instances such as std::vector but more often than not it's just a lot of (typical OOP) boilerplate for things that you do only once in a codebase anyway. Giving one-off things a type and a name and decoupling them from normal control flow (ripping them out of context) makes things just harder to understand. Better just make them global data, maybe even define the initialization/cleanup routines for that global data in-line with the rest of the control flow.
Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @04:39PM (#16682368)
I mean, having global resource managers is orthogonal to generating the events that cause resource destruction. And you're not 'ripping them out of context's, since the guard objects are still there where you would be doing the resource management manually.

EDIT: and I was the lead for a high availability C++ RTOS. I know that not all patterns fit for writing quality, available code. This fits remarkably well though. Even though we were totally async and didn't rely on the stack for context lifetimes.

Re:Avoid Else, Return Early (2013) by jstimpfle on Monday March 26, 2018 @05:31PM (#16682818)
> And you're not 'ripping them out of context's, since the guard objects are still there where you would be doing the resource management manually.

The guard object is there (e.g. "on the stack") but the destructor is not defined in-line but in a separate class method definition. Classes have always that implicit ideal of being "isolated". This is wishful thinking, of course. In the end all the parts of the code need to contribute to the program. That's why OOP codebases often end up as a terrible mess. Classes can't decide if they want to be isolated or involved. The compromise is all this terrible implicit state. (do you know the quote by Joe Armstrong about the banana, monkey and jungle?)

Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @05:45PM (#16682916)
Guard objects dont have to be on the stack. They work beautifully inside a context object that's registered with global manager if you're in an async codebase. std::move is a glourious thing.
Re:Avoid Else, Return Early (2013) by drawkbox on Monday March 26, 2018 @10:29AM (#16679150)
> starts to depend on the kind of resource to manage.

Exactly, simple but not too simple. If the condition does not need to return early or can't, you don't.

> quickly end up managing file descriptors, sockets, handles, GUI resources

In some of the cases you describe you wouldn't return early or depending on the need, but if you can you should to minimize work.

Some examples: file descriptors you'd return early if the file does not exist, sockets you'd return if dropped or compromised and you have performed cleanup, handles if null, gui resources can be lots of things but if you were doing something where you needed to cleanup you can cleanup and return. In C# for instance you might wrap some of that in a using such as files, sockets, streams etc. Most of your examples you might already be in the meat of the return early flow and usually there is still a return at the very end but in/after the meat.

Returning early isn't saying do a dirty break or asserting out, you don't return early before cleaning up if you are within some resource.

Re:Avoid Else, Return Early (2013) by Too on Monday March 26, 2018 @12:17PM (#16679990)
Single exit point is not compatible with exceptions which most GC languages use extensively. Resource management in such environments is done with finally/using/with.

Even C has auto cleanup with __cleanup__ attribute in both gcc and clang.

Re:Avoid Else, Return Early (2013) by boomlinde on Tuesday March 27, 2018 @10:18AM (#16688598)
I frequently use `goto` in C for this reason. A very common pattern is that I acquire some resources, do something with them, and have a section at the end of the function where I release them in LIFO order. This is labeled so that failure to acquire any resource will jump to a label just before the resources that were acquired before it are freed.

Other languages have constructs that obviates this pattern, for example Python has `with` blocks that acquire resources and free them upon leaving the block, and Go has a `defer` statement which adds code to some LIFO structure that always gets executed upon returning.

Re:Avoid Else, Return Early (2013) by recursive on Monday March 26, 2018 @10:28AM (#16679145)
I have doubts about whether "real" projects generally have these kinds of concerns. But even if they do, you can clean them up in a `finally`, or your language's equivalent.
Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @10:51AM (#16679306)
Most languages with try/catch/finally don't have checked exceptions, so handling resource management in the those schemes is really brittle.
Re:Avoid Else, Return Early (2013) by klmr on Monday March 26, 2018 @11:52AM (#16679762)
`finally`/RAII doesn’t depend on checked exceptions. It always runs (short of something that aborts the program without unwinding the call stack; and in those cases single-exit won’t save you).
Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @12:18PM (#16679995)
Mixing finally and RAII into the same category doesn't really make sense here.

And I'm not saying that finally depends on checked exceptions, I'm saying that pattern is very brittle. You change lower code to throw a new exception, and you change the above code to catch it like you're supposed to, but now the middle code has no idea that there's this new exception and leaks resources. So you end up either having brittle code who's correctness depends on implicit choices of the code around it, or you're wrapping pretty much all function bodies with try-catch-finally-rethrow.

Re:Avoid Else, Return Early (2013) by Chris_Newton on Monday March 26, 2018 @05:39PM (#16682870)
I find it a good rule of thumb to allocate and deallocate a resource at the same level in my design, and to maintain any associated handle at that level as well. I have encountered few situations where this didn’t work well, and almost all of them arose because an external module introduced some form of unavoidable asymmetry at its interface.

If you’re able to follow that pattern then safe handling of unusual control flows tends to follow naturally. You just adopt whatever mechanism your language provides for scheduling the code to clean up and release resources at the same time as you allocate those resources. Almost all mainstream languages at a higher level than C provide a suitable mechanism today: RAII, some variation of `with` or `using` block, try-finally, Lispy macros, Haskelly monad infrastructure, etc.

That way, the only places you need to write any extra logic are the layers where you allocate/deallocate and possibly lower layers that manage side effects on those resources in the unusual case that they require a specific form of recovery beyond the default behaviour in the event of aborting early.

Hopefully if you do have to work at low level like C or assembly language and you’re using forms of control flow that could bypass clean-up logic then you already have at least some coding standards established for how to manage resources safely as well.

Re:Avoid Else, Return Early (2013) by klmr on Tuesday March 27, 2018 @10:57AM (#16689037)
> middle code has no idea that there's this new exception and leaks resources

You misunderstand how `finally` works. The whole point is that it runs regardless of exception type. So the resulting code in the middle layer doesn’t need to know anything about the code it calls, and doesn’t leak resources, even when calling or called code changes: It’s not brittle.

> or you're wrapping pretty much all function bodies with try-catch-finally-rethrow.

No need for `catch` and rethrowing. And in the case of C# and modern Java, no need for the rest either: You only need to wrap resources that you allocate into `using` (C#) or `try` (Java; but not `try…finally`! [1]). Sure, it’s more verbose than RAII in other languages with automatic storage duration (like C++). But it effectively performs the same.

[1] https://docs.oracle.com/javase/tutorial/essential/exceptions...

Re:Avoid Else, Return Early (2013) by monocasa on Tuesday March 27, 2018 @11:45AM (#16689595)
I just had a brain fart.
Re:Avoid Else, Return Early (2013) by recursive on Monday March 26, 2018 @08:28PM (#16684069)
`finally` doesn't depend on catching any particular errors. If control flow exits its block for any reason, then the `finally` block executes.
Re:Avoid Else, Return Early (2013) by jstimpfle on Monday March 26, 2018 @11:34AM (#16679632)
Wrong. You need to exit before resource allocations :-). And in general if you allocate resources in most functions you're doing it wrong. Need global resource managers. Stack variables should only very rarely be resource owners.
Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @01:09PM (#16680502)
RAII works just as well for heap allocated objects as it does for stack allocated ones.
Re:Avoid Else, Return Early (2013) by jstimpfle on Monday March 26, 2018 @03:36PM (#16681758)
Sure, but allocating resources on the stack is not good for maintainability. Software is easier to understand if its state is not hidden in semi-permanent call stack frames.
Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @04:28PM (#16682252)
In general I agree with you, but that seems orthogonal to the subject at hand.
Re:Avoid Else, Return Early (2013) by jstimpfle on Monday March 26, 2018 @05:38PM (#16682864)
Parent's statement was that early returns are hard because you need to cleanup allocated resources. I don't think so, because there shouldn't (usually) be resources "on the stack".
Re:Avoid Else, Return Early (2013) by monocasa on Monday March 26, 2018 @05:53PM (#16682985)
But RAII is orthogonal to auto, static, or dynamic allocation.
Re:Avoid Else, Return Early (2013) by christophilus on Sunday April 01, 2018 @08:33PM (#16731391)
Is this true? C# and Go both have a nice way to clean up disposables on early return. I'd imagine most languages are similar these days?