Carlos Justiniano, HC.JS founder and a long-time programmer
I'm into full-stack JS development
I've consulted for numerous companies on commercial JS based products
What is clean code?
Code that is reasonably understandable
Code free of side effects
Code that is testable
AWARENESS IS HALF THE BATTLE
Identifying and labeling bad code
Poorly written code
Mental context switching and code complexity
Tech / code debt
Poorly written code
Very few of us set out to write poor code
In many environments business pressures stress code quality
Often clean code degrades to poor code over time
Mental context switches and code complexity
In computing a context switch is the basis of multitasking
In humans, a mental context occurs when you have to switch between various program states as you attempt to understand what a program is doing
The more complicated the code the more difficult the mental context switches
Definite correlation between the number of context switches and code complexity
Takeaway: seek to simplify
Technical debt ... referring to the eventual consequences of poor system design, software architecture or software development within a codebase. - Wikipedia
Why should you care?
Often the first person who has to deal with dirty code is the person who wrote it
Then others may have to deal - think Karma
There's a real cost to dealing with dirty code
QUICK TIPS
Quick tips for writing cleaner code
Write consistent looking code. Embrace a style guide.
Take care in naming your variables, functions and objects
Don't bring your Ruby, Python or {your_language_here} naming conventions - use JS naming conventions... i.e. camelCase, CapitalizeConstructorNames
Favor small functions and objects
Limit the number of lines of code in a file or module
To comment or not to comment?
It's the subject of some debate
We can agree that useless comments are worse than no comments
Con: Comment take work and can get out of date
Pro: Use comments to share an understanding that can't be easily be inferred from the code
Pro: Well structured comments can make the code easier to understand by providing visual separators
Tip: Consider JSDoc (http://usejsdoc.org/)
Example: JSDoc and Webstorm
/**
* transSingleExecute
* Execute a single transaction and handle commit or
* rollback, return promise
* @param transobj {object} - transaction object
* @param statements {array} - transactions statements
* @returns {promise|*|Q.promise}
*/
function transSingleExecute(transobj, statements) {
// :
return deferred.promise;
}
Don't use magic numbers
Magic numbers are numbers that appear in code which seem to come from nowhere
//Before
if (!this.loadedSubViews) { this.loadedSubViews = []; }
//After
if (!this.loadedSubViews) {
this.loadedSubViews = [];
}
Always include curly braces
//Before
if (!this.loadedSubViews)
this.loadedSubViews = [];
//After
if (!this.loadedSubViews) {
this.loadedSubViews = [];
}
TIPS FOR VARIABLES
Remember variable hoisting
In JavaScript variable declarations are handled before variable assignments
//Before
function foo() {
var pi = 3.14159265358979323846264338327950288419716;
var radius = 12;
console.log('PI, radius: ', pi, radius);
var circumference = 2 * pi * radius;
console.log('Circumference: ', circumference);
}
//After
function foo() {
var pi;
var radius;
var circumference;
pi = 3.14159265358979323846264338327950288419716;
radius = 12;
console.log('PI, radius: ', pi, radius);
circumference = 2 * pi * radius;
console.log('Circumference: ', circumference);
}
Move variable declarations to the top of their function scope
//approach #1
function foo() {
var pi, radius, circumference;
pi = 3.14159265358979323846264338327950288419716;
radius = 12;
console.log('PI, radius: ', pi, radius);
circumference = 2 * pi * radius;
console.log('Circumference: ', circumference);
}
//approach #2
function foo() {
var pi = 3.14159265358979323846264338327950288419716,
radius = 12,
circumference = 2 * pi * radius;
console.log('PI, radius: ', pi, radius);
console.log('Circumference: ', circumference);
}
Avoid single var abuse #1
//Before
function foo() {
var pi = 3.14159265358979323846264338327950288419716,
radius = 12,
circumference = 2 * pi * radius,
largerCircleCircumference = circumference * 10;
console.log('Larger circle circumference: ', largerCircleCircumference);
}
//After
function foo() {
var pi = 3.14159265358979323846264338327950288419716,
radius = 12,
circumference,
largerCircleCircumference;
circumference = 2 * pi * radius,
largerCircleCircumference = circumference * 10;
console.log('Larger circle circumference: ', largerCircleCircumference);
}
Avoid single var abuse #2
//Before
function foo() {
var pi = 3.14159265358979323846264338327950288419716,
radius = 12,
circumference,
largerCircleCircumference;
circumference = 2 * pi * radius;
largerCircleCircumference = circumference * 10;
console.log('Larger circle circumference: ', largerCircleCircumference);
}
//adding function declarations
function foo() {
var pi = 3.14159265358979323846264338327950288419716,
radius = 12,
circumference,
computeLargerCircle = function(factor) {
return circumference * 10;
};
circumference = 2 * pi * radius;
console.log('computeLargerCircle', computeLargerCircle);
console.log('Larger circle circumference: ', computeLargerCircle(10));
}
Avoid single var abuse #3
//Not great
function foo() {
var pi = 3.14159265358979323846264338327950288419716,
radius = 12,
circumference = function(radius) {
return 2 * pi * radius;
},
biggerCircle = function() {
return circumference(radius) * 2;
};
console.log('Bigger circle circumference: ', biggerCircle());
}
Avoid single var abuse #4
//Not great
function foo() {
var pi = 3.14159265358979323846264338327950288419716,
radius = 12,
circumference = function(radius) {
return 2 * pi * radius;
},
biggerCircle = function() {
return circumference(radius) * 2;
},
evenBiggerCircle = biggerCircle() * 2;
console.log('Bigger circle circumference: ', biggerCircle());
console.log('Even bigger circle circumference: ', evenBiggerCircle);
}
You don't have to assign vars right away
function foo() {
var pi, radius, evenBiggerCircle;
function circumference(radius) {
return 2 * pi * radius;
}
function biggerCircle() {
return circumference(radius) * 2;
}
pi = 3.14159265358979323846264338327950288419716;
radius = 12;
evenBiggerCircle = biggerCircle() * 2;
console.log('Bigger circle circumference: ', biggerCircle());
console.log('Even bigger circle circumference: ', evenBiggerCircle);
}
TIPS FOR CONDITIONALS
Avoid large conditional blocks #1
//Before
var getUserIDsToNotify = function(notifyTo, data) {
'use strict';
var userIDs = [];
var deferred = q.defer();
if (notifyTo === 'USERS') {
if (!data.user_ids) {
deferred.reject({success: false});
} else {
userIDs = data.user_ids;
deferred.resolve(userIDs);
}
} else if (notifyTo === 'GROUP') {
if (!data.group_id) {
deferred.reject({success: false});
} else {
groupLib.listGroupMembers(data.group_id)
.then(function(results) {
for (var index in results) {
userIDs.push(results[index].user_id);
}
deferred.resolve(userIDs);
})
.catch(function(error) {
deferred.reject(error);
});
}
} else if (notifyTo === 'ALL_GROUPS') {
// get all group ids and then users OR get all the users
groupLib.listAllGroupMembers()
.then(function(results) {
for (var index in results) {
userIDs.push(results[index].user_id);
}
deferred.resolve(userIDs);
})
.catch(function(error) {
deferred.reject(error);
});
}
return deferred.promise;
};
Avoid large conditional blocks #2
//After
function handleUsers(data, deferred) {
var userIDs = [];
if (!data.user_ids) {
deferred.reject({success: false});
} else {
userIDs = data.user_ids;
deferred.resolve(userIDs);
}
}
function handleGroup(data, deferred) {
var userIDs = [];
if (!data.group_id) {
deferred.reject({success: false});
} else {
groupLib.listGroupMembers(data.group_id)
.then(function(results) {
for (var index in results) {
userIDs.push(results[index].user_id);
}
deferred.resolve(userIDs);
})
.catch(function(error) {
deferred.reject(error);
});
}
}
function handleAllGroups(data, deferred) {
var userIDs = [];
groupLib.listAllGroupMembers()
.then(function(results) {
for (var index in results) {
userIDs.push(results[index].user_id);
}
deferred.resolve(userIDs);
})
.catch(function(error) {
deferred.reject(error);
});
}
function getUserIDsToNotify(notifyTo, data) {
'use strict';
var deferred = q.defer();
if (notifyTo === 'USERS') {
handleUsers(data, deferred);
} else if (notifyTo === 'GROUP') {
handleGroup(data, deferred);
} else if (notifyTo === 'ALL_GROUPS') {
handleAllGroups(data, deferred);
}
return deferred.promise;
};
TIPS FOR FUNCTIONS
Move nested functions to the top of the enclosing function after the var declarations
function foo() {
var pi, radius, evenBiggerCircle;
function circumference(radius) {
return 2 * pi * radius;
}
function biggerCircle() {
return circumference(radius) * 2;
}
pi = 3.14159265358979323846264338327950288419716;
radius = 12;
evenBiggerCircle = biggerCircle() * 2;
console.log('Bigger circle circumference: ', biggerCircle());
console.log('Even bigger circle circumference: ', evenBiggerCircle);
}