Refactoring Pro-Tip: I optimize my code for scannability, readability, and writeability in that order.
I won’t argue my case in detail, there’s a video if you’d rather watch me make it instead of reading it, and I’ll just sketch the case briefly here in the muse.
http://geepawhill.org/optimizing-a-program-and-programming/
When I’m coding, I notice that I spend significantly more time scanning the code than I do reading it, and significantly more time reading the code than I do writing it.
So when I optimize for my performance rather than the code’s performance, I focus first on scannability.
What’s this "scanning" thing? Well. It happens in a lot of different cases. We don’t really have one word for it, so I chose "scanning". What it amounts to is rapidly moving my eyes across a chunk of code, picking out features of it as I go.
What kind of features? Well, that depends on what I’m up to.
When a codebase is unfamiliar, I scan to pick out features like flow-of-control, naming, function or data dependencies.
When I know a codebase relatively well, I more often scan for things like: another call to the function I’m about to call; confirmation of something I think I know but want to be sure of; some syntax variant i can never remember (I’m looking at you stream operations).
When I am debugging, I usually am scanning for "that place where I suspect the problem is". That is, I believe something’s going south during the fritzifying, and I want to find where that happens in the code.
So. What things make scans easier or harder to do for me?
The easiest code in the world to scan is a straight line of well-named method calls:
openTheDoor()
getInTheSeat()
closeTheDoor()
TurnTheKey()
StepOnTheAccelerator()
I barely have to glimpse that code to get it in to my head. Notice, I have to do way more to understand it deeply. That’s okay, when I’m scanning code I’m not trying to understand it deeply. I’m trying to, I spoze in this case, figure out where the key comes from or some such.
An aspect of that little sample that’s less obvious typographically: it is all at the same level conceptually. To get a feel for this, imagine if the second line said, instead of getInTheSeat()
, something like:
seat.put( body.parts["ass"].substring(2,9).
(Presumably, the 2th through 8th part of the ass string contains the cheeks. Idunno, whaddayawantfromme? You get the point, I’m sure.)
Every branching point that enters that straight line also makes it harder to scan. If the branch is an if it makes me have to hold two states in my head simultaneously, or to pause and confirm one of the states. (Schrodinger’s Geek?)
If it’s an if-only branch — that is, there is no else — that easier than if-else. switches are harder than that. nested if’s are harder still. a loop with nested if-else’s is the hardest code there is to scan.
So in practical terms, I put it a lot more simply: I prefer to minimize indentation. I do this by a) killing it off entirely when I can, and b) making it function-wide when I can’t.
I think you’ll buy "Kill when I can" without further argument, though you might wonder how that’s done, and we’ll ultimately have to talk about that. But what’s this "function-wide" thing?
I tend to put the code one puts inside a multi-statement scope in a whole separate function.
if( condition ) { // multi-statement blah-blah-to-do-the-thing }
else { // multi-statement other thing }
becomes
if(condition) doTheThing()
else otherThing()
I might even then put that whole if in yet another function:
doTheRightThingDoneByThoseTwoBranches()
.
This depends on whether the two branches are really doing the same "thing" using different techniques, an extremely common situation.
There are lots of variations on this theme. Loop bodies, switch bodies, if-else-bodies. All of that stuff that introduces indents, I’m prone to pulling out separately.
I also use early returns. (I swore I was gonna get to this.) And for the same reason: because an early return kills one level of indentation from everything that comes after it. (Mentally, it does more than that, but that’s the non-rigorous shorthand.)
(A note: some folks use "guard clause" to characterize early returns. I don’t. Guard clauses are usually about detecting a no-op condition and not doing anything. Early returns can still do things.)
Often, when I encounter slalom code, I can dramatically flatten its curves — reduce its indentation — to a simple series of un-nested if’s, just by using early returns.
I optimize for scannability first, over readability or writability, cuz I scan more than read or write. A big target is to kill or move indentation. A big technique for doing that is early returns.
Have a lovely late Thursday night! Tomorrow’s another day in the silicon mines.
Pingback: Pratique du TDD, comment convertir des chiffres en nombres romains ?