Code Signal to Noise

In my post on LibertyJS I raved about the talk “Javascript is Too Convenient” as well as a few other Functional Programming talks. A lot of them centered around the core concept that code is meant to be read more than it is written, so you should increase the signals and decrease the noise. Recently, I have been adding the “Ratio of Signal to Noise” to my mental checklist when writing and refactoring code, and how can I use function composition or curring to increase that ratio.

The first example where this became apparent to me was in refactoring some utils in a javascript module.

We had code (written for old browser compliance before babel existed) that looked a little like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* Gets a cookie from the browser
*
* @param String name
* Cookie to get the value of
*
* @return String
*/

function getCookie(name) {
var cookieName
, cookieValue
, cookieArray = document.cookie.split(';');

for (var index = 0; index < cookieArray.length; ++index) {
cookieName = cookieArray[index].substr(0, cookieArray[index].indexOf('=')).trim();
cookieValue = cookieArray[index].substr(cookieArray[index].indexOf('=') + 1);

if (cookieName == name) {
return unescape(cookieValue);
}
}
}

/**
* Gets the value of a variable in the query string of this URL.
*
* @param String variable
* Name of the variable to get the value of
*
* @return String
*/

function getQueryVariable(variable) {
var querystring = location.search.split('?')[1];
if (isDefined(querystring)) {
var vars = querystring.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (pair[0] == variable) {
return isDefined(pair[1]) ? pair[1] : '';
}
}
}

return null; // Query Variable not found
}

/**
* Set a cookie in the browser
*
* @param String name
* Name of the cookie to set
* @param value
* Value to set for the cookie
* @param Number minutes
* Duration in minutes until the cookie expires
*/

function setCookie(name, value) {
var expirationDate
, expirationString
, cookieValue;

// Expire cookie on 1/17/2038 (due to 32-bit integer overflow bug in some browsers)
var minutes = (2147299200000 - +new Date()) / 60000;

expirationDate = new Date();
expirationDate.setTime(expirationDate.getTime() + (minutes * 60 * 1000));

expirationString = '; expires=' + expirationDate.toUTCString();

cookieValue = escape(value);

document.cookie = name + '=' + cookieValue + expirationString + '; path=/';
}
```

So basically some helper methods you could copy off of W3Schools to manage
querystrings and cookies.

One of the first things I noticed was that the JSDoc Documentation for `setCooke()`
was wrong. There is no `minutes` parameter. I have a friend at work who calls
comments "lies"; and that applies perfectly here. The comments have gotten out
of sync with the code and are now misleading and decreasing the readability of
the code.

Not only that, in that short code blob, we have 26 lines of comments to ~ 31 lines
of code, but only 23 code statements. There are more lines of comment than statements.
There was a period in my development career that I would have looked at this code
and thought it was great. It is well documented and commented. But this module
was now too long to look at without scrolling.

This made me come to a realization:

> Code Comments can be noise

Sure code comments help convey meaning, but take a look at the `getCookie()` method.
```javascript

/**
* Gets a cookie from the browser
*
* @param String name
* Cookie to get the value of
*
* @return String
*/

function getCookie(name) {
...

It spends 9 lines of code comment stating the obvious, that this method gets a cookie. Developers know that cookie keys are strings and they return strings. I would much rather see a typescript def like

1
2
function getCookie(name: str): str {
...

That conveys the same amount of meaning in one line as the 9 lines of JS doc. Which makes the 9 lines of JS Doc noise.

The other thing I noticed is we are taking up a ton of real estate here for what are basically boilerplate functions. There is no control or coordinate functions here, these are just calculations; and not only just calcuations, boilerplate calculations that could live in any app.

I didn’t end up using typescript in the refactor of this module yet, but I did refactor this in ES6 to look something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const getKeyFromDelimitedString = (input_string, delimiter) => key => {
let keyPairs = {};
input_string.split(delimiter).map(hash => {
let [_key, _value] = hash.split('=');
keyPairs[_key.trim()] = decodeURIComponent(_value.trim());
});
return keyPairs[key];
};

const getQueryString = getKeyFromDelimitedString(location.search.split('?')[1], '&');
const getCookie = getKeyFromDelimitedString(document.cookie, '; ');

const setCookie = (name, value) => {
let properties = {
'expires': new Date(2147299200000).toUTCString(),
'domain': 'example.com',
'path': '/'
};
document.cookie = `${name}=${encodeURIComponent(value)};` + Object.entries(properties).map(kv => kv.join('=')).join('; ');
};

We went from 72 lines down to 20. We made use of function currying to dry up the fact that both querytrings and cookies are strings delimited by some character, made up of {key}={value} pairs. We named the currying function something obvious, and made sure the names of the resulting curried functions were as good as documentation.

This refactor expressed the same functionality from 72 lines and 23 statements down to 20 lines and 11 statements. Besides the bit of ES6 specific syntax, I would argue the readibility hasnt suffered; and now the whole thing can easily fit inside a window without scrolling.

In conclusion, dont blindly apply comments everywhere, because it may be affecting the readibility of your code, if not just dilluting your code. Absolutely apply comments where needed, but make sure it is increasing your signal to noise ratio.