├── resources └── resources.md ├── part-1 ├── 1.0-intro.md ├── 1.1-tooling.md └── 1.2-good-practices.md ├── part-3 ├── 3.9-voodoo.md ├── 3.2-reproduce.md ├── 3.8-nondeterminism.md ├── 3.3-sanity.md ├── 3.0-intro.md ├── 3.7-narrowing.md ├── 3.5-logging.md ├── 3.1-psych.md ├── 3.4-post-mortem.md └── 3.6-interactive.md ├── .gitignore ├── part-2 ├── 2.0-intro.md ├── 2.1-testing.md └── 2.2-error-handling.md ├── part-5 └── 5.0-intro.md ├── part-4 └── 4.0-intro.md ├── SUMMARY.md └── README.md /resources/resources.md: -------------------------------------------------------------------------------- 1 | In progress 2 | 3 | * McConnell, *Code Complete* 2nd ed. 4 | -------------------------------------------------------------------------------- /part-1/1.0-intro.md: -------------------------------------------------------------------------------- 1 | # 1. Prevention 2 | 3 | > "An ounce of prevention is worth a pound of cure." 4 | -------------------------------------------------------------------------------- /part-3/3.9-voodoo.md: -------------------------------------------------------------------------------- 1 | # Voodoo 2 | 3 | Try not to rely on poorly-understood ritual debugging actions; consider them a last resort. 4 | 5 | * ["Have you tried turning it off and on again?"](https://www.youtube.com/watch?v=nn2FB1P_Mn8) 6 | * `rm -rf node_modules && npm install` 7 | * Still seek understanding even if works 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf -------------------------------------------------------------------------------- /part-3/3.2-reproduce.md: -------------------------------------------------------------------------------- 1 | # Reproduce the Error 2 | 3 | Before you can even think of identifying the cause of your problem, make sure you know what the problem even is. 4 | 5 | * Can you reproduce the bug at will? 6 | * Does the bug occur in other circumstances / situations? 7 | * Can you describe the bug clearly to a stranger? 8 | * "The cart doesn't work" is meaningless. "When I click on this button, it adds two items instead of one" is useful. 9 | -------------------------------------------------------------------------------- /part-3/3.8-nondeterminism.md: -------------------------------------------------------------------------------- 1 | # Nondeterminism 2 | 3 | * Intermittent, semi-random errors are usually due to nondeterministic code. Be extra-careful with certain patterns. 4 | * Async anything 5 | * Random / seeded elements 6 | * Initialization 7 | * Race conditions 8 | * Complex state (bad syncing of data across app) 9 | * State based on external factors (API, etc.) 10 | * Use mock objects / methods to force a predictable testing scenario. 11 | -------------------------------------------------------------------------------- /part-3/3.3-sanity.md: -------------------------------------------------------------------------------- 1 | # Sanity Checks 2 | 3 | Sometimes the problem is "stupid", yet you can burn a lot of time chasing it down. Some easy things to check, which can make a big difference: 4 | 5 | * is the server actually running? 6 | * is the server _restarting_ (e.g. if you use `nodemon`)? Or have you restarted it? 7 | * is this the right file? 8 | * are you _suuuure_? Really, try adding a `console.log` to the very top. 9 | * is it saved? 10 | * is your build process working? 11 | -------------------------------------------------------------------------------- /part-2/2.0-intro.md: -------------------------------------------------------------------------------- 1 | # 2. Detection 2 | 3 | Discovering a bug usually occurs because of any of the following: 4 | 5 | * Automated testing fails 6 | * Impossible to compile / transpile / launch / load 7 | * Program crashes or stops behaving as expected while developing 8 | * Program crashes or stops behaving as expected for user 9 | 10 | Woe is the developer who discovers a bug and has no idea when it was introduced. It could be *anywhere*. Immediate, obvious feedback makes it far easier to track down… and means fewer irate customers. 11 | -------------------------------------------------------------------------------- /part-5/5.0-intro.md: -------------------------------------------------------------------------------- 1 | ## 5. Reinforcement 2 | 3 | With the fault found and fixed, now is the time to follow through and make this bug hunt worthwhile. 4 | 5 | * Perform some sanity checks on the new codebase. Think through the logic. 6 | * *After* committing the fix, refactor the code if it is warranted. 7 | * Add a new unit test to your suite guarding against incorrect behavior. Confirm your test works by breaking the code again (temporarily). 8 | * Consider other locations where you may have made the same mistake. Investigate. 9 | * Enjoy your healthy codebase! 10 | -------------------------------------------------------------------------------- /part-3/3.0-intro.md: -------------------------------------------------------------------------------- 1 | # 3. Diagnosis 2 | 3 | Diagnosis is detective work, scientific experiment, hunting, and research all combined. Chances are, if you were looking for debugging tips, this is the section you were interested in. But as you can see, there is a ton you can do about bugs before we even get to debugging proper. Take prevention and detection seriously. 4 | 5 | ## Mandatory 6 | 7 | * Your client console (e.g. Chrome console) should be open. Any errors? 8 | * Your server console (e.g. iTerm) should be open. Any errors? 9 | * Any other relevant logs / processes should be visible. Any errors? 10 | -------------------------------------------------------------------------------- /part-3/3.7-narrowing.md: -------------------------------------------------------------------------------- 1 | # Narrowing Down 2 | 3 | * How far do we get? 4 | * Log or step through each nested level down 5 | * Confirm the right route is being hit 6 | * Break dense statements into many small statements 7 | * Apply binary search (aka the "Wolf Fence" algorithm) 8 | * Comment out half the suspicious code 9 | * Problem unchanged? It's not in the commented section. 10 | * Comment out half the remaining suspicious code 11 | * Repeat until the problem is found logarithmically 12 | * Use *temporal* binary search with [`git bisect`](http://bit.ly/2k4hGZN) 13 | * Use `git diff` (or view diff on GitHub) for more insight 14 | -------------------------------------------------------------------------------- /part-2/2.1-testing.md: -------------------------------------------------------------------------------- 1 | # Automated Testing 2 | 3 | It is difficult to overstate the tremendous value of automated testing in general. Test-Driven Development (TDD) is also a useful approach to coding with many benefits. 4 | 5 | * Examples: [Mocha](https://mochajs.org/), [Jasmine](https://jasmine.github.io/), [Blue Tape](https://github.com/spion/blue-tape) 6 | * Help structure your approach (TDD) 7 | * Confirm code works as expected (TDD) 8 | * Prevent *regressions* (working code breaking in the future)! 9 | * Running the test suite is an easy, painless way to check all is well 10 | * Integrates with other tools such as Husky and CI services (see Prevention) 11 | -------------------------------------------------------------------------------- /part-2/2.2-error-handling.md: -------------------------------------------------------------------------------- 1 | # Error Handling 2 | 3 | * It is considered by some to be a mistake in the JS language that you can `throw` any value. Stick to throwing only `Error` objects or objects which extend `Error`. 4 | * Errors automatically capture valuable stack trace information 5 | * Other developers may assume your code will pass in or throw `Error` instances, and their code will not work correctly otherwise 6 | * [Creating custom Error types](https://mzl.la/2jbVhJS) 7 | * Always remember to *handle errors* in async code (callbacks, promises) 8 | * The alternative is the dreaded *silent error*! 9 | * In a Node-style errback: `function (err, data) { if (err) doSomethingWith(err); ... }` 10 | * At the end of a promise chain: `.catch(someErrorHandler)` 11 | -------------------------------------------------------------------------------- /part-3/3.5-logging.md: -------------------------------------------------------------------------------- 1 | # Proper Logging 2 | 3 | * Spray 'n' pray (`console.log` everywhere) is better than nothing at all, but still among the most primitive forms of debugging. 4 | * Do not commit `console` or `debugger` statements to code. Debug code is a temporary measure and pollutes the runtime standard output. 5 | * If you want persistent, namespaced debugging calls, [use the `debug` module](https://www.npmjs.com/package/debug). That is fine to commit. 6 | * Annotate your `console.log`s with descriptive, searchable, visible strings. 7 | * `console.log('======== FOO:, foo)` >>> `console.log(foo)` 8 | * Don't concatenate; use commas. `console.log('foo:', foo, 'bar', bar)` will pretty-print the `foo` and `bar` values (interactively in Chrome), which is better than seeing their stringified form. 9 | -------------------------------------------------------------------------------- /part-4/4.0-intro.md: -------------------------------------------------------------------------------- 1 | # 4. Treatment 2 | 3 | Finding the bug is most of the work. But fixing it can still be done well or poorly. 4 | 5 | * STOP. Do not just blindly change values to see if it works. You can "fix" a bug by canceling it out with another bug, which is even worse than the first one. And you learn nothing. 6 | * By the same token, do not "patch" bad behavior with exceptional cases. You've solved nothing, and added technical debt to your app. 7 | * Understand the code in its larger / deeper context. 8 | * Figure out what *should* be the fix, based on your understanding. 9 | * Implement by changing one thing at a time. Work incrementally, in small pieces. 10 | * Confirm the fix with several different tests. 11 | * Run your automated testing suite. 12 | * Once you have apparently implemented the fix… `git commit`! 13 | * RESIST THE URGE to refactor immediately after the fix. COMMIT FIRST! 14 | -------------------------------------------------------------------------------- /part-3/3.1-psych.md: -------------------------------------------------------------------------------- 1 | # Psychology and Approach 2 | 3 | * People see what they want to to see. Stop *reading* your code and start *dissecting* it. 4 | * Example: did you know there are two "to"s in in a row in the above line? 5 | * And two "in"s in the previous line? 6 | * Stop assuming. "Be the machine." Take things piece by piece and *test assumptions*. 7 | * Use your head. Don't just try random stuff, *think*. Form and test hypotheses. 8 | * Check easy & fast stuff first, even if it's unlikely. 9 | * Consult documentation and other resources. 10 | * Get more eyes on the code. Different perspectives help. 11 | * Use "rubber duck" (aka "confessional") debugging: explain your problem carefully, step-by-step, to someone else. In the process you will often realize your mistake. 12 | * Assume the error is your fault! It almost always is. 13 | * But ok, check the GitHub issues if you're convinced it's the library 14 | * Be suspicious of recently-changes & often-buggy sections of code 15 | * Give yourself a break. Take a walk. Come back to the problem with fresh eyes. 16 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [1.0 Prevention](part-1/1.0-intro.md) 5 | * [1.1 Assistive Tooling](part-1/1.1-tooling.md) 6 | * [1.2 Good Practices & Style](part-1/1.1-tooling.md) 7 | * [2.0 Detection](part-2/2.0-detection.md) 8 | * [2.1 Automated Testing](part-2/2.1-testing.md) 9 | * [2.2 Error Handling](part-2/2.2-error-handling.md) 10 | * [3.0 Diagnosis](part-3/3.0-intro.md) 11 | * [3.1 Psychology & Approach](part-3/3.1-psych.md) 12 | * [3.2 Reproduce the Error](part-3/3.2-reproduce.md) 13 | * [3.3 Sanity Checks](part-3/3.3-sanity.md) 14 | * [3.4 Post-Mortem Debugging](part-3/3.4-post-mortem.md) 15 | * [3.5 Proper Logging](part-3/3.5-logging.md) 16 | * [3.6 Interactive Debugging](part-3/3.6-interactive.md) 17 | * [3.7 Narrowing Down](part-3/3.7-narrowing.md) 18 | * [3.8 Nondeterminism](part-3/3.8-nondeterminism.md) 19 | * [3.9 Voodoo](part-3/3.9-voodoo.md) 20 | * [4.0 Treatment](part-4/4.0-intro.md) 21 | * [5.0 Reinforcement](part-5/5.0-intro.md) 22 | * [Resources](resources/resources.md) 23 | -------------------------------------------------------------------------------- /part-3/3.4-post-mortem.md: -------------------------------------------------------------------------------- 1 | # Post-Mortem Debugging 2 | 3 | When your app crashes, all is not lost. If it gasped out a stack trace with its dying breath, you may have all you need to track down the problem. 4 | 5 | * Read the error message, but for *clues*, not gospel 6 | * Error messages are sometimes just string labels a developer wrote. They may be misleading or badly worded, and may have nothing to do with the root cause of the error. 7 | * Think about what the error is saying, and how it might apply to your code… and might not 8 | * Google unfamiliar error messages if necessary 9 | * Read the *whole* message. For example, npm errors always append a boilerplate warning on the bottom. If you haven't scrolled up to read the *actual* error message, you are missing the important part. 10 | * Read the stack trace! 11 | * Ignore the traces from library code. 99.9% of the time, the fault is yours, not the library. 12 | * Note your own functions in the trace 13 | * Go to the line number(s) and column(s) identified 14 | * If you are lucky, the error will be evident, or at least local 15 | * If you aren't lucky, the line & column will be irrelevant. Let it go. 16 | -------------------------------------------------------------------------------- /part-3/3.6-interactive.md: -------------------------------------------------------------------------------- 1 | # Interactive Debugging 2 | 3 | Relying solely on tons of `console.log` statements is like sprinting through a crime scene snapping a couple photos, and then trying to deduce what happened back in the dark room. It's much better to walk around at your leisure, looking and thinking carefully as you go. Interactive debuggers let you control program execution and investigate every detail. 4 | 5 | * Use the `debugger;` statement. 6 | * Learn the [Chrome dev tools](http://bit.ly/2k498lH) 7 | * The debugger 8 | * Normal and conditional (right-click) break-points 9 | * Stepping over, into, and out of functions 10 | * scoped variables and watch expressions 11 | * call stack 12 | * [The extended Console API](http://bit.ly/2iEP7pv) 13 | * Filterable log levels: `log` === `debug`, `info`, `warn`, `error` 14 | * Organization: `group`, `groupEnd` 15 | * Data: `table`, `dir` 16 | * Testing: `assert` 17 | * [Node has a debugger too](https://nodejs.org/api/debugger.html) 18 | * Built-in debugging client 19 | * `node debug yourFile.js` starts an interactive debugging session 20 | * `node --debug yourFile.js` launches the process in debug mode so other programs can investigate. Unlikely you'll use this. 21 | * `node --inspect yourFile.js` is an experimental feature which lets you debug Node from Chrome! 22 | * [VSC debugger](https://code.visualstudio.com/Docs/editor/debugging) (uses Node debugger) 23 | * click the debug panel and create a `launch.json` 24 | * VSC is pretty good about configuring `launch.json` automatically for Node, but you will probably have to tweak the start file 25 | -------------------------------------------------------------------------------- /part-1/1.1-tooling.md: -------------------------------------------------------------------------------- 1 | # Assistive Tooling 2 | 3 | Automated help is free and will save you many times. Not using it is basically selecting "hard" difficulty on the start screen of your career. 4 | 5 | * Linters 6 | * Like a guardian angel 7 | * [ESLint](http://eslint.org/) is a modern, configurable, well-designed linter for JS 8 | * [eslint-config-fullstack](https://github.com/fullstackacademy/eslint-config-fullstack) is designed to aid students in error prevention and learning best practices 9 | * Integrate ESLint into your editor of choice 10 | * Autoformatters 11 | * Reveal the true structure of your code, not what you *think* is the structure 12 | * Can format online at [JS Beautifier](http://jsbeautifier.org/) 13 | * Get an extension for your editor (e.g. [JSFormat](https://github.com/jdc0589/JsFormat)) 14 | * Git 15 | * Make *frequent*, *small* commits 16 | * Commit only related changes (did you know you can commit just some files / lines?) 17 | * Final (pushed) commits should contain only *working code* (to your knowledge) 18 | * Make commits before moving on (e.g. refactoring) 19 | * [Yarn](https://yarnpkg.com/) 20 | * Makes npm package installation *deterministic* (and also more performant). No more "but it works on my machine / but it worked last month…" 21 | * Learn [SEMVER](http://semver.org/) and what major.minor.patch means. Then realize that it guarantees nothing. 22 | * [husky 🐶](https://github.com/typicode/husky) 23 | * Lets you register git hooks which can run your testing suite before a `commit` or `push` or similar 24 | * Continuous Integration 25 | * Examples: [Codeship](https://codeship.com/), [Travis](https://travis-ci.org/) 26 | * Can integrate with GitHub and automatically run test suite for branches / PRs 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Debugging JavaScript Applications 2 | 3 | ## A Practical Distillation for Students 4 | 5 | Faults in computer problems were theorized as far back as 1843, when Ada Lovelace noted of Babbage's Analytical Engine, "Granted that the actual mechanism is unerring in its processes, the *cards* may give it wrong orders." Almost 160 years later, NIST reported that software errors cost the US [$59 billion annually]((http://bit.ly/2k2z8he)). Clearly, some of the cards are wrong. However, unlike Grace Hopper's famous [moth](https://upload.wikimedia.org/wikipedia/commons/8/8a/H96566k.jpg), most of the time the culprit is ourselves. 6 | 7 | Debugging is a sanitization procedure consisting of: 8 | 9 | * **Preventing** bugs in the first place, through good practices and assistive tooling. 10 | * **Detecting** bugs when they first arise, through proper error handling and testing. 11 | * **Diagnosing** and locating bugs, through the scientific method and interactive tooling. 12 | * **Treating** bugs, through reasoned analysis and patient refactoring. 13 | * **Reinforcing** code health, through additional testing and reflection. 14 | 15 | The heroic `console.log` may be your first line of defense, but it need not be your last. Anyone can improve their debugging skills, with great payoff. Studies show a 20:1 difference in time to debug between experienced and inexperienced developers, also leaving (and creating!) far fewer bugs in the process (McConnell, *Code Complete*). 16 | 17 | --- 18 | 19 | *This [GitBook](https://www.gitbook.com) began as a [Gist](https://gist.github.com/glebec/8a0d06e54a4b3f95a33392f948e97b6a) for [Fullstack Academy](https://www.fullstackacademy.com/) students. The source is hosted on [GitHub](https://github.com/glebec/debugging-js) where you can open [issues](https://github.com/glebec/debugging-js/issues) or contribute edits via [pull requests](https://github.com/glebec/debugging-js/pulls). The book can be easily read, browsed, and commented on [here](http://bit.ly/debugging-js).* 20 | -------------------------------------------------------------------------------- /part-1/1.2-good-practices.md: -------------------------------------------------------------------------------- 1 | # Good Practices & Style 2 | 3 | > "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." —Brian W. Kernighan 4 | 5 | Code style is not just about opinionated aesthetics. It is about reducing complexity, improving maintainability, and consequently eliminating the *potential* for bugs. Thousand-page books have been written on this subject, but here are some tips that will keep many bugs away. 6 | 7 | * Functions are fun 8 | * Use *small* functions. Try imposing an arbitrary limit (10 lines is plenty). 9 | * Prefer *pure* functions. Pure functions neither rely on nor influence the surrounding scope. They take in inputs and return outputs — nothing more. That makes them easy to reason about (stateless). 10 | * Prefer *single-purpose* functions. If your function does two things, maybe it should be two functions. 11 | * Prefer *deterministic* functions. Indeterminism is hard to test, reproduce, check, and reason about. 12 | * KISS (Keep It Simple, Stupid) 13 | * Remember [Rob Pike's 5 Rules of Programming](http://users.ece.utexas.edu/~adnan/pike.html) 14 | * Readability > smallness 15 | * Simple > clever 16 | * Formatting matters! 17 | * Conciseness comes from economy of logic, not economy of lines 18 | * Whitespace improves clarity 19 | * Lines rarely need to be very long; set a 100 column ruler in your editor as a guide 20 | * Use clear variable names; `flavor` > `flvr` >>> `f` 21 | * Avoid ambiguous / similar variables; [`current` and `total`] >>> [`numi` and `numt`] 22 | * Indent your code properly / consistently. Indentation reveals structure. 23 | * State is icky. 24 | * The biggest mental tax on a programmer is keeping track of state 25 | * It is very hard to keep state in sync across an application 26 | * Reduce and remove state whenever it won't result in a big increase in complexity 27 | * Prefer `const` to `let`. Prefer `let` to `var`. Put variables in the smallest scope possible. 28 | * Don't copy-paste from others 29 | * Make sure you understand exactly what a code snippet does before using it 30 | * You never know when online text will include weird formatting characters, like the invisible [zero-width space](https://en.wikipedia.org/wiki/Zero-width_space) 31 | * Do copy-paste from yourself 32 | * If you wrote something useful, put it in a gist, reuse it in other projects 33 | * Another upside to small single-purpose pure deterministic functions! 34 | * Know thyself 35 | * Keep track of your own mistakes, and learn from them 36 | --------------------------------------------------------------------------------