Error Driven Development
in JavaScript

Error Driven Development is a pun on Test Driven Development. So besides writing tests, you should more importantly, write errors! And by errors I mean that the program should "throw" an error when something is wrong.

You might think that writing code that intentionally makes a program "crash" makes it unreliable, but it's quite the contrariety. To make a bug free program, it should crash at any opportunity, on all kind of errors ...

It should not only crash, it should scream loud at your face (the developer): And if it's running in production, the error should be logged, and a developer notified. And the program/daemon instantly restarted.

Code examples

Take this simple function as an example:

function sum(x, y) {
	return x + y;
}

What will happen if it's called with only the first argument? Or with an array as second argument?

sum(1, 2)     => 3
sum(1)        => NaN
sum(1, [1,2]) => '11,2'
sum(1, "1")   => '11'

In JavaScript we get a lot "for free". Throwing an error in JavaScript (nodejs) crashes the program and prints out a call stack. We can then provide the rest of the information in the Error message.


function sum(x, y) {
	if(!isNumeric(x) throw new Error("Expected first argument x=" + x + " to be a numeric value!");
	if(!isNumeric(y) throw new Error("Expected second argument y=" + y + " to be a numeric value!");
	
	return x + y;
	
	function isNumeric(n) {
		return !isNaN(parseFloat(n)) && isFinite(n);
	}
}

function addPerson(name, age) {
	if(name.length < 1) throw new Error("name=" + name + " is only " + name.length + " characters long!");
	if(age < 0 || age > 150) throw new Error("age=" + age + " is below zero or above 150!");  
	//... Some code here
}

function disable(settings, option) {
	if(!settings.hasOwnProperty(option) throw new Error("option=" + option + " does not exist in settings=" + JSON.stringify(Object.keys(settings)) );
	//... Some code here
	if(settings[option] != false) throw new Error("settings[" + option + "]=" + settings[option] + ", expected it to be false!");
}

Sometimes we do not want to crash though, but then we can use try-catch!

// Important that all these functions run
var gotError = false;
for(var=0; i<funs.length; i++) {
	try {
		funs[i](); // Execute the function
	}
	catch(err) {
		console.warn("Error: " + err.message + "\n" + err.stack);
		gotError = true;
	}
}
if(gotError) throw new Error("Got error when running functions! See console log");


Are you insane?

You can take the error checking to another level by checking for stuff that should never happen.
A few extra if's here and there will not slow down your program, unless you do it in a tight loop. But it can keep you sane. And one nanosecond of extra compute time is better then spending several hours debugging an error that should never happen.
And weird stuff does happen! For example "bit flips" or "data rot", like a bad hard drive, RAM, or a bit error over the wire, that passes the CRC.

Still don't believe me? Error checks is common in life depending software, like avionics. It's usually much better to quickly reboot a micro-service, then to have a bad state, like having the altimeter show the wrong value, or spreading the bad state all over the system.

Human errors

Must bugs are caused by those who write the software. By having your functions constantly check the inputs (arguments) for errors, and show friendly error messages, most bugs will be found quickly during manual testing.
This is especially important in JavaScript, where most data types can be mixed together without any errors or warnings by the compiler.
But before you rant on JavaScript for being "weakly typed", guess whats true about all bugs ever made in a strongly typed language? They all passed the type checker!

Writing errors VS "unit" testing

function magicNumber(n) {
	
	if(n < 1 || n > 10) throw new Error("n needs to be between 1 to 10");
	
	n = n * 3;
	n = n + 6;
	n = n * 3;
	
	var str = n + "";
	
	n = 0;
	
	for(var i=0; i<str.length; i++) {
		n = n + parseInt(str[i]);
	}
	
	return n;
}

Unit test

assert(magicNumber(1) == 9, "magicNumber should always return number 9");
assert(magicNumber(2) == 9, "magicNumber should always return number 9");
assert(magicNumber(3) == 9, "magicNumber should always return number 9");
assert(magicNumber(4) == 9, "magicNumber should always return number 9");
assert(magicNumber(5) == 9, "magicNumber should always return number 9");
assert(magicNumber(6) == 9, "magicNumber should always return number 9");
assert(magicNumber(7) == 9, "magicNumber should always return number 9");
assert(magicNumber(8) == 9, "magicNumber should always return number 9");
assert(magicNumber(9) == 9, "magicNumber should always return number 9");
assert(magicNumber(10) == 9, "magicNumber should always return number 9");

Error test

if(n != 9) throw new Error("Expected n to be 9");

The advantage of error checks over "unit testing" is that you simply can't have your "unit tests" cover every single state. While the error checks test real world data.

There are cases when error checks wont be enough though. For example if you have a function that does many things, like a food machine:

function foodMachine(ingredients) {
	return plate;
}

Every time you implement a new dish to that function, or you find a bug, make sure you also write a test that covers it! Or you will end up with so called regression bugs, which means your changes caused another bug, or you cause a (dejva-vu) bug that you have already fixed before.

Unit test

assert(foodMachine([
	"pasta", 
	"bacon", 
	"parmesan", 
	"egg", 
	"cream"
]) == "Carbonara", "Expected Carbonara");

You should however avoid such functions. Having your programs, modules and functions only do one thing will be much easier with less bugs, and easier to test, both manually, automatic and with error checks.

But whatever you do, do not make your tools restrict how you design your code.
A common mistake with Test Driven Development is dividing the code up too much, into smaller parts, which makes it easier to test, but you'll lose context of what the code does.
This probably deserves it's own blog post/rant, but I've seen a lot of code bases lately, that is so fragmented that it's impossible to see the code flow, and where those tiny parts fits into the whole. And while there's code reuse inside the program itself, their "modules" are useless without the rest of the program.


Written by Mars 22, 2016, updated 23 June 2016.


Follow me via RSS:   RSS https://z├Ąta.com/rss_en.xml (copy to feed-reader)
or Github:   Github https://github.com/Z3TA