Introducing DILOS Principles for JavaScript Code

Written by kealanparr | Published 2020/10/16
Tech Story Tags: dilos | solid-principles | javascript | software-architecture | programming | software-development | design-patterns | coding-standards

TLDR The SOLID principles have been created as pillars of creating flexible, understandable and maintainable code. They can add days onto your dev time to implement them properly, and most people don't care or worry much about code quality. The DILOS principles are strong, secure pillars of architecting terrible code. Don't add code to objects it doesn't need. Don't abstract anything away. Make the person who calls into your function understand every single line of what you are doing if they want to use it.via the TL;DR App

The SOLID principles have been created as pillars of creating flexible, understandable and maintainable code. They can add days onto your dev time to implement them properly, and most people don't care or worry much about code quality, so I created some better ones.
The DILOS principles have been created as strong, secure pillars of architecting terrible code.
I personally have implemented the DILOS principles for creating messy, obfuscated and bloated code. Let's introduce them:

DILOS stands for:

  • D — Dependency Inversion inverted principle
  • I — Interface bundling principle
  • L — Liskov removal principle
  • O — Open closed principle
  • S — Several responsibilities principle

Dependency-Inversion inverted principle

High level modules must depend on low level modules. Depend on concretions not abstractions.
To abstract something away means to hide away the implementation details inside something, sometimes a prototype, sometimes a function. So when you call the function you don't have to understand exactly what it is doing. If you had to understand every single function in a big codebase you would never code anything. It would take months to finish reading.
Here, we want to flip that. Don't abstract anything away. This means do as little as possible in smaller modular functions and do it all in one monolithic function. Make the person who calls into your function understand every single line of what you are doing if they want to use your work.
Here's an example of clean code:
function hitAPI(url, httpMethods){
	// Implementation example
}

hitAPI("https://www.kealanparr.com/retrieveData", "GET");
hitAPI("https://www.kealanparr.com/retrieveInitialData", "GET");
Can you see how you don't have to worry about what
hitAPI
does? We just pass a URL and a HTTP request and it's dealt with.
Now this is highly re-usable, and maintainable. This function can deal with all the different URL's in one place. We have done our best to let high level functions (functions we might put in a base prototype to share with lots of place below it) not depend on any low level functions.
So let's invert the Dependency-Inversion principle
How about the below?
function hitDifferentAPI(type, httpMethods){
	if (this instanceof initialLoad) {
		// Implementation example
	} else if (this instanceof navBar) {
		// Implementation example
	} else {
		// Implementation example
	}
}
We now have made our high level api request rely on lots of lower level type's. Mission accomplished, it's no longer cleanly generic & depends on types lower in it's inheritance chain.

Interface bundling principle

Clients should not be forced to depend upon [code] that they do not use.
Interfaces in other languages are used to define method's and properties that different objects will have.
Here what the principle is saying, is don't add code to objects it doesn't need. Don't bundle too much un-related functionality together. Which is total nonsense.
We want to ensure we continually bundle loosely related code paths together all in one place. Essentially, depend on things you don't even need.
We talk more about this later in the Several responsibilities principle but remember this principle, and allow it to extend further and influence everything. Have you ever spent hours looking through hundreds of files trying to find your bug? Not anymore. Have one JS file called
main.js
and put all of your code in there.
Make your initial site load slower, by loading everything up-front, rather than lazily loading JS scripts as and when you need.
Code for things you might need later down the line now, just in-case you do need them.
If someone asks for a banana in your code, give them a gorilla holding a banana. Always over-deliver what the client needs, they will thank you for it.
Do your best to keep your functions as few as possible. Do you think this might fit better by encapsulating this in a new function elsewhere and abstracting the logic away? No. Don't make this more confusing than it has to be. Just copy and paste the code where you need it.
We ideally would only have 1 object for our code flow. In very, very big codebases we might have 2 objects. It's very often called the God object anti-pattern, where one object has to be used everywhere as it does too much, but we'll talk more about that later.

Liskov segregation principle

Build software out of parts where the children can't be swapped for the parents.
A definite red flag here would be that you're even using inheritance in your code. Make sure you are copy-pasting the code instead of inheriting. This is explained beautifully by the Copy-Paste anti-pattern, where you shouldn't be abstracting away the common features of your code into modular, re-usable functions but duplicating the code anywhere you need it. This will increase technical debt (code you have to go back and fix properly later) and ensure changing one piece of code, requires multiple searches to find every place it is used in the codebase.
DRY code means Don't Repeat Yourself. WET code is the opposite, and we Write Everything Twice. Multiple times if necessary. Fear inheritance and copy-paste as necessary.
But if you do need to make use of the Liskov segregation principle, ensure object's prototypes lower in the inheritance chain (children) when swapped with something higher in their inheritance chain (parents) should not work as expected.
Why?
Because if we don't follow the Liskov-segregation principle then we are starting to build accurate, robust inheritance chains. abstracting away logic away into encapsulated base prototypes/objects. We also are beginning to logically group the different methods in the prototype chain and their particular overrides making code paths more expected and discoverable.
If you have accurately followed this principle, due to children not being usable where a parent is, you have completely made inheritance useless anyway. If your program blows up when trying to reference functions that don't exist in their children- you have completely killed any benefit inheritance would have given you - which is exactly our goal with this principle.

Open closed principle

Objects should be open for modification, but closed for extension.
Good code normally extends object's code, to limit modifying a base prototype. So that the object that has done the extension can deal with it's own state and what new functionality it needs to do (deal with the little changes only the child needs to do.)
This is nonsense, and what we really should be aiming for is:
  • Open for modification— to make any changes, we have to change the base prototype/function.
  • Closed for extension— if you extend, you are starting to modularise the code, stop and make sure everything is done in a base prototype/function.
The above two steps will also promote heavy if branching as you deal with every scenario. So you end up with something like:
function makeSound(animal) {
	if (animal == "dog") {
		return "bark";
	} else if (animal == "duck") {
		return "quack";
	} else if (animal == "cat") {
		return "meow";
	} else if (animal == "crow") {
		return "caw";
	} else if (animal == "sheep") {
		return "baa";
	} else if (animal == "cow") {
		return "moo";
	} else if (animal == "pig") {
		return "oink";
	} else if (animal == "horse") {
		return "neigh";
	} else if (animal == "chicken") {
		return "cluck";
	} else if (animal == "owl") {
		return "twit-twoo";
	} else {
		/// It has to be a human at this point
		return "hi";
    }
}
Now if you need to change something you just simply add another
if 
check.
This ties into the several responsibilities principle you will learn more about below, but the main thing is, have everything in a base function/prototype.
This will help you promote the Fragile Base Class anti-pattern, where by changing the base function/prototype you end up with bugs in the other touch points where this function is called.
For example, lets say human should no longer fall into the else, and you add the new animal wolf you will introduce bugs (unless you update the place where you're expecting human to be logged).

Several responsibilities principle

Ensure that your functions/objects have multiple responsibilities.
Good coders too often separate out their code into multiple different objects or modules. My problem with this, is that it is confusing. I can't remember what they all do.
Here's an example:
const godObject = {
	handleClicks:     function(){},
	getUserName:      function(){},
	handleLogin:      function(){},
	logTransactionId: function(){},
	initialSiteLoad:  function(){}	
};
godObject 
is responsible for so many aspect of the site. Payments, log in, site load, logging transaction id's and all click functionality on the site. Great. If you have a bug, you know it can only be here.
Make sure everywhere in your codebase needs access to
godObject
. Make it do everything.
What we really want in the code is high coupling (ensuring lots of parts of our system depend on each other) and low cohesion (we puts lots of random bits and pieces together).
This anti-pattern is sometimes called the Swiss Army Knife because you only really need it as scissors to cut something, but it also can be a nail file, saw, pair of tweezers, bottle opener and be a cork screw too.
What I am re-iterating over and over again here, is to keep everything together and bundle, bundle, bundle.

Conclusion

I hope this has eloquently described how software should be written to maximise time lost debugging, frustration and piling on technical debt.
I post my articles on Twitter if you want to keep up to date with my writing.

Written by kealanparr | Principal architect @ http://kealanparr.com & interested in reading, finance, exercise & tech
Published by HackerNoon on 2020/10/16