“Separation of concerns” is, perhaps, the most important topic in software development. Understanding how we can properly separate disparate parts of our code so they logically make sense while reducing our coupling to other areas is an essential task in modern software development. Within Node.js logic is generally separated using “modules”.
Those who use Node.js are very familiar with “modules” as they form the backbone to Node development, mostly via npm (Node Package Manager). In these cases, the “modules” expand the functionality of Node and make difficult tasks easier, great examples include express and request. In fact, Node itself is built in a module concept, enabling developers to bring in only what they need. Node also enables you to write custom modules, which are a great way to build solutions which are testable and maintainable.
To begin, this is some code I wrote a while back, it works but its not written very well, and it certainly isnt taking advantage of Node.js provides us:
// prototype functions String.prototype.asWeekNumber = function() { var result = this.match(/\d\d?/); return parseInt(result[0].toString()); }; // actual job code function LoadSchedule() { var request = require("request"); request({ url: "some/url/returning.json", json: true }, function(error, response, body) { if (!error && response.statusCode == 200) { var body = JSON.parse(body.replace(/,+/g, ',')); var key = Object.keys(body)[0]; body[key].forEach(function(item) { if (item.length == 10) { var game = createGame(item); addGame(game); } }); } }); } function createGame(array) { return { gameDay: array[0], gameTime: array[1], gameState: 'P', awayTeam: array[3], awayTeamScore: 0, homeTeam: array[5], homeTeamScore: 0, weekNumber: array[8].asWeekNumber(), year: parseInt(array[9]) }; } function addGame(game) { // doing some SQL Server stuff }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
For the most part, this looks like most of the JavaScript you see every day. It is purely functional and defines many random functions. Most of these cannot be reused without copying which is an obvious violation of DRY (Dont Repeat Yourself). So what can we do?
Thinking about this from a separation of concerns perspective, we probably want to move the code that is making the request and parsing the return into a module. This way, the LoadSchedule method isnt aware of what is being done just that it IS being done.
Here is the updated code for LoadSchedule following the refactor
function LoadSchedule() { var spSchedule = require("score_predict_schedule"); spSchedule.getCurrentSchedule({ success: function(games) { games.forEach(function(game) { if (game.weekType != "PRE") { // do not load the preseason addGame(game); } }); }, fail: function(error) { console.log(error); } }); } function addGame(game) { // do some SQL Server stuff here }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
As you can see, it is A LOT cleaner. The important line is require(“score_predict_schedule”). This bring in our module. Because we have it located under node_modules we dont even need to specify a path. If we look at the source code inside:
// parser reference var parser = require("./week_parser"); // Module for handling schedule retrieval module.exports = { _createGameFromType10: function(array) { var weekInfo = parser.parseWeekInformationFromString(array[8]); // return the game parsed from a Type 10 result }, _createGameFromType8: function(array) { var weekInfo = parser.parseWeekInformationFromString(array[6]); // return the game parsed from a Type 8 result }, getCurrentSchedule: function(handler) { var request = require("request"); var gameList = []; var self = this; request({ url: "http://some/url/returning.json", json: true }, function(error, response, body) { if (handler === undefined) { console.log("You must define a handler"); return; } if (!error && response.statusCode == 200) { var body = JSON.parse(body.replace(/,+/g, ',')); var key = Object.keys(body)[0]; body[key].forEach(function(item) {
if (item.length == 10) { gameList.push(self._createGameFromType10(item)); } if (item.length == 8) { gameList.push(self._createGameFromType8(item)); } }); handler.success(gameList); } else { handler.fail(error); } }); } };
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
We see that this has allowed us to break up different parsing branches and thereby allow our caller to not worry about HOW things get parsed, just received the list so it can perform the next step.
Something interesting here is the definition of parser. I was able to also refactor the logic which some specialized parsing into a separate sub module within our custom module. That being the case, I could reference it directly, sans the .js extension. Its definition is identical to this.
The advantage with this approach is now I can more easily write unit tests and bootstrap files to test this code. Before, I would have had to load the entire file and call my method. Seems easy, but it does make testing more difficult in the long run. In addition, if I ever had to do a similar operation I would have had to duplicate large chunks of that code. With this new approach, I can just call require and be done with it.
You will find an approach like this in most software development projects. The rule is “verify all the smaller pieces work so when you put them together into something bigger, its more likely to ‘just work’”.