A váratlanra számítva – A hibakezelés legjobb gyakorlatai Angularban
“A váratlanra számítani alaposan modern intellektusról tanúskodik.” – Oscar Wilde
Ez a cikk a hibakezelés központosításáról szól Angularban. Tárgyalok néhány gyakori témát, mint például:
- kliensoldali hibák
- szerveroldali hibák
- felhasználói értesítés
- követési hibák
Mutatok néhány kódrészletet menet közben, végül pedig megadom a teljes példa linkjét.
Spanyol változat:
- Esperando lo Inesperado – Buenas prácticas para el manejo de errores en Angular
Kit okoljunk a hibákért?Link erre a részre
Miért vannak hibák az alkalmazásainkban? Miért nem tudunk olyan kódot írni a specifikációkból, ami mindig működik?
Végeredményben emberek készítenek szoftvereket, és hajlamosak vagyunk hibázni. Néhány ok a hibák mögött lehet:
- Az alkalmazás bonyolultsága
- Az érintettek közötti kommunikáció
- A fejlesztő hibái
- Az időnyomás
- A tesztelés hiánya
A listát még hosszan lehetne folytatni. Ezt szem előtt tartva eljön az idő, amikor a váratlan megtörténik, és hibát dobunk.
Catch them if you canLink to this section
A szinkron kivételek elkapásához a kódban hozzáadhatunk egy try/catch
blokkot. Ha a try
belsejében hibát dobunk, akkor catch
azt és kezeljük. Ha ezt nem tesszük meg, akkor a szkript végrehajtása leáll.
try { throw new Error('En error happened');}catch (error) { console.error('Log error', error);}console.log('Script execution continues');
Ez érthető módon nagyon gyorsan fenntarthatatlanná válik. Nem próbálhatjuk mindenhol a kódban a hibákat elkapni. Globális hibakezelésre van szükségünk.
Catch’em allLink erre a szakaszra
Szerencsére az Angular biztosít egy horgot a központosított kivételkezeléshez az ErrorHandlerrel.
A
ErrorHandler
alapértelmezett implementációja aconsole
hibaüzeneteket írja ki.
Módosíthatjuk ezt a viselkedést egy olyan osztály létrehozásával, amely megvalósítja az ErrorHandler-t:
import { ErrorHandler } from '@angular/core';@Injectable()export class GlobalErrorHandler implements ErrorHandler { handleError(error) { // your custom error handling logic }}
Ezt a gyökérmodulunkban biztosítjuk, hogy megváltoztassuk az alkalmazásunk alapértelmezett viselkedését. Az alapértelmezett ErrorHandler osztály helyett a mi osztályunkat használjuk.
<>Copy@NgModule({ providers: })
Így most már csak egy helyen kell módosítanunk a hibakezelés kódját.
Kliensoldali hibákLink erre a szakaszra
A kliensoldalon, ha valami váratlan történik, JavaScript hibát dobunk. Két fontos tulajdonsága van, amit használhatunk.
- message – A hiba ember által olvasható leírása.
- stack – A hiba veremkövetése az előzményekkel (call stack), hogy mely fájlok voltak “felelősek” a hiba előidézéséért.
Tipikusan a message tulajdonság az, amit a felhasználónak mutatunk, ha nem írjuk meg a hibaüzeneteket.
Szerveroldali hibákLink erre a szakaszra
A szerveroldalon, ha valami rosszul megy, egy HttpErrorResponse-t kapunk vissza. A JavaScript hibához hasonlóan ez is rendelkezik egy message tulajdonsággal, amelyet értesítésekhez használhatunk.
A hiba állapotkódját is visszaadja. Ezek különböző típusúak lehetnek. Ha négyessel kezdődik (4xx), akkor az ügyfél valami váratlant csinált. Ha például a 400-as státuszt kapjuk (Bad Request), akkor a kliens által küldött kérés nem az volt, amire a szerver számított.
A ötössel (5xx) kezdődő státuszok szerverhibák. A legtipikusabb az 500 Internal Server Error, egy nagyon általános HTTP státuszkód, amely azt jelenti, hogy valami elromlott a szerveren, de a szerver nem tudta pontosabban megmondani, hogy mi a pontos probléma.
A különböző típusú hibáknál hasznos egy olyan szolgáltatás, amely elemzi az üzeneteket és a stack traces-t belőlük.
Hiba szolgáltatásLink erre a szakaszra
Ezzel a szolgáltatással adjuk hozzá a logikát a szerver és a kliens hibaüzeneteinek és stack traces-einek elemzésére. Ez a példa nagyon leegyszerűsített. Fejlettebb felhasználási esetekhez használhatunk valami olyasmit, mint a stacktrace.js.
A logika ebben a szolgáltatásban attól függ, hogy milyen hibákat kapunk a backendtől. Attól is függ, hogy milyen üzenetet akarunk megjeleníteni a felhasználóinknak.
Általában nem mutatjuk meg a stack trace-t a felhasználóinknak. Ha azonban nem termelési környezetben vagyunk, előfordulhat, hogy a stack trace-t meg akarjuk mutatni a tesztelőknek. Ebben a forgatókönyvben beállíthatunk egy olyan zászlót, amely megmutatja a stack trace-t.
<>Copyimport { Injectable } from '@angular/core';import { HttpErrorResponse } from '@angular/common/http';@Injectable({ providedIn: 'root'})export class ErrorService { getClientMessage(error: Error): string { if (!navigator.onLine) { return 'No Internet Connection'; } return error.message ? error.message : error.toString(); } getClientStack(error: Error): string { return error.stack; } getServerMessage(error: HttpErrorResponse): string { return error.message; } getServerStack(error: HttpErrorResponse): string { // handle stack trace return 'stack'; }}
HttpInterceptorLink erre a szakaszra
A HttpInterceptor az Angular 4.3.1-gyel került bevezetésre. Lehetőséget biztosít a HTTP-kérések és -válaszok elfogására, hogy átalakítsuk vagy kezeljük őket, mielőtt továbbadnánk őket.
Két felhasználási esetet valósíthatunk meg az interceptorban.
Először is, egyszer vagy többször is újrapróbálhatjuk a HTTP-hívást, mielőtt hibát dobnánk. Bizonyos esetekben, például ha időtúllépést kapunk, folytathatjuk anélkül, hogy dobnánk a kivételt.
Ezért használjuk az RxJS retry operátorát, hogy újra feliratkozzunk a megfigyelhetőre.
További példák az ilyen viselkedésre:
- Egy megfigyelhető szekvencia újratöltése hiba esetén egyéni kritériumok alapján
- A RxJS ereje exponenciális backoff használata esetén
Ezután ellenőrizhetjük a kivétel állapotát, hogy 401 nem engedélyezett hibáról van-e szó. Token-alapú biztonság esetén megpróbálhatjuk frissíteni a tokent. Ha ez nem működik, akkor átirányíthatjuk a felhasználót a bejelentkezési oldalra.
<>Másolásimport { Injectable } from '@angular/core';import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';import { Observable, throwError } from 'rxjs';import { retry, catchError } from 'rxjs/operators';@Injectable()export class ServerErrorInterceptor implements HttpInterceptor { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request).pipe( retry(1), catchError((error: HttpErrorResponse) => { if (error.status === 401) { // refresh token } else { return throwError(error); } }) ); }}
Itt egyszer újrapróbáljuk, mielőtt ellenőriznénk a hiba állapotát, és újra eldobnánk a hibát. A biztonsági tokenek frissítése nem tartozik ennek a cikknek a tárgykörébe.
Az általunk létrehozott interceptort is meg kell adnunk.
<>Copyproviders:
NotificationsLink erre a szakaszra
Az értesítésekhez az Angular Material Snackbar-t használom.
<>Copyimport { Injectable} from '@angular/core';import { MatSnackBar } from '@angular/material/snack-bar';@Injectable({ providedIn: 'root'})export class NotificationService { constructor(public snackBar: MatSnackBar) { } showSuccess(message: string): void { this.snackBar.open(message); } showError(message: string): void { // The second parameter is the text in the button. // In the third, we send in the css class for the snack bar. this.snackBar.open(message, 'X', {panelClass: }); }}
Ezzel egyszerű értesítéseket kap a felhasználó hiba esetén.
A szerveroldali és kliensoldali hibákat különbözőképpen tudjuk kezelni. Értesítések helyett megjeleníthetünk egy hibaoldalt.
HibaüzenetLink erre a szakaszra
A hibaüzenetek számítanak, és ezért kell, hogy legyen valamilyen jelentésük, hogy segítsék a felhasználót a továbblépésben. A “Hiba történt” megjelenítésével nem mondjuk el a felhasználónak, hogy mi a probléma, vagy hogyan oldja meg azt.
Ha ehhez képest, ha ehelyett valami olyasmit mutatunk, hogy “Sajnálom, elfogyott a pénze.”, akkor a felhasználó tudja, hogy mi a hiba. Kicsit jobb, de nem segít neki a hiba megoldásában.
Még jobb megoldás lenne, ha azt mondanánk, hogy utaljon még pénzt, és megadnánk egy linket egy pénzátutalási oldalra.
Ne feledjük, hogy a hibakezelés nem helyettesíti a rossz UX-et.
Azt értem ez alatt, hogy ne legyenek elvárt hibák. Ha a felhasználó megtehet valamit, ami hibát dob, akkor javítsd ki!
Ne engedj át egy hibát csak azért, mert egy szép hibaüzenetet készítettél hozzá.
NaplózásLink erre a szakaszra
Ha nem naplózzuk a hibákat, akkor csak az a felhasználó tud róluk, aki belefut. Az információk elmentése azért szükséges, hogy később meg tudjuk oldani a hiba elhárítását.
Ha már eldöntöttük, hogy tároljuk az adatokat, azt is meg kell választanunk, hogy hogyan mentjük el őket. Erről később bővebben.
Hová mentsük az adatokat?
A központosított hibakezeléssel nem kell túlságosan sajnálnunk, hogy későbbre hagyjuk a döntést. Már csak egy helyen kell módosítanunk a kódunkat. Egyelőre naplózzuk az üzenetet a konzolra.
<>Copyimport { Injectable } from '@angular/core';@Injectable({ providedIn: 'root'})export class LoggingService { logError(message: string, stack: string) { // Send errors to be saved here // The console.log is only for testing this example. console.log('LoggingService: ' + message); }}
HibakövetésLink erre a szakaszra
A webes alkalmazásunkban előforduló hibákat szeretnénk azonosítani, mielőtt a felhasználók találkoznának velük. A hibakövetés a problémák proaktív azonosításának és a lehető leggyorsabb javításának folyamata.
Azért nem dőlhetünk hátra, és nem várhatjuk el, hogy a felhasználók jelentsenek nekünk hibákat. Ehelyett proaktívnak kell lennünk a hibák naplózásával és nyomon követésével.
A hibákról akkor kell tudnunk, amikor azok bekövetkeznek.
Ezért létrehozhatjuk a megoldásunkat. Azonban miért találnánk fel újra a kereket, amikor annyi kiváló szolgáltatás létezik, mint például a Bugsnag, a Sentry, a TrackJs és a Rollbar, amelyek erre a területre specializálódtak.
Egy ilyen front-end hibakövetési megoldás használatával rögzíthetjük és visszajátszhatjuk a felhasználói munkameneteket, így a saját szemünkkel láthatjuk, hogy pontosan mit tapasztalt a felhasználó.
Ha nem tudsz reprodukálni egy hibát, akkor nem tudod kijavítani.
Más szóval, egy megfelelő hibakövetési megoldás figyelmeztethet, ha hiba lép fel, és betekintést nyújthat abba, hogyan lehet megismételni/megoldani a problémát.
Egy korábbi cikkben, a Hogyan küldjünk hibákat a Slackbe az Angularban, beszéltem a Slack használatáról a hibák követésére. Példaként itt is használhatnánk:
<>Másolásimport { Injectable } from '@angular/core';import { SlackService } from './slack.service';@Injectable({ providedIn: 'root'})export class LoggingService { constructor(private slackService: SlackService) { } logError(message: string, stack: string) { this.slackService.postErrorOnSlack(message, stack); }}
A robusztusabb megoldás megvalósítása nem tartozik ennek a cikknek a tárgykörébe.
All together nowLink erre a szakaszra
Mivel a hibakezelés alapvető fontosságú, ezért ez kerül először betöltésre. Emiatt nem tudunk függőségi injektálást használni a szolgáltatások konstruktorában. Ehelyett manuálisan kell injektálnunk őket az Injectorral.
<>Másolatimport { ErrorHandler, Injectable, Injector } from '@angular/core';import { HttpErrorResponse } from '@angular/common/http';import { LoggingService } from './services/logging.service';import { ErrorService } from './services/error.service';import { NotificationService } from './services/notification.service';@Injectable()export class GlobalErrorHandler implements ErrorHandler { // Error handling is important and needs to be loaded first. // Because of this we should manually inject the services with Injector. constructor(private injector: Injector) { } handleError(error: Error | HttpErrorResponse) { const errorService = this.injector.get(ErrorService); const logger = this.injector.get(LoggingService); const notifier = this.injector.get(NotificationService); let message; let stackTrace; if (error instanceof HttpErrorResponse) { // Server Error message = errorService.getServerMessage(error); stackTrace = errorService.getServerStack(error); notifier.showError(message); } else { // Client Error message = errorService.getClientMessage(error); stackTrace = errorService.getClientStack(error); notifier.showError(message); } // Always log errors logger.logError(message, stackTrace); console.error(error); }}
KövetkeztetésLink erre a szakaszra
A hibakezelés egy vállalati alkalmazás sarokköve. Ebben a cikkben központosítottuk azt az ErrorHandler alapértelmezett viselkedésének felülírásával. Ezután számos szolgáltatást adtunk hozzá:
- Hiba szolgáltatás az üzenetek és a stack traces elemzésére.
- Értesítési szolgáltatás a felhasználók hibákról való értesítésére.
- Logging szolgáltatás a hibák naplózására.
Egy interceptor osztályt is implementáltunk:
- Hiba dobása előtt visszahívjuk.
- Egy bizonyos hibák ellenőrzése és megfelelő válaszadás.
Ezzel a megoldással elkezdheti a hibák követését, és remélhetőleg jobb élményt nyújt a felhasználóknak.
Példakód a GitHubon.
Futtassa a kódot a StackBlitz-en.
ForrásokLink erre a szakaszra
- Hibakezelés & Angular by Aleix Suau
- Hibák kezelése Javascriptben: The Definitive Guide by Lukas Gisder-Dubé
- Global Error Handling with Angular 2+ by Austin
- Angular applications – error handling and elegant recovery by Brachi Packter
- The Art of the Error Message by Marina Posniak
- JavaScript Weekly: Graceful Error Handling by Severin Perez
Diskurzus a közösséggel
Leave a Reply