Every console.log() line in your JavaScript code has to be removed before pushing to production. With your own custom debug() method, you can safely leave all of your debug code in-place and just switch them “off” before going live.
The JavaScript console is an invaluable asset in the never-ending quest for sufficiently-tested code. For most JavaScript developers, a day at the office would not be complete without at least a few lines of debug code such as these:
|
console.log(‘foo = ‘ + i); console.log(‘starting AJAX call...’); console.dir(someObject); //etc... |
But the problem is that code needs to be removed. That’s fine if it’s just a quick test. But what if there are 20 places in your code where you need to keep track of a value, trace program flow and logic, or test the return value of a function?” Well, that’s a lot of temporary code to keep track of and remember to delete. Even more frustrating is that in a month or two, when business wants more changes, you’ll probably wind up writing the same kinds of debug messages and putting them in the same places, only to have to search your script for “console” once again so that you can remove all of these debug statements before pushing to production.
There is certainly more than one way to skin a cat here, but a simple approach is to write your own custom debug function that shows messages, and knows when it should suppress those messages (i.e. when your code is running in production).
Creating a Custom Debug Function
First, let’s create a couple of functions that do things to objects. We’ll want to debug this code so that along the way, we can check to make sure that the functions are in-fact returning the object that is expected (combineObjects), or making the changes to the object that we pass in (arrayToUpper).
Example # 1
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
|
var obj1 = { name: 'alfred e newman', phone: '212-555-1212' }, obj2 = { account: '010203', address: '404 Mad Ave. - MadCity NY, 12345' }, myArr = ['monday','tuesday','wednesday','thursday','friday'], combineObjects = function(objA,objB){ var newObj = {}, merge = function(baseObj,addObj){ for(var prop in addObj){ if(addObj.hasOwnProperty(prop)){ baseObj[prop] = addObj[prop]; }; }; }; merge(newObj,objA); merge(newObj,objB); return newObj; }, arrayToUpper = function(arr){ for(var i = 0; i < arr.length; i++){ arr[i] = arr[i].toUpperCase(); }; return arr; } console.dir(combineObjects(obj1,obj2)); console.dir(arrayToUpper(myArr)); |
In Example # 1, we have created two simple objects. Let’s imagine that they were created separately somewhere else in our code, and need to be merged in order to create one complete “customer object”. We also have an array that contains the five days of the work week. This array has no functional relation to the two objects. The are all just variables that we can use to test our code.
The combineObjects() function takes two objects as arguments and returns a new object that contains the combined properties of the two original objects. The arrayToUpper() function takes an array as an argument and converts its string elements to upper-case.
For the sake of simplicity, I did not bother doing any kind of verification or type-checking in either method. In the real world, I highly recommend that these kinds of methods contain some kind of verification code at the top so that they fail gracefully.
For example, what if we passed two arrays to combineObjects()? Or what if arrayToUpper() received an array of functions as an argument? This is messy business. I’ve provided a few suggestion on how to create that kind of functionality in an earlier blog post: Validating JavaScript Function Arguments.
So, when you run Example # 1 in your JavaScript console, you will see that we have in-fact created a new object that is the sum of Obj1 and Obj2, and we have changed the array “myArr” so that all of its elements are now uppercase strings. But what I don’t like is that we have written two lines of “test” code that need to be removed at some point (i.e. the two console.dir() statements). I would like to include the testing in our functions so that we can simply call each function, not writing any special code that we need to keep track of and then delete before the production rollout.
Example # 2
|
var debugMode = true, myDebug = function(msg,callback){ //if app not in debug mode, exit immediately if(!debugMode || !console){return}; //console.log the message if(msg && (typeof msg === 'string')){console.log(msg)}; //execute the callback if one was passed-in if(callback && (callback instanceof Function)){ callback(); }; }; |
In Example # 2, we have created a variable named “debugMode”. This tells our custom debug function whether or not messages should be output. If “debugMode” is set to “false”, then our custom debug function simply does nothing.
Ok, let’s look at our new “myDebug()” function. This function takes two arguments: a message and a callback. The message should be a string and if so, it will be output in the console. I tend to imagine messages such as “function getAccount started…” or “AJAX success callback fired….” etc. These are messages that your code can send to help “tell a story.”
The purpose of the callback is to allow for more complex messages. For example, you may want to inspect an object at that very moment. So, in addition to the text message, you can pass an anonymous function that contains a line such as “console.dir(myData)”. You could even include your “myDebug()” call inside of a “for” loop, inspecting the state of an object on each iteration of the loop. The sky’s the limit here. In general, I tend to feel that the text message and optional callback are enough for you to provide useful debugging messages for your app.
Example # 3 A
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
|
var //show debug messages debugMode = true, myDebug = function(msg,callback){ //if app not in debug mode, exit immediately if(!debugMode || !console){return}; //console.log the message if(msg && (typeof msg === 'string')){console.log(msg)}; //execute the callback if one was passed-in if(callback && (callback instanceof Function)){ callback(); }; }, //objects to merge obj1 = { name: 'alfred e newman', phone: '212-555-1212' }, obj2 = { account: '010203', address: '404 Mad Ave. - MadCity NY, 12345' }, //array for testing myArr = ['monday','tuesday','wednesday','thursday','friday'], //combines two objects combineObjects = function(objA,objB){ var newObj = {}, merge = function(baseObj,addObj){ for(var prop in addObj){ if(addObj.hasOwnProperty(prop)){ baseObj[prop] = addObj[prop]; }; }; }; merge(newObj,objA); merge(newObj,objB); //debug message myDebug('the new combined object is: ',function(){ console.dir(newObj); }); return newObj; }, //converts an array of strings to upper-case arrayToUpper = function(arr){ //code that does something with arr for(var i = 0; i < arr.length; i++){ arr[i] = arr[i].toUpperCase(); //debug message myDebug( ('the new string for element # ' + (i + 1) + ' is: ' + arr[i]) ); }; } //call the functions var foo = combineObjects(obj1,obj2) arrayToUpper(myArr); |
In Example # 3A, we have implemented our custom debug function: “myDebug”. Now we can simply call the functions combineObjects() and arrayToUpper() the way we normally would in our code. The “debugging” code that examines values and outputs informative messages now exists in the actual functions that do the work. When it is time to push this code to production, simply change the value of “debugMode” to “false”, and away we go. One line of code is all it takes to suppress all of our debug messages (see example # 3B below for a demonstration of this).
Example # 3B:
|
var //DO NOT show debug messages debugMode = false, /* THE REST OF THE CODE IS THE SAME */ //added for proof-of-concept console.dir(foo); console.dir(myArr); |
In Example # 3B, we have set “debugMode” to “false”, which means that you do not see any debug messages. But to complete our proof-of-concept, we add two console.dir() statements to the end of our code, which demonstrates that the code once again, performed as expected and our custom debug method “myDebug” behaved exactly as designed: no console messages.
Summary
In this article, we learned how to create a custom debug function. We discussed the value of minimizing “special” testing code that needs to be tracked and then removed before deploying to production. We also discussed how to design our custom debug function so that it can output text messages as well as execute anonymous functions. We also covered how to implement a very simple “off” switch. This will suppress any debug messages, which is recommended when pushing your code to production.