Diamo un volto ai bug

Lo sviluppo software ci obbliga ogni giorno a dover fare scelte. Da una card del nostro backlog nascono analisi, riflessioni e discussioni riguardanti architetture, pattern e sezioni di codice dove abbiamo già implementato “qualcosa di simile” che quindi ci potrebbe portare ad un refactoring del nostro progetto per renderlo sempre migliore. Ma cosa succede se la card, invece di essere un task o una story è un bug?

Perché il bug è considerato un second-class citizen

Questa è la definizione di bug tratta da Wikipedia:

A software bug is an error, flaw or fault in a computer program or system that causes it to produce an incorrect or unexpected result, or to behave in unintended ways.

Quando ci assegniamo, o peggio ci viene assegnata, una card di bug tra le possibili reazioni si annoverano le seguenti:

  • ma perché proprio a me?
  • si, va beh… però se il cliente avesse fatto così, cosà, poi così ed infine cosà il malfunzionamento non ci sarebbe stato: possiamo chiudere la card dicendo al cliente che c’è un workaround? 🙂

Spesso il bug, non ha alcuna dignità: zero! E’ un incidente, è percepito come una perdita di tempo che ci fa spazientire, che rallenta lo sviluppo della nostra applicazione ed il raggiungimento del break even. Inoltre è sempre e solo un bug, indipendentemente che questo ci faccia spendere 4 settimane o pochi minuti per la sua risoluzione, la risposta è sempre “sto lavorando a questo bug”, mentre se sto implementando una nuova api, e dobbiamo descrivere l’implementazione di una nuova funzionalità, spendiamo mille lusinghiere parole per esprimere tutta la nostra soddisfazione. Perché questo? perché il bug è solo un bug.

In realtà non è così: nel 1985 Jim Gray propose una classificazione dei bug basata sul tipo di failure da essi evidenziata nel sistema. Si, uso il termine evidenziata perché il bug c’è ed è li per “causa” nostra, indipendentemente che sia frutto di un’analisi incompleta che lascia scoperto qualche flusso oppure che sia un null non gestito nel codice. Inutile dire che siccome abbiamo fatto unit-testing ed abbiamo una code coverage del 99,7% il nostro applicativo non avrà bug: magari non ce l’ha oggi, ma potrebbe averlo domani, oppure… potremmo non essercene accorti oggi, ma potremmo ahimè venirne a conoscenza domani. E quando ci verrà evidenziato, è inutile prendersela con il bug: ripeto, è una nostra creatura e nel momento in cui emerge, meglio se in fase di sviluppo, test o qa, dobbiamo esserne “felici” perché è anche grazie alla sua risoluzione se il nostro sistema può migliorare e, talvolta, evolvere.

Una volta individuato, quindi, dovremmo poterlo classificare perché è dalla sua classificazione che possiamo raccogliere preziosi feedback in merito alla qualità di ciò che stiamo producendo.

Iniziamo quindi a dare un nome ed un volto ai bug prendendo spunto da chi ha saputo osservare e modellizzare tutto ciò che ci sta attorno: i fisici.

Categorizzazione dei Bug

Il Bohrbug

Il Bohrbug, è un bug solido e facile da gestire. Diciamo che è un bug “amico” dello sviluppatore perché è deterministico: non cambia il proprio comportamento ed è relativamente facile da trovare. Questo bug è anche definito un permanent fault, proprio per la sua natura deterministica. Un esempio di Bohrbug può essere un null non gestito, la chiamata ad una rotta API errata che mi restituisce 404, una divisione per 0. Sono bug facilmente eliminabili con una buona suite di test ed una buona analisi del task da implementare. La categorizzazione di questo bug prende spunto dal modello atomico di Niels Bohr attraverso il quale l’atomo venne modellizzato come un nucleo piccolo e denso attorno al quale orbitano elettroni. E’ un modello che ha una struttura simile a quella del sistema solare, nel quale il Sole gioca la parte del nucleo ed i pianeti quella degli elettroni con l’unica differenza che la forma di attrazione in questo caso non è la forza elettrostatica bensì la forza di gravità. Insomma, un modello “solido” e deterministico, facile da comprendere e da “maneggiare”.

L’Heisenbug

L’Heisenbug è un bug più subdolo che sembra non esserci… mentre lo stiamo studiando. Vi è capitato, soprattutto nei primi anni di lavoro, di “risolvere” un problema con dei Thread.Sleep? Magicamente tutto funzionava… in Debug. Poi si andava in Release, le ottimizzazioni del compilatore producevano un altro eseguibile e… tadaaaaa, il programma non funzionava più. Allora si tornava in Debug e tutto funzionava: perché? Perché l’heisenbug è scaltro e, se lo osservi, si nasconde. Un altro tipico caso in cui un heisenbug si può nascondere è quando usiamo il multi-threading: oggi con i costrutti async/await la programmazione è molto più semplice, ma attenzione che il diavolo si nasconde nei dettagli ed ecco che sulla mia macchina, col debugger acceso, i thread si comportano in un modo e su un’altra macchina, ad esempio con più core, in un altro: i problemi di concorrenza non sono mai piacevoli da identificare e risolvere. La categorizzazione di questo bug prende spunto dalla teoria di Werner Karl Heisenberg, che per primo teorizzò l’effetto osservatore secondo il quale l’osservazione di un sistema ne altera il suo stesso stato. In elettronica tale fenomeno è chiamato probe effect e fornisce una spiegazione al perché un sistema collegato ad uno strumento di debug o analisi altera il proprio comportamento.

Lo Schrödinbug

Mai sentito parlare del gatto di Schrödinger? Se non ne avete mai sentito parlare, avrete visto qualche simpatica maglietta indossata da qualche vostro amico nerd (in CodicePlastico ne circolano a bizzeffe 😀 ). Questo tipo di bug non si manifesta finché il codice non viene eseguito almeno una volta. Quindi, il nostro applicativo funziona alla grande, senza alcun problema per X tempo, poi ad un certo punto smette di funzionare. perché? Ah ecco… perché da ieri il cliente ha iniziato a chiamare quell’API che avevamo fatto per Pinco, che poi non ha mai usato, e che il commerciale ha detto che c’era perché ne avevamo parlato alla macchinetta del caffè (prima del Covid, naturalmente). Ecco il bug c’è, ma è silente: verrà evidenziato solo quando verrà eseguito il codice almeno una volta. Insomma è come il gatto di Erwin Schrödinger all’interno della scatola con la fialetta letale: il gatto è vivo o morto e per scoprirlo devo necessariamente aprire la scatola.

Il Mandelbug

Questo tipo di bug appare così complesso da risolvere perché il suo comportamento è caotico e non deterministico. Spesso è legato ad una catena di bug che vengono scoperti man mano che ci si addentra nelle profondità del codice: insomma il comportamento è quello della self-similarity. Questa categoria prende spunto dai frattali di Benoit Mandelbrot, dove “un oggetto, nel nostro caso un comportamento, si ripete nella sua forma allo stesso modo su scale diverse, e dunque ingrandendo una qualunque sua parte si ottiene una figura simile all’originale.”

L’Higgs-bugson

L’Higgs-bugson è un bug ipotetico: si, avete capito bene, non siamo certi che il bug ci sia. Ipotizziamo la sua esistenza a partire da alcuni, magari pochi e frammentari, eventi di log e da racconti più o meno ricchi di pathos di utenti che facendo così, cosà e poi ancora così hanno visto che non funzionava. Poi però, quando proviamo noi a fare le stesse operazioni, spesso non riusciamo a riprodurre lo scenario descrittoci perché non riusciamo a capirne l’origine. Prende il nome dal tanto famigerato Bosone di Higgs, che è

un bosone elementare, massivo e scalare associato al campo di Higgs, che svolge un ruolo fondamentale nel Modello standard conferendo la massa alle particelle elementari. Inoltre il bosone di Higgs garantisce la consistenza della teoria, che senza di esso porterebbe a un calcolo di probabilità maggiore di uno per alcuni processi fisici.

Ok, diciamocelo chiaro… secondo voi, ‘sta roba, esiste?! Io ho provato a replicarla ma non ci sono riuscito 😀 (scherziamo naturalmente, e speriamo di non offendere nessuno)

L’Hindenbug

Anche se non prende spunto dal mondo della fisica, per completezza è corretto citare anche l’Hindenbug, un bug catastrofico che potrebbe comportare la perdita di dati e anche causare problemi ad apparati IT o portare alla paralisi di uno o più sistemi. E’ evidentemente un bug maggiore, di un livello di gravità davvero rilevante. Questo tipo di bug può verificarsi quando viene inviato un parametro errato ad una serie di dispositivi IoT o microservizi remoti che potrebbe portare allo spegnimento del sistema o al crash delle applicazioni che ricevono quel valore. Vista l’entità degli effetti di questo bug spesso provoca la perdita di dati e, nei casi più estremi, del cliente. Vista la perdita di dati ed i possibili danni infrastrutturali potrebbe non essere facile, se non impossibile, risalire alla causa. Per questo l’Hindenbug è il terrore di ogni sviluppatore. Il nome prende spunto dal disastro aereo che si verificò negli Stati Uniti nel 1937 quando il dirigibile tedesco Hindenburg prese fuoco a causa di una perdita di idrogeno, le cui cause sono ancora oggetto di studio. Il disastro costò la vita a 36 persone.

E ora… acchiappiamoli!

Indipendentemente dal nome e dalla categoria, un bug non è mai una cosa bella da scoprire e per questo dobbiamo evitare il più possibile il “piacere” di crearli. Per evitare di introdurre bug nel nostro sistema possiamo adottare diverse strategie:

  • TDD (Test-Driven Design), una tecnica di design su cui il nostro mentore Emanuele può intrattenervi per intere giornate: se siete interessati, questo video fa per voi. Ricordatevi che se la suite di test che test non viene eseguita ad ogni push sul repository, non serve praticamente a niente: l’accordo verbale di “lanciare i test prima di committare” è totalmente inutile. Quindi dotatevi di uno strumento di Continuous Integration per far emergere quanto prima eventuali problemi e tenete monitorata la code coverage.
  • Fare Code Review prima di chiudere la issue facciamo un controllo incrociato del codice. Questo permette di trovare eventuali errori, da semplici typo o dipendenze non usate, a errori architetturali. E’ anche un buon modo per trasferire conoscenze.
  • Implementate dei test d’integrazione a partire da dati fake. Json-generator vi consente, ad esempio, di generare dati json casuali anomini a partire da un template con cui popolare il vostro database NoSQL e provare le vostre query. Esistono generatori di dati fake scritti in ogni linguaggio, da Faker a Bogus. Implementare un meccanismo di notifica in caso di errori che possibilmente contenga lo stack trace in modo da poter iniziare l’analisi da un punto di partenza: Sentry è un ottimo strumento per questo scopo.
  • Mai sentito dire “Quattro occhi vedono meglio di due?” Lavorare in Pair Programming aiuta certamente a risolvere i problemi più velocemente, ma attenzione ai tempi perché non è detto che 4 mani (e quindi ben 20 dita) sulla tastiera producano codice a velocità doppia! Se l’obiettivo è comunque la qualità del codice, questa tecnica è certamente una garanzia.
  • Limitare l’uso delle dipendenze allo stretto necessario è una buona pratica per ridurre possibili vulnerabilità. Ci sono ottime librerie che fanno tantissime cose mirabolanti, ma nel nostro applicativo ne sfruttiamo l’1% e non abbiamo idea di cosa faccia il restante 99%: questo è un tipico scenario da Schrödinbug.
  • Loggare, loggare, loggare. I log devono essere ricchi di dettagli e ci devono consentire di ricostruire il flusso di esecuzione fino al raggiungimento della condizione di errore. A questo scopo uno strumento molto utile è Papertrail che consente di avere un log ricco, attivabile facilmente e fruibile da remoto. Scrivere codice ordinato e leggibile ci aiuta a trovare bug nascosti tra le righe.

Conclusioni

Curare la qualità del nostro codice è essenziale per limitare la possibilità di introdurre bug. Conoscere quali possono essere gli effetti di un bug ci può aiutare ad attuare strategie per limitare la probabilità che si verifichino. Categorizzarli ci fornisce indicazioni (o chiamiamole pure metriche) su dove e come concentrare i nostri sforzi per aumentare ulteriormente la qualità del nostro, già ottimo, applicativo.