# Bad Example: Silent Error > An error-handling pattern that violates Atelier principles. Each violation is cited. ## The Code ```typescript async function getUser(id: string): Promise { try { const user = await db.query('SELECT * FROM users WHERE id = $1', [id]); return user; } catch (e) { return null; } } async function processOrder(orderId: string): Promise { const order = await getOrder(orderId); if (!order) { return; // silently do nothing } // ... process } // Usage in a route router.get('/users/:id', async (req, res) => { const user = await getUser(req.params.id); if (!user) { res.status(404).json({ error: 'Not found' }); } else { res.json({ data: user }); } }); ``` ## Violations ### Errors P2 Fail Loudly (Errors) - `catch (e) { return null }` swallows the error. The caller cannot distinguish "user not found" from "database down." - A database outage returns 404s. The operator never knows. Silent failure. - **Fix:** Catch and re-throw with context, or return a typed error (`Result`). Never `null` for "an error happened." ### Errors P3 Fail Specifically (Errors) - `return null` is the least specific response. It could mean: not found, db error, network error, permission error. - The caller's `if (!user)` cannot distinguish these. The 404 is a lie if the real cause was a 500. - **Fix:** Return `Result` or throw. The error type/code carries the specificity. ### Errors P1 Errors are Data (Errors) - `null` is not data. It is the absence of data. Conflating "error" with "absence" loses information. - The error (a database failure) was data; it was thrown away and replaced with `null`. - **Fix:** Errors are values. Return the error value, not a sentinel absence. ### Errors P4 Preserve Context (Errors) - The catch block discards `e`. The stack trace, the error message, the cause — all gone. - The log has no record. The operator cannot debug. - **Fix:** Log the error with context. Wrap and re-throw: `throw new Error('getUser failed', { cause: e })`. ### Errors P9 Errors are Logged (Errors) - The error is not logged. The handling (return null) is the entire response. The log is missing. - An error that is not logged is an error that cannot be investigated. - **Fix:** `logger.error({ err: e, userId: id })` before returning/rethrowing. ### Errors P10 Errors Don't Lie (Errors) - `return null` claims "no user" when the truth may be "database down." The function lies. - The 404 response claims "not found" when the truth may be "internal error." The API lies. - **Fix:** The response status must match the actual condition. 500 for server errors, 404 for not found. ### Observability P2 Correlation, P3 Context (Observability) - No `request_id`. No correlation across services. - No context in the (missing) log. "What was the user doing?" is unanswerable. - **Fix:** Propagate `request_id`. Log with path, method, user_id. ### Security P8 Fail Securely (Security) - The silent failure is fail-open in disguise. If `getUser` fails due to an authz check throwing, the catch returns `null`. - The caller treats `null` as "not found" and may proceed, or may 404. Either way, the security failure is hidden. - **Fix:** Distinguish "not found" (404) from "authz error" (403) from "db error" (500). Never collapse them into `null`. ## What This Example Reveals The silent error is the most common and most damaging anti-pattern. It violates Errors P2 (Fail Loudly), P3 (Fail Specifically), P1 (Errors are Data), P4 (Preserve Context), P9 (Errors are Logged), P10 (Errors Don't Lie) — six of ten error principles in one catch block. The downstream effects: - Operators cannot debug (no log, no context). - Users see wrong errors (404 for a 500). - Security failures hide (authz error becomes "not found"). - The system appears healthy when it is not (no metrics, no logs). The fix is always the same: **never swallow an error**. Log it, wrap it, rethrow it, or return it as a typed value. Never `return null` for "something went wrong."