Reducing Code Nesting
eflorenzano.com
eflorenzano.com
For things like the first example, why not something like (in Ruby):
# Returns a cached user object either by their user_id or by their username.
def get_cached_user(user_id = nil, username = nil)
user = cache.get_user_by_id(user_id) ||
cache.get_user_by_username(username) ||
db.get_user_by_id(user_id) ||
db.get_user_by_username(username) ||
raise ValueError('User not found')
cache.set_user(user, user.id, user.username)
return user
end
You just try all your getters, cheapest first, and when the getter returns a nil or false value, the next one will be tried. Once one is found, no more are tried. Then you just set the resulting value in your cache and return the value. One return statement, no ugly nested ifs, and your unnecessary statements never evaluate, which is what you want anyhow.My point still stands though: nesting is bad. The takeaway from your comment is that you can solve the issue sometimes by factoring out smaller more focused functions instead of returning early, as you've demonstrated.
Now, some people will claim that having 8-character indentations makes the code move too far to the right, and makes it hard to read on a 80-character terminal screen. The answer to that is that if you need more than 3 levels of indentation, you're screwed anyway, and should fix your program.
Yes indentation != nested ifs, but in the end its about the same. I don't mind a bit of nesting, but I think Linus is onto something with >3 meaning: you've done something wrong, step away from the keyboard and think about what you've done.
A single if statement in a class method would put the indentation at 3, meaning if you have something like this...
class MyClass(object):
...
def my_method(self, param):
try:
self.calculate(param)
except MyException:
if param == 100:
...
else:
...
you're screwed? I don't see how you can set such a definitive measure of bad code. class Foo(object):
def my_method(self):
try:
if foo is bar:
self.quux()
except:
pass
This seems acceptable to me. Factor out the try block if you want more conditionals and use polymorphism instead of conditionals. Rather than writing: class X(object):
def quux(self):
if self.foo is self.bar:
self.quux_foo()
else:
self.quux_bar()
Write: class X(object):
def quux(self):
self.quux_bar()
class FooIsBar(X):
def quux(self):
self.quux_foo()
No conditionals and your intent is encoded in your class hierarchy instead of inside some random method.Feel free to correct me if I'm wrong. Still trying to learn the difference between inheritance and "evil" inheritance :)
Roles let you implement pieces of classes without being a full class yourself. A classic example is a "Comparable" role, that requires the consumer to implement a "cmp" function, and then provides lt, gt, and eq implemented in terms of "cmp". You mix this into your class that does cmp, and now it gets lt, gt, and eq for free. You also get the security of knowing that you're calling the right method; duck typing will treat a tree's bark and a dog's bark as the same actions, while roles will let you determine whether you have an object with a wooden outer layer, or an object that can talk like a dog.
Python's philosophy doesn't seem to push delegation or composition, so we use inheritance in the above example to stick with Python programmer's expectations. Just beware of invoking the wrong type of bark; duck typing is flexible, but it ain't safe.
That particular cliché doesn't work if the essential complexity of data/algorithms you're working with implies more than three levels of decision-making. This can easily occur with graph-related operations like subgraph matching and graph rewriting, for example.
Splitting out the inner levels of such algorithms into separate functions doesn't gain you anything (unless the particular nesting you've got represents a subgraph that is meaningful in its own right out of context, of course) so deep nesting within a single function may be making the best of a fundamentally awkward modelling problem.
You should go one step further and conclude that nesting is a warning sign often indicative of bad code, and that just fixing the nesting is not the solution to the problem.
But somehow I still havent internalized the fact that when assembly code is generated for such an OR statement it is done in a way such that the if any sub statement is evaluated to be true then all the other sub statements are not evaluated at all.This is true with ANDs as well.
Can you please give me a link that proves conclusively that gcc and clang do this?
Short-circuiting can lead to errors in branch prediction on modern processors, and dramatically reduce performance (a notable example is highly optimized ray with axis aligned box intersection code in ray tracing)[clarification needed]. Some compilers can detect such cases and emit faster code, but it is not always possible due to possible violations of the C standard. Highly optimized code should use other ways for doing this (like manual usage of assembly code).[citation needed]
That sounds like someone may be thinking about the conditional-move and family on some CPU instructions. That could certainly interact with the branch prediction, but there's nothing about the operators that necessitate it.
Sure, it may be that some compilers do generate different code and some runs faster than others, but that's just not the kind of thing you can tell without benchmarking those very specific conditions. If you're writing a ray tracer, you're probably are going to want to hand-tune the assembly but that's not the common case of C and C++ programming.
You would need something in-between like:
# Returns a cached user object either by their user_id or by their username.
def get_cached_user(user_id = nil, username = nil)
user = cache.get_user_by_id(user_id) ||
cache.get_user_by_username(username)
if not user:
user = db.get_user_by_id(user_id) ||
db.get_user_by_username(username) ||
raise ValueError('User not found')
cache.set_user(user, user.id, user.username)
return user
endIt's dependent on your infrastructure and demands, of course, but I probably wouldn't bother optimizing that unless cache sets were prohibitively expensive.
I usually try to make them use the form:
return x if y
To me, this makes it easier to follow a particular flow when reading code.
As soon as I encounter a return that matches it's conditional, I don't have to care about the rest of the method anymore.
During debugging, this also quickly lets me test assumptions by putting some log statements right after the return that should be occuring.
The only time early returns irk me is when they're deeply nested, but as mentioned by TFA and others in this thread, having deeply nested code is a bit of a code smell in its own right.
In SQL there is the handy COALESCE function [1] which does exactly what I want in this case: It simply returns the first non-NULL value of its arguments, even if it is FALSE, 0 or "".
def get_cached_user(user_id = nil, username = nil)
user = coalesce(
cache.get_user_by_id(user_id),
cache.get_user_by_username(username),
db.get_user_by_id(user_id),
db.get_user_by_username(username)
)
if user.nil? raise ValueError('User not found')
cache.set_user(user, user.id, user.username)
return user
end
I always wonder why none of the other programming languages provide a handy COALESCE function/operator out of the box. That way, they wouldn't encourage bug-provoking hacks such as abusing the "||" operator.Of course you can always implement your own COALESCE function, but that won't provide the nice short-circuit ability. So this is only feasible in languages with macros (e.g. Lisp), or languages which are lazy-evaluated (e.g. Haskell).
All other languages should either provide a COALESCE function/operator, or a sane alternative to "||" which skips only NULL values and nothing else.
[1] http://www.postgresql.org/docs/9.1/static/functions-conditio...
`x ?? y` returns y iff x is null.
In daily practice, I've hardly ever used it. I use early returns all the time, though, and the code becomes very nice and readable.
In Ruby, only nil and false are false, so you can use || to catch 0s and empty strings. In Perl, 0s and empty strings are false, but there's the // operator (as well as the || or or operators) that catches the first defined value.
I'm not sure if this is something specific to the database framework that you're using - sorry if you know all that already.
But there's still trouble with things like boolean configuration variables ...
> In Perl [...] there's the // operator
Thanks for mentioning that! [1] The // operator is indeed a clean operator, almost equivalent to COALESCE.
So we have Perl and SQL. Any other language with such an operator?
[1] http://perldoc.perl.org/perlop.html#C-style-Logical-Defined-...
I've found quite the opposite.
Furthermore, I've found that early returns allows one, even in the original post's example, to easily specify a distinct error message for each case.
Sometimes you can't avoid it, especially if you're working with duck typing functions (which are a whole 'nother discussion), and it's not a hard-and-fast rule that multiple returns are evil. There are a lot of legitimate reasons to use them - performance being the primary one - and I would absolutely take them over an 8-deep nested if structure. But, they're often a warning sign that I'm putting too many eggs in that one basket.
I disagree entirely. Nesting manifests the conditional-evaluation context in the presentation of the code. Without nesting, the context shifts must be held purely in the mind of the programmer (instead of offloading that storage to the layout of the code.) In dynamic languages especially, I've also found that early returns lead to more test failing while refactoring (or more bugs if the code has low test coverage.)
The issue I have with named functions (as shown in the article) is that context for inner blocks must be passed through the outer blocks, which means your intermediate function signatures have some irrelevant (to the local scope) cruft. There are some rough corner-cases where nested code is far less complicated than juggling context batons.
What you say works well when you can physically see the structure in the code. With deep nesting in large functions, you can't - it's easy to get lost in it.
That said, I can understand what people say about multiple returns leading to bugs in refactoring - personally, I always prefer early and multiple returns to nested code, and I've developed the habit of checking the full function for any exit points whenever I refactor it.
To each his own, I guess.
http://en.wikipedia.org/wiki/Cyclomatic_complexity
I wrote an article on a similar concept here:
http://www.jasonlotito.com/programming/blocks/
It covers one method I use to reduce the complexity of my functions.
let cacheUser user =
cache.set(user.id,user)
user
let anonymousUser id =
Some(new User())
let sourceList = [(fun id -> cache.getUser(id),
(fun id -> db.getUser(id)),
anonymousUser]
let getCachedUser id =
sourceList
|> List.pick (fun source -> source id)
|> cacheUser(Edit: I posted before you edited.)
OCaml/F# are too convenient that way. There's a lot of library stuff that you won't always think to look for.
let sourceList = [cache.getUser; db.getUser; anonymousUser]Likewise, engineers who have at least played with FP tend to be sensitive to code smell. In the absence of other, firmer data, I'm willing to loan some trust to a programmer with FP experience.
Also many times early return is an exact translation of the way we think in our natural languages: "if that is this way don't continue at all", "if this precondition is meet return that value", and so forth.
With structured code, if you put a statement after a block it will be executed (notwithstanding exceptions, but that is a slightly different matter...), no matter what is changed. But if you have returns, breaks, etc. that structural condition is lost.
If you have a function with a statement, or block, at the very end, to clear something up or something, but then someone puts an early return in, the clear-up will be missed. There is a certain error-proneness there.
The best use of the early-return pattern ('replace nested conditional with guard clauses' in the Refactoring book) is where that is the only thing going on in the function. So you are effectively reading/understanding the whole thing as an 'early-return function'.
(And software is very different from natural languages. When you look at software what you see is not language, but a machine. And it has a particular hierarchical structure.)
RAII, GC, and/or try/finally solve the "error-proneness" of early returns, and in a language like C you can simulate that with a simple (good use of) goto.
def get_cached_user(user_id=None, username=None):
"""
Returns a cached user object either by their user_id or by their username.
"""
user = (cache.get_user_by_id(user_id)
or cache.get_user_by_username(username)
or db.get_user_by_id(user_id)
or db.get_user_by_username(username))
if not user:
raise ValueError('User not found'))
cache.set_user(user, id=user.id, username=user.username)
return userThe inability to trivially use null objects in conditionals is, in fact, one of the things I dislike in Ruby (where only `false` and `nil` are falsy).
Sure, abusing or in this way might seem hackish, but I feel like it's a net win for readability.
I prefer the early return style. I generally try to put all error handling / sanity checking code up top with early returns (ie, the preconditions of the function), so that the "meat" of the code at the bottom of the function can be more concise and easier to grok. However, this requires a reading style of "first read the bottom of the function to get the gist of it, and then read all of the pre conditions above it". Unfortunately I have to explain that to my coworkers, bu when I do, they seem to understand wha I'm going for.
The admonition to use single returns from a function arose from the days when a) GOTO was the major flow-control primitive and b) Dijkstra and others were starting to build up a head of steam on the we-should-treat-programs-as-mathematical-proofs thing.
If you have a single return point out of a function, it's easier to prove various things about it.
But like a lot of findings, it broke loose of its moorings in the problems of that time and floated away to lead an exciting care-free life of its own.
Analysis has improved since then, but more to the point, single-return code often leads to carrying around a bunch of book-keeping variables whose only purpose it is to artificially prop up that nostrum. They do not relate to the problem domain: they add to the difficulty of understanding the code.
That's why I feel single-return is a principle that is well past its glory days. Return from a function where it makes sense.
Even in trivial 4 line functions I assign my return values to a variable and then return that at the end of the function.
This may just be my pedantry left over from when I first studied C at university, but more than once I've spent a while trying to figure out why a return value wasn't what I was returning (from some code that had been previously written by someone else) only to find that there was a return statement on the 3rd or 4th line of the function.
One could certainly argue that if a function were concise and readable and commented and all that stuff then this wouldn't be a problem, but that's not always the case and a little return statement sitting someone in the middle of the function can really throw you sometimes
function compile(filename, ready) {
return fs.readFile(filename, make_fn)
function make_fn(err, data) {
if(err) return ready(err)
ready(null, new Function(data))
}
}
Which neatly addresses the desire to retain closures, while avoiding unnecessary nesting.Also, whenever possible, I like to nix callbacks by using Function#bind:
res.on('data', accum.push.bind(accum))
// vs:
res.on('data', function(data) { accum.push(data) }) Array.prototype.push.bind(accum)
[].push.bind(accum)
(i realize the latter is wasteful, but arrays are so cheap that you wouldn't notice unless in a hot loop)There's one (and only one) non-DOM jquery method I enjoy for that sort of things, because it avoids repetition: $.proxy(Object, String)
accum.push.bind(accum)
becomes $.proxy(accum, 'push')
I think it spells out the intent better than the bind dance.Of course, things would still be better if javascript just bound method on instance prototype lookup.
For instance, this:
for (file in files) {
if (isOK(file)) {
... // nested two levels
}
}
becomes: for (file in files) {
if (!isOK(file)) continue;
... // nested only one level
}A do/while condition would satisfy this situation. or while True: if False: break in python
if (condition)
return true;
return false;
is no less readable and sometimes more intuitive than the equivalent bool x = false;
if (condition)
x = true;
return x; return condition;Maybe someone wants to post a 'better' example and we'll see where it goes?
if (condition())
return 10;
return 20;
versus val = 20;
if (condition())
val = 10;
return val; val = condition() ? 20 : 10;
return val; return condition() ? 10 : 20;return if condition() 10 else 20
I am not sure this is more readable though.
That first get_cached_user example probably doesn't need to be nested. I don't know about Python but I would expect an optimizing compiler to short circuit the redundant conditionals if it really mattered. If it really did matter for performance, the aesthetics become a distant second consideration.
I don't see anything wrong with the get_media_details example, other than perhaps the logic of the example itself. It looks readable to me, but a blank line after 9, 13, and 19 would help with readability. If it were to grow more complicated I might look at refactoring using some sort of object polymorphism.
To me the only thing worse than complex deeply-nested control structures is code that tries to hide that complexity for aesthetic reasons in control structures that are only superficially simpler.
Code that has low CC feels strange if you're not used to it, but there are some good reasons to like it. One of the things that I find appealing to it is that by putting things into named functions, you're better expressing your intent by giving it a name.
It more important to be aware of it than to live by it. We're all adults here. ( Well, most of us anyway. ) So, as long as we understand the tradeoffs, then we can decide for ourselves when the complexity is too great.
Especially since I didn't notice that I was reading a different language (subconsciously, code is code) and then it kind of hit me in the face that python doesn't have curlys
I was able to grok it, but I think the points could have been made better by sticking to one language.
http://book.realworldhaskell.org/read/code-case-study-parsin...
get_user :: Cache -> Database -> Maybe Id -> Maybe Username -> Maybe User
get_user cache db id name =
(id >>= get_user_by_id cache) <|>
(name >>= get_user_by_username cache) <|>
((id >>= get_user_by_id db) <|>
(name >>= get_user_by_username db)) >>=
\user-> cache_user cache user >> return user) <|>
Nothing
So while I like the look of this code more than I like the look of the Python, it's still shit. The problem here is not that there is nesting, it's that the code isn't well-factored.The first problem is that the function does too much. There should be two methods, one that accepts a username argument and another that accepts an id argument. The dispatching between the two types of invocation is one level of nesting that is completely unnecessary; get_user_by_id(42) and get_user(id=42) are both equally easy to read, but the latter is much harder to implement cleanly.
The next problem is that the caching logic is handled in this random data lookup function, instead of some place where we can reuse the common pattern of "check cache and return, otherwise fetch, cache, and return". So let's write that:
def cached_lookup(cache, object, method, key):
# already cached
if cache.contains(key):
return cache.get(key)
# not cached; lookup in database and add result to cache
value = object.method(key)
if value is not None:
cache.insert(key, value) # we are punting on calculating the key
# from the value here, but that is something
# the cache could be taught to do with a
# decent metaobject protocol.
return value
Now we can rewrite the get_user functions: def get_user_by_id(self, id):
return cached_lookup(self.cache, self, 'get_user', {'id':id})
def get_user_by_name(self, name):
return cached_lookup(self.cache, self, 'get_user', {'username':name})
Now the functions are separated by concern. The get_user_by_* functions do one thing: compute a cache key and ask the cache for the object. The cache is then responsible for the fetching logic. That means if we want to change how caching works (perhaps to add eviction), we only need to change one thing in one place. If we made every get_foo_by_bar function handle caching, the code would quickly become unmaintainable.So to summarize, nesting draws our attention to problems, but removing the nesting is not the solution to the problem. Neither are monads. The solution to the problem is to refactor.
OK, with changes in nesting you could just use a whitespace-free diff, but then you can't just apply/commit it. If you apply it, you have to reindent before committing. (This is tricky in python.) Alternatively, if you get a full diff, you have to apply it and then extract a whitespace-free diff yourself in order to read the actual changes properly.
I have to say that I really feel that to get readability, over nesting too, you often should refactor a bit the code.
I would in fact write the get_cached_user method by using separate methods and a @cache decorator. Every single function is very readable by itself.
(I'm not fluent in python, pseudo-python follows... but you should get the idea)
def cache(function_to_cache):
'''
A Decorator that caches a function result
'''
def wrapper(param):
cache_key = function_to_cache.name + " on " + param
if cache.contains(cache_key):
return cache.get(cache_key)
else
value = function_to_cache(param)
cache.set(cache_key, value)
return value
return wrapper
@cache
def get_cached_user_by_id(id):
return db.get_user_by_id(id)
@cache
def get_cached_user_by_username(username):
return db.get_user_by_username(username)
def get_cached_user(user_id = None, username = None):
user = None
if user_id:
user = get_cached_user_by_id(user_id)
else if username:
user = get_cached_user_by_username(username)
if not user:
raise ValueError('User not found')
return userI think decorators are complex enough that I can't immediately look at the thing and know what its doing in the same way as early returns. A closure that takes a function pointer with some introspection magic is just a lot of mental juggling. At least with your case the complexity doesn't compound and is pushed as low as possible (as per Code Complete).
A final note: functions can have static members just like classes in python. fuction_to_cache.name would access the .name member of the function if such a thing existed (it will be an AttributeError? in this case). You are looking for function.__name__ I believe.
(Moreover it should handle multiple args, and there are better and more popular implementation such as @memoize)
However my assumption in this specific case is that the project is not small and therefore that decorator and this "pattern" could be used somewhere else too. If this is not the case I agree with you that it would just add too much complexity and it would hence be better to not have the decorator at all. The get_cached_user_by_username and get_cached_user_by_id would just handle the cache hit/miss by themselves.
Still it would be much more readable, because the main point I would say is to separate a long or too nested method in more methods.
def get_cached_user_by_id(id):
user = cache.get_user_by_id(id)
if not user:
user = db.get_user_by_id(id)
cache.set_user(user)
return user
def get_cached_user_by_username(username):
user = cache.get_user_by_username(username)
if not user:
user = db.get_user_by_username(username)
cache.set_user(user)
return user
def get_cached_user(user_id = None, username = None):
user = None
if user_id:
user = get_cached_user_by_id(user_id)
else if username:
user = get_cached_user_by_username(username)
if not user:
raise ValueError('User not found')
return user
(Thanks for the .__name__ tip... I have not use python for a while) >>> import this
...snip...
Flat is better than nested. do_some_file_thing(File) ->
Resp = case file:open(File, [raw, binary, read]) of
{ok, Fd} ->
{timestamp, now(), process_file_data(Fd)};
Error ->
{timestamp, now(), Error}
end,
case Resp of
{timestamp, Start, {ok, Processed}} ->
{ok, Start, now(), Processed};
{timestamp, Start, Error} ->
Error
end.Code is a tree, code is about nesting. If you do not like nesting, you do not like code.
Code is not 'text'. You do not read code top to bottom like text. Code has a structure, and you read that structure.
And if an example of 'improvement' doubles the line count, you have a pretty good indication you are doing something wrong.
What seems to have happened is a small piece of advice has been taken too far. The early-return shortcut is reasonable. It is indeed advocated by Fowler and Beck (who deserve some trust) -- they call it 'replace nested conditional with guard clauses'. But that is something very particular. It does not suggest removal of all nesting in general.
return $user if $found; cache.set_user(user, id=user.id, username=user.username)
twice in his improved solution a bit of a giant red flag?A pity, because the general idea and techniques in the article are fine, but the examples try so hard to be simple that they fail to illustrate the point: the actual "improved" versions of the code are much worse than the supposedly broken originals.
Regardless, the two rules I focus on:
* Idents are intents to modularize http://www.jasonlotito.com/programming/blocks/
* Comments indicate future refactoring http://www.jasonlotito.com/programming/comments/
They are both covered here:
I guess that's one of the dangers of using contrived examples. Or maybe it's just proof that this is as much a matter of taste as anything.
Its bad rap is entirely deserved. Goto is a fine tool in a few cases, but it's an awful general-purpose tool when better control structures exist, and its usage in this case — common when structured programming got its start — makes code significantly worse.
Whenever code starts getting complicated, I break it out into a subfunction in the where clause, which means I give it a name, which makes my code more self-documenting, and incidentally keeps the indentation level sane. Breaking things out into lots of little functions like this often makes it apparent when the function in the where clause is more generic, and does belong at the toplevel, and then the code is already separated into a function that's easy to move out of the where clause (closures do mean additional parameters sometimes need to be added, but the compiler will make sure you get this right).
The other nice thing haskell brings to the table, which would be more helpful in the first example given, is the ability to write your own control flow functions. For the first example, which keeps trying different actions from a list until one succeeds, and returns its value, I would write a generic function to do that. Its type signature would be:
firstM :: (Monad m) => [m (Maybe a)] -> m (Maybe a)
Of course, I don't need to write that function.. I can just paste the above type signature into Hayoo, and get directed to an existing implementation: http://hackage.haskell.org/packages/archive/darcs/latest/doc...If I did need to write firstM, I'd feel special to have been the first to think up such a generic and useful function. So it's win-win-win all the way. :)
Anyway, the code to use it would look something like this:
get_cached_user uid name = fromMaybe nouser <$> find
where
cache a = do
r <- a
cache_set_user uid name r
return r
nouser = error "user not found"
find = firstM
[ cache_get_user_by_id uid
, cache_get_user_by_username name
, cache $ db_get_user_by_id uid
, cache $ db_get_user_by_username name
]
As another example of this refactoring of control flow, looking at the cache function above I realized I've written those three lines several times before. So I just added this to my personal library: observe :: (Monad m) => (a -> m b) -> m a -> m a
observe observer a = do
r <- a
observer r
return r
(I seem to be the first person to think of this function.. yay!)With this, the "cache" function can be written as just
cache = observe $ cache_set_user uid name