Few pastimes are more pointless and unproductive than arguing about coding style. As long as the basic requirement of consistency is present, there is really next to nothing that wouldn’t fly. You may hear stories of certain… unusual approaches, like using 3-space indentation to “offend all equally”, but it still doesn’t give you the license to criticize – or worse yet, disobey. If you chose to contribute to a particular codebase, you follow the standard, period.
With this out of the way, I will go ahead to pick on my personal pet peeve by pointing out that the rule of single return from function is just dumb!… To my excuse, however, it is not really a stylistic rule, which is also one of the reasons I consider it inane. Since it changes the algorithmic structure of a function, it’s actually about semantics and should be treated as architectural guideline.
Even then, what’s wrong with avoiding more than one
return statement? A couple of things come to mind.
There is a particular “idiom” that you may encounter from time to time, especially in beginner’s code, or just code written by someone who had a particularly bad day. Here’s how it goes:
While it looks completely silly, its only felony is carrying the result through one more place than it’s absolutely necessary. But we would never write such a thing with clear mind. We would point it out immediately in code review. We would fix it whenever encountered.
And yet, it’s essentially the same pattern as in the code below:
It has just one redundant layer of complexity: the
success variable. We could remove it and replace the assignments to it by direct
return true; and
return false;. But that, of course, would violate the rule of single return point, and therefore code meant to follow it is stuck with flaws equivalent to having no idea how basic control statements and boolean logic works.
If we are happily condemning the use of more than one
return statement, we could as well stay completely true to our beliefs. Why not prohibit all of such statements, and in fact use a language that just plain doesn’t support them?…
I’m totally baffled by any claims purporting that this technique makes code more readable. Not only the return value is now semi-hidden, disguised as one of the ordinary variable assignments, but also the return point itself is completely lost. An execution path may end inside an
while inside a
switch, somewhere deep in the function innards, and you wouldn’t even notice without tracing all the closing
end;s, or following the changes in indentation levels.
I’m all for disincentivizing long functions but really, there are much better ways to do that.
Although by far the most braindead, the rule of single return is unfortunately not the only one of its kind. There is a whole class of misguided principles that apply to certain flow control instructions:
continue. What they share in common is the great potential of obfuscating the real meaning of some parts of the algorithms, simply because of forcing the code to be structured in a specific, rigid way.
Why it’s bad? Because one size does not fit all. Abusing early returns is hardly commendable:
but avoiding them at all costs won’t make the code better by any stretch:
Since every function is different, trying to mold them into some over-specified template will just end awry.
I’ve had a peculiar kind of awful realization after listening to a C++ talk earlier today. The speaker (Bjarne Stroustrup, actually) went over a few defining components of the language, before he took a much longer stop at templates. In C++14, templates are back in the spotlight because of the new constraints feature, intended to make working with (and especially debugging) templates much more pleasant.
Everyone using C++ templates now is probably well accustomed to the cryptic and excessively long error messages that the compiler spits out whenever you make even the slightest of mistakes. Because of the duck typing semantics of template arguments, those messages are always exposing the internals of a particular template’s implementation. If you, for example, worked with STL implementation from Visual C++, you would recognize the internal
__rb_tree symbol; it appeared very often if you misused the
set containers. Its appearance was at best only remotely helpful at locating the issue – especially if it was inside a multi-line behemoth of an error message.
Before constraints (or “concepts lite”, as they are dubbed) improve the situation, this is arguably the worst part of the C++ language. But alas, C++ is not the only language offering such a poor user experience. As a matter of fact, there is a whole class of languages which are exactly like that – and they won’t change anytime soon.
Yes, I’m talking about the so-called scripting languages in general, and Python in particular. The analogies are striking, too, once you see past the superfluous differences.
Take the before mentioned duck typing as an example. In Python, it is one of the core semantical tenets, a cornerstone of language’s approach to polymorphism. In current C++, this is precisely the cause of page-long, undecipherable compiler errors. You just don’t know whether it’s a duck before you tell it to quack, which usually happens somewhere deep inside the template code.
But wait! Python et al. also have those “compiler errors”. We just call them stacktraces and have interpreters format them in much a nicer, more readable way.
Of course unlike template-related errors in C++, stacktraces tend to be actually helpful. I pose, however, that it’s mostly because we learned to expect them. Studying Python or any other scripting language, we’re inevitably exposed to them at the very early stage, with a steady learning curve that corresponds to the growing complexity of our code.
This is totally different than having the compiler literally throw its innards at you when you try to sort a list of integers.
What I find the most interesting in this whole intellectual exercise is to examine what solutions are offered by both sides of the comparison.
Dynamic languages propose coping mechanisms, at best. You are advised to liberally blanket your code with automated tests so that failing to quack is immediately registered before the duck (er, code) goes live. While some rudimentary static analysis and linting is typically provided, you generally cannot have a reasonable idea whether your code doesn’t fail at the most basic level before you actually run it.
Now, have you ever unit-tested the template specification process that the C++ compiler performs for any of your own templates? Yeah, I thought so. Except for the biggest marvels of template metaprogramming, this may not be something that even crosses your mind. Instead, the established industry practice is simply “don’t do template metaprogramming”.
But obviously, we want to use dynamic languages, and some of us probably want to do template metaprogramming. (Maybe? Just a few? Anyone?…) Since they clearly appear to be similar problems, it’s not very surprising that remedies start to look somewhat alike. C++ is getting concepts in order to impose some rigidity on the currently free-form template arguments. Python is not fully on that path yet but the signs are evident, with the recent adoptions of enums (that I’ve fretted about) as the most prominent example.
If I’m right here, it would be curious to see what lies at the end of this road. In any case, it will probably have been already invented fifty years ago in Lisp.
If you use a powerful HTML templating engine – like Jinja – inevitably you will notice a slow creep of more and more complicated logic entering your templates. Contrary to what many may tell you, it’s not inherently bad. Views can be complex, and keeping that complexity contained within templates is often better than letting it sip into controller code.
But logic, if not trivial, requires testing. Exempting it by saying “That’s just a template!” doesn’t really cut it. It’s pretty crappy excuse, at least in Flask/Jinja, where you can easily import your template macros into Python code:
When writing a fully featured test suite, though, you would probably want some more leverage Importing those macros by hand in every test can get stale rather quickly and leave behind a lot of boilerplate code.
Fortunately, this is Python. We have world class tools to combat repetition and verbosity, second only to Lisp macros. There is no reason we couldn’t write tests for our Jinja templates in clean and concise manner:
JinjaTestCase base, implemented in this gist, provides evidence that a little
__metaclass__ can go a long way :)