Ditch That Else
preslav.me
preslav.me
I was young and impressionable so when a senior developer advised me of this nugget of "best practice" I spent the next couple of years writing awful code, jumping through hoops to mangle the logic in my methods such that they would only ever have a single return statement at the end.
Thank goodness I eventually awoke from that terrible practice. I still occasionally hear people espouse this, but not too often nowadays.
Hewing more towards the idiom while trying to force functions towards a single return resulted in… a lot of statements, producing a lot of intermediate state. And it all gets much harder to follow.
I’ve since embraced early edge cases and early returns, largely because it allows more idiomatic use of a functional style in an imperative context. And all of that makes the code a great deal easier to understand than it would be otherwise.
As a self taught hobbyist (crap) programmer that doesn't try too hard to make 'good' code. It nevertheless seems obvious to me that you'd want to deal with the errors first. I kind of disagree with the articles Pareto principle. 80% of programming is working out all the ways that things could go wrong, so it's better to think about those first, rather than getting into to juicy bits and leave the error handling for 'later'
Maybe it was from my ASM phase, and wanting to avoid the extra branch?
Or maybe it's because 'professional' programming is more cargo culting than we'd like to admit?
I don't recall this requiring much code mangling though?
It means defining the return variable at entry and set its value to the "failed" value (eg NULL or false) and return it once at the bottom.
So you only touch the return on the happy path which reduces the number of error related else's, can reduce the need for guards too, it's consistent and you don't get caught out by early returns.
I still use this when it suits the application.
I refactored it all as the author suggests, out of desperation to better understand it in a debugger if more code could fit on a page. To my astonishment the memory leak disappeared, so it has always since been a habit, sometimes I still rewrite code this way just to better understand it, I think it looks better.
It can be confusing but I really appreciate one-liner `unless` statements in ruby for guards
edit: not a fan of unless/else though
This rule is actually what got me into the practice of returning early from the edge case so many years back. I think it was enabled by default in the—then very popular—AirBnB coding standard.
Gosh I am working on beating that out of my Ruby folks. Sure I came into the team from other languages and they're all Ruby natives, and sure this is accepted practice in the Ruby community. But it's just awful and I won't accept it.
Maybe try adding some `unless` statements, I've heard they really help.
The “fail early” principle is a widely accepted pattern, and that’s exactly what the `unless` statement facilitates, while communicating that is exactly what it’s doing.
for filename in os.listdir(dirname):
if not filename.endswith(".png") and not filename.endswith(".jpg"):
continue
# process a png or jpg image
than to try and have a nested `if filename.endswith(".png") or filename.endswith(".jpg")`, particularly as it drops one nesting level and is easier to read. Unfortunately, Python lacks targeted break/continue so you can't break out of two nested loops without some contortions; in such cases, I find it useful to refactor code into smaller functions just to be able to use early return from a nested context.Before blindly rewriting, have a mental picture of what is context/state at the point of early return, which at each time has to achieve the goals of function, setting background states/data points/return pointers/etc. Its easy only in simple functions.
It's also robbing them of a learning opportunity, and it's creating more work for you.
Mention it on the PR, maybe show an example of how you would write it in a code snippet, and re-review after they've taken a stab at it.
Having it completely separate when it's only ever going to be used from one place isn't exactly the clearest and means extra variable passing. Pulling it into a nested procedure, though, makes it very clear what's going on and much easier to follow. My general rule is no flow control other than bailout in anything more than a dozen lines or so.
There is one very well-made YouTube video on it, IIRC.
Whether you do return-early or nested if-else, there's little reason for having such lengthy functions. Dealing with THAT should be the priority, not the nesting vs return-early.
I’ve inherited codebases with this overemphasis on very short functions and what I saw were very specific utility functions in dozens of different modules, all over the codebase, often with funky names, surrounded by dead code, and never a clear path to and from it. Sometimes a single function that spans even a couple of hundred lines, is preferable.
When the edge cases get bloated, it’s time to make a validator function for your input and leave the main function with happy path only
... it also makes branch predictions more accurate