Contents
1 Introduction 1
1.1 Who is our audience? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 What tools and ideas do we cover? . . . . . . . . . . . . . . . . . . . . . . 2
1.3 How are these lessons laid out? . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 How did we get here? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5 How can people use and contribute to this material? . . . . . . . . . . . . 4
1.6 Who helped us? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Systems Programming 7
2.1 How can we list a directory? . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2 What is a callback function? . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 What are anonymous functions? . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4 How can we select a set of files? . . . . . . . . . . . . . . . . . . . . . . . . 13
2.5 How can we copy a set of files? . . . . . . . . . . . . . . . . . . . . . . . . 16
2.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3 Asynchronous Programming 23
3.1 How can we manage asynchronous execution? . . . . . . . . . . . . . . . . 23
3.2 How do promises work? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3 How can we chain operations together? . . . . . . . . . . . . . . . . . . . . 27
3.4 How are real promises different? . . . . . . . . . . . . . . . . . . . . . . . . 29
3.5 How can we build tools with promises? . . . . . . . . . . . . . . . . . . . . 31
3.6 How can we make this more readable? . . . . . . . . . . . . . . . . . . . . 34
3.7 How can we handle errors with asynchronous code? . . . . . . . . . . . . . 36
3.8 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4 Unit Testing 41
4.1 How should we structure unit testing? . . . . . . . . . . . . . . . . . . . . 41
4.2 How can we separate registration, execution, and reporting? . . . . . . . . 42
4.3 How should we structure test registration? . . . . . . . . . . . . . . . . . . 43
4.4 How can we build a command-line interface for testing? . . . . . . . . . . 46
4.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5 File Backup 53
5.1 How can we uniquely identify files? . . . . . . . . . . . . . . . . . . . . . . 53
5.2 How can we back up files? . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.3 How can we track which files have already been backed up? . . . . . . . . 59
5.4 How can we test code that modifies files? . . . . . . . . . . . . . . . . . . . 61
5.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
vii
viii Contents
6 Data Tables 69
6.1 How can we implement data tables? . . . . . . . . . . . . . . . . . . . . . . 70
6.2 How can we test the performance of our implementations? . . . . . . . . . 73
6.3 What is the most efficient way to save a table? . . . . . . . . . . . . . . . 75
6.4 Does binary storage improve performance? . . . . . . . . . . . . . . . . . . 76
6.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
7 Pattern Matching 83
7.1 How can we match query selectors? . . . . . . . . . . . . . . . . . . . . . . 83
7.2 How can we implement a simple regular expression matcher? . . . . . . . . 87
7.3 How can we implement an extensible matcher? . . . . . . . . . . . . . . . 89
7.4 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
8 Parsing Expressions 99
8.1 How can we break text into tokens? . . . . . . . . . . . . . . . . . . . . . . 100
8.2 How can we turn a list of tokens into a tree? . . . . . . . . . . . . . . . . . 103
8.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
9 Page Templates 111
9.1 What will our system look like? . . . . . . . . . . . . . . . . . . . . . . . . 112
9.2 How can we keep track of values? . . . . . . . . . . . . . . . . . . . . . . . 113
9.3 How do we handle nodes? . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
9.4 How do we implement node handlers? . . . . . . . . . . . . . . . . . . . . . 117
9.5 How can we implement control flow? . . . . . . . . . . . . . . . . . . . . . 121
9.6 How did we know how to do all of this? . . . . . . . . . . . . . . . . . . . 123
9.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
10 Build Manager 127
10.1 What’s in a build manager? . . . . . . . . . . . . . . . . . . . . . . . . . . 128
10.2 Where should we start? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
10.3 How can we specify that a file is out-of-date? . . . . . . . . . . . . . . . . 133
10.4 How can we update out-of-date files? . . . . . . . . . . . . . . . . . . . . . 134
10.5 How can we add generic build rules? . . . . . . . . . . . . . . . . . . . . . 136
10.6 What should we do next? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
10.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
11 Layout Engine 145
11.1 How can we size rows and columns? . . . . . . . . . . . . . . . . . . . . . . 146
11.2 How can we position rows and columns? . . . . . . . . . . . . . . . . . . . 149
11.3 How can we render elements? . . . . . . . . . . . . . . . . . . . . . . . . . 151
11.4 How can we wrap elements to fit? . . . . . . . . . . . . . . . . . . . . . . . 154
11.5 What subset of CSS will we support? . . . . . . . . . . . . . . . . . . . . . 156
11.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
12 File Interpolator 165
12.1 How can we evaluate JavaScript dynamically? . . . . . . . . . . . . . . . . 165
12.2 How can we manage files? . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
12.3 How can we find files? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
12.4 How can we interpolate pieces of code? . . . . . . . . . . . . . . . . . . . . 173
12.5 What did we do instead? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
12.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
Contents ix
13 Module Loader 179
13.1 How can we implement namespaces? . . . . . . . . . . . . . . . . . . . . . 179
13.2 How can we load a module? . . . . . . . . . . . . . . . . . . . . . . . . . . 181
13.3 Do we need to handle circular dependencies? . . . . . . . . . . . . . . . . . 182
13.4 How can a module load another module? . . . . . . . . . . . . . . . . . . . 185
13.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
14 Style Checker 191
14.1 How can we parse JavaScript to create an AST? . . . . . . . . . . . . . . . 191
14.2 How can we find things in an AST? . . . . . . . . . . . . . . . . . . . . . . 193
14.3 How can we apply checks? . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
14.4 How does the AST walker work? . . . . . . . . . . . . . . . . . . . . . . . 196
14.5 How else could the AST walker work? . . . . . . . . . . . . . . . . . . . . 198
14.6 What other kinds of analysis can we do? . . . . . . . . . . . . . . . . . . . 201
14.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
15 Code Generator 207
15.1 How can we replace a function with another function? . . . . . . . . . . . 207
15.2 How can we generate JavaScript? . . . . . . . . . . . . . . . . . . . . . . . 209
15.3 How can we count how often functions are executed? . . . . . . . . . . . . 211
15.4 How can we time function execution? . . . . . . . . . . . . . . . . . . . . . 213
15.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
16 Documentation Generator 219
16.1 How can we extract documentation comments? . . . . . . . . . . . . . . . 219
16.2 What input will we try to handle? . . . . . . . . . . . . . . . . . . . . . . . 222
16.3 How can we avoid duplicating names? . . . . . . . . . . . . . . . . . . . . 226
16.4 Code is Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
16.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
17 Module Bundler 233
17.1 What will we use as test cases? . . . . . . . . . . . . . . . . . . . . . . . . 233
17.2 How can we find dependencies? . . . . . . . . . . . . . . . . . . . . . . . . 235
17.3 How can we safely combine several files into one? . . . . . . . . . . . . . . 239
17.4 How can files access each other? . . . . . . . . . . . . . . . . . . . . . . . . 241
17.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
18 Package Manager 249
18.1 What is semantic versioning? . . . . . . . . . . . . . . . . . . . . . . . . . 249
18.2 How can we find a consistent set of packages? . . . . . . . . . . . . . . . . 250
18.3 How can we satisfy constraints? . . . . . . . . . . . . . . . . . . . . . . . . 252
18.4 How can we do less work? . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
18.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
19 Virtual Machine 261
19.1 What is the architecture of our virtual machine? . . . . . . . . . . . . . . . 261
19.2 How can we execute these instructions? . . . . . . . . . . . . . . . . . . . . 263
19.3 What do assembly programs look like? . . . . . . . . . . . . . . . . . . . . 266
19.4 How can we store data? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
19.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
x Contents
20 Debugger 275
20.1 What is our starting point? . . . . . . . . . . . . . . . . . . . . . . . . . . 275
20.2 How can we make a tracing debugger? . . . . . . . . . . . . . . . . . . . . 277
20.3 How can we make the debugger interactive? . . . . . . . . . . . . . . . . . 281
20.4 How can we test an interactive application? . . . . . . . . . . . . . . . . . 283
20.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
21 Conclusion 291
A License 293
B Code of Conduct 297
C Contributing 299
D Bibliography 301
E Glossary 303
Index 325
The best way to learn design in any field is to study examples, and some of the best examples of software design come from the tools programmers use in their own work. Software Design by Example: A Tool-Based Introduction with JavaScript therefore builds small versions of the things programmers use in order to demystify them and give some insights into how experienced programmers think. From a file backup system and a testing framework to a regular expression matcher, a browser layout engine, and a very small compiler, we explore common design patterns, show how making code easier to test also makes it easier to reuse, and help readers understand how debuggers, profilers, package managers, and version control systems work so that they can use them more effectively.
This material can be used for self-paced study, in an undergraduate course on software design, or as the core of an intensive weeklong workshop for working programmers. Each chapter has a set of exercises ranging in size and difficulty from half a dozen lines to a full day’s work. Readers should be familiar with the basics of modern JavaScript, but the more advanced features of the language are explained and illustrated as they are introduced.
All the written material in this project can be freely reused under the terms of the Creative Commons - Attribution license, while all of the software is made available under the terms of the Hippocratic License. All proceeds from sale of this book will go to support the Red Door Family Shelter in Toronto.
Teaches software design by showing programmers how to build the tools they use every day
Each chapter includes exercises to help readers check and deepen their understanding
All the example code can be downloaded, re-used, and modified under an open license