de Rina Artstain

Nu am avut niciodată prea multe păreri despre gestionarea erorilor. Acest lucru poate veni ca un șoc pentru oamenii care mă cunosc destul de opinionat (într-un mod bun!), Dar da. Dacă intram într-o bază de cod existentă, am făcut tot ceea ce au făcut înainte și, dacă scriu de la zero, am făcut tot ce mi s-a părut corect în acel moment.

Când am citit recent secțiunea de tratare a erorilor din Cod curat de către unchiul Bob, a fost prima dată când m-am gândit la acest subiect. Ceva mai târziu am dat de o eroare care a fost cauzată de eșecul unui cod în tăcere și mi-am dat seama că ar putea fi timpul să ne gândim mai mult la asta. S-ar putea să nu pot schimba modul în care sunt tratate erorile în întreaga bază de cod la care lucrez, dar cel puțin aș fi informat cu privire la ce abordări există și care sunt compromisurile și, știți, aveam o părere despre această problemă .

Așteptați-vă la Inchiziția spaniolă

Primul pas de gestionare a erorilor este identificarea când o „eroare” nu este o „eroare!”. Desigur, acest lucru depinde de logica de afaceri a aplicației dvs., dar, în general, unele erori sunt evidente și ușor de remediat.

  • Aveți un interval de date de la „până la” înainte de „de la”? Comutați comanda.
  • Ai un număr de telefon care începe cu + sau conține liniuțe unde nu te aștepți la caractere speciale? Elimina-le.
  • Null colectare o problemă? Asigurați-vă că îl inițializați înainte de a accesa (folosind inițializare leneșă sau în constructor).

Nu întrerupeți fluxul de cod pentru erorile pe care le puteți remedia și, cu siguranță, nu întrerupeți utilizatorii. Dacă reușești să înțelegi problema și să o rezolvi singur, fă-o.

ad-banner
Cum sa gestionati erorile cu gratie esecul in tacere nu
Fotografie de Lance Anderson pe Unsplash

Returnarea valorilor nule sau alte valori magice

Valori nule, -1 unde se așteaptă un număr pozitiv și alte valori „magice” returnează – toate acestea sunt rele care mută responsabilitatea pentru verificarea erorilor către apelantul funcției. Aceasta nu este doar o problemă, deoarece provoacă proliferarea și multiplicarea verificării erorilor, este, de asemenea, o problemă, deoarece depinde de convenție și necesită ca utilizatorul dvs. să fie conștient de detalii de implementare arbitrare.

Codul dvs. va fi plin de blocuri de cod ca acestea care ascund logica aplicației:

return_value = possibly_return_a_magic_value()if return_value < 0:   handle_error()else:    do_something()
other_return_value = possibly_nullable_value()if other_return_value is None:   handle_null_value()else:   do_some_other_thing()

Chiar dacă limba dvs. are un sistem de propagare a valorii anulabile încorporat – aceasta este doar aplicarea unui patch ilizibil codului fulger:

var item = returnSomethingWhichCouldBeNull();var result = item?.Property?.MaybeExists;if (result.HasValue){    DoSomething();}

Trecerea valorilor nule la metode este la fel de problematică și veți vedea adesea că metodele încep cu câteva linii de verificare a intrării valide, dar acest lucru este cu adevărat inutil. Majoritatea limbajelor moderne oferă mai multe instrumente care vă permit să fiți explicit cu privire la ceea ce vă așteptați și să ignorați acele verificări de aglomerare a codului, de exemplu, definirea parametrilor ca fiind anulabili sau cu un decorator adecvat.

Coduri de eroare

Codurile de eroare au aceeași problemă ca și valorile nule și alte valori magice, cu complicația suplimentară de a trebui, bine, să se ocupe de codurile de eroare.

S-ar putea să decideți să returnați codul de eroare printr-un parametru „out”:

int errorCode;var result = getSomething(out errorCode);if (errorCode != 0){    doSomethingWithResult(result);}

Puteți alege să vă înfășurați toate rezultatele într-o construcție „Rezultat” de genul acesta (sunt foarte vinovat de acesta, deși a fost foarte util pentru apelurile ajax în acel moment):

public class Result<T>{   public T Item { get; set; }   // At least "ErrorCode" is an enum   public ErrorCode ErrorCode { get; set; } = ErrorCode.None;    public IsError { get { return ErrorCode != ErrorCode.None; } } }
public class UsingResultConstruct{   ...   var result = GetResult();   if (result.IsError)   {      switch (result.ErrorCode)      {         case ErrorCode.NetworkError:             HandleNetworkError();             break;         case ErrorCode.UserError:             HandleUserError();             break;         default:             HandleUnknownError();             break;      }   }   ActuallyDoSomethingWithResult(result);   ...}

Da. E foarte rău. Proprietatea Item poate fi încă goală din anumite motive, nu există nicio garanție reală (în afară de convenție) că atunci când rezultatul nu conține o eroare, puteți accesa în siguranță proprietatea Item.

După ce ați terminat cu toate aceste gestionări, trebuie să traduceți codul de eroare într-un mesaj de eroare și să faceți ceva cu el. Adesea, în acest moment ați ascuns suficient problema inițială încât să nu aveți detaliile exacte despre ceea ce s-a întâmplat, astfel încât să nu puteți raporta în mod eficient eroarea.

În plus față de acest cod teribil de inutil inutil de exagerat de complicat și ilizibil, există o problemă și mai gravă – dacă dvs. sau altcineva vă schimbați implementarea internă pentru a gestiona o nouă stare invalidă cu un nou cod de eroare, codul de apel nici un fel de a ști ceva de care trebuie să se ocupe s-a schimbat și va eșua în moduri imprevizibile.

Dacă la început nu reușești, încearcă, prinde, în sfârșit

Înainte de a continua, s-ar putea să fie un moment potrivit pentru a menționa că eșecul de cod nu este un lucru bun. Eșuarea în tăcere înseamnă că erorile pot rămâne nedetectate pentru o vreme înainte de a exploda brusc în momente incomode și imprevizibile. De obicei în weekend. Metodele anterioare de tratare a erorilor permite să eșuezi în tăcere, așa că poate, doar poate, nu sunt cel mai bun mod de a merge.

1611283326 419 Cum sa gestionati erorile cu gratie esecul in tacere nu
Fotografie de Scott Umstattd pe Unsplash

În acest moment, dacă ați citit Cod curat probabil că vă întrebați de ce cineva ar face vreodată ceva în loc să arunce doar o excepție? Dacă nu ați făcut-o, ați putea crede că excepțiile sunt rădăcina tuturor răurilor. Simțeam la fel, dar acum nu sunt atât de sigur. Ai grijă de mine, să vedem dacă putem fi de acord că excepțiile nu sunt toate rele și ar putea fi chiar utile. Și dacă scrieți într-o limbă fără excepții? Ei bine, este ceea ce este.

O notă interesantă, cel puțin pentru mine, este că implementarea implicită pentru o nouă metodă C # este de a arunca o NotImplementedException, în timp ce implicit pentru o nouă metodă python este „pass”.

Nu sunt sigur dacă aceasta este o convenție C # sau doar cum a fost configurat Resharper-ul meu, dar rezultatul este practic configurarea Python pentru a eșua în tăcere. Mă întreb câți dezvoltatori au petrecut o lungă și tristă sesiune de depanare încercând să-și dea seama ce se întâmpla, doar pentru a afla că au uitat să implementeze o metodă de substituent.

Dar așteptați, ați putea crea cu ușurință o mizerie aglomerată de verificare a erorilor și aruncarea excepțiilor, care este destul de similară cu secțiunile anterioare de verificare a erorilor!

public MyDataObject UpdateSomething(MyDataObject toUpdate){    if (_dbConnection == null)    {         throw new DbConnectionError();    }    try    {        var newVersion = _dbConnection.Update(toUpdate);        if (newVersion == null)        {            return null;        }        MyDataObject result = new MyDataObject(newVersion);        return result;     }     catch (DbConnectionClosedException dbcc)     {         throw new DbConnectionError();     }     catch (MyDataObjectUnhappyException dou)     {         throw new MalformedDataException();     }     catch (Exception ex)     {         throw new UnknownErrorException();     }}

Deci, desigur, aruncarea excepțiilor nu vă va proteja de codul necitit și de gestionat. Trebuie să aplicați aruncarea excepției ca strategie bine gândită. Dacă domeniul dvs. de aplicare este prea mare, aplicația dvs. ar putea ajunge într-o stare inconsistentă. Dacă scopul tău este prea mic, vei ajunge la o mizerie aglomerată.

Abordarea mea asupra acestei probleme este următoarea:

Coerență rulezzz. Trebuie să vă asigurați că aplicația dvs. este întotdeauna într-o stare consecventă. Codul urât mă întristează, dar nu la fel de mult ca problemele reale care afectează utilizatorii oricărui lucru pe care îl face codul tău. Dacă asta înseamnă că trebuie să înfășurați fiecare pereche de linii cu un bloc try / catch – ascundeți-le în altă funcție.

def my_function():    try:        do_this()        do_that()    except:        something_bad_happened()    finally:        cleanup_resource()

Consolidați erorile. Este bine dacă tu aveți grijă de diferite tipuri de erori care trebuie tratate în mod diferit, dar faceți o favoare utilizatorilor dvs. și ascundeți-o intern. Extern, aruncați un singur tip de excepție doar pentru a le informa utilizatorilor că a mers ceva greșit. Nu ar trebui să le pese cu adevărat de detalii, asta e responsabilitatea ta.

public MyDataObject UpdateSomething(MyDataObject toUpdate){    try    {                var newVersion = _dbConnection.Update(toUpdate);        MyDataObject result = new MyDataObject(newVersion);        return result;     }     catch (DbConnectionClosedException dbcc)     {         HandleDbConnectionClosed();         throw new UpdateMyDataObjectException();     }     catch (MyDataObjectUnhappyException dou)     {         RollbackVersion();         throw new UpdateMyDataObjectException();     }     catch (Exception ex)     {         throw new UpdateMyDataObjectException();     }}

Prinde devreme, prinde des. Prindeți excepțiile cât mai aproape de sursă la cel mai mic nivel posibil. Mențineți consistența și ascundeți detaliile (așa cum s-a explicat mai sus), apoi încercați să evitați erorile de manipulare până la nivelul cel mai înalt al aplicației dvs. Sperăm că nu există prea multe niveluri pe parcurs. Dacă puteți elimina acest lucru, veți putea separa în mod clar fluxul normal al logicii aplicației dvs. de fluxul de gestionare a erorilor, permițând codului dvs. să fie clar și concis, fără a amesteca preocupări.

def my_api():    try:        item = get_something_from_the_db()        new_version = do_something_to_item(item)        return new_version    except Exception as ex:        handle_high_level_exception(ex)

Vă mulțumim că ați citit până aici, sper că a fost de ajutor! De asemenea, încep doar să-mi formez opiniile cu privire la acest subiect, așa că aș fi foarte fericit să aud care este strategia dvs. de gestionare a erorilor. Secțiunea de comentarii este deschisă!