I'm Jen and I’m in my mid-30’s, making a career change into tech. I'm on Manchester Codes’ Software Engineering FastTrack course - a part-time, 24-week bootcamp based in Manchester, UK. It's week 8, and this week we've been working on creating a virtual pet, using JavaScript.
A quick recap on where we're up to with bootcamp
Here's what we've covered in the last few weeks:
- Setting up our dev environment (installing VS Code, Node.js and npm, installing Git, setting up GitHub)
- How to use the command line
- Using Git & GitHub
- HTML & CSS
- The DOM
- JavaScript basics
- Test-driven development (TDD)
I've written a few blog posts documenting my bootcamp experience so far in more detail - you can check them out here.
Creating a virtual pet
First, let me set your expectations...we did not create a fully-formed, all-singing-all-dancing 1990s Tamagotchi (I know, I'm disappointed too - I will still have to make a trip to my parents' attic after lockdown to see if mine is still up there).
We did, however, write a JavaScript programme that creates a virtual pet. You can give it a name, it gets older, it gets hungry and it needs feeding and walking. And, it also dies. Sorry about that. It can even adopt a child.
Learning objectives
This project covered a lot of learning objectives:
- Creating a simple Node Package Manager (NPM) project
- Using Git and GitHub to track progress
- Installing and using Jest testing framework
- Creating a clear README document
- Testing using Jest and TDD principles
- Using the Node REPL for feature testing
- Debugging using a stack trace
- Using
module.exports
andrequire()
- Creating objects using constructor functions
- Understanding the concept of prototypes in JavaScript
- Using JavaScript classes
- Using getter methods
- Using guard clauses
- Throwing exceptions/errors
Setting up the project
❯ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
Using the command line, we created a new git repository. Then we initialised an NPM project. This was a new one on me. To do this, you change directory into your newly created virtual-pet
directory and run the npm init
command. It then walks you through creating a package.json
file. You either type in the relevant info when prompted or press enter and it will enter a 'sensible default', as it says above. This newly-created package.json
file sits in your directory/repository and contains high-level info about your project, and some important things that the project will need to run properly (more on this shortly).
So we've created a new git repository on our local machine and initialised an NPM project, which has created a package.json
file. We'll be writing and running unit tests for our project (see my previous blog post on testing) so we need to install Jest (a testing framework for JavaScript projects). To do this, we run npm install -D jest
which installs Jest as 'development dependency' - dependencies are other pieces of software that our project needs to run. Once it has finished installing, we can see that it has updated our package.json
file to show Jest as a dependency. We then manually changed the test value in the file to say "jest"
so that it knows what to do when we run our npm test
command later.
After that we created a new repo on GitHub and linked it to our local repository. One more step before moving on: installing Jest has put a whole bunch of node_modules
in our repository. These are critical for our project but we don't want all these things filling up our GitHub repo. To ensure that they just remain on our local machine and don't get pushed to Github, we created a .gitignore
file and added the line node_modules/
to it. So now when we push to GitHub, all those modules will be automatically ignored. Clever Stuff.
Using the Node REPL
What on earth is a REPL? No, I didn't know either. You know that bit in Chrome Dev Tools, the console, where you can type JavaScript? That's a REPL too. It stands for Read, Eval, Print Loop:
Read - the program sits there, waiting for you to type something in. When you type and press enter, it reads what you've just written
Eval - then it evaluates what you've written and does whatever you asked it to do
Print - it prints the result of that evaluation
Loop - it goes back and waits for you to type something else in
(Thank you Manchester Codes for the explanation of a REPL, which I've paraphrased above. I read lots of definitions and theirs was by far the clearest.)
Think of it as a playground or 'sandbox' in your terminal where you can type in code and play around with it. When you first install Node.js on your machine, which we did at the start of bootcamp, it installs the Node REPL too. To access it, you just type node
in your terminal and it will open a new session. To quit once you're done type .exit
or ctrl + C
twice.
This project introduced me to 'feature testing' using the Node REPL. In addition to writing unit tests, we were going to manually test to see whether our code was working in the REPL.
Following the instructions we were given, we typed const pet = new Pet()
in the REPL and hit enter. What happened? Uncaught ReferenceError: Pet is not defined
is what happened! This was, of course, to be expected because we've not written a line of code yet to create our virtual pet, but it acted like a failing test in the red-green-refactor loop (see previous blog post on TDD). Once we'd written the virtual pet code, we could try that feature test again to check it works as expected.
Creating the virtual pet
Are you still with me? Fantastic! So we've set up our project, done a quick (failing) test in the Node REPL, and now we're ready to start writing some actual code. Almost. First, we need to write our first unit test (because TDD).
In our virtual-pet directory, we created two new folders - a src
directory and a _tests_
directory. In the _tests_
directory we set up a pet.test.js
file and wrote our first (failing) unit test, to test that the constructor function we were about to write returns an object:
describe('constructor', () => {
it('returns an object', () => {
expect(new Pet('Fido')).toBeInstanceOf(Object);
});
});
We ran the test and it failed (as expected).
Then we created a pet.js
file in our src
directory and wrote the constructor function. Using module.exports
and require
(see below) we made the code in the pet.js
file available to the pet.test.js
file.
We ran the test and it passed. Hurrah.
So now we had established our working pattern - feature test in the REPL, write a failing unit test, write the code, run the tests again, refactor - we continued working away until all the code had been written to allow our pets to get hungry, become unfit, get fed, go for a walk etc etc. Here's a snippet of that completed code:
class Pet {
constructor(name) {
this.name = name;
this.age = AGE_AT_CREATION;
this.hunger = HUNGER_AT_CREATION;
this.fitness = FITNESS_AT_CREATION;
this.children = [];
}
get isAlive() {
return this.fitness > 0 && this.hunger < 10 && this.age < 30;
}
growUp() {
if (!this.isAlive) {
throw new Error('Your pet is no longer alive :(');
}
this.age++;
this.hunger += HUNGER_INCREMENT;
this.fitness -= FITNESS_DECREMENT;
}
walk() {
if (!this.isAlive) {
throw new Error('Your pet is no longer alive :(');
}
if ((this.fitness + FITNESS_INCREMENT) <= MAX_FITNESS_LEVEL) {
this.fitness += FITNESS_INCREMENT;
} else {
this.fitness = MAX_FITNESS_LEVEL;
};
}
The complete finished code is on my GitHub here if you would like to see it in full.
Using module.exports
and require()
In a nutshell, a module is a piece of code that does something, that you want to reuse in other bits of code. To bundle that piece of code up, and make it available for other bits of code to use, you can use module.exports
and require()
. (I won't go into more detail here but this is a helpful article if you want to know more).
Guard clauses and throwing exceptions
Once we'd written the basic functionality for the pet, we added an extra bit of functionality that checked whether the pet was, in fact, alive. (If the pet got too old, too unfit, or too hungry, it would die). To achieve this we added a guard clause - in our case, this is essentially a statement to check whether something is true or not before running the subsequent code - none of the walk
, feed
or growUp
functions should work if the pet was already dead. In addition, if the guard clause checked the state of the pet and found it to be dead, our code would throw an error message. This was all new to me, but we were given the info and tools to write our own errors. Here's an example of the code that achieves that:
growUp() {
if (!this.isAlive) {
throw new Error('Your pet is no longer alive :(');
}
Refactoring to avoid magic numbers
This project also introduced me to the concept of magic numbers. A magic number is a number that is hard-coded in your code (i.e just written as a number like 9
or 245
) that is meaningless to anyone reading your code - instead it should be saved as a variable with a meaningful name. It makes your code more reusable and more readable and easier to understand.
// Instead of:
const hoursInADay = 168 / 7;
// Make your code easier to understand by using meaningful variables:
const HOURS_IN_A_WEEK = 168;
const DAYS_IN_A_WEEK = 7;
const hoursInADay = HOURS_IN_A_WEEK / DAYS_IN_A_WEEK;
It's conventional to write variable names using CAPITALS and underscores in this situation.
Phew!
In summary, this was a great project that brought together everything we had learned so far and introduced us to object oriented programming principles, and how they relate to JavaScript in particular. Next up in bootcamp, another OOP project, this time involving cruise ships. Watch this space!
Cover image by Robert Eklund on Unsplash