Il concetto di bug, un tempo precluso al solo processo di sviluppo del software, è oggigiorno di dominio comune. A eccezione dei più giovani, tutti noi ricordiamo l’ingente lavoro di aggiornamento e sanificazione a livello globale di tantissimi apparati di controllo per scongiurare quello passato alla storia come Millennium Bug, ossia l’incapacità dei calcolatori più datati, che memorizzavano l’anno corrente attraverso solo le ultime due cifre, di rappresentare l’anno 2000 correttamente, ritenendolo invece il 1900, con imprevedibili conseguenze sul loro corretto funzionamento.
Nell’immaginario collettivo però si pensa che i malfunzionamenti dei programmi siano causati o dell’incapacità degli sviluppatori o da una specifica politica di sviluppo votata al risparmio, principalmente per quanto concerne il mercato delle app per gli smartphone. Premesso che la maggioranza di quei programmi è pur sempre scritta da non professionisti, e che comunque ogni release finale di un software idealmente non dovrebbe presentare alcun difetto, la presenza di bug all’interno di un software, anche d’alto taglio commerciale, è assolutamente fisiologica, poiché non è mai possibile prevedere perfettamente il comportamento dello stesso software sui sistemi più disparati; inoltre più la struttura del software stesso o del team di sviluppo è complessa, più le probabilità che si verifichino dei bug aumentano. L’importante è sempre venirne a capo e cercare di correggerne il più possibile attraverso delle opportune patch, evitando però di introdurne di ulteriori, ipotesi assolutamente non trascurabile, come si vedrà in seguito.
S’è bloccato, ah questi bug!
Innanzitutto occorre definire precisamente cos’è un bug: un bug è un errore o un malfunzionamento di un software che porta a dei risultati inattesi, inconcludenti o nella peggiore delle ipotesi pericolosi per i dispositivi che eseguono tale software. Inoltre vengono considerati bug anche eventuali falle di sicurezza che possono permettere l’accesso a utenti non autorizzati o malintenzionati. Letteralmente ‘bug’ in inglese vuol dire ‘baco’ o in generale ‘piccolo insetto’, mentre in senso lato si può tradurre come ‘imperfezione’; ironia della sorte, il primo accertato bug report della storia dell’informatica è datato 1947, quando fu riportata come causa di un malfunzionamento di un computer militare statunitense una falena che si incastrò tra i circuiti.
Un bug da ormai oltre un decennio non è più da considerarsi un errore sintattico di scrittura del codice del software, data l’ormai massiccia e capillare diffusione di sistemi e ambienti di sviluppo (chiamati IDE, integrated development environment) che rilevano e correggono sul nascere tali problemi. Inoltre anche le limitazioni di memoria e di potenza di calcolo degli scorsi decenni, causa per l’appunto del Millennium Bug (oltre a una reticenza generale nell’aggiornare i sistemi dettata dal risparmio economico) sono state abbondantemente superate. E allora, perché ci sono ancora i bug? Per una svariata gamma di errori possibili nelle varie fasi di sviluppo, ma raggruppabili in due tipi: semantici e di runtime.
Gli errori semantici o logici sono tutti quelli dovuti a logiche sbagliate in fase di progettazione degli algoritmi di risoluzione. Molto schematicamente, lo sviluppo di un software si compone di una fase progettuale, durante la quale sono definite le problematiche e progettati i rispettivi algoritmi di risoluzione, e di una fase di sviluppo effettivo, composta dalla scrittura del codice e test di quest’ultimo. Se nella prima fase si commettono degli errori, essi si propagheranno sistematicamente in fase di sviluppo, generando risultati inconcludenti o non funzionando affatto, anche se il codice è sintatticamente corretto: in poche parole, non è scritto male come deve funzionare, ma proprio è sbagliato cosa deve fare.
Un esempio storico di errore logico è il celebre bug del livello 256 di Pac-Man, nella versione classica arcade. Il bug scatta a causa di un overflow (esubero) della variabile associata al livello in corso, che nel gioco originale era memorizzata in un solo byte: un byte può rappresentare un numero intero positivo compreso tra 0 (tutti e otto i bit pari a 0) e 255 (tutti e otto i bit pari a 1). A ogni avanzamento di livello seguiva una semplice aggiunta di 1 per l’aggiornamento della relativa variabile. Il numero del livello in corso veniva utilizzato nell’algoritmo che disegnava su schermo gli sprite (è stato spiegato nel primo degli articoli sulla grafica cosa sono) degli oggetti acquisiti nei livelli precedenti (i frutti, le chiavi, etc.): se il gioco corrente arrivava al 256° livello, il contatore però passava da 255 di nuovo a 0, poiché la somma di 1 porterebbe a un teorico numero di nove bit, tutti 0 tranne un 1 iniziale, e quest’ultimo che non può essere memorizzato (appunto, è in esubero). Ciò comportava un malfunzionamento dell’algoritmo di disegno degli oggetti, che andava a prendere dati in locazioni della ROM alle quali non avrebbe dovuto avere accesso, generando una mappa a schermo piena di caratteri senza senso e compromettendo irrimediabilmente il gioco.
Gli altri errori sono detti di runtime o d’esecuzione e si manifestano appunto durante l’esecuzione del programma, anche se gli algoritmi sono sia semanticamente che sintatticamente corretti. Quasi sempre avvengono o a causa di valori in input particolari o a causa di un’errata gestione delle memorie, ad esempio accedendo o sovrascrivendo in locazioni alle quali di norma non si dovrebbe accedere. I bug a tempo d’esecuzione sono estremamente comuni nei videogiochi, poiché nel loro sviluppo è largamente usato il paradigma della programmazione a eventi, secondo il quale il flusso del programma e le istruzioni da eseguire sono dipendenti, piuttosto che da scelte di programmazione, dal verificarsi o meno di una serie di eventi esterni, ovvero i comandi che il giocatore dà agli elementi di gioco che controlla.
Ecco altri due esempi di bug storici nei videogiochi dovuti questa volta a errori d’esecuzione: il Pokémon MissingNo. di Pokémon Rosso e Blu e il Minus World del primo Super Mario Bros. Entrambi i bug scattavano a causa di particolari azioni effettuate volutamente o meno dal giocatore: ognuna di queste azioni si traduceva durante l’esecuzione del programma in accessi errati alla ROM e alle memorie buffer, causando errori nel comportamento del gioco. In particolare MissingNo., ovvero Missing Number (numero mancante), è, in breve, la risposta a schermo di un caricamento dal buffer nel quale era stato precedentemente salvato un valore non concludente per la funzione del gioco che gestisce le battaglie casuali: anziché trovare i dati di un Pokémon, il gioco trovava dati relativi ad altre sottofunzioni, i quali però venivano interpretati dal gioco come se fossero un Pokèmon effettivo. Il Minus World o -1 World invece scattava a causa di un caricamento errato di valori presenti nella ROM del gioco, causato a sua volta da un’invocazione alla funzione di gestione delle warp zones con valori errati nelle variabili di controllo, generando dunque a schermo una versione di un livello acquatico riproposto all’infinito fino al game over.
Pavimenti scivolosi
Altre tipologie di eventuali risposte indesiderate da parte di un software videoludico sono i glitch (letteralmente ‘scivolamento’), termine mutuato dall’ingegneria che identifica un errore di picco improvviso e di breve durata di un segnale. Con glitch nell’ambito dei videogiochi si intende un comportamento anomalo di alcune sue funzioni, di solito quelle grafiche, che non necessariamente compromettono l’esperienza di gioco, ma anzi possono tramutarsi in vantaggi e agevolazioni non previste per il giocatore. Un glitch ad esempio può essere l’aggiramento di una barriera di fine livello, una scorciatoia creata a causa di un’interazione sbagliata tra alcune tile, etc. Comunemente bug e glitch vengono trattati come sinonimi, ma ciò è sbagliato: un glitch non è un errore di programmazione, ma una risposta bizzarra a una scelta progettuale definita durante lo sviluppo che si può avere grazie a una precisa e spesso maliziosa serie di comandi da parte del giocatore, che in fase di test viene trascurata. Non è sbagliato però affermare che alcuni glitch possano poi far scattare dei bug, ovvero che da scelte progettuali comunque corrette si possa arrivare a errori di risposta che “rompono” il gioco e che possono danneggiare il sistema. Gli esempi precedenti di MissingNo. e del Minus World ne sono una prova, poiché entrambi quei bug si verificano a partire da un glitch dovuto a una serie di azioni da parte del giocatore che genera una risposta a video anomala, come l’attraversamento di un muro.
Altri esempi storici di glitch possono essere tutti quelli che permettevano l’utilizzo di oggetti di gioco normalmente preclusi, come ad esempio lo Swordless Link glitch di The Legend of Zelda: Ocarina of Time, oppure crearne istanze a dismisura, come ad esempio il W-Item glitch di Final Fantasy VII o l’item duping glitch di Diablo 2. Anche i videogiochi più recenti soffrono di glitch, ad esempio in Cuphead è possibile completare comodamente la seconda parte della boss battle finale non passando verso la nuova schermata e sparando verso l’alto, dove risiede l’effettiva hitbox del boss.
Nonostante generino vantaggi “illegittimi” per il giocatore, i glitch non sono neanche trucchi o aree test del giochi, dato che quest’ultimi vengono lasciati di proposito all’interno del codice al fine di poter effettuare i test più velocemente. I glitch sono estremamente ricercati dagli speedrunners, ovvero i giocatori che competono tra loro per cercare di battere un particolare videogioco nel minor tempo possibile; la ricerca sistematica di eventuali glitch viene chiamata glitching ed è una delle caratteristiche essenziali per i beta tester, ossia personale esterno al team di sviluppo ingaggiata per effettuare test di versioni quasi definite di un videogioco, chiamate in gergo beta version.
Metterci una pezza
Sia durante la fase di test che dopo l’effettivo rilascio del software, quando vengono rilevati dei bug parte sistematicamente un’attività finalizzata alla loro rimozione chiamata debugging. Tale attività consiste in una serie di fasi ben distinte, dall’identificazione dei bug rilevati durante i test o segnalati dagli utenti finali fino al loro tentativo di rimozione attraverso modifiche del codice sorgente o delle strutture dati. La delicatezza del debugging risiede proprio nelle fasi di modifica, poiché è necessario comunque preservare la generale integrità della struttura del software, non manomettendone assolutamente i punti chiave. Inoltre la possibilità di introdurre nuovi errori o comportamenti inaspettati nel tentativo di correggere dei bug non è affatto trascurabile. Questi sono i motivi principali per cui, nonostante vengano correttamente rilevati, molti bug non possono essere corretti, o al massimo solo parzialmente. Un team di sviluppo può avere anche i migliori programmatori e ampie risorse economiche, ma se eventuali tentativi di correzione recano più danni che benefici, essi semplicemente non vengono perseguiti, e si lavora sostanzialmente alla circoscrizione dei bug per limitare al minimo gli eventi che possono scatenarli, attraverso un’opportuna manualistica.
Se una correzione al codice sorgente è possibile, efficace e opportunamente testata, essa viene successivamente rilasciata agli utenti finali del software attraverso delle versioni rivedute chiamate bugfix o più comunemente patch (in inglese traducibile in ‘pezza’ o ‘toppa’). Anche l’utilizzo di questo termine deriva dall’era pionieristica dell’informatica: gli storici calcolatori militari degli anni Quaranta e Cinquanta ricevevano i programmi e i dati in ingresso attraverso dei rotoli di carta perforata, ed eventuali errori di perforazione venivano corretti attraverso delle toppe di carta da applicare fisicamente sull’errore. La metafora è rimasta tutt’ora, eventuali errori vengono corretti proprio “mettendoci una pezza sopra”, correggendoli attraverso sovrascrittura o riscrittura. Nell’ambito videoludico le patch possono essere rilasciate non solo per la correzione di eventuali bug, ma anche ad esempio per modificare modelli o sprite oppure per ricalibrare in alcuni punti gli algoritmi che governano i comportamenti dei nemici gestiti dalla CPU.