Fork me on GitHub
con Valerio Galano

Il podcast dove si ragiona da informatici

Un informatico risolve problemi, a volte anche usando il computer

Riflessioni e idee dal mondo del software

Episodio del podcast

Il debug: una questione di metodo e strumenti

31 ottobre 2021 Podcast Episodio 72 Stagione 2
Il debug: una questione di metodo e strumenti

Descrizione

C’è chi lo teme, chi lo odia, ma a me non dispiace affatto. Oggi ti parlo di debug dal mio punto di vista.

I link dell’episodio di oggi:
Sondaggio Jetbrains - https://www.jetbrains.com/lp/devecosystem-2021/php/#PHP_which-php-frameworks-and-platforms-do-you-regularly-use-if-any
Perché ci sono bug nei software? - https://www.spreaker.com/episode/41342954

Attrezzatura:
Shure Microfono Podcast USB MV7 - https://amzn.to/3862ZRf
Neewer NW-5 Pannello fonoassorbente - https://amzn.to/3rysTFP

Utilizzando i link affiliati, il costo di un qualsiasi acquisto non sarà maggiore per te, ma una piccola parte del ricavato servirà per sostenere il progetto.

Produttori:
Alex Ghibellini, anonimo

Sostieni il progetto

Sostieni tramite Satispay
Sostieni tramite Revolut
Sostieni tramite PayPal (applica commissioni)
Sostieni utilizzando i link affiliati di Pensieri in codice: Amazon, Todoist, Readwise Reader, Satispay

Partner

GrUSP (Codice sconto per tutti gli eventi: community_PIC)
Schrödinger Hat

Crediti

Sound design - Alex Raccuglia
Voce intro - Maria Chiara Virgili
Voce intro - Spad
Musiche - Kubbi - Up In My Jam, Light-foot - Moldy Lotion, Creativity, Old time memories
Suoni - Zapsplat.com
Cover e trascrizione - Francesco Zubani

Mostra testo dell'episodio

Nascondi

Quella che segue è una trascrizione automatica dell'episodio.

Nella programmazione, o più in generale nello sviluppo software, il debug, cioè quella attività di scovare le cause di un problema e risolverle, viene considerata fra le più complicate in assoluto. Ma in realtà, se si ci ferma un attimo a riflettere sui meccanismi alla base di tale attività, ci si rende conto che essa è comune a molti ambiti e a molte discipline. In campo medico, ad esempio, viene definita diagnosi differenziale, in campo matematico inferenza bayesiana, ma in pratica si tratta di formulare la giusta tesi a partire da un insieme di indizi che si hanno a disposizione. Sigla. Benvenuti su Pensieri in Codice, il podcast dove si ragiona da informatici, con Valerio Galano. Non ricordo esattamente dove, ma tempo fa mi capitò di leggere una battuta che recitava più o meno in questo modo. Quando fai il debug, stai praticamente investigando su un crimine del quale sei tu stesso il colpevole. In effetti, spesso è così, scriviamo un codice o implementiamo una soluzione software combinando insieme vari strumenti che abbiamo a disposizione e dopo un po’ scopriamo, anzi in realtà spesso lo scopre qualcun altro, che c’è qualcosa che non funziona a dovere, c’è un bug. Allora noi prontamente interveniamo, andiamo a controllare, cerchiamo e troviamo il problema, lo correggiamo e tutto torna a funzionare perfettamente fino al prossimo bug. Questa operazione, questo intervento prende appunto il nome di debug, o debugging, o fare debug insomma. E per chi lavora nel campo dello sviluppo software, come me ad esempio, si tratta di un’operazione all’ordine del giorno. Al di là infatti della battuta, capita spesso di dover intervenire su bug sia propri sia generati da codice di altri. Qui su Pensieri in Codice, debug, te ne ho già parlato in passato, nell’episodio 43 ad esempio ti ho illustrato più o meno in dettaglio le varie tipologie di bug di base e si tratta di un argomento molto interessante, quindi ti lascio il link dell’episodio in descrizione se non l’hai ascoltato e vuoi recuperarlo. Ma affinché tu possa seguire il discorso che sto per farti, ti ricordo semplicemente che i bug si dividono principalmente in tre categorie, bug di sintassi, di runtime e di logica. I bug di sintassi sono abbastanza poco significativi per quello che diremo oggi, dato che basta un buon ambiente di sviluppo o un compilatore per essere in grado di individuarli e segnalarli praticamente subito. I bug di runtime e di logica invece sono quelli realmente un po’ più problematici, perché possono in qualche modo sfuggire a controlli automatici e finire per manifestarsi all’interno dei software proprio nel momento in cui li si sta utilizzando. Può quindi capitare che un tester, cioè un tecnico addetto ai test, stia provando una funzionalità e il sistema non si comporti esattamente nel modo in cui ci si aspetterebbe, o addirittura può capitare che qualcuno stia presentando una demo di quel software e BOOM il sistema va in crash. O ancora, e questo è stranamente molto probabile, un cliente riesce a scovare quel caso limite che causa degli errori nel comportamento del software o dei risultati incongruenti o addirittura il blocco del processo o qualsiasi altra cosa che i clienti sono sempre così bravi a scovare. E il più delle volte per caso. Ma a prescindere da tutto, che cosa fa uno sviluppatore quando gli viene effettivamente segnalato che nel suo software c’è un bug? Beh è semplice, se vogliamo rifarci alla battuta che ti citavo all’inizio, fa quello che farebbe un investigatore alle prese con un nuovo caso, indaga. Per prima cosa si reca di persona sulla scena del crimine, per osservarla, per cercare indizi. Il che vuol dire che accede al software in questione personalmente, per provare a vedere o a riprodurre il problema con i propri stessi occhi. Innanzitutto è infatti necessario capire se è stato davvero commesso un crimine, cioè se il bug esiste veramente, e poi se è possibile raccogliere delle prove sulla scena, cioè cercare di individuare caratteristiche e condizioni del bug, come quando e perché si è verificato. Alle volte fare questa cosa è possibile, ma altre volte no. Può capitare che i bug si manifestino solamente in particolari situazioni, in condizioni specifiche, e quando si prova a riprodurli non si riesce a farlo almeno agevolmente. E oltre a ciò va anche considerato che non sempre chi segnala un bug può essere ritenuto affidabile. Un utente ad esempio può aver male interpretato il funzionamento del software e segnalare un problema sulla base di quanto crede che debba succedere. Ma se il programmatore ha invece un pizzico di fortuna, il bug invece è proprio lì, in bella mostra, e la sua presenza rappresenta un punto di inizio da cui partire per capire la causa del problema. Un investigatore direbbe che ha una pista su cui indagare. Completata questa fase, però, arriva il momento di interrogare i testimoni, sempre se ce ne sono. Se possibile, infatti, il nostro investigatore chiede ai utenti tutta una serie di informazioni. Ad esempio se è la prima volta che appare questo bug, o se l’hanno già riscontrato in qualche caso simile in precedenza, se sono in grado di farlo comparire in modo volontario o evitarlo quando desiderano, o se lo percepiscono come un qualcosa di aleatorio. Dalle loro risposte, infatti, si cerca di stabilire e di circoscrivere un ambito nel quale il problema si manifesta, e con esso anche l’impatto che ha sull’utilizzo del software. Proprio come un detective, infatti, il programmatore potrebbe avere più casi aperti sui quali lavorare e, in queste circostanze, è sempre meglio stabilire una priorità, comunque una sorta di gerarchia. Proprio come indagare su un omicidio è più importante che indagare su uno, magari uno scippo, risolvere un bug che blocca il sistema o che sta deteriorando i dati nel database è più importante che risolvere un colore sbagliato nell’interfaccia o una stringa tradotta male. In ogni caso, la cosa più importante che il nostro detective cerca di stabilire nelle fasi di indagini preliminari è rappresentata dalle circostanze nelle quali si è perpetrato il crimine o nelle quali si è verificato il bug. Nello specifico, se possibile, quale operazione si stava svolgendo, quali utenti la stavano eseguendo, quali input erano coinvolti e perfino data e ora nella quale si è verificato il problema. Tutte queste informazioni si riveleranno poi importanti nelle fasi successive, delle quali una è sicuramente il controllo delle telecamere di sicurezza, che nel software sono i file di log. Con qualche indizio come la data o il nome utente o degli specifici dati in input è infatti possibile ricercare all’interno di questi file, che solitamente sono enormi, il preciso istante in cui il bug si è manifestato e, se i log sono sufficientemente dettagliati e precisi, si può anche ricostruire che cosa stava accadendo nel sistema in quel momento. E se un’analisi del genere va a buon fine, solitamente si riescono a tirare fuori molte informazioni interessanti, ad esempio il contesto in cui si è verificato il bug, ma anche i dati che stavano venendo elaborati e quali erano i loro valori, magari lo stato del sistema, il carico che in quel momento c’era nella macchina e molti altri aspetti che si potrebbero rivelare utili ai fini della risoluzione del caso. Una volta raccolto infatti il maggior numero possibile di indizi si può finalmente passare alle indagini di laboratorio. Tutte queste informazioni vanno infatti analizzate, schematizzate e infine utilizzate per creare una scena del crimine simulata, diciamo così, nel tentativo di riprodurre il bug in un ambiente controllato. Uno sviluppatore infatti è solitamente in possesso di uno o più ambienti di test, che può configurare in modo da simulare nel modo più fedele possibile le condizioni che scatenano il problema in questione, appunto per avere la possibilità di farlo manifestare a comando. Spesso infatti si parla proprio di riproducibilità e questo per spiegare che un bug non riproducibile è quasi impossibile da correggere. A questo punto comunque l’indagine è quasi terminata, al nostro programmatore non resterà altro che analizzare il codice alla ricerca della istruzione o la configurazione o il componente che causa il bug e potrà dire di aver scoperto il colpevole. Qui però apro un attimo una piccola parentesi. Vorrei che facessi molta attenzione che nella metafora che ti sto proponendo il colpevole non è una persona, è un codice. E questo te lo dico perché a volte in alcuni ambienti ho visto iniziare la caccia alle streghe alla ricerca di colui che ha sbagliato perché magari non ha previsto una certa situazione o addirittura ha commesso proprio un errore nello scrivere un codice. E ci tengo a chiarire che per me questa cosa non solo non ha grande utilità, ma anzi molto spesso tende ad essere controproducente per il morale dell’intero team. Quindi, dicevo, una volta scovato il codice o il componente o la configurazione che causa il bug, e cioè il colpevole della nostra indagine, non resta altro che catturarlo. E nella nostra metafora la cattura consiste nel modificare o addirittura riscrivere quello che è il codice incriminato, ovviamente però tenendo sempre presente che bisogna mantenere intatte tutte le altre funzionalità del software. Spero con questa mia metafora di aver espresso in maniera comprensibile il modo in cui io normalmente affronto la risoluzione di un bug, anche considerando che la mia cultura sul metodo investigativo in realtà è frutto più che altro della visione di svariate stagioni di Law & Order, ma a questo punto vorrei provare ad ampliare un po’ il discorso. Devi sapere che l’idea per questo episodio, più o meno come accade per ogni episodio di Pensieri in Codice, mi è venuta nel momento in cui ho letto qualcosa che mi ha particolarmente colpito. Nello specifico, qualche giorno fa mi è capitato di mettere le mani su un sondaggio che ti lascio in descrizione di JetBrains. JetBrains è una società che produce un’ampia gamma di ambienti di sviluppo molto potenti e molto vari per più linguaggi e più tecnologie. In questo sondaggio, in particolare per la parte effettuata tra gli utilizzatori di PHPStorm, quindi gli sviluppatori di software in PHP come me, tra le tante domande ve n’era una sugli strumenti utilizzati per il debug, e le risposte a questa domanda per me sono state assolutamente sorprendenti. Tra gli utilizzatori di questo specifico ambiente di sviluppo, che è lo stesso che utilizzo anch’io, che sempre dal sondaggio risultano essere per la stragrande maggioranza professionisti dello sviluppo e quasi per la metà senior, solamente il 29% utilizza uno strumento professionale per fare debug. Esclusa una piccolissima minoranza che dice di non debuggare proprio, il restante 68% svolge le proprie operazioni di debug inserendo funzioni apposite per mostrare a video le informazioni delle variabili o delle strutture in generale. Il PHP, infatti, offre alcune funzioni specifiche come dump, vardump o roba simile e se tu stesso non sei uno sviluppatore o magari non utilizzi uno strumento di debug professionale, cioè uno strumento appositamente progettato e realizzato per fare questa attività, probabilmente questa percentuale non ti colpirà più di tanto e quindi provo a spiegarti qual è la differenza tra i due approcci. E ovviamente per fare questo utilizzerò i concetti del PHP, dato che per me sono più congeniali, mentre lascio a te il compito di applicare queste informazioni al tuo specifico contesto, se magari utilizzi diversi linguaggi o diverse tecnologie. Dunque, come ti dicevo prima, per me fare debug significa bene o male investigare e in particolare utilizzare uno strumento di debug rientra in quella che prima abbiamo definito la fase di riproduzione in laboratorio del bug. Siamo infatti nel punto in cui abbiamo predisposto le condizioni che riteniamo sufficienti a riprodurre il problema e quindi facciamo eseguire il nostro codice per capire dove si trova esattamente l’errore e come fare a correggerlo. E per ottenere questo risultato senza impazzire dobbiamo avere un sistema per controllare quali operazioni sta svolgendo il nostro codice in un dato momento, quali dati stanno venendo manipolati e in che modo, quindi il contenuto delle variabili, il risultato delle operazioni e così via. E ripeto, per effettuare questi controlli ben il 68% degli intervistati utilizza delle funzioni aggiuntive nel codice. Il che vuol dire che si posiziona all’interno del codice e lo modifica per farsi stampare determinati valori o fare interrompere l’esecuzione in un dato momento. In tal modo il programma esce di colpo ad un certo punto dell’esecuzione e il programmatore può analizzare lo stato dei dati così come sono in quell’istante. Se non trova quello che sta cercando, torna indietro, sposta le funzioni di stampa interruzione più avanti o più indietro nel codice e rilancia il tutto e rianalizza il risultato. E prosegue in questa serie di operazioni fintanto che non riesce a individuare la radice del problema. Chi invece utilizza un sistema di debugging, nel mio caso io prediligo Xdebug con phpStorm, lavora più o meno in questo modo. Nei punti che ritiene interessanti per la risoluzione del problema invece di funzioni di stampa dei dati piazza dei breakpoint, i quali non sono altro che dei piccoli segnalibri che l’ambiente di sviluppo è in grado di interpretare. Nel caso di phpStorm, così come per tantissimi altri editor, si tratta semplicemente di fare un clic affianco alle righe di codice che ci interessano. Poi, una volta scelti tutti i nostri breakpoint, non resta che lanciare l’esecuzione del codice in modalità debug. A questo punto l’esecuzione prosegue normalmente fino ad incontrare uno dei breakpoint piazzati all’inizio e a quel punto si ferma. Ma in questo caso non è un blocco come quelli descritti in precedenza, con il programma che si ferma ed esce. Semplicemente l’esecuzione resta sospesa in attesa di poter proseguire, ma contemporaneamente l’ambiente di sviluppo mostra al programmatore tutta una serie di informazioni sul codice e sui dati, esattamente così come sono nell’istante in cui l’esecuzione si è fermata. A questo punto è possibile esplorare il contenuto delle variabili, ricostruire sequenze di funzioni invocate, addirittura si possono apportare modifiche ai dati ed eseguire codice arbitrario. E tutto questo nell’esatto stato in cui l’esecuzione è stata sospesa. Una volta poi che ha fatto tutte le prove e le analisi del caso, il programmatore può decidere di far avanzare l’esecuzione una riga per volta, magari per vedere cosa accade riga per riga all’interno del codice, oppure passare direttamente al breakpoint successivo, oppure ancora lasciare che il programma proceda fino alla fine o venga bloccato e magari ricominci da capo. Ora, io non lo so se sono riuscito a renderti l’idea, ma la differenza nell’esperienza di utilizzo è enorme. Personalmente, da quando ho iniziato a utilizzare il debugger non ne ho mai più fatto a meno. Spesso io lo uso perfino in fase di sviluppo del codice, lanciando il programma, facendolo fermare ad un breakpoint e provando istruzioni per vederne immediatamente i risultati senza dover poi rilanciare pagine o processi. Ormai, qualunque sia il progetto sul quale devo lavorare, il mio setup di base comprende sempre il debugger attivo e funzionante. Ma ancora non è tutto. Questo episodio verrà lunghissimo. Nel mondo reale alcuni bug sono tostissimi. Se si vuole avere qualche speranza di poterli correggere, non basta solo padroneggiare degli strumenti e avere un buon approccio investigativo, diciamo così. Occorre anche farsi trovare pronti e applicare tutta una serie di contromisure prima che i bug si verifichino. E la lista di cose che mi viene in mente in questo momento probabilmente non è completa ed è anche molto lunga e ognuno dei punti meriterebbe un episodio a sé. Ma proverò almeno a fare un elenco che magari ci servirà come punto di partenza per un’interessante discussione. Sicuramente il modo in cui si scrive il codice può avere un forte impatto nel momento in cui si dovrà andare ad analizzare e correggere un bug. Un codice ad esempio robusto e magari disaccoppiato potrebbe rendere più semplice la scrittura di una patch. Un codice pulito, magari elegante, scritto rispettando gli standard di progetto potrebbe anche essere d’aiuto nel momento in cui lo si andrà a rileggere nel tentativo di capire il senso delle singole istruzioni e se c’è qualcosa che non va tra esse. E lo stesso potrebbe valere per dei commenti scritti bene, dove per bene intendo che effettivamente aiutino a capire il funzionamento del codice. Non parlo ad esempio di quei commenti tipo ritorna il valore scritto sulla istruzione return. C’è poi l’utilizzo dei log. Loggare ogni volta che viene effettuata un’operazione della quale magari in futuro sarebbe interessante poter sapere il risultato e porsi il problema di quali informazioni saranno utili per un’eventuale risoluzione di problematiche future è sicuramente un ottimo modo per utilizzare i log. Una riga ad esempio che descrive importazione fallita mi servirà a ben poco se non sarò in grado di ricostruire quale importazione di quale elemento, gli ID coinvolti, tutto quello che serve per riconoscere quale operazione veniva eseguita in quel momento. Ci sarebbe poi tutto un discorso da fare sul modo di scrivere le interfacce e non intendo solo le interfacce verso gli umani anche quelle verso gli altri sistemi e poi il fatto di gestire tutti i casi particolari come lo spazio esaurito se si lavora con dei file con il file system o altri tipi di risorse da verificare se sono disponibili se sono occupate o la gestione di errori in caso di una connessione che potrebbe non sempre essere funzionante e tante altre cose del genere e tutto questo è da considerare ancora prima che si verifichi un qualsiasi bug. Nel momento in cui poi il problema ci verrà segnalato come abbiamo visto ci servirà sicuramente un po di capacità investigativa, degli strumenti professionali come gli ambienti di sviluppo, i sistemi di testing, i strumenti di editing e debugging ma oltre a questo ci sono altre cose da tenere presenti. Prima fra tutte un corretto atteggiamento mentale, essere convinti che il proprio codice sia perfetto ed esente da bug ad esempio è un problema di molti sviluppatori così come prendere per oro colato qualsiasi segnalazione da parte di un qualsiasi utente e invece

Nascondi