There is a single advice I would give to anyone writing software, and specially to great devs : Always optimize your code for your most junior developers. I agree that it is not a very popular advice, but it is the one that, in my experience, give the biggest bang for the buck.

Code is usually optimized for readability.

Yet it always takes the seniors devs perspective. Syntactic sugar is used & abused, since it does enable very terse code. Since less code is usually lauded for its lower bug ratio, it looks like a good idea. A perceived side-effect is that it will even educate juniors devs by emulation & example.

And, a very good idea it is. At first.

Rationale

A summary of the rationale is not really technical.

More human & management related.

In the end, you cannot fight turn over, as skilled devs will leave, no matter your strategy to retain them. Reasons for it are multiple and well known, so I won’t discuss them here.

Yet, it is this particular reason that usually kills your product : brain drain.

Added to that harsh reality, there’s another reality : finding senior devs to replace them is hard, it’s much easier to find juniors ones. Bigger pool, and smaller costs. Happier Management.

Also, let’s face it, seniors devs can be very performant, but most of them are not that flexible anymore. The vast majority do come with their habits, which enables them to leverage their performance, but unfortunately quickly ends up as a tech patchwork. Only very few, super awesome ones, are blending in. They tend to follow the existing code patterns, changing it only when it makes absolute no sense. And even then, in a global & coherent manner.

It is not really a popular opinion, but that’s exactly one of the reasons why the whole industry is moving towards “low-code/no-code” solutions : To be able to quickly onboard junior devs.

A nice side effect is that, as junior devs don’t have much technical baggage, the code itself needs to be very self-explanatory. And it cannot contain much idioms, as those would impeded understanding.

Which means that, when you need to increase velocity, you will be able to quickly add some cheap workforce. Which is a much better sell to management than spending at least 3 months sourcing a senior that will take 3 months to product meaningful code since the various used idioms are plentiful and not compatible with each others.

Implementation

Code reviews are key

Doing is is actually very simple :

  1. Always have the most juniors devs in your team do code reviews. It shall not stop the seniors to do code reviews, but ensure that junior review are mandatory. The 2 groups won’t find the same issues and therefore are complementary more than antagonists.

  2. Never explain anything outside code. If some reviewer asks a “why?” question, change the code itself as a reply.

  3. The explanations cannot be in PR comments, in a wiki, or worst : in a meeting.

This will means that the code will naturally evolve towards self-explanation for anyone. There will be not much context needed, and that will be a very good thing.

Imagine a critical incident happening in the middle of a week-end night1. Your SRE team will have an on-call person, which will know where the the code is, but has not really good knowledge of that said codebase.

That person will be able to understand it much quicker, specially considering that he will be under great stress as everyone is counting on him to find a quick & adequate solution. Yet he is not in full spirit, being the middle of the night.

That will enable a timely & correct response, that lowers the priority and makes it possible to defer until a normal timing for additional investigations/fixes when the whole team is back.

Besides, even your future self is another person given enough time.

XKCD: Future Self

A side-effect of that implementation is that some patterns will emerge that one can solidify in rules in order to speedup & streamline the process upfront.

Pair programming isn’t dead

It sounds like the death of pair-programming, but the latter serves a different need. Its execution is similar but needs to be adjusted with knowledge from the previous section.

  • Pair programming enables more creativity. This means that one can pair 2 developers that have the same level of seniority & expertise.

  • Pair programming enables faster code-review. This means that one can pair with a junior developer, but similarly than for regular code-reviews, if there is a question, it has to be answered in the code. Not live orally. As the important meaning from the question is “there is currently some information missing” would be lost.

Relevant coding-style examples

I obviously gathered some coding-style idiosyncrasies that fit very well in that context. It also has the nice side effect of making wrong code obviously wrong.

1SLOC - One Statement per Line of Code

1SLOC has its own post now. The concept is also now known as 1OPS which conveys better the idea.

Here is the rest for historical purposes according to my Immutability Rules.

This enables a very good understanding of what is currently done.

The rule very simple :

  1. Always do only 1 operation type per line, be it addition, boolean logic or bitwise operations. We can have multiple of the same.

  2. Every operation is assigned to a temporary variable that has a meaningful name. This enables to follow what is done very nicely as it reduce the reader’s needed context.

  3. Assignments to temporary variables don’t count towards the 1SLOC, but assignments towards ones with a different scope do.

The most important item is the meaningful name of variables. It is the item that improves understanding the most. And usually a compiler does optimize them away, as it would with an unnamed, temporary, variable.

Instead of

int s = a + b + c + (y > 6 || y < 1) ? y : 0;

do

boolean isYTooBig = y > 6;
boolean isYTooSmall = y < 1;
boolean isYInBounds = isYTooBig || isYTooSmall;
boolean yIfInBounds = isYInBounds ? y : 0;
int s = a + b + c + yIfInBounds;

And there the accurate reader noticed that the result isn’t correct, as there’s a bug in isYInBounds. It is reversed. The correct code is of course:

boolean isYTooBig = y > 6;
boolean isYTooSmall = y < 1;
boolean isYOutsideBounds = isYTooBig || isYTooSmall;
boolean yIfInBounds = isYOutsideBounds ? 0 : y;
int s = a + b + c + yIfInBounds;

Unit suffix

For every quantity that has a unit, always suffix with that unit.

The following code looks now obviously wrong

double delay_ms = 1000;
double frequency_hz = 1/delay_ms;

It should be fixed as

double delay_ms = 1000;
double frequency_hz = 1000/delay_ms;

It also works with encoding.

my $value = 1000;
my $value_hex = sprintf "%x", $value;

Do not prefix any variable according to its scope.

A very common practice is to prefix variables with their scope. A typical example would be to prefix with g the global scope, a the argument scope, and not prefix the local scope.

But then it fells short since we can have many local scopes.

Also, the same variable will have 2 different names in a sub function, as the caller has it as a local one, whereas the callee has it as arguments.

For example, look at this code

int gPlayerX;
int gPlayerY;

int gMinX;
int gMinY;
int gMaxX;
int gMaxY;

/* Return the middle number */
int middle(int aMin, int aValue, int aMax) {
    if (aMin <= aValue && aValue <= aMax) return aValue;
    if (aValue <= aMin && aMin <= aMax) return aMin;
    return aMax;
}

void update(int aDx, int aDy) {
	gPlayerX = middle(gMinX, gPlayerX + aDx, gMaxX);
	gPlayerY = middle(gMinY, gPlayerY + aDy, gMaxY);
}

And compare it to

int player_x;
int player_y

int MIN_X;
int MIN_Y;
int MAX_X;
int MAX_Y;

/* Return the middle number */
int middle(int min, int value, int max) {
    if (min <= value && value <= max) return value;
    if (value <= min && min <= max) return min;
    return max;
}

void update(int dx, int dy) {
	int new_player_x = player_x + dx;
	int new_player_y = player_y + dy;

	int clamped_player_x = middle(MIN_X, new_player_x, MAX_X);
	int clamped_player_y = middle(MIN_Y, new_player_y, MAX_Y);

	player_x = clamped_player_x;
	player_y = clamped_player_y;
}

The last player_x = clamped_player_x is particularly interesting, as it clearly shows that this assignment is one to a different scope. Therefore it has its own line with the 1SLOC rule number 3.

One can also pedantically argue about the middle() function breaking 1SLOC, and I agree. I didn’t notice at first, but upon reading the code some months later I admit that it took me longer than expected to understand it. Sure, the function is pretty obvious in itself, but still.

That previous statement is a testament to the fact that naming things correctly is what helps understanding the most. Which is the whole point of 1SLOC.

Q.E.D.

  1. It always happens at inconvenient moments. Mostly because when it happens at convenient ones, no-one is remembering those, as they are quickly dealt with and become non-events.