General, high-level question for people who work with code regularly. I was repu
ID: 647125 • Letter: G
Question
General, high-level question for people who work with code regularly.
I was repurposing some old, outdated Javascript code into a Chrome extension, and to help me understand it, I wrote some quick code to help (see below). I regularly put messages in my code such as dbmsg('Begin function loadDefaults()'); or dbmsg('Settings saved to storage'); and the message is sent to the console. Whenever I need to debug something where the standard console messages aren't helping, I just turn on debugging. I figure this method is pretty simple and can be ported to other languages.
What other ways do you build debugging into your programs? I'm self-taught, so I don't know the standard practices, but I'm more interested in unique ways you've found to do this sort of thing.
/** Debugging Tools
*
* Set debugMode to 1 to show error messages, 0 to hide messages.
*/
var debugMode = 1;
function dbmsg(message) {
if ( debugMode == 1 ) {
var date = new Date();
var time = date.getHours() + ':'
+ date.getMinutes() + ':'
+ date.getSeconds();
console.log('DBM - ' + time + ': ' + message);
if ( chrome.runtime.lastError ) {
console.log('DBM lastError - ' + chrome.runtime.lastError);
}
} else if ( debugMode == 0 ) {
return;
}
}
if ( debugMode == 1 ) {
console.log('Debug mode is currently ON. Each debug message is preceeded with "DBM" and the time the message was generated.');
// Listen for changes in storage
chrome.storage.onChanged.addListener(function(changes, namespace) {
for (key in changes) {
var storageChange = changes[key];
dbmsg('Storage key "%s" in namespace "%s" changed. ' +
'Old value was "%s", new value is "%s".',
key,
namespace,
storageChange.oldValue,
storageChange.newValue);
}
});
}
Explanation / Answer
Debugging by creating execution traces is a much undervalued technique. It is a valuable tool for the programmers tool kit. Like any tool, it has its area of application; at other times a break point debugger is more useful, or generating a stack trace.
Debugging by traces is most use
when no other debugger is available, for various reasons.
when stopping the program would mess with timers.
when debugging a multithreaded program where you need to see actual interleaving of threads.
when a (subtle) bug develops over a long execution sequence.
The difference is that a break point debugger gives you a snapshot of the program state, and the trace shows what happens over time. Bugs that are hard to solve with one tool can be easier to crack with another tool.
The way to build trace tools varies with the target language. What is constant is the featutes to provide. Here is my list of basic features
Able to be turned on and off by setting a flag. The flag value can be hard-coded or set at run-time by debugging code.
For compiled languages the effect of the debugging code should be able to be completely removed. When turned off the debugging code should have no effect on the program. Debugging code can have bugs too, and always gives a performance hit.
Support a way of logging that a function or method has been entered. The log should contain the filename / class name, the method / function name, and each argument value.
Support a way of logging method or function exit, including return value.
Support for logging an arbitrary message.
Support logging to file or console.
More advanced features include
Indenting each log message according to the call depth.
Prefixing each log message with a date and time stamp.
Support multiple log files where N are retained.
Support for a maximum log file size, either by wrapping to the start or switching to a new file.
Support for logging levels, from none to extremely detailed.
Ability to select logging by source file / class name or feature name.
In C or C++, this can all be achieved with conditional compilation of macros in include files. By setting a compiler flag the macros can be completely turned off without touching the source code, an important ability to have. The code can be much more compact in C++ be because we can take advantage of polymorphism.
In Java, we can implement the debug code in a pair of jar files, one with all the debugging methods implemented, and the other with empty methods. This takes advantage of the JVM's optimisation abilities. Of course Log4J should be considered.
In scripting languages you can mix and match the above techniques to achieve the same effect. For html / javascript you can use the script tag to include the required javascript debugging source. If the page is generated from PHP you can conditionally include the debugging code in the page.
The basic features can be built in about 50 lines of code (as I recall, since I'm not as my desk machine), and the advanced features can be another 100 or so lines.
The function trace features described above can be implemented in Javascript (using your dbmsg function) as (not tested):
function dbcall( /* name, args */ ) {
var msg = arguments[0] + '( ';
for (var i = 1; i < arguments.length - 1; i++)
msg += arguments[i] + ', ';
msg += arguments[arguments.length - 1] + ' )';
dbmsg( msg );
}
function dbretn( /* name, value */ ) {
var msg = arguments[0] + ' returns ';
msg += arguments[1];
dbmsg( msg );
}
In a language that supports macros we can set up a macro that calls dbretn that does the return, but in javascript we need to add the call in front of each return as in
...
dbretn( `MyFunction`, value );
return value;
The storage.changed listener you show in the question is a good example of how such debugging facilities should be adapted the each environment.
Related Questions
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.