Bugs and Errors
Lecturer: Иo1lz
JavaScript’s looseness is a hindrance.
Its concept of bindings and properties is vague enough that it will rarely catch typos before actually running the program.
true * "monkey";
NaN
JavaScript can be made a little stricter by enabling strict mode
function canYouSpotTheProblem(){
"use strict";
for (counter = 0; counter < 10; counter++){
console.log("Happy happy");
}
}
canYouSpotTheProblem();
putting the string "use strict"
function Person(name){ this.name = name; }
let ferdinand = Person("Ferdinand");
console.log(name);
// -> Ferdinand
"use strict"
function Person(name){ this.name = name; }
let ferdinand = Person("Ferdinand"); // forget new
// -> TypeError: Cannot set property 'name' of undefined
There some ways to find the programming mistake:
Automated test
Add a comment before the function to describe its type
// (VillageState, Array) -> {direction: string, memory: Array}
function goalOrientedRobot(state, memory){
// ...
}
There are several JavaScript dialects that add types to the language and check them. The most popular one is called TypeScript.
the process of writing a program that tests another program.
function test(label, body){
if (!body()) console.log(`Failed: ${label}`);
}
test("convert Latin text to uppercase", () => {
return "hello".toUpperCase() == "HELLO";
});
test("convert Greek text to uppercase", () => {
return "χαιρετε".toUpperCase() == "ΧΑΙΡΕΤΕ";
});
test("don't convert case-less characters", () => {
return "مرحبا".toUpperCase() == "مرحبا";
});
test runners
There exist pieces of software that help you build and run collections of tests by providing a language suited to expressing tests and by outputting informative information when a test fails.
the more external objects that the code interacts with, the harder it is to set up the context in which to test it
function numberToString(n, base = 10){
let result = "", sign = "";
if(n < 0){
sign = "-";
n = -n;
}
do{
result = String(n % base) + result;
n /= base;
}while(n > 0);
return sign + result;
}
console.log(numberToString(13, 10));
13
1.3
0.13
0.013
…
1.5e-323
13
1
0
Instead of n /= base, what we actually want is n = Math.floor(n / base) so that the number is properly “shifted” to the right.
function numberToString(n, base = 10){
let result = "", sign = "";
if(n < 0){
sign = "-";
n = -n;
}
do{
result = String(n % base) + result;
n = Math.floor(n / base);
}while(n > 0);
return sign + result;
}
console.log(numberToString(13, 10));
Not all problems can be prevented by the programmer
// Run on the browser
function promptNumber(question){
let result = Number(prompt(question));
return result;
}
console.log(promptNumber("How many trees do you see?"));
// Run on the browser
function promptNumber(question){
let result = Number(prompt(question));
if(Number.isNaN(result)) return null;
else return result;
}
console.log(promptNumber("How many trees do you see?"));
In many situations, Returning a special value is a good way to indicate an error.
function lastElement(array){
if(array.length == 0){
return {failed: true};
}else{
return {element: array[array.length - 1]};
}
}
what if the function can already return every possible kind of value?
When a function cannot proceed normally, what we would like to do is just stop what we are doing and immediately jump to a place that knows how to handle the problem.
This is what exception handling does.
// Run on the browser
function promptDirection(question){
let result = prompt(question);
if(result.toLowerCase() == "left") return "L";
if(result.toLowerCase() == "right") return "R";
throw new Error("Invalid direction: " + result);
}
function look(){
if(promptDirection("Which way? ") == "L"){
return "a house";
}else{
return "two angry bears";
}
}
try{
console.log("You see", look());
}catch(error){
console.log("Something went wrong: " + error);
}
The effect of an exception is another kind of control flow.
Every action that might cause an exception, which is pretty much every function call and property access, might cause control to suddenly leave your code.
// Run on the browser
const accounts = {
a: 100,
b: 0,
c: 20
};
function getAccount(){
let accountName = prompt("Enter an account name: ");
if(!accounts.hasOwnProperty(accountName)){
throw new Error(`No such account: {$accountName}`);
}
return accountName;
}
function transfer(from, amount){
// Transfers a sum of money from a given account to another
if(accounts[from] < amount) return;
accounts[from] -= account;
accounts[getAccount()] += amount;
}
function transfer(from, amount){
if(accounts[from]] < amount) return;
let progress = 0;
try{
accounts[from] -= amount;
progress = 1;
accounts[getAccount()] += amount;
progress = 2;
}finally{
if(progress == 1){
accounts[from] += amount;
}
}
}
When an exception makes it all the way to the bottom of the stack without being caught:
for(;;){
try{
let dir = promtDirection("Where?");
console.log("You chose ", dir);
break;
}catch(e){
console.log("Not a valid direction. Try again.");
}
}
class InputError extends Error {}
function promptDirection(question){
let result = prompt(question);
if(result.toLowerCase() == "left") return "L";
if(result.toLowerCase() == "right") return "R";
throw new InputError("Invalid direction: " + result);
}
for(;;){
try{
let dir = promptDirection("Where? ");
console.log("You chose ", dir);
break;
}catch(e){
if(e instanceof InputError){
console.log("Not a valid direction. Try again.");
}else{
throw e;
}
}
}