There is an adage in the Erlang community that we should make our code work, then make it beautiful, then, and if truly necessary, make it all run fast. This insight is rendered from decades of coding of many brilliant hackers. In fact, the last part is probably a nod to Donald Knuth’s famous caution against early optimization.
It may look familiar to some; or maybe it’s new. Certainly, there are variations of this in other languages for sure. But all the same, one may coil up and ask, why is beautiful in the list?
Making code beautiful is essential, and there’s a reason it is parked in the number-two spot pecking order. If our code isn’t beautiful, it’s unlikely that it will convey just what the hell is going on.
Since I’m no level-ten Erlang poet, I consult a refactoring style guide that ensures my code will be straightforward to read. A lot of this guideline is lifted from Garrett Smith’s fantastic insight on the same matter — with some light re-purposing here and there. If you need some tips on getting your Erlang code past the mere working stage and into the beauty pageant, then please, read on.
I call this module-writing heuristic ‘fucrs’ (pronounced any way you like).
Give me an ‘F’ : function-ize
After a version of an Erlang module is working, I go back and turn all my ‘case’ expressions into plain ol’ functions. For example:
foo(X) -> case X of bar -> void; _Else -> undefined end.
foo(X) -> foo1(X). foo1(bar) -> void; foo1(_Else) -> undefined.
This practice is a gold-standard for me, as I immediately noticed my code to be vastly more lucid. ‘F’ also serves as a reminder to turn any haphazard ‘funs’ into normal functions, too. Sometimes you need ‘funs’ — of course — but ordinarily, writing a plain function is a much cleaner way to present things. Once done, I move on to the next letter.
Gimme a ‘U’ : un-nest
This is an easy one to do, but sadly, it is not popular. It’s not alarming to see code in the wild with functions entrenched six or seven times. And incredibly, the Elixir language has introduced a language devise, the pipe operator, to better mollify this malpractice. Obviously, nesting runs rampant. The ‘fucrs’ guideline takes an extreme conservative stance on this: nesting should never happen, ever. The values passed into functions must only be variables, or basic data constructs like lists, tuples, etc.; never a function. For example:
foo(X) -> final_function(maybe_function(X)).
would be re-written:
foo(X) -> Maybe = maybe_function(X), final_function(Maybe).
Spelling things out so pedantically makes code dead-simple & clear. Yes, there is a tad more code, but you will also note that nothing is hiding. Un-nesting simply dumbs things down. Now, who wouldn’t want that after hours of squinting at a screen?
Gimme a ‘C’ : canonicalize
It’s nice to open a module and see some familiar faces. Although there is no standard naming convention for functions, ‘fucrs’ makes an attempt to include the ones from Garrett’s talk whenever possible. Functions named as ‘new,’ ‘start,’ ‘init,’ etc. are pleasantly unsurprising and can help your modules have an air a familiarity.
Gimme an ‘R’ : rename
Quite related to canonicalization, is to do a pass on the code in order to rename variables/atoms as necessary. I find that in the heat of the moment, a naming will occur out of some emotional response to something. For example, I once caught an atom I had named ‘stupid_foo’ which at the time meant quite a bit, but means little thereafter. Naming things can be hard, but I find when I am tasked with renaming things in bulk, it’s easier than coming up with great names while also tasked with getting code to work. Expressive names are helpful to your future self, and others. Use simple, deliberate words as much as possible.
Finally; ‘S’ : seven-ize
Someone smart, somewhere, at some time, did a study which concluded that the human brain can easily hold six or seven items in short-term memory with little trouble, but beyond that, it becomes taxing. This applies nicely to lines of code in a function. Amazingly, after all the above guidelines have been followed, bringing a function’s lines-of-code count down to a maximum of seven is surprisingly easy to do. It may seem crazy, but I challenge you to try it; first as an exercise, then come to your own conclusions. For me, I tend to max-out around five lines-of-code per function with no effort, though at first, seven was an easier goal. The reason should be clear: a reader can keep a handful of expressions in mind when reading such a terse function; but reasoning a behemoth, it goes against what our brains are capable of.
So, there you have it: a one-word style guide to consult after your Erlang code is ticking along without error, but could benefit from a face-lift. Give it a try, and be sure to thank Garrett if ‘fucrs’ works for you.