Programming Lesson 7: Errors, validation & security mindset
Learn how to think defensively: catch mistakes early, validate inputs, and avoid leaking sensitive data.
Learning goals
- Understand different kinds of errors and what their messages tell you.
- Practice defensive programming: validate inputs and fail early with clear messages.
- Recognize trust boundaries and treat external input as untrusted.
- Know why secrets must be protected and the conceptual difference between hashing and encryption.
What are errors?
Errors are signs that something unexpected occurred: a thrown error, an exception, or an error message and stack trace produced by the runtime. Stack traces help you find where the error originated (conceptually).
Common categories
- Syntax errors — code can't be parsed.
- Runtime errors — code fails while running (e.g., calling a method on undefined).
- Validation errors / business rules — input doesn't meet expected rules.
Defensive programming
Validate inputs (types, ranges) and fail early with clear messages. Keep checks readable and specific.
function validateNumber(n) {
if (typeof n !== 'number') throw new Error('Expected number');
if (!Number.isFinite(n)) throw new Error('Number must be finite');
return true;
}
try {
validateNumber('x');
} catch (err) {
console.error('Validation failed:', err.message);
}
Trust boundaries
Treat any data originating outside your trusted code (user input, files, networks) as untrusted. Sanitize, validate, and avoid implicitly trusting values.
Secrets & safe handling
Never store secrets in plain text. Prefer established safe practices: don't log secrets, and limit who can read them.
Hashing vs encryption (conceptual)
Hashing: one-way transform used for storing password representations — you can't (shouldn't) recover the original from the hash.
Encryption: two-way transform that protects data but allows authorized parties to decrypt and read it.
// pseudocode
// Hashing (store salt + hash(password)) — cannot recover password
// Encryption (encrypt data with key) — can decrypt when key is available
Basic threat thinking
- Data leaks: avoid exposing sensitive values in logs, error messages, or outputs.
- Logging secrets accidentally: never log raw tokens or passwords.
- Unsafe defaults: choose secure defaults and require explicit opt-in for weaker behavior.
Examples
Validating a number input
function safeParseInt(s) {
const n = Number(s);
if (!Number.isFinite(n)) throw new Error('Invalid integer');
return Math.trunc(n);
}
safeParseInt('42'); // 42
// safeParseInt('abc') -> throws Error('Invalid integer')
Throwing an Error with a clear message
function requirePositive(n) {
if (n <= 0) throw new Error('Expected a positive number');
return n;
}
Hash vs encrypt (high-level pseudocode)
// hash(password) -> store hash
// encrypt(data, key) -> store ciphertext; can decrypt with key
// Use hashing for passwords; encryption when you need to read data later.
Practice tasks
- Write
validateAge(value)that returns age if 0 ≤ age ≤ 120, otherwise throws a clear Error. - Write
safeParseInt(str)that returns a number or throws; test with '10' → 10, 'x' → Error. - Explain why logging a user-provided token to debug output could be dangerous.
Common mistakes
- Relying on implicit type coercion for validation.
- Returning vague error messages that don't help debugging.
- Logging secrets or including them in error outputs.
- Using home-grown crypto or rolling your own hash/encryption — prefer vetted libraries and high-level APIs.