Capstone: build a small library end to end · Lesson 77 of 77 · project
Reviewing your own library, and knowing when it is done
About 65 minutes.
By the end of this lesson you can
- Review your own code against the brief rather than against your memory of writing it.
- Work through a fixed checklist so the review does not depend on your mood.
- Name the trade-off behind each decision you decided to keep.
- Decide when version 1 is done, and write down what belongs in the next one.
You cannot read your own code with fresh eyes, so use a list instead
Why it matters
When you read code you wrote yesterday, you do not read what is there. You read what you meant, because your memory helpfully fills in the intention behind every line. That is why your own bugs hide so well from you and are so obvious to somebody else within thirty seconds.
You cannot switch that off by trying harder. What you can do is stop relying on judgement and use a list. A checklist asks the same questions every time, in the same order, whether you are pleased with the code or sick of it, and it asks about things your memory cannot paper over: is there a check for this criterion, does this function say what it does with bad input, does this name still make sense.
This is the last lesson of the course because it is the one that makes the rest durable. Anyone can write code that works today. The reason to review is that the code has to work in three months, when the only thing left of today is what is written down.
The checklist itself
Explanation
Ten items, in this order, because the early ones catch the expensive problems. One: every acceptance criterion from lesson 1 has a check, and that check fails when you deliberately break the code. Two: every criterion is currently green, or is written down as dropped. Three: every public function says what it does with wrong input - reports it, throws, or is documented as trusting its caller.
Four: no function returns undefined by accident on any path. Five: the empty case is answered deliberately for every function that takes a list. Six: nothing inside the library prints, unless printing is its job - a library that logs is a library you cannot use quietly. Seven: no function changes data its caller handed in, unless changing it is the point and the name says so.
Eight: every name would make sense to somebody who has not read the body, and no comment is explaining what a name should have said. Nine: you can state the trade-off behind each decision you kept - not defend it, just say what you gave up. Ten: the out-of-scope list is up to date, so that everything missing is missing on purpose.
Run it top to bottom, write down what fails, and only then start fixing. Reviewing and fixing at the same time is how a review turns into an afternoon of rewriting item three while items four to ten go unread.
Read the surface before the insides
An analogy
Think about how you meet somebody else's library. You do not start at line one and read to the end. You look at the list of functions it offers, you read their names and arguments, and you guess what they do. Only when the guess fails do you open the body.
So review in that order. Write down the seven function signatures of readlog on their own - toSession, summarise, pagesByTag, validateSession, createSession, loadSessions, report - and read them as somebody who has never seen the insides. Can you tell what each one takes and returns? Do two of them sound like the same thing? Is there one whose name promises something the body does not deliver?
This ten-minute exercise finds problems that no amount of reading the bodies will, because the bodies are exactly where your memory of writing them is strongest.
Three things to look for that tests will not catch
A common mistake
A result whose shape depends on the data. If a summary object gains a field only when some condition is true, then every caller has to test for that field, and the ones that forget will read undefined and carry on. A result should have the same fields every time, with a value saying no, rather than a missing field meaning it.
A copy that is not a copy. Copying a list of records with slice() gives you a new list pointing at the very same record objects, so changing one through the copy changes it through the original as well. Tests that only count the items pass happily while the caller's data quietly changes underneath them.
A catch that protects nothing, as lesson 4 showed. Wrapping a try round code that does not throw looks careful and is decoration - and worse, it makes a reader believe the failure has been handled when nobody has checked anything at all.
Check there: Properties can be added to an object after it is created, and nonexistent properties have the value undefined - which is why a field that only sometimes exists is indistinguishable from one that was never set.
Done is 'every criterion met', not 'nothing left to improve'
A mental model
There is always something left to improve. A faster tally, a nicer message, one more edge case. If your definition of done is 'I can think of nothing else', you will never ship anything, and the ideas will keep arriving faster than you can implement them.
Done for version 1 is exactly this: every acceptance criterion is met, every check is green, and everything you decided not to do is written down where the next person can find it. That is a state you can reach in an afternoon and defend in a review.
The ideas you did not build are not waste, they are version 2 - and writing them down is what stops them nagging at you. 'Keep a date on each session', 'use a Set for unique titles when the log gets big', 'attach the errors array to the thrown error as well': three lines in a file, and your head is clear.
Where this course stops, and where to go from here
Explanation
Be clear about what you have and have not learned. You can model data, write and name functions, handle failure deliberately, prove your code works with checks you wrote yourself, and improve code without breaking it. That is the language, and it is genuinely the hard part - it transfers to every JavaScript project you will ever open.
What this course did not teach, because the sandbox it runs in has none of them, is the platform: the browser's DOM, the network, files, timers and everything asynchronous that goes with them, and the runtimes and frameworks built on top. Nothing here pretended otherwise, and that honesty is the point - a course that showed you fetch in a sandbox with no network would have been teaching you a story.
So the next step is deliberately outward, and this is where the redirecting posture of this product is not a cop-out but the right advice: the official documentation is now readable to you in a way it was not before. Reference pages assume you know what a function, an argument, a return value and an object are. You do. Read MDN's guide pages on the DOM and on promises, and this time the sentences will land.
And build the next small thing. The loop you have practised - brief, criteria, shape, checks, code, refactor, review - does not change with the size of the project. Only the number of times round it does.
Recap
Recap
You cannot review your own code from memory, so use a fixed list of questions in a fixed order, and write down failures before fixing any of them. Read the public surface first, as a stranger would. Watch for results whose shape depends on the data, copies that share the objects inside them, and try blocks round code that cannot throw. Done means every criterion met and everything else written down as version 2 - not the absence of ideas. Then go and read the official docs, which are now written in a language you can read.
Worked examples
Every output below was produced by actually running the code in the same sandbox your exercises run in — it is not a prediction.
The review, run as a list rather than a feeling
The checklist as something you can actually work through, with two items honestly unticked. Notice the last line: it does not say 'nearly', it says false.
1const checklist = [2 "Every acceptance criterion has a test that fails when I break it.",3 "Every public function says what it does with wrong input.",4 "No function needs a comment to explain what its name means.",5 "Nothing prints unless printing is the job.",6 "I can name the trade-off I made in each decision.",7];8 9let done = 0;10const answers = [true, true, false, true, false];11 12for (let index = 0; index < checklist.length; index += 1) {13 const mark = answers[index] ? "[x] " : "[ ] ";14 console.log(mark + checklist[index]);15 if (answers[index]) {16 done += 1;17 }18}19 20console.log("Ready to ship: " + (done === checklist.length));- line 10
Two honest noes. A review where everything passes first time is usually a review that was not really run.
- line 20
One clear answer rather than a feeling about how it went. Either every item passed or the thing is not ready - and 'not ready' is information, not failure.
What it prints
[x] Every acceptance criterion has a test that fails when I break it.[x] Every public function says what it does with wrong input.[ ] No function needs a comment to explain what its name means.[x] Nothing prints unless printing is the job.[ ] I can name the trade-off I made in each decision.Ready to ship: false
Closing the loop: the criteria from lesson 1, checked against real code
The same criteria you wrote before any code existed, now run against tiny versions of the functions. This is what 'done' looks like - the plan and the code agreeing, out loud.
1function toSession(raw) {2 return { title: raw.title.trim(), pages: Number(raw.pages) };3}4 5function summarise(sessions) {6 return {7 count: sessions.length,8 pages: sessions.reduce((total, session) => total + session.pages, 0),9 };10}11 12function check(label, actual, expected) {13 const pass = JSON.stringify(actual) === JSON.stringify(expected);14 console.log((pass ? "PASS - " : "FAIL - ") + label);15}16 17check("AC1 a raw entry becomes a record with a trimmed title", toSession({ title: " Dune ", pages: "30" }).title, "Dune");18check("AC2 an empty log reports zero pages", summarise([]).pages, 0);19check("AC4 summarise hands back a value and prints nothing", typeof summarise([]), "object");- line 17
The label is the acceptance criterion, word for word, from the plan you wrote in lesson 1. That is what makes this a review of the brief rather than a review of the code's opinion of itself.
- line 19
A criterion about what the library does NOT do - it returns rather than prints. Negative promises are checkable too, and they are the ones that get broken by accident.
What it prints
PASS - AC1 a raw entry becomes a record with a trimmed titlePASS - AC2 an empty log reports zero pagesPASS - AC4 summarise hands back a value and prints nothing
The bug a checklist catches and a test might not
slice() makes a new list - and the new list points at the very same record objects. Changing one through the copy changes it through the original, while every test that only counts items stays green.
1const sessions = [{ title: "Dune", pages: 30 }];2const copy = sessions.slice();3 4copy[0].pages = 1;5 6console.log(sessions[0].pages);7console.log(copy.length === sessions.length);- line 2
A new array, genuinely. What it holds is not new: both arrays point at the same single record object.
- line 4
Written through the copy, and there is only one record to write to.
- line 6
1, not 30. The 'copy' did not protect the original at all - and a test that checked only the lengths would have been perfectly happy.
What it prints
1true
Check yourself
Not scored, not stored. Getting one wrong is the useful part.
Two calls to the same function, two different-looking results. What is printed, and what should the review say about it?
function summarise(list) { const out = { count: list.length, pages: 0 }; for (const item of list) { out.pages += item.pages; } if (out.pages > 500) { out.long = true; } return out;}console.log(summarise([{ pages: 300 }, { pages: 260 }]));console.log(summarise([{ pages: 10 }]));
What is the honest definition of 'version 1 is done'?
A checklist item fails the evening before you wanted to be finished. What is the honest move?
No exercise in this lesson
A review is a judgement about code a person reads: there is no return value for the sandbox to compare, and grading a checklist would score guesswork about our wording. The deliverable is your completed checklist and the written list of what you left for version 2.
Worth remembering
- Why review with a fixed checklist instead of just reading the code carefully?
Because you read what you meant, not what is there. A list asks the same questions in the same order regardless of mood, and asks about things memory papers over.
- What is wrong with a result that gains a field only when some condition holds?
A missing field reads as undefined, so every caller must know that absent means false. Always set the field with a value saying no, so the shape never changes.
- When is version 1 done?
When every acceptance criterion is met, every check is green, and everything you chose not to build is written down as version 2 - not when you run out of ideas.
Check this lesson against the source
We wrote the explanation; we did not invent the facts. This is the page that backs them.
Check there: The Guide's contents list the areas this course taught in its own words - grammar and types, control flow, functions, objects, iteration - and is the page to return to for the exact rules and for the platform topics the sandbox cannot run.
Check there: slice returns a shallow copy: object references are copied, so the new array and the original refer to the same objects.
Prefer to read the source in a structured breakdown? Turn this doc into a breakdown