Sviluppo software: gli errori di programmazione da evitare
Proprio come il mondo dell’arte è pieno di pareri estremamente divergenti su ciò che rende una grande opera d’arte, i programmatori spesso non sono d’accordo su ciò che rende eccellente un codice, almeno al di là del requisito di base che non deve andare in crash. Ogni sviluppatore ha infatti le proprie regole e linee guida, ma possono sorgere problemi quando compensiamo eccessivamente un errore andando nella direzione opposta. Supponiamo che il vostro team di sviluppo eviti la trappola x scegliendo invece la y, per poi scoprire che quest’ultima ha i suoi problemi, il che porta a un altro lungo weekend perso.
La buona notizia è che potete imparare sia dall’errore iniziale, sia dalla compensazione eccessiva. La strada migliore per raggiungere il nirvana è spesso quella intermedia e in questo articolo analizziamo alcuni degli errori di programmazione più comuni e i pericoli che si corrono facendo il contrario.
Lavorare in fretta e furia
Ignorare le basi è uno dei modi più semplici per produrre codice instabile e a rischio di crash. Forse questo significa ignorare come un comportamento arbitrario dell’utente possa influenzare il vostro programma. Il testo inviato sarà sempre della lunghezza giusta? I formati delle date seguono lo standard corretto? Il nome utente viene verificato rispetto al database? Il più piccolo errore può causare il fallimento del software. Un modo per risolvere questo rischio è sfruttare le funzioni di cattura degli errori del codice.
Ossessione per i dettagli
Si dice che un buon programmatore è colui che guarda in entrambe le direzioni quando attraversa una strada a senso unico. Questa tendenza può però ritorcersi contro. Un software eccessivamente curato può infatti rallentare le vostre operazioni e può capitare che in un sistema troppo “controllato” non venga eseguita alcuna elaborazione perché il codice si perde in un labirinto di verifiche e autenticazioni. La sfida consiste nel progettare gli strati di codice in modo da verificare i dati al loro primo apparire e poi lasciarli passare. Certo, ci saranno degli errori, ma il controllo degli errori serve proprio a questo.
Troppa complessità teorica
Alcuni programmatori amano lo studio degli algoritmi. Si divertono a progettare strutture dati e algoritmi complessi perché vogliono costruire lo stack più efficiente possibile. Ogni livello o libreria deve essere perfetto. Può essere anche una cosa positiva, ma in molti casi il risultato finale è un’applicazione enorme che consuma troppa memoria e gira lentissima. In teoria sarà veloce, ma lo vedrete solo quando ci saranno 100 miliardi di utenti con 50 milioni di documenti per utente.
Gran parte della teoria algoritmica si concentra sulla scalabilità degli algoritmi e delle strutture dati. L’analisi si applica solo quando i dati diventano grandi e, in molti casi, la teoria non tiene conto della quantità di codice necessaria per ridurre il tempo ed elude dettagli cruciali. Uno dei maggiori sprechi di tempo è il recupero dei dati dalla memoria principale o, peggio, da un database nel cloud. Concentrarsi sui problemi pratici di dove vengono memorizzati i dati e quando vi si accede è meglio di una struttura di dati elaborata.
Non c’è abbastanza complessità teorica
Il rovescio della medaglia dell’impantanarsi nella teoria della programmazione è ignorare il lato teorico di una struttura dati o di un algoritmo. Il codice scritto in questo modo potrebbe funzionare senza problemi sui dati di prova, ma si impantana al momento dell’implementazione, quando gli utenti iniziano a inserire i loro record nel sistema.
Scalare bene è una sfida e spesso è un errore trascurare i modi in cui la scalabilità può influenzare il funzionamento del sistema. A volte è meglio considerare questi problemi nelle prime fasi della pianificazione, quando il pensiero è più astratto. Alcune funzioni, come il confronto tra ogni dato inserito e un altro, sono intrinsecamente quadratiche, il che significa che le vostre ottimizzazioni potrebbero crescere esponenzialmente più lentamente.
Pensare a quanta teoria applicare a un problema è un po’ un metaproblema perché la complessità spesso aumenta in modo esponenziale. A volte la soluzione migliore è un’iterazione accurata con molto tempo a disposizione per i test di carico. Una vecchia massima afferma che “l’ottimizzazione prematura è una perdita di tempo”. Iniziate con un programma di base, testatelo e poi correggete le parti più lente.
Troppa fiducia nell’intelligenza artificiale
Siamo in un momento in cui sta diventando chiaro che gli algoritmi di intelligenza artificiale possono fornire risultati sorprendenti. I risultati sono sorprendentemente realistici e migliori del previsto, tanto che molti credono sia arrivata l’era del computer senziente. L’IA, in effetti, fornisce a volte dati incredibilmente utili. I programmatori hanno sostituito i motori di ricerca con grandi modelli linguistici perché non sopportano più tutte le pubblicità e le funzioni “amplificate” create dagli umani. Diffidano dell’interferenza umana e si affidano al machine learning.
È importante, però, capire esattamente cosa possono fare gli algoritmi e come funzionano. I sistemi di machine learning analizzano i dati e quindi costruiscono una funzione elaborata che li imita. Sono come pappagalli intelligenti che trasmettono testi. Il problema è che sono programmati per fornire tutto con la stessa sicura autorità, anche quando si sbagliano completamente. Nel peggiore dei casi, un’IA può sbagliare del tutto e non rendersene conto più di quanto lo facciamo noi.
Non ci sono abbastanza dati di addestramento
Un modello di intelligenza artificiale è tanto valido quanto lo sono i suoi dati di addestramento. Ora che gli algoritmi di machine learning sono abbastanza validi da poter essere eseguiti da chiunque, i programmatori saranno chiamati a inserirli nello stack per qualsiasi progetto. Il problema è che gli strumenti di IA sono ancora “inquietanti” e imprevedibili. Possono dare ottimi risultati, ma anche commettere errori madornali. Spesso il problema è che i dati di addestramento non sono sufficientemente ampi o rappresentativi.
Un “cigno nero” è uno scenario che non è stato coperto dai dati di addestramento. Sono rari, ma possono confondere completamente un’intelligenza artificiale. Quando gli eventi non sono presenti nei dati di formazione, l’IA può produrre una risposta casuale. La raccolta di dati non è l’attività che i programmatori sono soliti svolgere. Addestrare un modello di intelligenza artificiale significa raccogliere dati invece di scrivere solo logica. È una mentalità diversa da quella a cui siamo abituati, ma è essenziale per creare modelli di intelligenza artificiale affidabili.
Affidare la sicurezza alle scatole magiche
Siete preoccupati per la sicurezza? Basta aggiungere un po’ di crittografia. Peccato che non sia tutto così semplice. Il mondo sta appena iniziando a capire il problema della condivisione di troppo codice in troppe librerie. Quando è apparso il bug di Log4j, molti manager sono rimasti scioccati nel trovarlo profondamente integrato nel loro codice. Molte persone si sono affidate a questo strumento, tanto da trovarlo all’interno di librerie che a loro volta si trovano all’interno di altre librerie incluse in un codice eseguito come servizio autonomo.
A volte il problema non è solo in una libreria ma anche in un algoritmo. La crittografia è una delle principali fonti di debolezza, afferma John Viega, co-autore di 24 Deadly Sins of Software Security: Programming Flaws and How to Fix them. Troppi programmatori pensano di poter collegare la libreria di crittografia, premere un pulsante e ottenere una sicurezza assoluta. Il National Institute of Standards and Technology, ad esempio, ha appena annunciato il ritiro di SHA-1, un primo standard per la costruzione di un messaggio hash; sono stati infatti trovati abbastanza punti deboli da rendere necessario un cambio di rotta. La realtà è che molti di questi algoritmi magici presentano sottili punti deboli e, per evitarli, è necessario imparare qualcosa di più di quello che c’è scritto nella sezione “avvio rapido” del manuale.
Troppa fiducia nel cliente
I programmatori spesso dimenticano che non hanno il controllo completo del loro software quando questo viene eseguito sulla macchina di qualcun altro. Alcuni dei peggiori bug di sicurezza compaiono quando gli sviluppatori danno per scontato che il dispositivo client faccia la cosa giusta. Ad esempio, il codice scritto per essere eseguito in un browser può essere riscritto dal browser per eseguire qualsiasi azione arbitraria. Se lo sviluppatore non ricontrolla tutti i dati in arrivo, tutto può andare storto.
Uno degli attacchi più semplici si basa sul fatto che alcuni programmatori si limitano a passare i dati del cliente al database, un processo che funziona bene fino a quando il cliente non decide di inviare SQL invece di una risposta valida. Se un sito web chiede il nome di un utente e lo aggiunge a una query, l’attaccante potrebbe digitare il nome x; DROP TABLE users;. Il database assume doverosamente che il nome sia x e passa al comando successivo, cancellando la tabella con tutti gli utenti.
A peggiorare le cose, ci sono le gravi vulnerabilità di sicurezza che possono sorgere quando falle apparentemente benigne vengono concatenate tra loro. Un programmatore può consentire al client di scrivere un file, presumendo che i permessi della directory blocchino qualsiasi scrittura. Un altro potrebbe aprire i permessi solo per risolvere un bug casuale. Prese singolarmente, non danno problemi, ma messe tutte insieme queste decisioni di codifica possono dare accesso arbitrario al client.
Non c’è abbastanza fiducia nel cliente
Anche un eccesso di sicurezza può portare a dei problemi. Forse non si tratta di buchi clamorosi, ma di problemi generali per l’intera azienda. I siti di social media e gli inserzionisti hanno capito che una sicurezza eccessiva e una raccolta di dati invasiva possono scoraggiare la partecipazione.
Per questo motivo molti sviluppatori web cercano di ridurre il più possibile la sicurezza, non solo per rendere più semplice l’utilizzo dei loro prodotti, ma anche per evitare di dover difendere più della quantità minima di dati necessaria. Una delle ultime tendenze è quella di eliminare del tutto le password. Le persone non riescono più a tenerne traccia e quindi i siti web inviano un’e-mail monouso per l’accesso che non è molto diversa da un messaggio di ripristino della password. Si tratta di un meccanismo più semplice che alla fine è altrettanto sicuro.
Chiudere la fonte
Una delle sfide più difficili per qualsiasi azienda è determinare quanto condividere con gli utenti del software. John Gilmore, co-fondatore di una delle prime aziende di software open source, Cygnus Solutions, afferma che la decisione di non distribuire il codice va a discapito dell’integrità del codice stesso. La distribuzione è infatti uno dei modi più semplici per incoraggiare l’innovazione e, soprattutto, per scoprire e risolvere i bug.
“Un risultato pratico dell’apertura del codice è che persone sconosciute contribuiranno a migliorare il vostro software. Troveranno bug e cercheranno di risolverli, aggiungeranno funzionalità e miglioreranno la documentazione. Anche quando il loro miglioramento è stato fatto in modo amatoriale, qualche minuto di riflessione rivelerà spesso un modo più armonioso per ottenere un risultato simile”, ha affermato Gilmore. I vantaggi sono ancora più profondi. Spesso il codice stesso diventa più modulare e meglio strutturato man mano che gli altri lo ricompilano e lo spostano su altre piattaforme. La semplice apertura del codice costringe a rendere le informazioni più accessibili, comprensibili e quindi migliori.
L’apertura come panacea
Sono stati lanciati milioni di progetti open source e solo una minima parte ha attirato più di un paio di persone che hanno contribuito alla manutenzione, alla revisione o all’estensione del codice. In altre parole, il detto di W.P. Kinsella “se lo costruisci, la gente verrà” non sempre produce risultati esaltanti.
Se da un lato l’apertura consente ad altri di contribuire e quindi di migliorare il vostro codice, dall’altro il solo fatto che sia aperto non serve a molto se non c’è un incentivo per i collaboratori esterni a impegnarsi. L’apertura da sola non previene le falle di sicurezza, non elimina i crash e non rende un mucchio di codice non finito intrinsecamente utile. Le persone hanno altre cose da fare e un mucchio di codice aperto spesso fa concorrenza a un lavoro retribuito.
L’apertura di un progetto può anche aggiungere nuovi costi di comunicazione e documentazione. Un progetto closed-source richiede una solida documentazione per gli utenti, ma un progetto open source richiede anche la documentazione delle API e delle road map per lo sviluppo futuro. Questo lavoro extra è utile per i grandi progetti, ma può appesantire quelli più piccoli.
Troppo spesso, inoltre, il codice che funziona in parte viene pubblicato su GitHub con la speranza che decine di sviluppatori si mettano a lavorare su di esso: una decisione che può far deragliare lo slancio di un progetto prima che sia veramente avviato. Il bug Goto Fail di Apple e la vulnerabilità di Log4j sono solo due buoni esempi di errori nascosti in bella vista per anni. La buona notizia è che alla fine qualcuno li ha trovati. La cattiva notizia è che nessuno di noi sa cosa non è stato ancora trovato.
L’apertura di un progetto di sviluppo può infineallontanare il sostegno finanziario e incoraggiare una sorta di “governo della folla”. Molte aziende open source cercano di mantenere il controllo su alcune funzioni proprietarie; in questo modo hanno la possibilità di convincere le persone a pagare per sostenere il team di sviluppo principale. I progetti che invece si affidano più a volontari che a programmatori pagati spesso scoprono che i volontari sono imprevedibili. Anche se la competitività e la creatività possono dare grandi risultati, alcuni tornano a progetti closed-source, dove la struttura, la gerarchia e l’autorità supportano uno sviluppo più metodico.