A lot of people will probably know how to refactor code, and many probably know about Object-Oriented programming too. But this doesn’t apply to everyone, and some people may not even know where to start with refactoring code, even if they do understand the concept of OO.
In any case, I decided that I’d document my refactoring of some code I’ve previously written, partly so I could be a lot more meticulous about it (exposing your code to criticism makes you far more pedantic about each line), but also to maybe help someone struggling with the two ideas, or some of the issues that I currently have with my code.
I’m splitting it up into a series of articles, so that I can focus on each point of the refactoring process and make it clear what I’m doing and why I’m doing it! So I’ll start with the introduction, and the first stage of the process: introducing objects!
I’m using some Javascript code here, but much the article could be applied to refactoring code in any programming language.
The Current Code
The code I’m editing is the code used by the Auto-Scrobbler, here’s the source code we’re starting with:
/*
Auto Scrobbler for Universal Scrobbler
This needs to be cleaned up, the global variables and procedural
style is not perfect!
*/
function addLatest() {
tracks = document.querySelectorAll(".userSong");
lastValue = (typeof lastValue == "undefined") ? tracks[1].querySelector("input").value : lastValue;
for (var i = 0; i < tracks.length; i++) {
var item = tracks[i];
if (item.querySelector("input").value == lastValue) {
i = tracks.length;
lastValue = tracks[0].querySelector("input").value;
} else {
item.querySelector("input").checked = true;
autoScrobblerScrobbled++;
}
}
doUserScrobble();
autoScrobblerScrobbledElm.innerHTML = autoScrobbler.scrobbled;
}
function loadThenAdd() {
doRadioSearch();
setTimeout(addLatest, 30000);
}
function start() {
loadThenAdd();
document.autoScrobbleUID = setInterval(loadThenAdd, 300000);
autoScrobblerStart.disabled = true;
autoScrobblerStop.disabled = false;
}
function stop() {
clearInterval(document.autoScrobbleUID);
lastValue = undefined;
document.autoScrobbleUID = 0;
autoScrobblerStop.disabled = true;
autoScrobblerStart.disabled = false;
}
document.querySelector("#disclaimersContainer").innerHTML += "<div id=\"autoScrobbler\" style=\"background: #FFFFFF; border-top: 1px solid #000000; border-left: 1px solid #000000; position: fixed; bottom: 0; height: 50px; width: inherit;\"><input id=\"autoScrobblerStart\" type=\"button\" value=\"Start auto-scrobbling\" onclick=\"start();\" /> | <input id=\"autoScrobblerStop\" type=\"button\" value=\"Stop auto-scrobbling\" onclick=\"stop();\" /></div>";
autoScrobblerStart = document.getElementById("autoScrobblerStart");
autoScrobblerStop: = document.getElementById("autoScrobblerStop");
autoScrobblerScrobbled = 0;
autoScrobblerScrobbledElm = document.getElementById("autoScrobblerTracksScrobbled");
start();
Some Issues
Global Variables
The only way we can pass round data is by setting up half a dozen global variables. Privacy isn’t really an issue in Javascript, you can access anything, there’s no privacy (without a lot of hacking an memory hogging). But it’s more of an issue of efficiency, all the variables will fill up the browser memory, making the code pretty inefficient.
If nothing else, it just fills up the list of global variables, just for your piece of code. Which means other developers will have to work around your big mass of variables.
To add to this, I’ve done some weird tomfoolery where I wanted to ensure that I was setting a global variable, by inserting variables into the “document
” variable, which usually holds all the elements in the current document. If anything this should’ve been done in the “window
” variable, where global variables are set by default. This would mean I wouldn’t be playing about with document properties, which is very bad practise!
No Chance of Multiple Instances
It doesn’t apply to this piece of code so much, but you can’t run multiple instances of this code. If I wanted to automatically scrobble two radio stations simultaneously (though I’m not sure why I’d want to), I wouldn’t be able to in the code’s current state. This would be more of an issue for something like an element fading script. If it was written in the same way as the code above, you’d only be able to fade one element on the page – that’s not very useful!
Difficult to extend, especially for others!
Because of the way that the code is written, extra functionality is hard to implement, old methods need to be rewritten (sometimes completely) to add any sort of functionality. It’d be nice to fix this, even allow other plugins to work with mine.
The way it’s written isn’t the most intuitive either, there’s no real flow, and if you want to follow the code as a developer who’s never seen the code before, you’ll be getting RSI from scrolling up and down if the code gets any longer! What’s worse is that there’s just one comment, the rest of the code has no information about what each function does.
It uses inline CSS and inline event handling
The code currently injects some HTML into the page, to give the user some control over the automatic scrobbling. I wrote the whole plugin in the Chrome console, and at the time, both inline CSS and inline event handling seemed like the quickest way to write things. However, in terms of best practise, unobtrusive Javascript and CSS efficiency, it isn’t a good option to write any styles or event handles inline.
Limited Compatibility
It would be lovely to run one function or line of code, and expect it to work with every browser on every platform. Unfortunately, that’s just not how things are. Older browsers had a far more primitive method of handling events, and IE chose it’s own special way of doing things as well (as usual). Further to that, the W3C standard is also entirely different, so we need three different lines of code just to do one thing, this is also true of other functions I use in my code. As part of the refactor, it’d be great to get this compatibility for more browsers.
Let’s fix this
Those issues aren’t nice to have in production code, they may have been fine when I was trying to whip up something as quick as possible, but I’ve now drawn a line under it, and revealed it to others. So the code needs to be refactored. Refactoring won’t add any functionality to the plugin, nor will it change the resulting product or the data that is passed through the code.
In fact, perfect code refactoring would leave an end-user with the exact same functional experience as they had before. The difference is in how many resources are required to run the program, how easy it will be to add to that program later and how well other developers will be able to understand the code if they should look at it.
We’ll start with those global variables, we still want to be able to access them in any scope…
Aside: Scope
“A scope is the context in which a variable name or other identifier is valid and can be used.”
I’ve been meaning to write an article on the scope of variables and the problems I’ve been tripped up by just by refactoring code poorly, unfortunately I haven’t been able to do that as of yet!
You’ll need an idea of scope for this series of articles to make any sense at all, so here’s why we need to deal with different scopes in our specific example:
- In it’s current state, there are two scopes:
- Variables which are first set with the
var
keyword have a local scope. This means that, for instance, in theaddLatest()
function, the variableitem
may only be accessed within that function. If theloadThenAdd()
function referenced the variable “item
“, an error would be thrown, as it would not exist (becauseloadThenAdd()
is outside ofaddLatest()
scope).- Variables which are first set without the
var
keyword have a global scope. This means that the variable could be referenced and used anywhere. So thestart()
function can set a value todocument.autoScrobbleUID
, and thestop()
function can use that value to stop the auto-scrobbling of tracks.- Unless you know that you want to be using data between several functions, or that you want to retain data after the function has finished running, you should always put variables in a local scope. If you don’t, you can find some very tricky problems will occur (suddenly, you’ll find yourself overwriting variables in other parts of your code and other parts never running because the conditions aren’t right). I have in fact broken this rule in
addLatest()
, wheretracks
is used only byaddLatest()
and it doesn’t need to be retained after the function finishes, but it isn’t defined/set using thevar
keyword before it.- We use global variables for a lot of the variables in this code, because they are required by multiple functions.
- We also use the setInterval() function in the code, and any code you run within it has it’s own scope (it wouldn’t be able to access any local variables in the start() function for instance), so this also requires something with more access than local variables to do it’s job.
- Understanding scope can help to make your programs more efficient which will increase performance as well!
- The refactoring of our code will add a third scope into the mix, which solves many of these problems.
So we need to offer these variables globally (or at least in a wider scope than the local scope of each function), without cluttering up the rest of the page.
I am now going to refactor the code in five stages, I will be writing an article dedicated to each.
Objects
Javascript (and many other languages) has a variable type which, at it’s very simplest, will group other variables, data, even functions together… Read how I introduced objects into the code
Moving to Object-Oriented code patterns
So now we have our group of variables under the name autoScrobbler
, I said earlier that we could also include functions under this group… Read how I moved my code to a more Object-Oriented pattern
Extendible, Readable, Descriptive
We’re nearly there with the Javascript, the worst has been cleaned up generally. Now we’re getting on to being developer-friendly, which is pretty important, you want people to extend your program… Read how I begin making my code more developer friendly.
Cleaning Up Our Messy User Controls
We’re pretty close to being done here in terms of refactoring our Javascript. But the Auto-Scrobbler has a UI, an incredibly simple UI, but it still has one… Read how I clean up the messy UI code.
Compatibility
