Incremental Development and Debuggin
Incremental Development and Debugging
Incremental Development
Incremental Development refers to the procedure of writing only enough code to be tested, testing that code and only proceeding when the code is working correctly. This means that you limit the amount of new code to a program, to the smallest amount that can be tested, then test before adding any more code. As you gain experience, you can probably add a greater amount of lines of code before testing. However, thinking that you can speed development by testing your code less often and writing bigger chunks of code at a time is incorrect thinking.
Incremental development works for every programming language and is probably the most important tool a programmer can employ in developing code. It takes some discipline to follow this procedure, but failing to use incremental development is one of the main reasons why hard to fix bugs show up in the code.
A simple example of using incremental development
Suppose that you are building a simple web page that makes use of some JavaScript. Let’s assume that you want to keep the HTML markup separate from the JavaScript code. So, you create the following index.html file that contains the starting HTML markup:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="module" src="./index.mjs"></script>
</head>
<body>
<h1>A simple webpage</h1>
</body>
</html>
I served out this page using the Node.js package, http-server, using 'localhost' and port 8080. You can see the <h1> element on the page.
This looks okay, but remember we are going to include some JavaScript on line 5 of this file. If you are doing JavaScript programming, you need to get used to looking in the DevTools console. This is the area where errors are shown, and also where you can print messages that help you to debug your JavaScript code. A simple way to bring up the DevTools console in Chrome or Chromium is to hit the F12 key. In the next screen shot, you can see that I have done this and also moved the DevTools down to the bottom of the window:
The messages are saying that the files index.mjs and favicon.ico cannot be found. This is because those files have not yet been created. Actually, you can ignore warnings about favicon.ico not being found, as that is not a required file. But, we should create the index.mjs file. That is where we will place all our JavaScript. If you look back at the code for index.html on line 5, you will see the following:
<script type="module" src="./index.mjs"></script>
The src attribute is a path to the file containing the JavaScript code. The dot (.) at the beginning of ./index.mjs, stands for the current directory. This means that index.mjs should be saved in the same folder (directory) as index.html.
In terms of incremental development, we only have a few lines for index.html. By serving out this page using http-server, we verified that the <h1> element is being displayed correctly. But, we also found out that we are missing the file index.mjs. So, let’s create index.mjs in the same directory as index.html
console.log('index.mjs is running');
After saving this file, you can reload the browser and you will see the following:
Now, the console shows the message 'index.mjs is running'. That is what console.log() does. It sends the argument (the contents in the parentheses) to the console. In JavaScript programming, the console.log() function is very important as it allows you to use incremental development more easily and it is essential for debugging JavaScript code.
In terms of incremental development, we now know that index.mjs is in the correct location and that it runs without any errors so far.
Let’s add to index.mjs. The lines we add will be lines that we will start on main JavaScript file with for all our lessons.
if (document.readyState === "loading") {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
function init() {
console.log('init called');
}
Lines 1-5 define an if statement that handles two cases. Lines 1-3 handle the first case which is executed if the HTML markup is still being rendered. If the HTML markup is still being rendered, line 2 will be executed. This will make the program wait for the event 'DOMContentLoaded' to occur. If that event occurs, the init() function will be called. Lines 3-5 handle the second case for the if statement. This will be executed if document.readyState is not "loading". This means that the HTML markup is already finished rendering. So, for this second case, what happens is that line 4 causes the init() function to be called. If you think this through, lines 1-5 are making it so that the init() function is called when the HTML markup has finished being rendered. This is very important, as otherwise the JavaScript code could be referring to an HTML element that has not yet been created.
Lines 7-9 define the init() function. This is the function where initialization for the application takes place. Think of this as the starting point for the JavaScript exection for the application. For this simple case, all that is done is that we use console.log() to print 'init called' to the console.
This shows a good start to making a simple web application that you can think of as kind of a template that would work for the lessons we will be covering.
The next increment
Let’s go a little further for our web application. Let’s just add a <button> element to index.html. This <button> element needs to have a unique id so that we can easily reference it from the JavaScript code. So, let’s start with modifying index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="module" src="./index.mjs"></script>
</head>
<body>
<h1>A simple webpage</h1>
<button id="mybutton">Click me</button>
</body>
</html>
The new line is 9. This adds a <button> element with an id="mybutton" attribute. The button will be labeled with "Click me". Here is what this looks like in the browser:
So, we can see that the button has been added. Now, let’s modify index.mjs so that clicking on that button will cause a message to be displayed in the console.
if (document.readyState === "loading") {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
function handleMyButton() {
console.log('You clicked on mybutton');
}
function init() {
console.log('init called');
const mybutton = document.getElementById("mybutton");
mybutton.addEventListener('click', handleMyButton);
}
The new lines are 7-9,13-14. Line 13 gets a reference to the button we just added. That reference is needed so we can do something with that button. Line 14 adds an EventListener. In this case, the event that will be listened for is the click event. That is the event that is generated when a user clicks on that button. Line 14 specifies that the handler (function) for this 'click' event will be the handleMyButton() function.
Lines 7-9 define the handleMyButton() function. All this function does is write 'You clicked on mybutton' to the console.
The screen shot shown next, shows the web page with the DevTools console open.
you can see that the first thing shown is 'init called', as the init() function starts the execution. Then, the console shows 'You clicked on mybutton', in response to the user clicking on that button.
Looking at our latest versions of index.html and index.mjs, you can see that only a few new lines were added. But, the effect of those lines can be tested, so that is exactly what we do. This is incremental development. If our tests don’t show what we expect, there are only a few new lines to look at. So, we know where the errors must be. That is the strength behind the incremental development procedure. You can easily find errors because there are a limited number of lines where the errors could occur. And, you won’t have hard to track errors creeping into the code, because you catch the errors as soon as you add the few new lines.
Testing edge cases, runtime errors
For some programs, you will be performing some kind of calculation or manipulating data in a way that can generate runtime errors. The characteristic of runtime errors, is that they only show up when the program runs. To help avoid runtime errors, you should examine any possible edge cases. These cases are where values used in the calculation or data that is manipulated can cause invalid results. If this were a math calculation, this could be where you are dividing by zero, or trying to take the square-root of a negative number. If you were manipulating some strings by combining them, this could be when one or more of the strings is a blank string.
A good thing to think about is if your program uses input values, what would happen if the input values are not what you expect them to be. If you expect a number, but instead get a string, what happens? Asking yourself this type of question when your first add code that uses those input values, is a good way to avoid problems down the line. Unlike syntax errors that will prevent the JavaScript code from running (so you know there is an error), runtime errors only show up when you run the program. So, they take a different kind of process to prevent. But, by employing incremental development and thinking about possible invalid input, you will go a long ways towards avoiding runtime errors.
Debugging
Debugging code is greatly simplified by practicing incremental development. If you only have a small number of errors and a limited number of lines where the error is coming from, you can fix the code relatively easily. When I taught Java programming classes, it was common to see some beginner’s program with 30 or more errors. I could help the student to fix their code, but the student likely would not have learned anything. So, they are likely to wind up in the same situation the next time, unless they start practicing incremental development.
So, limiting the number of errors is a kind of debugging before the fact. And, that is what you should strive to do. However, what if you have to try to fix code that has lots of errors or has so many lines that you don’t know where to look to find the error(s). This is where you try to do something like incremental development after the fact. The way to do this is to make use of block or multiline comments. These are comments that let you block out sections of the code. If you can figure out where reasonable break points are, you can comment out everything after a break point. If the error still persists, then you know that it is in the code that is not commented out. If the error disappears, then the error is in the lines of code that you commented out. Hopefully, by practicing incremental development you don’t have to resort to this, but if you are dealing with someone else’s code this can show up. So, keep this in mind, as it can be a valuable tool to have in your bag.