Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time

Writing Robust Code

By now you have learned the core features of JavaScript as used by NodeJS and, in this chapter you will learn about some important features of NodeJS and some useful tools to make you a more productive programmer.

There is a slide deck https://goo.gl/XHBbMx to accompay this worksheet. Make sure you are familiar with its contents before proceeding.

In this chapter you will be covering:

  1. Linters
  2. Debugging
  3. Documentation

Any section above marked with an asterix * should be considered essential knowledge.

1 Using a Linter

JavaScript is a powerful language and includes a number of powerful features but it also includes features that, whilst they may produce working code, will make your code difficult to read and debug! A linter is a program that analyses your scripts for programmatic and stylistic errors and will flag up anything that may cause problems. They define a professional subset of the language and reject any code that doesn't meet that standard.

There are a number of linters available for the JavaScript language. The original one was written by Douglas Crockford (the author of JavaScript: The Good Parts). This enforced his idea of good JavaScript and was notorously not configurable! There are now a number of popular linters available, your choice should be based on what will run in your IDE as well as your personal preferences. The most popular linters are:

  1. JSLint, the original, by Douglas Crockford
  2. JSHint
  3. ESLint which is supported by many IDEs and code editors

EsLint is highly configurable through a hidden config file that needs to be added to the project root directory. When you submit your coursework you must demonstrate that it contains no errors or warnings when run using an identical configuration file.

1.1 The Linter Configuration

ESLint is completely configurable through a configuration file .eslintrc which should be located in the project's root directory. By default any file or directory starting with the period (.) character is hidden although most editors will show them by default. A sample configuration file has been provided in the sample code repository. Open it and take a look at the structure.

The .eslintrc file uses a JSON string to organise its settings which are in three sections.

{
	"env": {
		"es6": true,
		"jasmine": true,
		"node": true
	},
	"parserOptions": {
		"ecmaVersion": 8
	},
	"rules": {
		"no-var": 2,
    "semi": [1, "never"],
		"arrow-spacing": [1, { "before": true, "after": true }]
	}
}

Near the bottom of the file list you should see a file called .eslintrc, the initial dot (.) in the filename caused it to be hidden by default. Open the file now.

  1. Notice that the file contents are using the JSON format which should be familiar to you.
  2. There are three JSON objects, env, parseOptions and rules.
  3. The env object describes the environment you are using. In our example we indicate that we will be using the ECMA6 syntax, are writing NodeJS code, and will be using Jasmine tests.
  4. the parserOptions object defines the version of JavaScript it should use. Because we will be using the cutting edge language constructs we choose version 8.
  5. The rules object defines the rules we want to apply. As you can see we are requiring tab indentation, single quotes and avoiding the use of semicolons on each line. The full list of rules can be found in the ESLint Documentation.
  6. Each rule has a reporting level where 0 means disabled, 1 means warning and 2 means error. This affects how the rule violations are reported.
  7. Some rules allow for additional options. If these are specified, both the reporting level and options need to be in an array.

Whilst you need to use the supplied configuration file in your assignment you should take time to understand the range of rules available and adding them to your .eslintrc configuration file. To make this process easier you can use this handy ESLint Rule Generator.

1.2 Running ESLint

Although there are plugins for many popular IDEs you should get used to running the linter from the terminal. ESLint is available as a NodeJS package which allows you to run it from the terminal. Since you may find yourself using an editor with support baked in, why would you want to do this?

  1. Some editors don't have ESLint support.
  2. Running ESLint in the Terminal gives a summary of the linting errors.to check if all the scripts are fixed.
  3. It can be configured to fix many of the most common error such as whitespace and semicolon usage.
  4. The linting can be run as part of the Continous Integration and Continous Deployment process (more on this in a later worksheet)
  5. Finally, it will be used during the marking of your assignment to make sure your code is formatted correctly!

1.2.1 Test Your Knowledge

lets install, configure and run the console-based linter.

  1. Start by opening a terminal window and navigating to the shopping/ directory.
  2. Install the NodeJS ESLint package npm install eslint --save-dev. This installs it and adds it to your package.json file in the devDependencies section.
  3. Run the executable node_modules/.bin/eslint index.js. This runs the eslint executable located in the hidden .bin/ directory in the node_modules/ directory.
  4. You will see a list of all the errors and warnings found together with a summary with the total number of errors and the total number of warnings.
  5. Open the index.js file and, using the linter report, fix the issues. You should re-run the linter regularly to check your progress.
  6. Run the linter on the module using node_modules/.bin/eslint modules/shopping.js and note the errors it finds.
  7. Now run the same command but this time pass the fix flag, node_modules/.bin/eslint --fix modules/shopping.js
  8. Open the shopping.js file and notice that most of the issues have gone. The fix tool is not perfect, it may have introduced new errors so use it with caution!
  9. Manually correct any errors until the linter reports 0 errors and warnings.
  10. Finally run the linter against all the files in the project using node_modules/.bin/eslint . to ensure there are absolutely no linting errors in your code. Well done!

2 Debugging

As our script grow larger, so the odds of introducing a bug. So how can we locate and fix them?

One option would be to insert lots of console.log() statements into our code. This would help us to:

  1. Track the program flow.
  2. Track the contents of variables and objects.

A much better solution is to use a debugger. A debugger allows you to:

  1. Pause execution of the code at any point.
  2. Step through the code one line at a time.
  3. Monitor the values of variables and objects at any point.

Debuggers offer a number of features. Breakpoints allow the code to be paused on specific lines of code. Code execution can be run a line at a time or resumed. Functions can be stepped in and out. Watchers can be used to monitor variables and objects.

There is a powerful debugger built-in to NodeJS and two alternative ways to work with it.

  1. A terminal debugger controlled through the terminal window.
  2. A visual debugger that allows you to interact using the mouse. There are two main choices.
    • A TCP-based debugger that uses the Chrome browser.
    • Using the integrated debugger built into modern code editors.

It is important to learn both these.

2.1 Terminal Debugger

NodeJS offers a terminal debugger which allows you to debug code directly in the terminal window. Whilst it is not the most intuitive of tools it does offer an impressive range of features. lets take a look at these in action. Start by opening the simpleDebug.js file in the shopping directory. Notice the debugger statement on line 10, this is now we insert a breakpoint. We can run the script in debug mode using.

node debug simpleDebug.js
< Debugger listening on 127.0.0.1:5858
connecting to 127.0.0.1:5858 ... ok
break in simpleDebug.js:2
  1 
> 2 'use strict'
  3 
  4 const data = new Map()
debug> 

Notice that the debugger has paused at the first line of code but has not executed it yet. You are now presented with a debug> prompt where you can enter a debugger command. These are:

Command Abbr Meaning
cont c continue execution
next n step next
step s step in
out o step out
quit exit the debugger
restart restart the script

It is also possible to attach and remove a watcher to or from a variable or object and list all watched variables.

Command Meaning
watch('expr') add expr to watch list
unwatch('expr') remove expression from watch list
watchers list all watchers and values

Breakpoints are added directly to the code

Script is run in debug mode

Can add watchers to monitor variables

Commands for stepping through code.

Let's try out some of the debugger features.

  1. At the prompt lets add some variables to the watch list.
    • It would be good to see the content of the item variable which should hold the name of the item we are adding. type debug> watch('item') to add this variable to the watch list.
    • The items should get added to the Map() object. Its would be helpful to see what it contains by typing debug> watch('[...data]'), note the use of the spread operator.
  2. Now we can run the code. The first breakpoint is just after we have asked for the command so we can continue running the code from where it is currently (the first line), it will stop at the breakpoint. type debug> c, we will see the command prompt and can enter the first item (lets add bread).

Notice that the two objects in the watch list are listed followed by the current line of code plus a few lines before and after.

Watchers:
  0: item = "<error>"
  1: [...data] = []

  8 do {
  9 	input = String(readline.question('enter command: ')).trim()
>10 	debugger
 11 	if (input.indexOf('add ') === 0) {
 12 		const space = input.indexOf(' ')

At the moment the code is paused on the breakpoint. We can now single-step through the code by entering debug> n. You should repeat the command, stepping over each line of code until there is a value assigned to the item variable.

We are about to execute the code that handles the item quantity so it would be a good idea to add the qty variable to the watch list, debug> watch('qty'). Continue single stepping through the code until the new item has been added to the item map. Notice that as soon as the if statement has exited, both the item and qty variables are flagged as <error>, this is because they were defined inside the first if statement and are now out of scope.

2.1.1 Test Your Knowledge

  1. If you run the script outside the debugger you will notice that there are some logical errors. Use the debugger to trace the flow of the code and the values of the variables and objects to help you locate the error and fix it.
  2. The script should ignore the case of the item you are entering, so Bread, bread and BREAD should all be treated as the same.
    • Use the debugger to see what currently happens when you add these three variations.
    • The script should store a single product Bread and a quantity of 3 units.
    • Use the debugger to help you implement this new feature.
  3. Finally you should be able to remove a product using the command remove bread. This should remove the item from the list. Use the debugger to help you implement this feature.

2.2 Visual Debugger

Whilst the terminal debugger offers the key features it is not simple to use. A visual debugger is far more intuitive.

Many modern editors include built-in debuggers and it is worth learning how these work but even it your editor does not have this facility, NodeJS provides an out of process TCP-based debugger that connects to the Chrome web browser. We will explore this. Start by opening the shopping/ directory. The main script is index.js. Open this, notice it imports a module called shopping.js which is located in the modules/ directory.

To start the visual debugger you need to navigate to the shopping/ directory and run the main index.js script but pass two flags.

node --inspect --debug-brk index.js
  1. The --inspect flag starts the debugger.
  2. The --debug-brk flag pauses the debugger on the first line of the script.

Then open about:inspect in the Chrome browser. You should see some options appear in the browser window. Click on the Open dedicated DevTools for Node link and this will open a new window containing the visual debugger. Let's take a look at the key features and how these relate to the terminal debugger we have already used.

  1. The screen is split into 3 panes. We don't need the left one so this can be closed using the button to the left of the simpleDebug.js tab.

    • The middle pane contains the code we are debugging. Rather than adding debugging commands into the source code we can click in the left gutter (where the line numbers are) to add blue breakpoint markers. Add one to first if statement and another to the second one.
  2. The right-hand pane contains a number of collapsable sections. In this exercise the only one you need to keep open is the Scope pane. This lists the variables and objects and is grouped into different scopes.

    • The Local scope section contains the variables and object within the current script.
    • The Global scope is not needed.
    • If the script is in a block you will see another section which lists the locally scoped variables.

At the top of this right-hand pane are the script execution buttons, these replicate the debugger commands we used in the previous exercise.

Start by clicking on the Resume script execution button which will run the script up to the next breakpoint. The debugger is now greyed out because it is waiting for input in the terminal. Switch to the terminal and type add bread, this will return control to the debugger.

Now try the next button, Step over next function call. This will step through your code one line at a time. Repeat this until you get to the List.add(item) line.

Now it's time to use the next button, Step into next function call. This will load the shopping.js file and place the program execution on the first line of the add function. If we used the Step over button we would have run this function and returned control to the next line in the index.js script.

Use the Step out of current function button. This returns control to the controlling script back in index.js.

Now use the buttons to loop back to the List.add(item) line but this time use the Step over next function call button. Notice that this time control moves directly to the next line without going into the function definition.

One really useful feature is that the browser-based debugger will automatically reconnect if the dvript is restarted. Press ctrl+c to quit the script then restart with node --inspect --debug-brk index.js and see how the debugger reconnects.

3 Documentation

Whenever we write a program it is important that it is fully documented. A simple solution is to add comments to the code which can then be read by anyone who opens the script. A better solution would be to write up detailed human readable documentation.

A documentation generator is a tool that takes structured comments added to source files and turns them into properly-formatted documentation. We will be using the jsdoc tool which is based on the JavaDoc tool used by Java programmers. JSDoc is a markup language used to annotate JavaScript source code files. Using comments containing JSDoc, you can add documentation describing the application programming interface of your code and turn these into a documentation website.

In this exercise you will learn how to use JSDoc to create detailed professional documentation for your code.

3.1 Adding JSDoc Comments

So what needs to be documented?

  1. all function signatures (names, purpose, parameter, return types)
  2. any exceptions that may be thrown.
  3. an explanation of the purpose of any obscure lines of code

Let's look at a simple example, taken from modules/shopping.js

/**
 * Returns details for the named item.
 * @param   {string} item - The item name to retrieve.
 * @returns {string} The name of the item
 * @throws  {InvalidItem} item not in list.
 */
exports.getItem = item => {
	if (data.get(item) === undefined) {
    // error thrown in the item is not in the Map()
		throw new Error('item not in list')
	}
	return data.get(item)
}

Notice that the JSDoc comments start with /** and end with */. Each line starts with an asterix * and the first line describes the purpose of the function. All parameters are then listed together with their preferred data type and this is followed by the return value and its expected type. Finally any errors/exceptions thrown by the function are shown.

You should also add standard JavaScript comments to explain the code itself. These begin with //. Even though these won't be extracted into the documentation they are important when trying to understand the code logic.

3.2 Generating the Documentation

JSDoc is available as a NodeJS plugin. It has already been added as a Dev Dependency in the package.json file and so it should already be installed. Lets use it to build the documentation for our modules.

$ node_modules/.bin/jsdoc -d docs/jsdoc/ modules/

This will create a docs/jsdoc/ directory containing the complete documentation as a website.

.
├── fonts
├── index.html  < this is the documentation home page...
├── module-shopping.html
├── scripts
├── shopping.js.html
└── styles

Right-click on the index.html file and choose preview. This will open the home page. The module list can be found down the right-hand side of the screen. Locate the shopping link and click to see the full documentation.

  1. Notice that each variable and method definition has been lifted from the source code.
  2. Notice that each method has a description, this was added to the source code file using the special JSDoc markers /** */.
  3. The getItem() method documentation includes a lot more detail, this is defined in block tags:
  • a list of the parameters and their types
  • an explanation of the return value and type
  • a list of any exceptions that could be thrown

There are a lot more features that can be added to this, you should take time to read the full documentation to find out what block tags can be used.

3.3 Test Your Knowledge

  1. Your challenge is to complete the documentation for the shopping.js module. You should use the getItem() documentation as a guide.
    • Make sure you add the JSDoc documentation block and also add comments to explain the code.
  2. To check your results, run the documentation tool and reload the documentation web page.
  3. Carefully read through the generated documentation to ensure it is both complete and makes sense.