Cu (fără) blocări ale aplicației, vă rog!

Publicat: 2020-02-12

Programatorii încearcă să evite blocările în codul lor. Dacă cineva își folosește aplicația, aceasta nu ar trebui să se întrerupă sau să se închidă în mod neașteptat. Aceasta este una dintre cele mai simple măsurători ale calității – dacă o aplicație se blochează des, probabil că nu este bine făcută.

Blocările aplicației apar atunci când un program este pe cale să facă ceva nedefinit sau rău , cum ar fi împărțirea unei valori la zero sau accesarea resurselor restricționate pe o mașină. Ar putea fi, de asemenea, făcut în mod explicit de către programatorul care a scris aplicația. „Asta nu se va întâmpla niciodată, așa că o voi sări peste el” – este o gândire destul de comună și nu complet nerezonabilă. Există unele cazuri care pur și simplu nu pot apărea, niciodată, până când... se întâmplă.

Promisiuni încălcate

Unul dintre cele mai frecvente cazuri în care știm că ceva nu se poate întâmpla este API-urile. Am convenit între backend și frontend - acesta este singurul răspuns de server pe care îl puteți obține pentru această solicitare. Administratorii acestei biblioteci au documentat comportamentul acestei funcții. Funcția nu poate face nimic altceva. Ambele moduri de a gândi sunt corecte, dar ambele pot cauza probleme.

Când utilizați o bibliotecă, vă puteți baza pe instrumentele lingvistice pentru a vă ajuta să gestionați toate cazurile posibile. Dacă limbajul pe care îl utilizați nu are nicio formă de verificare a tipului sau analiză statică, trebuie să vă ocupați singur de asta. Totuși, puteți verifica acest lucru înainte de a expedia în mediul de producție, așa că nu este mare lucru. Poate fi greu, dar citești jurnalele de modificări înainte de a-ți actualiza dependențele și a scrie teste unitare, nu? Fie că utilizați sau faceți o bibliotecă, cu cât o tastare mai strictă o puteți oferi, cu atât mai bine pentru codul dvs. și pentru alți programatori.

Comunicarea backend-frontend este puțin mai grea. Este adesea cuplată slab, așa că schimbarea pe o parte poate fi făcută cu ușurință fără a fi conștienți de modul în care va afecta cealaltă parte. Schimbarea pe backend poate deseori să vă încalce ipotezele pe frontend și ambele sunt adesea distribuite separat. Trebuie să se termine prost. Suntem doar oameni și se întâmplă uneori să nu înțelegem cealaltă parte sau să uităm să le spunem despre acea mică schimbare. Din nou, asta nu este mare lucru cu predarea corectă a rețelei - răspunsul de decodare va eșua și știm cum să-l gestionăm. Chiar și cel mai bun cod de decodare poate fi afectat de un design prost, totuși...

Funcții parțiale. Design prost.

„Vom avea două variabile booleene aici: „isActive” și „canTransfer”, desigur că nu puteți transfera atunci când nu este activ, dar acesta este doar un detaliu.” Aici începe, designul nostru prost care poate lovi puternic. Acum cineva va face o funcție cu aceste două argumente și va procesa niște date pe baza ei. Cea mai simplă soluție este... doar crash într-o stare invalidă, nu ar trebui să se întâmple niciodată, așa că nu ar trebui să ne pese. Chiar ne pasă uneori și lăsăm un comentariu pentru a o remedia mai târziu sau pentru a întreba ce ar trebui să se întâmple, dar poate fi livrat în cele din urmă fără a finaliza această sarcină.

 // pseudo cod
funcția doTransfer(Bool isActive, Bool canTransfer) {
  Dacă ( este activ și poate transfera ) {
    // face ceva pentru transferul disponibil
  } else if ( nu este Activ și nu se poate transfera ) {
    // face ceva pentru transfer nu este disponibil
  } else if ( este activ și nu se poate transfera ) {
    // face ceva pentru transfer nu este disponibil
  } else { // alias (nu isActive și canTransfer)
    // există patru stări posibile
    // acest lucru nu ar trebui să se întâmple, transferul nu ar trebui să fie disponibil atunci când nu este activ
    crash()
  }
}

Acest exemplu ar putea părea prostesc, dar uneori s-ar putea să te prinzi în acel tip de capcană care este puțin mai greu de observat și de rezolvat decât aceasta. Veți ajunge cu ceva numit funcție parțială. Aceasta este o funcție care este definită numai pentru unele dintre posibilele sale intrări ignorând sau blocând altele. Ar trebui să evitați întotdeauna funcțiile parțiale (vă rugăm să rețineți că în limbile cu tastare dinamică majoritatea funcțiilor pot fi tratate ca fiind parțiale). Dacă limba dvs. nu poate asigura un comportament adecvat cu verificarea tipului și analiza statică, s-ar putea să se blocheze după ceva timp într-un mod neașteptat. Codul evoluează în mod constant și ipotezele de ieri ar putea să nu fie valabile astăzi.

Eșuează repede. Eșuează des.

Cum te poți proteja? Cea mai bună apărare este atacul! Există această vorbă drăguță: „Eșuează repede. Eșuează des.” Dar nu am fost doar de acord că ar trebui să evităm blocările aplicațiilor, funcțiile parțiale și designul prost? Erlang OTP oferă programatorilor un avantaj mitic că se va vindeca după stări neașteptate și se va actualiza în timpul rulării. Își permit asta, dar nu toată lumea are acest tip de lux. Deci, de ce ar trebui să eșuăm rapid și des?

În primul rând, pentru a găsi acele stări și comportamente neașteptate . Dacă nu verificați dacă starea aplicației dvs. este corectă, ar putea duce la rezultate chiar mai proaste decât blocarea!

În al doilea rând, pentru a ajuta alți programatori să colaboreze pe aceeași bază de cod . Dacă ești singur într-un proiect chiar acum, s-ar putea să fie altcineva după tine. S-ar putea să uiți unele ipoteze și cerințe. Este destul de comun să nu citești documentația furnizată până când totul funcționează sau nu documentezi deloc metodele și tipurile interne. În această stare, cineva apelează una dintre funcțiile disponibile cu o valoare neașteptată, dar validă. De exemplu, să presupunem că avem o funcție „așteptați” care ia orice valoare întreagă și așteaptă acea cantitate de secunde. Ce se întâmplă dacă cineva îi trece „-17”? Dacă nu se blochează imediat după ce faceți acest lucru, ar putea duce la unele erori grave și stări invalide. Așteaptă pentru totdeauna sau deloc?

Cea mai importantă parte a prăbușirii intenționate este să o faci cu grație . Dacă vă blocați aplicația, trebuie să furnizați câteva informații pentru a permite o diagnosticare. Este destul de ușor când utilizați un depanator, dar ar trebui să aveți o modalitate de a raporta blocările aplicației fără acesta. Puteți utiliza sisteme de înregistrare pentru a păstra aceste informații între lansările de aplicații sau pentru a le examina extern.

A doua cea mai importantă parte a prăbușirii intenționate este evitarea ca în mediul de producție...

Nu da gres. Vreodată.

Îți vei trimite codul în cele din urmă. Nu o poți face perfectă, de multe ori este prea scump să te gândești măcar să faci garanții de corectitudine. Cu toate acestea, ar trebui să vă asigurați că nu se va comporta greșit sau nu se va prăbuși. Cum poți realiza asta, deoarece am decis deja să ne prăbușim rapid și des?

O parte importantă a blocării intenționate este să o faci numai în medii care nu sunt de producție . Ar trebui să utilizați aserțiuni care sunt eliminate în versiunile de producție ale aplicației dvs. Acest lucru va ajuta în timpul dezvoltării și va permite identificarea problemelor, fără a afecta utilizatorii finali. Cu toate acestea, este mai bine să vă blocați uneori pentru a evita stările nevalide ale aplicației. Cum putem realiza asta dacă am făcut deja funcții parțiale?

Faceți imposibil de reprezentat stările nedefinite și invalide și reveniți la cele valide altfel. Ar putea suna ușor, dar necesită multă gândire și muncă. Indiferent cât de mult ar fi, este întotdeauna mai puțin decât căutarea erorilor, remedierea temporară și... utilizatorii enervant. Va face automat ca unele dintre funcțiile parțiale să fie mai puțin probabil să se întâmple.

 // pseudo cod
funcția doTransfer (stare de stat) {
  comutator ( stare ) {
    caz State.canTransfer {
      // face ceva pentru transferul disponibil
    }
    caz State.cannotTransfer {
      // face ceva pentru transfer nu este disponibil
    }
    caz State.notActive {
      // face ceva pentru transfer nu este disponibil
    }
    // Este imposibil să reprezentați transferul disponibil fără a fi activ
    // există doar trei stări posibile
  }
}

Cum poți face imposibile stările invalide? Să alegem două dintre exemplele anterioare. În cazul celor două variabile booleene „isActive” și „canTransfer” le putem schimba pe cele două într-o singură enumerare. Acesta va reprezenta în mod exhaustiv toate stările posibile și valide. Chiar și atunci cineva poate trimite variabile nedefinite, dar acest lucru este mult mai ușor de gestionat. Va fi o valoare nevalidă care nu va fi importată în programul nostru, în loc să fie trecută o stare invalidă în interior, ceea ce face totul mai greu.

Funcția noastră de așteptare poate fi, de asemenea, îmbunătățită frumos în limbi puternic tastate. Îl putem face să folosească numai numere întregi fără semn la intrare. Numai asta va rezolva toate problemele noastre, deoarece argumentele nevalide vor fi eliminate de compilator. Dar dacă limba ta nu are tipuri? Avem câteva soluții posibile. În primul rând - doar crash, această funcție este nedefinită pentru numere negative și nu vom face lucruri nevalide sau nedefinite. Va trebui să găsim o utilizare nevalidă a acestuia în timpul testelor. Testele unitare (pe care ar trebui să le facem oricum) vor fi foarte importante aici. În al doilea rând - acest lucru ar putea fi riscant, dar în funcție de context ar putea fi util. Putem reveni la valori valide păstrând afirmația în versiunile de non-producție pentru a remedia stările invalide atunci când este posibil. S-ar putea să nu fie o soluție bună pentru funcții ca aceasta, dar dacă facem valoare absolută a întregului, vom evita blocările aplicației. În funcție de limbajul concret, ar putea fi, de asemenea, o idee bună să aruncați/să ridicați o eroare/excepție. S-ar putea să merite să renunțați, dacă este posibil, chiar și atunci când utilizatorul vede o eroare, aceasta este o experiență mult mai bună decât blocarea.

Să mai luăm un exemplu aici. Dacă starea datelor utilizatorului din aplicația dvs. frontală este pe cale să fie invalidă în unele cazuri, ar putea fi mai bine să forțați deconectarea și să obțineți din nou date valide de la server în loc să vă blocați. Utilizatorul ar putea fi forțat să facă acest lucru oricum sau poate fi prins într-o buclă nesfârșită de blocare. Încă o dată – ar trebui să ne afirmăm și să ne blocăm în astfel de situații în medii care nu sunt de producție, dar nu lăsați utilizatorii să fie testeri externi.

rezumat

Nimănui nu-i plac aplicațiile instabile și care se blochează. Nu ne place să le facem sau să le folosim. Eșecul rapid cu afirmații care oferă un diagnostic util în timpul dezvoltării și testelor va surprinde o mulțime de probleme devreme. Retragerea la stările valide în producție va face aplicația dvs. mult mai stabilă. A face ca stările invalide să fie nereprezentabile va elimina o întreagă clasă de probleme. Acordați-vă puțin mai mult timp pentru a vă gândi înainte de a dezvolta cum să eliminați și să recurgeți la stările invalide și puțin mai mult în timpul scrierii pentru a include câteva afirmații. Puteți începe să vă îmbunătățiți aplicațiile astăzi!

Citeste mai mult:

  • Proiectare prin contract
  • Tip de date algebrice