Skip to content
Permalink
69955ddca5
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
294 lines (214 sloc) 19.3 KB
# 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:
3. Linters
4. Debugging
5. 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](http://www.jslint.com/help.html), the original, by Douglas Crockford
2. [JSHint](http://jshint.com/docs/)
3. [ESLint](http://eslint.org) 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.
```json
{
"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`.
1. 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.
2. 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.
2. 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](http://eslint.org/docs/rules/).
3. 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.
4. 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](http://eslint.org/docs/rules/) available and adding them to your `.eslintrc` configuration file. To make this process easier you can use this handy [ESLint Rule Generator](http://rapilabs.github.io/eslintrc-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](https://nodejs.org/api/debugger.html) 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]( http://usejsdoc.org) 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`
```javascript
/**
* 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.
```shell
$ 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](http://usejsdoc.org) 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.