error-handling
Error Handling
Error handling matters, but it must not take over the code. When the happy path disappears under a hedge of if (err) checks, the reader can no longer see what the code is for. The goal is to let the main algorithm breathe: keep the logic in one place and the error handling in another, translate other people's failures into your own at the boundary, and never reach for try/catch when a plain check would do.
This pairs with functions (an error handler is a function that does one thing — handling errors) and objects-and-data (the wrapper below is an object that hides a library behind a contract). Error and result types follow type-safety.
Use exceptions, not return/error codes
When a function signals failure with a return code, every caller has to check it, right now, inline — and the actual algorithm drowns in branching. Worse, the codes leak upward: each layer re-checks and re-returns. Throw instead. An exception lets the happy path read top-to-bottom as one clean story, and pushes the recovery logic out to the one place equipped to handle it.
// Bad — error codes. The enrollment logic is buried under status checks.
type Status = 'ok' | 'no-vault' | 'insufficient-galleons' | 'owl-lost'
const enrollStudent = (student: Student): Status => {
const vault = gringotts.findVault(student.vaultId)
if (vault === undefined) return 'no-vault'