Stop Using try/catch for Business Logic: Why Rust-Style Result Types Make Cleaner Code


Throwing exceptions for expected domain errors introduces three distinct engineering headaches:
Invisible Signatures: A function signature like getUser(id: string): User conceals that it can fail, leaving consumers blind to error paths without reading internal implementation.
Untyped Error Boundaries: Catch blocks typically receive an untyped error object (unknown or any), stripping type safety precisely where edge-case recovery is needed most.
Hidden Control Flow: Call stacks jump unpredictably, creating hard-to-trace bugs and unhandled promise rejections.


The Pattern: Type-Safe Result Objects
Instead of throwing exceptions, treat potential failure as a first-class return value using a discriminated union:


// Define explicit Success and Failure envelopes
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
export const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
export const err = <E>(error: E): Err<E> => ({ ok: false, error });


// Express domain errors as clear, exhaustively-checked types
type UserError = "USER_NOT_FOUND" | "UNAUTHORIZED" | "DATABASE_TIMEOUT";
async function fetchUser(id: string): Promise<Result<User, UserError>> {
const record = await db.find(id);
if (!record) return err("USER_NOT_FOUND");
return ok(record);
}
How Consumers Handle It
By returning a Result, the TypeScript compiler forces the caller to check .ok before accessing .value:
TypeScript
const result = await fetchUser("usr_102");


if (!result.ok) {
// result.error is fully typed as UserError
return handleFailure(result.error);
}
// result.value is safely narrowed to User
console.log(result.value.email);


The Golden Rule
Use Result Types for Expected Outcomes: Validation errors, missing records, unauthorized access, and parse failures.
Reserve throw for True Panics: Out-of-memory errors, broken network sockets, or unrecoverable hardware faults.


Discussion Question
Do you enforce explicit Result types / functional error handling in your team's codebases, or do you rely on standard exceptions and global catch middleware? Where has your approach broken down in production?


CTA
Looking to master robust design patterns, clean architecture, and type-safe systems with passionate developers?


👉 Join Developers & Coding at Techawks
Stop Using try/catch for Business Logic: Why Rust-Style Result Types Make Cleaner Code Throwing exceptions for expected domain errors introduces three distinct engineering headaches: Invisible Signatures: A function signature like getUser(id: string): User conceals that it can fail, leaving consumers blind to error paths without reading internal implementation. Untyped Error Boundaries: Catch blocks typically receive an untyped error object (unknown or any), stripping type safety precisely where edge-case recovery is needed most. Hidden Control Flow: Call stacks jump unpredictably, creating hard-to-trace bugs and unhandled promise rejections. The Pattern: Type-Safe Result Objects Instead of throwing exceptions, treat potential failure as a first-class return value using a discriminated union: // Define explicit Success and Failure envelopes type Ok<T> = { ok: true; value: T }; type Err<E> = { ok: false; error: E }; type Result<T, E> = Ok<T> | Err<E>; export const ok = <T>(value: T): Ok<T> => ({ ok: true, value }); export const err = <E>(error: E): Err<E> => ({ ok: false, error }); // Express domain errors as clear, exhaustively-checked types type UserError = "USER_NOT_FOUND" | "UNAUTHORIZED" | "DATABASE_TIMEOUT"; async function fetchUser(id: string): Promise<Result<User, UserError>> { const record = await db.find(id); if (!record) return err("USER_NOT_FOUND"); return ok(record); } How Consumers Handle It By returning a Result, the TypeScript compiler forces the caller to check .ok before accessing .value: TypeScript const result = await fetchUser("usr_102"); if (!result.ok) { // result.error is fully typed as UserError return handleFailure(result.error); } // result.value is safely narrowed to User console.log(result.value.email); The Golden Rule Use Result Types for Expected Outcomes: Validation errors, missing records, unauthorized access, and parse failures. Reserve throw for True Panics: Out-of-memory errors, broken network sockets, or unrecoverable hardware faults. Discussion Question Do you enforce explicit Result types / functional error handling in your team's codebases, or do you rely on standard exceptions and global catch middleware? Where has your approach broken down in production? CTA Looking to master robust design patterns, clean architecture, and type-safe systems with passionate developers? 👉 Join Developers & Coding at Techawks
0 Commenti 0 condivisioni 10 Views 0 Anteprima