# Bulletproof JavaScript with Systematic Development

JavaScript affords us so much flexibility that we can sketch up a program in minutes. Sketching is excellent for trying out new concepts or experimenting with new techniques. However, when we are trying to produce scalable and reliable software, sketching often produces unmaintainable spaghetti code. I propose a simple structure called Systematic Development through three stages of Plan, Build, and Test to create reliable software. Systematic Development applies to all programming languages but I will focus on how to use this structure in JavaScript.

I will illustrate the principles by creating a vector calculation library. First through a common sketching approach and then through the Systematic Development approach. The point is not to teach you how to write a vector calculation library so you don’t need to understand all the implementation details. However, I hope that a real-world project will help you understand and apply Systematic Development more easily.

# Sketching Approach

A vector has an x and y component

We can negate a vector

We can subtract vectors

All are good so far. However, now we want to handle not just 2d vectors but also 1d and 3d vectors. We need a total refactoring of all three functions because they assume the vectors are 2d. To detect what dimension the input vector is in, we will create three helper functions — `is1d`, `is2d`, and `is3d`.

Now `addVectors` look like:

Looking a little nastier with all the repeated object literal code.

We need to apply the same nastiness to `negateVector` :

We copy the `addVectors` function and change all `+` to `-` to get `subtractVectors` :

Now, we also want to represent vectors in magnitudes and directions and be able to mix the two forms in calculations. We need to refactor all three functions again to include vector form detection. 😨 We become frustrated and want to abandon the library.

# Systematic Development Approach

Before writing any code, we need to make a plan.

# Plan Stage

## 1. What do we want the program to achieve?

We want a vector library that

• supports 1d, 2d, and 3d vectors
• supports two vector forms — component and angular (magnitude and direction)
• can add, subtract, and negate vectors

Setting the goal of the program is very crucial. For instance, if we decided to support n-dimensional vectors, the program will be very different.

## 2. How can we represent data in the program?

We will use TypeScript to manage data structures so our program is type-checked at compile time.

Now we translate our English description of data (in this case vectors) from question 1 to types in TypeScript. First, a vector can either be a component or an angular vector. So we create a new custom type called `Vector` that has two variants — `VectorCom` for component vectors and `VectorAng` for angular vectors.

## How can we represent `VectorCom`?

Let’s look at 1d, 2d, and 3d vectors case by case:

## 1d vector in components

• x: a real number

## 2d vector in components

• x: a real number
• y: a real number

## 3d vector in components

• x: a real number
• y: a real number
• z: a real number

Because the number of components varies based on the dimension of the vector, it’s hard to represent `VectorCom` using objects. Instead, we will use a simple number array to account for the varying length.

## How can we represent `VectorAng`?

Let’s look at 1d, 2d, and 3d vectors case by case:

## 1d vector in direction and magnitude

• magnitude: a non-negative real number (magnitude ≥ 0)
• direction: left or right

## 2d vector in direction and magnitude

• magnitude: a non-negative real number (magnitude ≥ 0)
• direction: an angle between 0 and 360

## 3d vector in direction and magnitude

• magnitude: a non-negative real number (magnitude ≥ 0)
• direction: two angles (α and β) between 0 and 360

Because angular vectors in different dimensions share the same properties (magnitude and direction), we naturally represent angular vectors in objects which are key-value pairs. However, since the direction property is different between dimensions, we would need three separate object types for each dimension.

## 1d angular vector

We use `-1` and `1` to represent the left and right direction respectively. You can also use a boolean but `-1` and `1` makes more sense to me as they capture the negative (left) and positive (right) directions commonly used in Physics. In addition to `mag` (magnitude) and `dir` (direction), we also add a `dim` (dimension) property so we can handle each dimension differently in the calculation.

## 2d angular vector

Because TypeScript cannot restrict the range of the angle at compile-time, we can only specify `dir` as a `number`.

## 3d angular vector

Add one more angle to represent how high/low the vector is at the vertical z-axis.

## 3. How can we define the functionalities of the program?

We need three functionalities (in our case vector operations): add, negate, and subtract vectors.

Let’s look at each functionality in more detail and think about:

• the input of the functionality
• the output of the functionality

• input

Since adding a series of vectors tip to tail is relatively common, we will accept a list of vectors as input.

• output

We will return a resultant vector as output. Because a vector can either be in a component or angular form, we will add an input parameter called `form` for the user to specify what form they want. We will define a `VectorForm` type that can either be `"ang"` (angular) or `"com"` (component).

Let’s write the function signature for add:

## Negate

• input

We will accept one vector `v` as input.

• output

We will return a negated vector as output in `form` specified by the user.

Let’s write the function signature for negate:

## Subtract

• input

We will accept two vectors `v1` and `v2` as input.

• output

We will the result of `v1 — v2` as output in `form` specified by the user.

Let’s write the function signature for subtract:

🎉🎉🎉 We have successfully created a robust outline for our vector library! Let’s move on to the build stage!

# Build Stage

We will build the three functionalities in sequence.

Following the outline we created in the Plan Stage, we will put implementation details inside the `addVectors` function.

First, we will create a helper function called `addVectorComs` for adding component vectors.

STOP and TEST!

Even though the Test Stage is after the Build State, we should never put testing off until all functionalities are implemented. Instead, we will always do unit testing after implementing every single function. The reason is unit testing makes tracing, identifying, and fixing bugs much easier because you have much less code that can potentially contain bugs.

The above is just a demo test for `addVectorComs` using Jest. You should add more tests and especially more edge cases like zero vector.

In the `addVectors` function, we will convert all input vectors to component vectors and call the `addVectorComs` to produce a result. So we create another helper function to decompose a vector into its components.

STOP and TEST!

Now that we can add vectors, we need a way to convert the final component vector result to the `form` user wants. We will add a `formVector` helper function for that purpose.

Before defining the `formVector` function, we will specify the default `form` used by all functionalities (add, negate, and subtract) to be `"com"`(component) because all our vector operations produce a component vector by default.

We also need a way to convert component vectors to angular vectors. Let’s define a helper function called `angularizeVector` to do that.

STOP and TEST!

Let’s define `formVector` using `decomposeVector` and `angularizeVector `helper functions:

STOP and TEST!

No matter how simple the functions seem to be, write some tests to make sure they work as we intended.

Finally, let’s put all the helper functions together to create the `addVectors` function:

As we can see, the add function is quite simple and is built on top of modular helper functions. Because we did unit testing on all the helper functions, we are fairly confident that the `addVector` function will work as intended. but…

STOP and TEST!

Is this test complete? What’s missing?

Pause and look through the tests carefully and compare them to the actual inputs and outputs of `addVectors`.

• input

`addVectors` accept a list of vectors as input but we only tested lists containing 2 vectors. We should add empty lists and lists containing more than 2 vectors as input.

• output

`addVectors` can return both component and angular vectors as output but we only tested component vectors as output. We should add angular vector outputs by specifying the`form` parameter to be `"ang"`.

🎉🎉🎉 We have successfully built our first functionality — add!

We can follow a similar process of Build and Test for the other two functionalities. Due to the length of the article, I will not walk through them step by step. You can check out my GitHub repo for all the code.

Hopefully, you understand the three stages of Systematic Development — Plan, Build, and Test — and are able to apply them to create your own robust projects. 🚀

❤️ Open Source, Web Dev, programming languages, and Hanzi 漢字

## More from Kevin Li

❤️ Open Source, Web Dev, programming languages, and Hanzi 漢字