“Make your terminal color green and you are already following all sysadmin best practices” - someone in the internet

Code maintainability

/** Make code maintainable */
const MAINTAINABLE = true
const SUPER_MAINTAINABLE = [true, true, true]

for(let{selector:n,styleNames:r}of a)for(t of Array.from(
document.querySelectorAll("[style*=".concat(n,"]"))))

Sadly it’s not that easy to make code maintainable. Single boolean won’t do it 😐 Even array of booleans won’t work 😞

What is maintainable code

Code maintainability isn’t a thing that you “enable” or “fix once”. Code maintainability is continuum. It’s something you have to keep in mind writing every single line of code.

Code maintainability consists of multiple coding axioms applied: consistent code style, rules of thumb, best practices etc. Of course they can’t be randomly enforced on project

Well maintainable code must have following points (but not limited to):

  • reliable
  • well readable
  • easily changeable
  • testable
  • understandable without digging too deep
  • most likely a lot more

How to write maintainable code

“How to write perfectly maintainable code?”

“It’s simple. You don’t” - Mikk Tendermann

Some things to keep in mind:

  • Every best practice is suggestion, not a rule.

    You should always consider, if this suggestion makes sense for project or not

  • Think how your piece of logic fits into project as a whole, not just stylewise

    If whole project is using forEach loops to traverse over array it won’t be too good idea to suddenly start using recursion for it

  • Don’t do hype-driven development

    If you find yourself to think like: “MongoDB is cool, must use it for all use-cases!” or “JavaScript has classes?! Look how cool they are! I will use them EVERYWHERE now!”

    Then you are off to a bad start

  • Pick right tool for right job

    In a lot projects I’ve seen people need relational DB. So they use MongoDB?! and start searching (or writing) MongoDB drivers, that enforce schema which essentially kills whole point of MongoDB.

    Why not use relational DB in first place? (there are more of them than only MySQL and Oracle DB)

    Also, don’t try to program FPGAs in JavaScript. Just use C or similar


I’ve found it quite useful to think about worst case future, to keep company project cleaner:

What if company needs to reduce costs and replace whole dev team full of seniors with single junior dev.

Will that junior understand code instantly or will it take them some days / weeks to get thro?

It’s really interesting and hard place for junior. If it’s pain for him or her to get started, then most likely seniors had failed

Explaining dead code


Example time! Let’s take easy problem: “Swap value of 2 variables” There are countless solutions for it, but here are some examples in JS

let a=5,b=3;
a=a^b^(b^=(a^b));

And it’s longer counterpart (which is using temp variable)

let a = 5;
let b = 3;
let tmp = a;
a = b;
b = tmp;

Try setting a to "hello" in both example and see the output. Can you explain and fix problem in both cases?


Code maintainability is big topic, it can’t be covered by just a single blog post.

But searching for “code maintainability” or “how to write maintainable code” reveals quite a lof of good resources

There are some good books on it. Like “Clean Code” and “Code Complete” that I’d suggest to read

KISS, YAGNI, DRY etc

I really suggest to read this more in-depth article about similar topics

KISS - keep it simple stupid

It’s read quite a lot “keep it simple, stupid”. Stating that developer is stupid for making something too complex.

But there’s other way to read it “keep it simple stupid”. Stating that it’s stupidly simple.


To create software we first need to create mental model of it. Since software changes so will that mental model. Accurate model is required to write good software, but when software is complex and constantly changing it’s going to be hard to maintain that accurate model of soft.

Over time as project grows more and more complex mental models grow less and less accurate, which means that developers will become unhappy managing the mess, more bugs will be introduced and end users won’t be too happy. It’s essentially bad for everyone


It seems to be some common theme for developers, that they want to write complex code to show how smart they are.

I went thro this phase in my career too. Seems to be common theme for developers with oversized ego or for devs who are moving from junior to mid level (since they learn a lot of cool stuff then).

Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?”

Brian Kernighan


Easy way to clean up code is to remove all dead code. It has no consequences. There’s always possibility to check what you removed from Git history

It makes whole codebase smaller. Making it easily graspable and removes questions like “Why editing this code does nothing”

Removing dead code

.. Which brings us to

YAGNI - you aren’t going to need it

A mixture of dead code and feature (future) proofing which just makes codebase a pile of cheese.

It’s really common for devs to add features to code that might be required in the far-far future (maybe).

No one knows the future. Functionalities come and go, but written code still requires maintaining.

There’s a reason why in XP task should be done as late as possible (then there’s more information about task or task might be obsolete altogether)

Unless developers have infinite time they should only focus on what’s important for current business needs and no more.

Write as little functionality and flexibility as possible, while making it easy to introduce changes

Especially bad is configurability, never make everything configurable, add as little config as possible

DRY - Don’t repeat yourself

I personally find DRY really interesting. There are quite some devs who are die-hard DRY followers.

There’s hight chance of making code too complex when following DRY


Since this post is a wall of text already let’s add some example

Let’s say for whatever reason you start with DRY something like this (sum() is such a good example with 0 practicality in it. Hopefully gets the idea thro tho)

// First part of code
const machines = sum(cars, trucks);

// Second part of code
const animals = sum(cows, horses)

// Cast both params to Number and add them up
function sum (a, b) {
  return Number(a) + Number(b)
}

Now, for next iteration you actually need to need function that just adds things together without typecasting. Usually another method is made for it, which adds more code

// First part of code
const machines = sum(cars, trucks);

// Second part of code
const animals = add(cows, horses) // cowhorses?!

function sum (a, b) {
  return Number(a) + Number(b)
}

function add(a, b) {
  return a + b
}

But since those methods are not needed anymore you could remove both of them.

// First part of code
const machines = Number(cars) + Number(trucks);

// Second part of code
const animals = cows + horses // cowhorses?!

For some reason devs like to write code, but really hate to delete it


Previous example is not too much of an DRY problem, but general issue.

For more DRY example sometimes some small 1-2 liner helper function is needed in 1-2 parts of application.

Then it’s just cleaner to add this helper to part of application rather than creating separate standalone module out of it and importing it.

If it’s something small it’s easier to get what code is doing if you can glimpse it with a look, rather than going around multiple files

For any bigger chunk of logic DRY is really good way to keep code cleaner

WET - Write everything twice

WET has the same idea as DRY, but it allows “bigger margin of error”.

You are actually allowed to write some lines of code twice, without getting killed by your coworkers.

Easily readable code and cognitive overhead

Since we spend most of our time reading code, it should be pleasure to do it.

Reading code should be smooth like reading a book or poem

  • Use one language

    Try to use English (even if it’s not your primary language). If for some reason can’t use English, stick with one language and don’t mix them

    // Toiminto korjata seinΓ€
    function makeSeinIyi(sein, kachestvo, kiirus){/*(β•―Β°β–‘Β°οΌ‰β•―οΈ΅ ┻━┻*/}
    
  • Stick with consistent naming style

    There are many naming styles. camelCase, snake_case, cebab-case, PascalCase, SCREAMING_SNAKE_CASE etc

    Pick one and stick with it, don’t swap then in middle of code

    They are all to separate words from each other tomakecodemorereadable

    let is_done = false;
    let arrayOfThings = [THING_ONE, ThingTwo]
    

    Some languages have good practices to use different case for different data types.

    Classes tend to be in PascalCase, initialized classes in camelCase etc. Follow language standard.

  • Don’t add type prefix to variables (b for boolean, s for string etc)

    let b_is_done = false;
    let aArrayOfThings = [S_THING_ONE, CThingTwo]
    

    It just adds cognitive overload and takes some extra brain cycles to process it. Sadly brain cycles do not grow on trees.

    It’s better to use better variable naming that already hits it’s type. Like isDone refers to boolean already. While THING_ONE says nothing about what it is

  • Follow language standard

    Most languages have some standards, like using snake_case instead of camel- / PascalCase (looking at you Rust). It would be good idea to follow them

    That’s the thing that overrides all previous rules of thumb πŸ˜‹

  • Reduce meta code

    Several languages support optional type hinting (PHP and TS for example).

    It comes with a overhead tho and wastes some more brain cycles. Especially in case of PHP and TS, when language server can figure out types thus removing need of double defining.

    function doNothing(name:string, age: number) {
      // Useless type defination for tempName and age
      let tempName: string = name;
      let tempAge: number = age;
    }
    

    (Seems like TS syntax highlight is missing)

  • Let tooling to the minification

    There was this interesting piece of code circling the web, which essentially is an integrator function.

    It’s not minified code, it was written like this

    const calculate = (m,f,y,t,h)=>{
      for (var k=[],ki=0;ki<m.length;ki++){var _y=y.slice(),dt=ki?((m[ki-1][0])*h):0;for(var l=0;l<_y.length;l++) for(var j=1;j<=ki;j++)_y[l]=_y[l]+h*(m[ki-1][j])*(k[ki-1][l]);
        k[ki]=f(t+dt,_y,dt);
      }
      for (var r=y.slice(),l=0;l<_y.length;l++)for(var j=0;j<k.length;j++)r[l]=r[l]+h*(k[j][l])*(m[ki-1][j]);
      return r;
    }
    

    I have no doubts that whoever made it is really good in math. But please try to not write such code, even if it makes sense for all your math friends πŸ™‡β€β™‚οΈ

    Imagine how fun it is to debug it, if some calculation is off by 1

  • Use readable naming

    Good variable names are not single letter names (nor 2 letter)

    Good names are names that state what’s going on and what’s held in there. Something short and precise. Like

    firstName = 'Hurr';
    lastName = 'Durr';
    age = 98;
    // Don't go full enterprise Java mode
    ReponseSerializerContainerPolicyTagFacadeImporterCallbackTest
    

“There are only two hard things in Computer Science: cache invalidation and naming things.” – Phil Karlton

“There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.” – Leon Bambrick

“There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery” – Mathias Verraes

Pest practises to avoid AKA how to destroy codebase

It’s a tricky thing. You can’t write bad code, then everyone will notice that you write bad code and PRs will not be accepted.

Bad code needs to be “good” at the same time.

Follow all the rules

Follow all the rules

Following any “best practice” blindly is going to nuke project for sure, follow SOLID and DRY super strictly while throwing some Java enterprise naming in it and codebase will turn into lasagna tower before anyone can say anything.

What could they say in first place? Your following every “best practice” rule super strictly!

Create tons of functions

Everyone loves small functions. They are testable and they state exactly what they do. Break everything down to as small functions as possible. Then soon if you want to read the code all you have to do is jump between functions and it kills all productivity.

It’s super spaghetti code that looks like neat and testable code. In case someone asks why it’s required say it’s more unit testable. Everyone loves unit testable code

Could also try to remove any duplicate line of code and create functions like makeWhile(), makeFor(), makeSwitch() etc

// Why write this:
function add(a, b) {
  return Number(a) + Number(b);
}

// When you could write it like that:
function add(a, b) {
  return cast('Number', a) + cast('Number', b);
}

function cast(type, value) {
  if (type === 'Number') {
    return castToNumber(value);
  }
  throw new TypeError('Invalid type')
}

function castToNumber(value) {
  return Number(value);
}

Use shorthands and bit manipulation

Everyone loves bit manipulations and everyone is trained to think that less code is better.

Instead of using multiple booleans could make byte array and do manipulations on that to later on figure out some boolean values

If someone asks why it’s necessary you can just say it’s more efficient because it’s bitwise and bits are all magic for most devs

Use some cool oldschool boolean checks like ??!??! instead of || etc

Add error checks everywhere

It is to protect codebase from malicious users or other malicious developers who might want to do something bad.

Add error check before and after every statement. Also add all kinds of type casts everywhere.

If someone asks about it say it’s to protect from hackers, junior developers and cosmic rays that might cause bit flip

Use only recursion instead of loop

Loops are too easy to understand, but every time someone sees recursion they have to stop for a brief time and see what’s the recursion doing.

Replace all for, while, do-while forEach etc loops with recursion. Even better if you can add recursion to functions and parts of code that are usually called only once. The more recursion the better.

Recursion is good, it looks so advanced that most likely noone will ever ask you about them. If they do say it’s to protect from off by 1 error or that functional programming is next big thing and it’s only way forward.

Write a lot of comments

Write a lot of comments everywhere so that when someone changes code they also need to change comments, which is double work.

It’s even good to have huge paragraphs of comments in top of file, so that when they read code they must jump up and down constantly.

Also add something in lines of “It’s legacy code, interacting with multiple critical parts of system, do not change it under any circumstances”

Especially good if comment is not matching the code, but comment states to not modify code

It’s really easy to get thro code review, since everyone loves commented code and you can just state, that it’s to help future devs

Write a lot of code that you might need

Do everything opposite of YAGNI - you aren’t going to need it

Write a lot of configuration options, features, function parameters etc. It’s super easy way to make codebase several times bigger even tho most of the code is dead code.

Could just state that we are going to need all those features soon, if someone asks about them

Also add a lot of variables. Say that you are caching values or something.

Have many variables like, isDone and isNotDone, isItReallyDone, isntItReallyNotDone then sometimes update one and sometimes some other so that in some cases they all have same value.

Also good to update values in getters. For example when someone tries to get temperature isDone is flipped or set to some string

Even better to use byte arrays instead of booleans, since byte array is not affected by bit-flips. Could also make some cool complex bit-flip detection and correction code and mix it with Add error checks everywhere


Also fun fact. FireFox had some CSS error reported by user ~14 years ago. It got fixed by rewriting CSS engine in Rust.

Imagine this dude. Made issue 14 years ago. Firefox looked at it, went to invent new programming language, rewrite half of Firefox core in this new language and finally solved the bug and now he got email notification:

Bug #4049587 is fixed, can you try to reproduce with new version of Firefox?

How much overengineering is this?! Next time you stumble upon a bug think about creating your own programming language just to fix that bug

That issue is not why Rustlang was invented nor is it exact email, but still interesting