ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 149
Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 34
Abonament PDF

Cinci pași înspre un design software rezistent la erori

Alexandru Bolboacă
Agile Coach and Trainer, with a focus on technical practices
@Mozaic Works



PROGRAMARE

În cele două articole anterioare din TSM- Patru idei pentru îmbunătățirea software design-ului și Usable software design-am arătat cum să creăm un software design mai bun și am definit ideea de usable software design. Designul Software Utilizabil provine din observația simplă că dezvoltatorul este utilizatorul unui design software. Ipoteza mea este că utilizând principiile și practicile din Utilizabilitate în designul software se vor crea două beneficii importante: un timp mai rapid de implementare pentru sarcinile comune și integrarea mai rapidă a dezvoltatorilor noi într-o echipă existentă.

În acest articol, voi explora mai departe designul software utilizabil plecând de la ideea simplă că deși nimănui nu îi place să facă greșeli, acestea totuși apar. Deoarece designul software utilizabil înseamnă un design software care provoacă încântare când este utilizat de către dezvoltatori, ceva trebuie făcut pentru a preveni erorile. Așadar vreți ca designul software al vostru să fie lipsit de greșeli pentru a fi mai utilizabil?

Dar mai întâi, trebuie să înțelegeți un lucru important…

1. Este vina sistemului

În 1988, un cercetător cognitiv și-a asumat sarcina de a analiza modul în care sunt concepute obiectele de uz zilnic. Profesorul Donald Norman a explorat designul centrat pe utilizator în cartea sa "Designul lucrurilor uzuale" ("The Design of Everyday Things"), pornind de la psihologie:

Un ciclu vicios începe: dacă greșești la ceva, tu crezi că este vina ta. De aceea, consideri că nu poți duce la bun sfârșit acea sarcină. Drept rezultat, următoarea dată când trebuie să realizezi sarcina respectivă, tu crezi că nu poți și de aceea nici măcar nu mai încerci. Rezultatul este că nu poți, exact cum ai crezut. Ai căzut în capcana unei profeții care se autoîmplinește.

- Donald Norman, " The Design of Everyday Things"

Iar soluția:

Este timpul să inversăm situația: să dăm vina pe aparate și pe designul lor. (…) Este de datoria aparatelor și a celor care le proiectează să înțeleagă oamenii.

- Donald Norman, " The Design of Everyday Things"

Aceasta înseamnă că...

2. Greșelile dezvoltatorilor indică probleme în designul sistemului

Imaginați-vă următorul scenariu: descoperiți că Ionuț, Programatorul Junior, a făcut o greșeală când a lucrat la o sarcină. Vă dați seama că și alții au comis aceeași eroare înainte, iar soluția a fost documentată. Care este reacția voastră?

(Pun pariu că Ionuț din echipa voastră nu este chiar atât de junior!)

  1. Îi spuneți că există documentație pentru asta și îi indicați unde să citească.

  2. Îi explicați cum se face.

  3. Vă uitați la design și îl modificați astfel încât această problemă să nu se mai poată repeta.

Eu obișnuiam să reacționez în modul 1 și 2 și uneori încă le mai fac asta. Vechile obiceiuri presupun efort pentru a fi schimbate. Spre deosebire de acum cinci ani, acum înțeleg că aceasta ar putea crea un cerc vicios. Iată cum:

Cum se va simți Ionuț, Programatorul Junior, dacă răspunsul vostru este 1 sau 2? Reacția psihologică involuntară este să se simtă vinovat. Repetați scenariul de câteva ori și el va înceta să pună designul la îndoială. Drept consecință, veți avea un ciclu vicios.

Ceea ce m-a învățat Domnul Norman în schimb este că ar trebui să consider această situație drept vina sistemului și nu vina dezvoltatorului. Deci următorul lucru important este să îți dai seama cum să îmbunătățești sistemul astfel încât eroarea să nu se mai repete.

Iată trei moduri de a vă îmbunătăți designul pentru a preveni erorile.

3. Eliminați excepțiile

Uneori, metodele dau naștere excepțiilor atunci când sunt apelate într-o ordine diferită decât ar trebui. În anumite cazuri, este posibil să înlăturați complet excepțiile prin reproiectarea clasei. Iată un exemplu:

La diferite evenimente de măiestrie software în care exersăm tehnici de codare, am folosit TicTacToe drept o problemă. În mod tipic, sfârșim prin a avea o clasă Game, pe care cei mai mulți dezvoltatori o concep după cum urmează:

 

class Game{
    ...
    moveX();
    moveO();
    ...
}

 

Acest design duce la o potențială eroare: nimic nu mă împiedică să scriu următorul cod:

 

Game game = new Game();
game.moveO();
game.moveO();
game.moveO();

 

care este greșit, potrivit regulilor TicTacToe. Jucătorul X ar trebui să înceapă, apoi jocul ar trebui să continue cu mișcări alternante.

Răspunsul implicit al dezvoltatorilor care se confruntă cu această problemă este să modifice implementarea în ceva similar cu:

 

void moveX(){
 if(currentPlayer != Player.X){
 throw new NotTheTurnOfThePlayerException();
    }
}

 

Dar aceasta tot nu mă împiedică să scriu codul de mai sus. Este un pic mai bine, deoarece mă avertizează că am făcut ceva greșit. Totuși, aș argumenta că descoperirea greșelilor mele la execuție este prea târziu. A face un software rezistent la greșeli înseamnă să concepem sistemul astfel încât să fie (aproape) imposibil să îl utilizăm greșit.

Consider că următorul design este mai rezistent la greșeli:

 

class Game{
  Game(Player playerX, Player playerO);
  move();
  ...
}

Acest design conduce în mod tipic la un cod similar cu:

Game game = new Game(playerX, playerO);
game.move();

Nu văd nicio cale de a utiliza acest design altfel decât ar trebui. Nu numai că este ușor de utilizat, dar este de asemenea și ușor de învățat și rezistent la greșeli.

Clasa Game poate fi utilizată numai într-un singur fel, la fel cum există un singur mod de a introduce un card de memorie în slotul lui.

4. Treceți argumentele obligatorii în constructor

O greșeală comună este să creezi un obiect fără toți parametrii obligatorii. Dacă mai târziu este apelată o metodă, apar erorile.

De exemplu, păstrând problema TicTacToe:

 

Game game = new Game();
game.move(); // players have not been added to the game

TicTacToe poate fi jucat numai de către doi jucători, fie ei oameni sau computer. Probabil există și jocuri TicTacToe cu mai mult de doi jucători, dar nu îmi pot imagina un TicTacToe solitar.

De aceea, este normal să exprimăm această constrângere în constructor:

 

Game game = new Game(firstPlayer, secondPlayer);

 

Chiar dacă mai târziu decidem să implementăm versiunea TicTacToe cu mai mult de doi jucători, este ușor:

 

game.addPlayer(thirdPlayer);
game.addPlayer(fourthPlayer);
game.move();

 

5. Evitați obsesia primitivă

Indiciu: Acest obiect a fost implicat

Obsesia primitivă este un iz de cod foarte comun, pe lângă faptul că are un nume foarte sugestiv. A fost de asemenea și sursa unei pierderi de 125 milioane $ într-una dintre rarele ocazii în care se pot măsura pierderile cauzate de probleme de software.

Ward Cunningham discută despre asta:

Izul (the Smell): Obsesia primitivă utilizează tipuri de date primitive pentru a reprezenta ideile de domeniu. De exemplu, noi folosim un Șir (String) pentru a reprezenta un mesaj, un Întreg (Integer) pentru a reprezenta o sumă de bani, sau un Struct/Dicționar/Hush pentru a reprezenta un obiect specific.

Repararea: În mod tipic, noi introducem un ValueObject în locul datelor primitive, apoi privim ca la o magie cum codul din întreg sistemul indică FeatureEnvySmell și vrea să fie pe noul ValueObject. Mutăm acele metode și totul devine corect.

În cazul TicTacToe, este foarte tentant să scriem cod precum:

 

game.move("A1");
sau ca și acesta:
game.move(0, 0);

Există multe probleme cu acest design. Nimic nu mă împiedică să trimit coordonate greșite cum ar fi game.move(-1, 2000) sau game.move("Z9"). Pentru a evita problemele, va trebui să răspândim validări pe tot parcursul codului. În primul caz, procesarea șir va fi răspândită în jurul codului, fiind ușor să introducem erori off-by-one atunci când facem procesare în șir. Când cazurile limită (corner cases) sunt validate cu teste unitate, va trebui să repetați testele pe unitate pentru coordonate valide/ invalide în fiecare clasă pe care le utilizează.

Există o modalitate de a evita toate acestea: indiferent de cum introduceți coordonatele, convertiți-le imediat într-un obiect valoare. În cazul TicTacToe, domeniul problemei poate fi descris ușor: Panoul TicTacToe este format din 9 Locuri care au Coordonate, fiecare de la 1 la 3. Deci, de ce nu:

Place place = new Place(Coordinate.One, Coordinate.One);
game.move(place);

 

Utilizatorul acestui design nu mai poate apela metoda move() cu parametri greșiți.

Recapitulare

Oamenii care utilizează un sistem cu un design prost tind să se autoînvinovățească în loc să dea vina pe sistemul pe care îl folosesc. Eu afirm că acest lucru se întâmplă atât dezvoltatorilor de software care folosesc un design software existent, cât și utilizatorilor de obiecte fizice făcute de om. Donald Norman ne indică o cale de ieșire: ca designer, el înțelege că este în mod tipic vina sistemului și proiectează sistemul având în minte toleranța față de greșeli. Am văzut trei metode de a ne îmbunătăți designul unei clase pentru a fi mai rezistentă la greșeli: eliminarea excepțiilor, trecerea argumentelor obligatorii în constructor, evitarea obsesiei primitive. Am văzut că rezultatul este mai ușor de învățat, mai ușor de utilizat și în același timp evită erorile obișnuite.

Lectură suplimentară

Când nu îți poți proiecta interfețele pentru a preveni greșelile, Design By Contract vine în ajutor. Vă recomand să citiți despre aceasta, drept o altă metodă de a-ți face designul să nu fie permisiv la greșeli.

Acest articol s-a concentrat pe cum să îți faci designul software rezistent la erori prin utilizarea unor elemente de design software. Realitatea este mai complexă: pentru a face designul software să nu permită greșelile este cu siguranță nevoie de un feedback rapid oferit de programarea pereche, teste automatizate, integrare continuă și sprijin IDE.

Ce tipuri de greșeli faceți voi? Ce faceți pentru a le preveni? Aștept comentariile voastre.

Vă invit pentru o discuție mai aprofundată în cadrul Workshop-ului de Usable Software Design Vineri, 29 Mai, în cadrul I T.A.K.E. Unconference 2015, la Radisson Blu Hotel, Bucuresti. Au mai rămas doar câteva bilete.

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects