Savladajte TypeScript upravljanje pogreškama s uzorcima sigurnosti tipova. Gradite robusne aplikacije s prilagođenim pogreškama, tipskim čuvarima i monadima rezultata.
Upravljanje pogreškama u TypeScriptu: Sigurnosni obrasci za vrste iznimki
U svijetu razvoja softvera, gdje aplikacije pokreću sve, od globalnih financijskih sustava do svakodnevnih mobilnih interakcija, izgradnja otpornih i tolerancijskih sustava nije samo najbolja praksa – to je temeljna nužnost. Dok JavaScript nudi dinamično i fleksibilno okruženje, njegovo labavo tipiziranje ponekad može dovesti do iznenađenja u vremenu izvođenja, posebno kada se radi o pogreškama. Tu na scenu stupa TypeScript, donoseći statičko provjeravanje tipova u prvi plan i nudeći moćne alate za poboljšanje predvidljivosti i održivosti koda.
Upravljanje pogreškama ključni je aspekt svake robusne aplikacije. Bez jasne strategije, neočekivani problemi mogu dovesti do nepredvidivog ponašanja, oštećenja podataka ili čak potpunog kvara sustava. Kada se kombinira s TypeScriptovom sigurnošću tipova, upravljanje pogreškama transformira se iz defanzivnog zadatka kodiranja u strukturirani, predvidljivi i upravljivi dio arhitekture vaše aplikacije.
Ovaj sveobuhvatni vodič duboko zaranja u nijanse TypeScript upravljanja pogreškama, istražujući razne obrasce i najbolje prakse za osiguravanje sigurnosti tipova iznimki. Pomaknut ćemo se dalje od osnovnog try...catch bloka, otkrivajući kako iskoristiti TypeScript značajke za definiranje, hvatanje i obradu pogrešaka s neusporedivom preciznošću. Bez obzira gradite li složenu poduzeću aplikaciju, web uslugu velikog prometa ili napredno frontend iskustvo, razumijevanje ovih obrazaca osnažit će vas da pišete pouzdaniji, lakši za otklanjanje pogrešaka i održiviji kod za globalnu publiku razvojnih inženjera i korisnika.
Temelj: JavaScriptov Objekt pogreške i try...catch
Prije nego što istražimo TypeScript poboljšanja, nužno je razumjeti temelj upravljanja pogreškama u JavaScriptu. Osnovni mehanizam je Error objekt, koji služi kao osnova za sve standardne ugrađene pogreške.
Standardni tipovi pogrešaka u JavaScriptu
Error: Generički bazni objekt pogreške. Većina prilagođenih pogrešaka nasljeđuje ovo.TypeError: Označava da je operacija izvršena na vrijednosti pogrešnog tipa.ReferenceError: Baca se kada se napravi nevažeća referenca (npr. pokušaj korištenja nedeklarirane varijable).RangeError: Označava da je brojčana varijabla ili parametar izvan svog važećeg raspona.SyntaxError: Nastaje prilikom parsiranja koda koji nije valjani JavaScript.URIError: Baca se kada se funkcije poputencodeURI()ilidecodeURI()nepravilno koriste.EvalError: Odnosi se na globalnu funkcijueval()(manje uobičajeno u modernom kodu).
Osnovni try...catch blokovi
Temeljni način za obradu sinkronih pogrešaka u JavaScriptu (i TypeScriptu) je try...catch izraz:
function divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Division by zero is not allowed.");
}
return a / b;
}
try {
const result = divide(10, 0);
console.log(`Result: ${result}`);
} catch (error) {
console.error("An error occurred:", error);
}
// Output:
// An error occurred: Error: Division by zero is not allowed.
U tradicionalnom JavaScriptu, parametar catch bloka implicitno je imao any tip. To je značilo da ste error mogli tretirati kao bilo što, što je vodilo potencijalnim problemima u vremenu izvođenja ako ste očekivali određeni oblik pogreške, a dobili ste nešto drugo (npr. string ili broj). Taj nedostatak sigurnosti tipova mogao je učiniti upravljanje pogreškama krhkim i teškim za otklanjanje grešaka.
TypeScriptova evolucija: unknown tip u Catch klauzulama
S uvođenjem TypeScripta 4.4, tip varijable catch klauzule promijenjen je s any na unknown. Ovo je bilo značajno poboljšanje za sigurnost tipova. unknown tip prisiljava razvojne inženjere da eksplicitno suze tip pogreške prije rada s njom. To znači da ne možete jednostavno pristupiti svojstvima poput error.message ili error.statusCode bez prethodnog potvrđivanja ili provjere tipa error. Ova promjena odražava predanost jačim tipskim jamstvima, sprječavajući uobičajene propuste gdje razvojni inženjeri pogrešno pretpostavljaju oblik pogreške.
try {
throw "Oops, something went wrong!"; // Throwing a string, which is valid in JS
} catch (error) {
// In TS 4.4+, 'error' is of type 'unknown'
// console.log(error.message); // ERROR: 'error' is of type 'unknown'.
}
Ova strogost je značajka, a ne greška. Prisiljava nas na pisanje robusnije logike upravljanja pogreškama, postavljajući temelje za sigurne uzorke tipova koje ćemo sljedeće istražiti.
Zašto je sigurnost tipova u pogreškama ključna za globalne aplikacije
Za aplikacije koje opslužuju globalnu korisničku bazu i razvijaju ih međunarodni timovi, dosljedno i predvidljivo upravljanje pogreškama je od paramountnog značaja. Sigurnost tipova u pogreškama nudi nekoliko distinct prednosti:
- Poboljšana pouzdanost i stabilnost: Eksplicitnim definiranjem tipova pogrešaka sprječavate neočekivane padove u vremenu izvođenja koji bi mogli nastati pokušajem pristupa nepostojećim svojstvima na pogrešno oblikovanom objektu pogreške. Ovo dovodi do stabilnijih aplikacija, što je kritično za usluge gdje nedostupnost može imati značajne financijske ili reputacijske troškove na različitim tržištima.
- Poboljšano iskustvo razvojnog inženjera (DX) i održivost: Kada razvojni inženjeri jasno razumiju koje pogreške funkcija može baciti ili vratiti, mogu pisati ciljaniju i učinkovitiju logiku obrade. Ovo smanjuje kognitivno opterećenje, ubrzava razvoj i čini kod lakšim za održavanje i refaktoriranje, posebno u velikim, distribuiranim timovima koji se protežu kroz različite vremenske zone i kulturne pozadine.
- Predvidljiva logika upravljanja pogreškama: Sigurne pogreške tipova omogućuju iscrpno provjeravanje. Možete pisati
switchizraze iliif/else iflance koji pokrivaju sve moguće tipove pogrešaka, osiguravajući da nijedna pogreška ne prođe neobrađena. Ova predvidljivost je vitalna za sustave koji moraju pridržavati stroge sporazume o razini usluge (SLA) ili standarde regulatorne sukladnosti diljem svijeta. - Bolje otklanjanje grešaka i rješavanje problema: Specifični tipovi pogrešaka s bogatim metapodacima pružaju neprocjenjiv kontekst tijekom otklanjanja grešaka. Umjesto generičkog "nešto je pošlo po zlu", dobivate precizne informacije poput
NetworkErrorsastatusCode: 503iliValidationErrors popisom nevažećih polja. Ova jasnoća drastično smanjuje vrijeme provedeno u dijagnosticiranju problema, što je velika prednost za operativne timove koji rade diljem različitih geografskih lokacija. - Jasni API ugovori: Pri dizajniranju API-ja ili modularnih jedinica koje se mogu ponovno koristiti, eksplicitno navođenje tipova pogrešaka koje se mogu baciti postaje dio ugovora funkcije. Ovo poboljšava točke integracije, omogućujući drugim uslugama ili timovima da s vašim kodom komuniciraju predvidljivije i sigurnije.
- Omogućuje internacionalizaciju poruka o pogreškama: S dobro definiranim tipovima pogrešaka, možete mapirati specifične kodove pogrešaka na lokalizirane poruke za korisnike na različitim jezicima i kulturama.
UserNotFoundErrormože prikazati "User not found" na engleskom, "Utilisateur introuvable" na francuskom ili "Usuario no encontrado" na španjolskom, poboljšavajući korisničko iskustvo globalno bez izmjene temeljne logike upravljanja pogreškama.
Prihvaćanje sigurnosti tipova u upravljanju pogreškama ulaganje je u budućnost vaše aplikacije, osiguravajući da ostane robusna, skalabilna i upravljiva kako se razvija i služi globalnoj publici.
Obrazac 1: Provjera tipa u vremenu izvođenja (Sužavanje unknown pogrešaka)
S obzirom na to da su varijable catch bloka tipizirane kao unknown u TypeScriptu 4.4+, prvi i najtemeljniji obrazac je sužavanje tipa pogreške unutar catch bloka. Ovo osigurava da pristupate samo svojstvima koja su zajamčeno postojala na objektu pogreške nakon provjere.
Korištenje instanceof Error
Najčešći i najjednostavniji način sužavanja unknown pogreške je provjera je li ona instanca ugrađene Error klase (ili jedne od njezinih izvedenih klasa poput TypeError, ReferenceError, itd.).
function riskyOperation(): void {
// Simulate different types of errors
const rand = Math.random();
if (rand < 0.3) {
throw new Error("Generic error occurred!");
} else if (rand < 0.6) {
throw new TypeError("Invalid data type provided.");
} else {
throw { code: 500, message: "Internal Server Error" }; // Non-Error object
}
}
try {
riskyOperation();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`Caught an Error object: ${error.message}`);
// You can also check for specific Error subclasses
if (error instanceof TypeError) {
console.error("Specifically, a TypeError was caught.");
}
} else if (typeof error === 'string') {
console.error(`Caught a string error: ${error}`);
} else if (typeof error === 'object' && error !== null && 'message' in error) {
// Handle custom objects that have a 'message' property
console.error(`Caught a custom error object with message: ${(error as { message: string }).message}`);
} else {
console.error("An unexpected type of error occurred:", error);
}
}
Ovaj pristup pruža osnovnu sigurnost tipova, omogućujući vam pristup message i name svojstvima standardnih Error objekata. Međutim, za specifičnije scenarije pogrešaka, trebat će vam bogatije informacije.
Prilagođeni Tipski Čuvari za Specifične Objekt Pogreške
Često će vaša aplikacija definirati vlastite prilagođene strukture pogrešaka, možda sadržavajući specifične kodove pogrešaka, jedinstvene identifikatore ili dodatne metapodatke. Kako biste sigurno pristupili ovim prilagođenim svojstvima, možete stvoriti korisnički definirane tipove čuvara.
// 1. Define custom error interfaces/types
interface NetworkError {
name: "NetworkError";
message: string;
statusCode: number;
url: string;
}
interface ValidationError {
name: "ValidationError";
message: string;
fields: { [key: string]: string };
}
// 2. Create type guards for each custom error
function isNetworkError(error: unknown): error is NetworkError {
return (
typeof error === 'object' &&
error !== null &&
'name' in error &&
(error as { name: string }).name === "NetworkError" &&
'message' in error &&
'statusCode' in error &&
'url' in error
);
}
function isValidationError(error: unknown): error is ValidationError {
return (
typeof error === 'object' &&
error !== null &&
'name' in error &&
(error as { name: string }).name === "ValidationError" &&
'message' in error &&
'fields' in error &&
typeof (error as { fields: unknown }).fields === 'object'
);
}
// 3. Example usage in a 'try...catch' block
function fetchData(url: string): Promise<any> {
return new Promise((resolve, reject) => {
// Simulate an API call that might throw different errors
const rand = Math.random();
if (rand < 0.4) {
reject(new Error("Something unexpected happened."));
} else if (rand < 0.7) {
reject({
name: "NetworkError",
message: "Failed to fetch data",
statusCode: 503,
url
} as NetworkError);
} else {
reject({
name: "ValidationError",
message: "Invalid input data",
fields: { 'email': 'Invalid format' }
} as ValidationError);
}
});
}
async function processData() {
const url = "https://api.example.com/data";
try {
const data = await fetchData(url);
console.log("Data fetched successfully:", data);
} catch (error: unknown) {
if (isNetworkError(error)) {
console.error(`Network Error from ${error.url}: ${error.message} (Status: ${error.statusCode})`);
// Specific handling for network issues, e.g., retry logic or user notification
} else if (isValidationError(error)) {
console.error(`Validation Error: ${error.message}`);
console.error("Invalid fields:", error.fields);
// Specific handling for validation errors, e.g., display errors next to form fields
} else if (error instanceof Error) {
console.error(`Standard Error: ${error.message}`);
} else {
console.error("An unknown or unexpected error type occurred:", error);
// Fallback for truly unexpected errors
}
}
}
processData();
Ovaj obrazac čini vašu logiku upravljanja pogreškama znatno robusnijom i čitljivijom. Prisiljava vas na razmatranje i eksplicitnu obradu različitih scenarija pogrešaka, što je ključno za izgradnju održivih aplikacija.
Obrazac 2: Prilagođene klase pogrešaka
Iako su tipski čuvari na sučeljima korisni, strukturiraniji i objektno orijentirani pristup je definiranje prilagođenih klasa pogrešaka. Ovaj obrazac omogućuje vam iskorištavanje nasljeđivanja, stvarajući hijerarhiju specifičnih tipova pogrešaka koje se mogu precizno hvatati i obrađivati pomoću instanceof provjera, slično ugrađenim JavaScript pogreškama, ali s vašim vlastitim prilagođenim svojstvima.
Nasljeđivanje ugrađene Error klase
Najbolja praksa za prilagođene pogreške u TypeScriptu (i JavaScriptu) je nasljeđivanje bazne Error klase. Ovo osigurava da vaše prilagođene pogreške zadrže svojstva poput message i stack, koja su vitalna za otklanjanje grešaka i logiranje.
// Base Custom Error
class CustomApplicationError extends Error {
constructor(message: string, public code: string = 'GENERIC_ERROR') {
super(message);
this.name = this.constructor.name; // Sets the error name to the class name
// Preserve stack trace for better debugging
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
// Specific Custom Errors
class DatabaseConnectionError extends CustomApplicationError {
constructor(message: string, public databaseName: string, public connectionString?: string) {
super(message, 'DB_CONN_ERROR');
}
}
class UserAuthenticationError extends CustomApplicationError {
constructor(message: string, public userId?: string, public reason: 'INVALID_CREDENTIALS' | 'SESSION_EXPIRED' | 'FORBIDDEN' = 'INVALID_CREDENTIALS') {
super(message, 'AUTH_ERROR');
}
}
class DataValidationFailedError extends CustomApplicationError {
constructor(message: string, public invalidFields: { [key: string]: string }) {
super(message, 'VALIDATION_ERROR');
}
}
Prednosti prilagođenih klasa pogrešaka
- Semantičko značenje: Nazivi klasa pogrešaka pružaju trenutan uvid u prirodu problema (npr.
DatabaseConnectionErrorjasno ukazuje na problem s bazom podataka). - Proširivost: Možete dodati specifična svojstva svakom tipu pogreške (npr.
statusCode,userId,fields) koja su relevantna za taj specifični kontekst pogreške, obogaćujući informacije o pogrešci za otklanjanje grešaka i obradu. - Jednostavno identificiranje pomoću
instanceof: Hvatanje i razlikovanje između različitih prilagođenih pogrešaka postaje trivijalno pomoćuinstanceof, omogućujući preciznu logiku upravljanja pogreškama. - Održivost: Centralizacija definicija pogrešaka čini vaš kod lakšim za razumijevanje i upravljanje. Ako svojstva pogreške promjene, ažurirate jednu definiciju klase.
- Podrška alatima: IDE-ovi i lintersi često mogu pružiti bolja sugestije i upozorenja kada rade s različitim klasama pogrešaka.
Obrada prilagođenih klasa pogrešaka
function performDatabaseOperation(query: string): any {
const rand = Math.random();
if (rand < 0.4) {
throw new DatabaseConnectionError("Failed to connect to primary DB", "users_db");
} else if (rand < 0.7) {
throw new UserAuthenticationError("User session expired", "user123", 'SESSION_EXPIRED');
} else {
throw new DataValidationFailedError("User input invalid", { 'name': 'Name is too short', 'email': 'Invalid email format' });
}
}
try {
performDatabaseOperation("SELECT * FROM users");
} catch (error: unknown) {
if (error instanceof DatabaseConnectionError) {
console.error(`Database Error: ${error.message}. DB: ${error.databaseName}. Code: ${error.code}`);
// Logic to attempt reconnect or notify ops team
} else if (error instanceof UserAuthenticationError) {
console.warn(`Authentication Error (${error.reason}): ${error.message}. User ID: ${error.userId || 'N/A'}`);
// Logic to redirect to login page or refresh token
} else if (error instanceof DataValidationFailedError) {
console.error(`Validation Error: ${error.message}. Invalid fields: ${JSON.stringify(error.invalidFields)}`);
// Logic to display validation messages to the user
} else if (error instanceof Error) {
console.error(`An unexpected standard error occurred: ${error.message}`);
} else {
console.error("A truly unexpected error occurred:", error);
}
}
Korištenje prilagođenih klasa pogrešaka značajno podiže kvalitetu vašeg upravljanja pogreškama. Omogućuje vam izgradnju sofisticiranih sustava upravljanja pogreškama koji su istovremeno robusni i lako shvatljivi, što je posebno vrijedno za velike aplikacije sa složenom poslovnom logikom.
Obrazac 3: Rezultat/Either Monad Obrazac (Eksplicitno upravljanje pogreškama)
Iako try...catch s prilagođenim klasama pogrešaka pruža robusnu obradu iznimki, neki funkcionalni programski paradigmi tvrde da iznimke prekidaju normalni tok kontrole i mogu učiniti kod težim za shvaćanje, posebno kada se radi o asinkronim operacijama. Obrazac "Rezultat" ili "Either" nudi alternativu čineći uspjeh i neuspjeh eksplicitnim u povratnom tipu funkcije, prisiljavajući pozivatelja da obradi oba ishoda bez oslanjanja na `try/catch` za kontrolu toka.
Što je Rezultat/Either obrazac?
Umjesto bacanja pogreške, funkcija koja može ne uspjeti vraća poseban tip (često nazvan Result ili Either) koji inkapsulira ili uspješnu vrijednost (Ok ili Right) ili pogrešku (Err ili Left). Ovaj obrazac je čest u jezicima poput Rusta (Result<T, E>) i Scali (Either<L, R>).
Temeljna ideja je da vam sam povratni tip govori da funkcija ima dva moguća ishoda, a TypeScriptov tipski sustav osigurava da obradite oba.
Implementacija jednostavnog Result tipa
type Result<T, E> = { success: true; value: T } | { success: false; error: E };
// Helper functions to create Ok and Err results
const ok = <T, E>(value: T): Result<T, E> => ({ success: true, value });
const err = <T, E>(error: E): Result<T, E> => ({ success: false, error });
interface User {
id: string;
name: string;
email: string;
}
// Custom errors for this pattern (can still use classes)
class UserNotFoundError extends Error {
constructor(userId: string) {
super(`User with ID '${userId}' not found.`);
this.name = 'UserNotFoundError';
}
}
class DatabaseReadError extends Error {
constructor(message: string, public details?: string) {
super(message);
this.name = 'DatabaseReadError';
}
}
// Function that returns a Result type
function getUserById(id: string): Result<User, UserNotFoundError | DatabaseReadError> {
// Simulate database operation
const rand = Math.random();
if (rand < 0.3) {
return err(new UserNotFoundError(id)); // Return an error result
} else if (rand < 0.6) {
return err(new DatabaseReadError("Failed to read from DB", "Connection timed out")); // Return a database error
} else {
return ok({
id: id,
name: "John Doe",
email: `john.${id}@example.com`
}); // Return a success result
}
}
// Consuming the Result type
const userResult = getUserById("user-123");
if (userResult.success) {
console.log(`User found: ${userResult.value.name}, Email: ${userResult.value.email}`);
} else {
// TypeScript knows userResult.error is of type UserNotFoundError | DatabaseReadError
if (userResult.error instanceof UserNotFoundError) {
console.error(`Application Error: ${userResult.error.message}`);
// Logic for user not found, e.g., display a message to the user
} else if (userResult.error instanceof DatabaseReadError) {
console.error(`System Error: ${userResult.error.message}. Details: ${userResult.error.details}`);
// Logic for database issue, e.g., retry or alert system administrators
} else {
// Exhaustive check or fallback for other potential errors
console.error("An unexpected error occurred:", userResult.error);
}
}
Ovaj obrazac može biti posebno moćan kada se lančaju operacije koje mogu propasti, jer možete koristiti map, flatMap (ili andThen) i druge funkcionalne konstrukcije za obradu Result bez eksplicitnih if/else provjera na svakom koraku, odgađajući obradu pogrešaka do jedne točke.
Prednosti Rezultat obrasca
- Eksplicitna obrada pogrešaka: Funkcije eksplicitno deklariraju koje pogreške mogu vratiti u svojoj tipskoj signaturi, prisiljavajući pozivatelja da prizna i obradi sve moguće neuspješne stanja. Ovo eliminira "zaboravljene" iznimke.
- Referencijalna transparentnost: Izbjegavanjem iznimki kao mehanizma kontrole toka, funkcije postaju predvidljivije i lakše za testiranje.
- Poboljšana čitljivost: Put koda za uspjeh i neuspjeh jasno je razgraničen, što olakšava praćenje logike.
- Sastavljivost: Rezultat tipovi dobro se sastavljaju s funkcionalnim programskim tehnikama, omogućujući elegantno širenje i transformaciju pogrešaka.
- Nema
try...catchboilerplatea: U mnogim scenarijima, ovaj obrazac može smanjiti potrebu zatry...catchblokovima, posebno kada se sastavljaju višestruke operacije koje mogu propasti.
Razmatranja i kompromisi
- Opširnost: Može biti opširniji za jednostavne operacije ili kada se ne koriste funkcionalne konstrukcije učinkovito.
- Krivulja učenja: Razvojni inženjeri novi u funkcionalnom programiranju ili monadima mogu ovaj obrazac u početku smatrati složenim.
- Asinkrone operacije: Iako primjenjiv, integracija s postojećim asinkronim kodom temeljenim na Promises zahtijeva pažljivo omotavanje ili transformaciju. Knjižnice poput
neverthrowilifp-tspružaju sofisticiranije implementacije `Either`/`Result` prilagođene TypeScriptu, često s boljom podrškom za asinkrone operacije.
Rezultat/Either obrazac izvrstan je izbor za aplikacije koje daju prednost eksplicitnom upravljanju pogreškama, funkcionalnoj čistoći i jakom naglasku na sigurnosti tipova kroz sve putanje izvršavanja. Posebno je pogodan za kritične sustave gdje se mora izričito računati svaki mogući način kvara.
Obrazac 4: Centralizirane strategije upravljanja pogreškama
Iako pojedinačni `try...catch` blokovi i Rezultat tipovi obrađuju lokalne pogreške, veće aplikacije, posebno one koje služe globalnoj publici, iznimno profitiraju od centraliziranih strategija upravljanja pogreškama. Ove strategije osiguravaju dosljedno izvješćivanje o pogreškama, logiranje i povratne informacije korisnicima diljem cijelog sustava, neovisno o tome odakle je pogreška potekla.
Globalni obrađivači pogrešaka
Centralizacija upravljanja pogreškama omogućuje vam:
- Dosljedno logiranje pogrešaka u sustav za nadzor (npr. Sentry, Datadog).
- Pružanje generičkih, korisniku-prijateljskih poruka o pogreškama za nepoznate pogreške.
- Upravljanje brigama cijele aplikacije poput slanja obavijesti, vraćanja transakcija ili pokretanja prekidača.
- Osiguravanje da se PII (Osobno identifikacijski podaci) ili osjetljivi podaci ne izlažu u porukama o pogreškama korisnicima ili zapisima kršeći propise o privatnosti podataka (npr. GDPR, CCPA).
Backend (Node.js/Express) Primjer
U Node.js Express aplikaciji, možete definirati middleware za obradu pogrešaka koji hvata sve pogreške koje bacaju vaše rute i drugi middlewarevi. Ovaj middleware treba biti zadnji registrirani.
import express, { Request, Response, NextFunction } from 'express';
// Assume these are our custom error classes
class APIError extends Error {
constructor(message: string, public statusCode: number = 500) {
super(message);
this.name = 'APIError';
}
}
class UnauthorizedError extends APIError {
constructor(message: string = 'Unauthorized') {
super(message, 401);
this.name = 'UnauthorizedError';
}
}
class BadRequestError extends APIError {
constructor(message: string = 'Bad Request') {
super(message, 400);
this.name = 'BadRequestError';
}
}
const app = express();
app.get('/api/users/:id', (req: Request, res: Response, next: NextFunction) => {
const userId = req.params.id;
if (userId === 'admin') {
return next(new UnauthorizedError('Access denied for admin user.'));
}
if (!/^[a-z0-9]+$/.test(userId)) {
return next(new BadRequestError('Invalid user ID format.'));
}
// Simulate a successful operation or another unexpected error
const rand = Math.random();
if (rand < 0.5) {
// Successfully fetch user
res.json({ id: userId, name: 'Test User' });
} else {
// Simulate an unexpected internal error
next(new Error('Failed to retrieve user data due to an unexpected issue.'));
}
});
// Type-safe error handling middleware
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
// Log the error for internal monitoring
console.error(`[ERROR] ${new Date().toISOString()} - ${req.method} ${req.originalUrl} -`, err);
if (err instanceof APIError) {
// Specific handling for known API errors
return res.status(err.statusCode).json({
status: 'error',
message: err.message,
code: err.name // Or a specific application-defined error code
});
} else if (err instanceof Error) {
// Generic handling for unexpected standard errors
return res.status(500).json({
status: 'error',
message: 'An unexpected server error occurred.',
// In production, avoid exposing detailed internal error messages to clients
detail: process.env.NODE_ENV === 'development' ? err.message : undefined
});
} else {
// Fallback for truly unknown error types
return res.status(500).json({
status: 'error',
message: 'An unknown server error occurred.',
detail: process.env.NODE_ENV === 'development' ? String(err) : undefined
});
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// Example cURL commands:
// curl http://localhost:3000/api/users/admin
// curl http://localhost:3000/api/users/invalid-id!
// curl http://localhost:3000/api/users/valid-id
Frontend (React) Primjer: Error Boundaries
U frontend okvirima poput Reacta, Error Boundaries pružaju način za hvatanje JavaScript pogrešaka bilo gdje u njihovom stablu komponenti potomaka, logiranje tih pogrešaka i prikazivanje fallback UI-ja umjesto rušenja cijele aplikacije. TypeScript pomaže u definiranju svojstava i stanja za ove granice i provjeru tipa objekta pogreške.
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode; // Optional custom fallback UI
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
}
class AppErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
public state: ErrorBoundaryState = {
hasError: false,
error: null,
errorInfo: null,
};
// This static method is called after an error has been thrown by a descendant component.
static getDerivedStateFromError(_: Error): ErrorBoundaryState {
// Update state so the next render will show the fallback UI.
return { hasError: true, error: _, errorInfo: null };
}
// This method is called after an error has been thrown by a descendant component.
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// You can also log the error to an error reporting service here
console.error("Uncaught error in AppErrorBoundary:", error, errorInfo);
this.setState({ errorInfo: errorInfo, error: error });
}
public render() {
if (this.state.hasError) {
// You can render any custom fallback UI
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div style={{ padding: '20px', border: '1px solid red', borderRadius: '5px' }}>
<h2>Oops! Something went wrong.</h2>
<p>We're sorry for the inconvenience. Please try refreshing the page or contact support.</p>
{this.state.error && (
<details style={{ whiteSpace: 'pre-wrap', color: '#666' }}>
<summary>Error Details</summary>
<p>{this.state.error.message}</p>
{this.state.errorInfo && (
<p>Component Stack:<br/>{this.state.errorInfo.componentStack}</p>
)}
</details>
)}
</div>
);
}
return this.props.children;
}
}
// How to use it:
// function App() {
// return (
// <AppErrorBoundary>
// <SomePotentiallyFailingComponent />
// </AppErrorBoundary>
// );
// }
Razlikovanje operativnih od programerskih pogrešaka
Ključni aspekt centraliziranog upravljanja pogreškama je razlikovanje između dvije glavne kategorije pogrešaka:
- Operativne pogreške: Ovo su predvidljivi problemi koji se mogu pojaviti tijekom normalnog rada, često izvan osnovne logike aplikacije. Primjeri uključuju mrežne vremenske prekide, greške povezivanja s bazom podataka, nevažeći unos korisnika, datoteka nije pronađena ili ograničenja brzine. Ove pogreške treba obraditi graciozno, često rezultirajući korisnički-prijateljskim porukama ili specifičnom logikom ponovnog pokušaja. One obično ne ukazuju na grešku u vašem kodu. Prilagođene klase pogrešaka sa specifičnim kodovima pogrešaka izvrsne su za ovo.
- Programerske pogreške: Ovo su greške u vašem kodu. Primjeri uključuju
ReferenceError(korištenje nedefinirane varijable),TypeError(pozivanje metode nanull) ili logičke greške koje dovode do neočekivanih stanja. Ove su općenito neopozive u vremenu izvođenja i zahtijevaju popravak koda. Globalni obrađivači pogrešaka trebaju ih opsežno logirati i potencijalno pokrenuti ponovno pokretanje aplikacije ili upozorenja timu za razvoj.
Kategoriziranjem pogrešaka, vaš centralizirani obrađivač može odlučiti hoće li prikazati generičku poruku o pogrešci, pokušati oporavak ili eskalirati problem razvojnim inženjerima. Ovo razlikovanje je vitalno za održavanje zdrave i responzivne aplikacije u različitim okruženjima.
Najbolje prakse za tipski sigurno upravljanje pogreškama
Da biste maksimalno iskoristili prednosti TypeScripta u svojoj strategiji upravljanja pogreškama, razmotrite ove najbolje prakse:
- Uvijek sužavajte
unknownucatchblokovima: Od TypeScripta 4.4+,catchvarijabla jeunknown. Uvijek provodite provjere tipova u vremenu izvođenja (npr.instanceof Error, prilagođeni tipski čuvari) kako biste sigurno pristupili svojstvima pogreške. Ovo sprječava uobičajene pogreške u vremenu izvođenja. - Dizajnirajte semantički bogate prilagođene klase pogrešaka: Naslijedite baznu
Errorklasu kako biste stvorili specifične, semantički bogate tipove pogrešaka. Uključite relevantna svojstva specifična za kontekst (npr.statusCode,errorCode,invalidFields,userId) kako biste pomogli u otklanjanju grešaka i obradi. - Budite eksplicitni o ugovorima o pogreškama: Dokumentirajte pogreške koje funkcija može baciti ili vratiti. Ako koristite Rezultat obrazac, ovo je prisiljeno tipskom signaturom povratne vrijednosti. Za `try/catch`, jasni JSDoc komentari ili potpisi funkcija koji prenose potencijalne iznimke su vrijedni.
- Logirajte pogreške sveobuhvatno: Koristite strukturirani pristup logiranju. Uhvatite puni slijed poziva pogreške (stack trace), zajedno s bilo kojim prilagođenim svojstvima pogreške i kontekstualnim informacijama (npr. ID zahtjeva, ID korisnika, vremenska oznaka, okruženje). Za kritične aplikacije, integrirajte se s centraliziranim sustavom logiranja i nadzora (npr. ELK Stack, Splunk, DataDog, Sentry).
- Izbjegavajte bacanje generičkih
stringiliobjecttipova: Iako JavaScript to dopušta, bacanje sirovih nizova, brojeva ili običnih objekata onemogućuje tipski sigurno upravljanje pogreškama i dovodi do krhkog koda. Uvijek bacajte instanceErrorili prilagođene klase pogrešaka. - Iskoristite
neverza iscrpno provjeravanje: Kada radite s unijom prilagođenih tipova pogrešaka (npr. uswitchizrazu ili nizuif/else if), koristite tipski čuvar koji vodi do `never` tipa za završnielseblok. Ovo osigurava da ako se uvede novi tip pogreške, TypeScript će označiti neobrađeni slučaj. - Prevedite pogreške za korisničko iskustvo: Internim porukama o pogreškama su za razvojne inženjere. Za krajnje korisnike, prevedite tehničke pogreške u jasne, djelotvorne i kulturno prikladne poruke. Razmotrite korištenje kodova pogrešaka koji se mapiraju na lokalizirane poruke kako biste podržali internacionalizaciju.
- Razlikujte između oporavivih i neopravivih pogrešaka: Dizajnirajte svoju logiku upravljanja pogreškama kako biste razlikovali pogreške koje se mogu pokušati ponovno ili samostalno ispraviti (npr. mrežni problemi) od onih koje ukazuju na fatalni nedostatak aplikacije (npr. neobrađene programerske pogreške).
- Testirajte svoje putanje pogrešaka: Baš kao što testirate sretne putanje (happy paths), temeljito testirajte svoje putanje pogrešaka. Osigurajte da vaša aplikacija graciozno obrađuje sve očekivane uvjete pogreške i predvidljivo propada kada se dogode neočekivane.
type SpecificError = DatabaseConnectionError | UserAuthenticationError | DataValidationFailedError;
function handleSpecificError(error: SpecificError) {
if (error instanceof DatabaseConnectionError) {
// ...
} else if (error instanceof UserAuthenticationError) {
// ...
} else if (error instanceof DataValidationFailedError) {
// ...
} else {
// This line should ideally be unreachable. If it is, a new error type was added
// to SpecificError but not handled here, causing a TS error.
const exhaustiveCheck: never = error; // TypeScript will flag this if 'error' is not 'never'
}
}
Pridržavanje ovih praksi podići će vaše TypeScript aplikacije s pukog funkcionalnog na robusne, pouzdane i visoko održive, sposobne služiti raznolikoj korisničkoj bazi diljem svijeta.
Uobičajeni propusti i kako ih izbjeći
Čak i s najboljim namjerama, razvojni inženjeri mogu upasti u uobičajene zamke kada upravljaju pogreškama u TypeScriptu. Biti svjestan ovih propusta može vam pomoći da ih izbjegnete.
- Ignoriranje
unknowntipa ucatchblokovima:Propust: Izravno pretpostavljanje tipa
errorucatchbloku bez sužavanja.try { throw new Error("Oops"); } catch (error) { // Type 'unknown' is not assignable to type 'Error'. // Property 'message' does not exist on type 'unknown'. // console.error(error.message); // This will be a TypeScript error! }Izbjegavanje: Uvijek koristite
instanceof Errorili prilagođene tipove čuvara za sužavanje tipa.try { throw new Error("Oops"); } catch (error: unknown) { if (error instanceof Error) { console.error(error.message); } else { console.error("A non-Error type was thrown:", error); } } - Preterano generaliziranje
catchblokova:Propust: Hvatanje
Errorkada namjeravate obraditi samo specifičnu prilagođenu pogrešku. Ovo može prikriti temeljne probleme.// Assume a custom APIError class APIError extends Error { /* ... */ } function fetchData() { throw new APIError("Failed to fetch"); } function processData() { try { fetchData(); } catch (error: unknown) { // This catches APIError, but also *any* other Error that might be thrown // by fetchData or other code in the try block, potentially masking bugs. if (error instanceof Error) { console.error("Caught a generic error:", error.message); } } }Izbjegavanje: Budite što specifičniji. Ako očekujete specifične prilagođene pogreške, prvo uhvatite te. Koristite fallback za generičke
Erroriliunknown.try { fetchData(); } catch (error: unknown) { if (error instanceof APIError) { // Handle APIError specifically console.error("API Error:", error.message); } else if (error instanceof Error) { // Handle other standard errors console.error("Unexpected standard Error:", error.message); } else { // Handle truly unknown errors console.error("Truly unexpected error:", error); } } - Nedostatak specifičnih poruka o pogreškama i konteksta:
Propust: Bacanje generičkih poruka poput "Došlo je do pogreške" bez pružanja korisnog konteksta, otežavajući otklanjanje grešaka.
throw new Error("Something went wrong."); // Not very helpfulIzbjegavanje: Osigurajte da su poruke o pogreškama opisne i uključuju relevantne podatke (npr. vrijednosti parametara, putanje datoteka, ID-ove). Prilagođene klase pogrešaka sa specifičnim svojstvima izvrsne su za ovo.
throw new DatabaseConnectionError("Failed to connect to DB", "users_db", "mongodb://localhost:27017"); - Ne razlikovanje između pogrešaka usmjerenih korisniku i internih pogrešaka:
Propust: Prikazivanje sirovih tehničkih poruka o pogreškama (npr. slijed poziva, pogreške upita baze podataka) izravno krajnjim korisnicima.
// Bad: Exposing internal details to the user catch (error: unknown) { if (error instanceof Error) { res.status(500).send(`<h1>Server Error</h1><p>${error.stack}</p>`); } }Izbjegavanje: Centralizirajte upravljanje pogreškama kako biste presreli interne pogreške i preveli ih u korisniku-prijateljske, lokalizirane poruke. Logirajte tehničke detalje samo za razvojne inženjere.
// Good: User-friendly message for client, detailed log for developers catch (error: unknown) { // ... logging for developers ... res.status(500).send("<h1>We're sorry!</h1><p>An unexpected error occurred. Please try again later.</p>"); } - Mutiranje objekata pogrešaka:
Propust: Izmjena
errorobjekta izravno unutar `catch` bloka, posebno ako se zatim ponovno baca ili prosljeđuje drugom obrađivaču. Ovo može dovesti do neočekivanih nuspojava ili gubitka izvornog konteksta pogreške.Izbjegavanje: Ako trebate obogatiti pogrešku, stvorite novi objekt pogreške koji omotava izvornik, ili zasebno proslijedite dodatni kontekst. Izvorni objekt pogreške trebao bi ostati nepromjenjiv za potrebe otklanjanja grešaka.
Svjesnim izbjegavanjem ovih uobičajenih propusta, vaše TypeScript upravljanje pogreškama postat će robusnije, transparentnije i na kraju će doprinijeti stabilnijoj i korisniku-prijateljskijoj aplikaciji.
Zaključak
Učinkovito upravljanje pogreškama ključni je kamen profesionalnog razvoja softvera, a TypeScript podiže ovu kritičnu disciplinu na nove visine. Prihvaćanjem tipski sigurnih obrazaca upravljanja pogreškama, razvojni inženjeri mogu se pomaknuti od reaktivnog ispravljanja grešaka prema proaktivnom dizajnu sustava, gradeći aplikacije koje su inherentno otpornije, predvidljivije i održivije.
Istražili smo nekoliko moćnih obrazaca:
- Provjera tipa u vremenu izvođenja: Sigurno sužavanje
unknownpogrešaka ucatchblokovima pomoćuinstanceof Errori prilagođenih tipskih čuvara za osiguravanje predvidljivog pristupa svojstvima pogreške. - Prilagođene klase pogrešaka: Dizajniranje hijerarhije semantičkih tipova pogrešaka koji nasljeđuju baznu
Error, pružajući bogate kontekstualne informacije i olakšavajući preciznu obradu pomoćuinstanceofprovjera. - Rezultat/Either Monad Obrazac: Alternativni funkcionalni pristup koji eksplicitno kodira uspjeh i neuspjeh u povratnim tipovima funkcija, prisiljavajući pozivatelje da obrade oba ishoda i smanjujući oslanjanje na tradicionalne mehanizme iznimaka.
- Centralizirano upravljanje pogreškama: Implementacija globalnih obrađivača pogrešaka (npr. middleware, error boundaries) kako bi se osiguralo dosljedno logiranje, nadzor i povratne informacije korisnicima diljem cijele aplikacije, razlikujući između operativnih i programerskih pogrešaka.
Svaki obrazac nudi jedinstvene prednosti, a optimalan izbor često ovisi o specifičnom kontekstu, arhitektonskom stilu i preferencijama tima. Međutim, zajednička nit koja prolazi kroz sve ove pristupe je predanost sigurnosti tipova. TypeScriptov rigorozni tipski sustav djeluje kao moćni čuvar, vodeći vas prema robusnijim ugovorima o pogreškama i pomažući vam da uhvatite potencijalne probleme u vrijeme kompilacije umjesto u vrijeme izvođenja.
Usvajanje ovih strategija je ulaganje koje donosi dividende u stabilnosti aplikacije, produktivnosti razvojnih inženjera i ukupnom zadovoljstvu korisnika, posebno kada djelujete u dinamičnom i raznolikom globalnom softverskom krajoliku. Počnite integrirati ove tipski sigurne obrasce upravljanja pogreškama u svoje TypeScript projekte danas i gradite aplikacije koje se snažno odupiru neizbježnim izazovima digitalnog svijeta.