piątek, 7 października 2016

Tests === shitty code

Writing tests === trying to make yourself feel better about your shitty code


If you need to write tests for your code it simply means you *know* you wrote shitty code and you are trying to make yourself feel better about it by seeing your tests pass. If you have a simple function:


function add(a = NaN, b = NaN) {
return a + b;
}

This function is made obviously for the purpose of adding 2 numbers together and returning their sum. But what if we passed two strings to the function?

const result = add('a', 'b'); // result === 'ab'

In such case if we really want to make the result a number we could return a *NaN* value in case of incorrect arguments:

function add(a = NaN, b = NaN) {
if (typeof a !== 'number') {
return NaN;
}
if (typeof b !== 'number') {
return NaN;
}

return a + b;
}


We could say this functions is bulletproof now, because it will always return a number (*NaN* is a number, open up a console and check if you don't believe me). There might be another problem here, namely passing *Infinity* as argument, but this is rather a business logic concern rather than incorrect input type. We could add additional logic to return 0 or some other numbers in case of *Infinity* but that's not our problem here. We want to return a number from this function. By the way we can shorten the *if* statement to something like this

function add(a = NaN, b = NaN) {
if (typeof (a + b) !== 'number') {
return NaN;
}

return a + b;
}

Or even something like this

function add(a = NaN, b = NaN) {
const isNumber = typeof (a + b) !== 'number';
return isNumber ? (a + b) : NaN;
}

We could use the first version of our *add* function if we were lazy, but we can be more disciplined and write solid code instead of shitty one and praying for good fortune. The example I've shown here is obviously very basic and does not resemble any real world case scenario. It is merely to show that if you want to be able to say 'fuck testing, tests are for noobs' then you need to be consistent in your code, make sure you understand every line of code you write and there is nothing that could possibly slip through your type/value checks down the road.

In my opinion it's a waste of time to write tests for something that *obviously* is correct and will never fail. Based on our example here: if you want to return the result of adding *a* and *b* then just make sure both values are numbers and stop fucking around with testing it.

Now, I understand that in many companies people test the shit out of their code because there are lots of developers writing code of varying quality and hence the need for checking if the whole app won't break if some non-standard user comes along. But this is a matter of hiring rock-solid coders who know exactly what the fuck they're doing instead of random jQuery fanboys who don't even have a clue how to do basic stuff without their holy dollar function.

Yes, I think jQuery is shit and if I had a startup or whatever I would never hire someone who even thinks jQuery is useful *at all*. But that's another story. I might elaborate on that one in some post in the future.

poniedziałek, 30 maja 2016

Atomic code

Initially, when I was writing code 2 years ago, when I was a complete noob, I used to pack everything inside one huge function that was doing something. Whether it be some socket.on('event', function(){ ... } or some other functionality, I would literally try to do everything in one function, and it would grow and grow infinitely. Functions 200-300 lines of code long wouldn't impress me one bit back then. Now I would grab my head and probably start pulling hair off seeing such thing.

It was hard to read. I remember that feeling. It was really tiresome to read all that action happening in once place.
It was hard to maintain.
It was hard to understand after a while.

We, coders, are writers. We write stuff. We read stuff. We read more stuff than we write. And without proper training and approach we tend to screw things over and over.

I've been trying to change the way I write code. I try to divide code into smaller parts (functions) with meaningful names. Sometimes it's difficult to choose a good and short name for a function. It requires some active thought process. Now when I read my code, I notice most functions are quite small, 1-5 lines of code. Some are bigger (10-40) or even 80 but the general tendency is to make things smaller (I'm going to have another look at those 80 lines).

Now, atomic code is not to be confused with compact/short code. 1 letter variable names are good for minified, production ready code, but not for maintaining. It is good to know some shortcuts, sometimes they are useful, but it is important to remember that we write code mainly for humans to read and only sometimes for machines to execute.

Also, don't be afraid to split your code into separate files. I find it best to put 1 'class' into a file (+ dependencies etc) and name the given file with the name of the class.

That's it for this post.

niedziela, 29 maja 2016

Flat code

This is more of a philosophical approach to writing code in general. It is true that we, human beings, can do elaborate and create complicated things, but it is essential to notice that most of the time spent on 'programming' we actually spend 'maintaining' the code.

If you write 20 lines of code, then go to drink a cup of coffee and come back after 10 minutes, now you're maintaining the code you had written earlier. You might not remember what each part of the code does, you might need to read it again and recreate the whole picture in your mind. It doesn't matter how fast you write (being a programmer) because the time you spend on actual writing is, I would argue, far less than 10%, maybe even closer to 1% than the 10%.

I've noticed some uncomfortable habits people have when writing code, especially if statements. Nested if statements.

Let's say you have the following piece of code:

  • const condition_1 = ...;
  • const condition_2 = ...;
  • if(condition_1) {
    • if(condition_2) {
      • // do some stuff
    • }
  • }

Now let's have a closer look at this code. You have some 2 conditions (3 dots `...` represent some boolean value extracted from another method, ternary operator etc). What if I had more nested if's? What you can see here is the beginnig of so called 'Pyramid of Doom' where code marches to the right substantially fast (instead of downwards). My solution is this:
  • const condition_1 = ...;
  • const condition_2 = ...;
  • if(!condition_1) {
    • return; // end of story, nothing more to do here
  • }
  • if(!condition_2) {
    • return; // end of story, nothing more to do here
  • }
  • // do some stuff

If you are able to grasp the most basic if-then event chains then you surely have noticed that condition_2 cannot be executed if condition_1 isn't true, so everything after condition_1 doesn't matter if the condition equals false.

Obviously you might say 'Wait a second, but why are you returning? Shouldn't it all be wrapped in a function?'. It should indeed. My goal, when writing code, is to extract such nested if statements into a separate function and then inside the function I can use return to immediately stop any further execution. This way you have more atomic code, easier to understand and also easier to read. Seems more logical to me than terribly looking nested conditions.

That's it for this post, I might add/edit something soon.