domains / app-frameworks / honojs/Hono
Accurate null/undefined handling
Make nullability explicit in types and handle it precisely instead of papering over it with assumptions. 1. Type signatures must reflect real-world nullability. If a value can be `undefined` or `null` at runtime (e.g. `c.req.param('id')`, a ref's initial value, a route path when no match exists), the type and tests must say so instead of claiming a...
Make nullability explicit in types and handle it precisely instead of papering over it with assumptions.
- Type signatures must reflect real-world nullability. If a value can be
undefinedornullat runtime (e.g.c.req.param('id'), a ref’s initial value, a route path when no match exists), the type and tests must say so instead of claiming a non-nullable type. Add overloads when behavior differs based on whether an argument/initial value is provided, e.g.:export function useRef<T>(initialValue: T): RefObject<T> export function useRef<T>(initialValue: T | null): RefObject<T | null> export function useRef<T = undefined>(): RefObject<T | undefined> - Know the difference between “nullish” and “falsy” before reaching for
??or||.??only catchesnull/undefined; it will not catch an intentionally-empty string,0, orfalse. When an empty value (like an empty string) has special meaning, check for it explicitly rather than relying on??:// `??` won't treat '' as "missing" - check explicitly when empty string matters const headerName = options?.headerName ?? 'X-Request-Id' let requestId = headerName ? c.req.header(headerName) : undefined - Avoid redundant or unsafe null-handling. Don’t double-guard a value that optional chaining already makes safe (e.g. spreading
undefinedis a no-op, so...(args?.header ?? {})can simply be...args?.header), and don’t re-check a condition already guaranteed by an earlier check or type narrowing. Conversely, don’t rely on non-null assertions (!) to silence the compiler when an explicit check is possible—prefer narrowing over assertion: ```ts // Prefer this const contentType = headers.get(‘Content-Type’) if (contentType !== null && contentType.startsWith(‘multipart/form-data’)) { … }
// over this const contentType = headers.get(‘Content-Type’)! ```
-
When a field’s optionality in third-party/runtime data is inconsistent across versions or environments (e.g. AWS Lambda event headers,
multiValueHeaders, API Gateway v1 vs v2 payloads), model it as optional in the type and guard each access independently; don’t assume one field’s presence implies another’s, and don’t useelse ifbetween independently-optional fields. -
For “not found”/absent results, pick one consistent representation (e.g. returning
''instead ofundefinedto preserve a non-nullable return type) and document it, rather than silently changing a function’s return type or leaving the absent case undefined-but-untyped. Likewise, avoid introducing bugs whereundefinedis treated as “no cached value yet” versus a legitimately resolvedundefinedresult—use a sentinel object/wrapper instead of relying onundefinedas both “missing” and “empty” states.
Applying these rules prevents type-level lies, silent runtime bugs from falsy/nullish mixups, and either unsafe assertions or defensive-but-redundant null checks.