programowanieProgramista Backend

Opisz, jak działa mechanizm typów błędów użytkownika (Custom Errors) w TypeScript. Jak prawidłowo deklarować klasy błędów użytkownika, dlaczego warto określać ich typ i jak unikać pułapek związanych z 'instanceof' oraz serializacją błędów?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania: W JavaScript standardowe błędy są opisane za pomocą klasy Error. W dużych projektach TypeScript ważne jest, aby używać swojej hierarchii błędów do dokładnego przetwarzania sytuacji, ale są pewne niuanse dotyczące typów, dziedziczenia i zachowania 'instanceof'.

Problem: Przy dziedziczeniu po Error mogą wystąpić trudności: prototyp zostaje utracony, 'instanceof' działa niepoprawnie, typy mogą być opisane niepoprawnie, a serializacja często prowadzi do utraty stack trace. Bez jawnego określenia właściwości można napotkać błędy podczas przetwarzania błędów.

Rozwiązanie: Poprawnie definiować błędy użytkownika poprzez rozszerzenie klasy Error. Należy jawnie określić nazwę, ręcznie przywrócić prototyp (ważne dla ES5 i przy kompilacji do CommonJS), oraz typizować pola błędów.

Przykład kodu:

class ValidationError extends Error { code: number; constructor(message: string, code: number = 400) { super(message); Object.setPrototypeOf(this, ValidationError.prototype); this.name = 'ValidationError'; this.code = code; } } function process(user: string) { if (!user) throw new ValidationError('User required', 401); }

Kluczowe cechy:

  • Jawne przywracanie prototypu dla wsparcia 'instanceof' nawet przy transpilacji.
  • Typizacja użytkowych właściwości błędów.
  • Indywidualna klasa dla każdej logicznej grupy błędów.

Pytania z pułapką.

Dlaczego nie można pominąć wywołania Object.setPrototypeOf(this, ...)?

Jeśli nie wywołasz Object.setPrototypeOf(this, Class.prototype), 'instanceof' ValidationError nie zadziała przy kompilacji do ES5/CommonJS lub Babel. Doprowadzi to do sytuacji, w której bloki catch ValidationError nie przechwycą błędu.

class CustomErr extends Error {} const err = new CustomErr('msg'); console.log(err instanceof CustomErr); // false bez setPrototypeOf

Czy można pomijać pole name w błędzie użytkownika?

Jeśli nie ustawisz właściwości this.name, stos błędów i wyświetlanie w logach będą niepoprawne, co utrudni znalezienie przyczyny i klasyfikację błędów.

Czy błędy powinny być serializowalne, a jeśli tak — jak?

Błędy powinny być poprawnie serializowane (na przykład do logowania lub przesyłania przez sieć), inaczej JSON.stringify(new Error()) nie zwróci message i stack. Należy nadpisać metodę toJSON.

class SerializableError extends Error { toJSON() { return { name: this.name, message: this.message, stack: this.stack }; } }

Typowe błędy i antywzorce

  • Brak Object.setPrototypeOf prowadzi do niepoprawnego działania instanceof
  • Ignorowanie name i użytkowych właściwości
  • Serializacja błędu bez zaimplementowanego toJSON: tracimy stack/message

Przykład z życia

Negatywny przypadek

W projekcie wykonano po prostu class MyError extends Error, nie przywracając prototypu. Błędy były łapane przez if (err instanceof MyError), ale to nie działało, a kod cicho pomijał obsługę krytycznych sytuacji.

Plusy:

  • Kod wyglądał zwięźle
  • Nie było zbędnych powtórzeń

Minusy:

  • instanceof nie działał
  • Wyjątki nie były poprawnie logowane
  • Przy zmianie transpileatora ts->js pojawiła się niekompatybilność

Pozytywny przypadek

Zrealizowano poprawny CustomError, jawnie określono name, code i toJSON, testy pokryły przetwarzanie różnych typów błędów. W logach i obsługach catch struktura błędów stała się zrozumiała, dzięki czemu skrócił się czas poszukiwania błędów.

Plusy:

  • Jasna typowa hierarchia błędów
  • Pewna serializacja
  • Wieloplatformowość między przeglądarką a Node.js

Minusy:

  • Pojawiła się szablonowa logika w każdej klasie CustomError