> INDICE DEL CORSO < Articolo precedente: Sviluppare app multipiattaforma - Un’introduzione
Argomenti:
- Occorre sapere qualcosa sull'hardware?
- Macchine programmabili e algoritmi
- L’incertezza e l'arroganza dell’algoritmo: fa quello che vuole lui, quello che vogliamo noi o quello che capita?
- L'algoritmo agnostico
Occorre sapere qualcosa sull’hardware?
Generalmente noi programmatori, salvo motivate eccezioni, nel nostro lavoro ci concentriamo esclusivamente sul software: ci sono validi e circostanziati motivi per occuparci dell’hardware se ad es. dobbiamo far uso di determinati sensori o se abbiamo bisogno di funzionalità che richiedono almeno un certo tipo di cpu o una certa quantità di memoria. Ad ogni modo, nel caso della programmazione multipiattaforma, ignorare le specificità hardware dei vari dispositivi è particolarmente sensato, in quanto il nostro obiettivo principale è proprio quello di scrivere codice che funzioni “possibilmente” ovunque, ovvero su hardware anche molto diversi. Storicamente molti sono stati e continuano ad essere gli sforzi in tal senso.
Tra l’altro, per completezza, ci sono situazioni in cui veramente non sappiamo e non possiamo sapere nulla dell’hardware: pensiamo allo sviluppo di una web-app. Essendo basata su HTML5 + Javascript, sostanzialmente girerà dentro un browser, che potrebbe essere quello di uno smartphone iPhone, Android, Windows Phone e altri, potrebbe essere quello di un computer portatile, potrebbe persino essere lo schermo di un televisore. Non ho mai usato uno smart watch, ma se esso incorpora un browser, allora la nostra web-app potrebbe funzionare anche dentro un orologio da polso. In questi casi non soltanto non ci interessa minimamente l’hardware sottostante, ma saremo presi da ben altri problemi, ad es. quali sono i browser su cui fare i test e le modalità con cui l’utente dovrà interagire con l’app.
Proprio in questo esempio, è evidente che un’app pensata per funzionare su schermi grandi seguirà logiche diverse da una pensata per funzionare sui piccoli schermi dei nostri smartphone; allo stesso modo, un’app pensata per essere usata con una tastiera reale sarà fatta in modo diverso da una pensata per essere usata con una tastiera virtuale, che a sua volta sarà diversa da una destinata ad essere usata con un telecomando.
In sintesi, ciò che in primis ci interessa dell’hardware è come l’utente potrà e dovrà interagire con la macchina, in modo da pensare fin dall’inizio a come dovrà essere l’interfaccia utente.
Macchine programmabili e algoritmi
Generalmente ci si riferisce ai computer e alle loro varie declinazioni tecnologiche (smart-watch, smart-tv, smart-phone, smart-frullatore, smart-spazzolino, smart-..., ecc.) senza mai usare il termine che più propriamente identifica questi oggetti: macchine.
Più nello specifico, trattasi di macchine programmabili, inanimate, ovvero senza vita e prive di ogni forma di intelligenza o di altre caratteristiche umane (cognizione di sé, cognizione degli altri, empatia, sentimenti, ecc.), capaci di eseguire calcoli estremamente semplici dal punto di vista concettuale, e nulla di più. Tutto il resto, a cominciare da una vera forma di intelligenza, ce la mettono i programmatori.
Per dirla ancora più brutalmente, trattasi di macchine in cui segnali elettrici digitali vengono usati per rappresentare valori booleani (cioè “vero” o “falso”). L’algebra di Boole viene utilizzata per la costruzione di porte logiche (porte “and”, “or”, “not” e loro combinazioni), costruite all’interno di circuiti integrati basati su transistor.
Non mi dilungo oltre, l’importante è aver chiaro noi programmatori abbiamo a che fare con macchine programmabili: la distanza concettuale tra scrivere codice ad altissimo livello di astrazione, ovvero multipiattaforma, e i segnali elettrici che poi di fatto faranno funzionare il nostro codice, è così ampia che c’è il serio rischio di dimenticarci ciò con cui abbiamo a che fare. Specialmente in questo periodo storico, in cui c’è un’enorme enfasi sull’Internet delle Cose e sull’Intelligenza Artificiale, il rischio di esser fuorviati è enorme.
L’intelligenza è propria soltanto degli organismi viventi creati dalla natura: le nostre macchine, per quanto sofisticate, sono e rimangono macchine inanimate calcolanti, incapaci di intendere e di volere.
Detto ciò, per rendere queste macchine capaci di “fare qualcosa” noi scriviamo algoritmi. Al di là delle varie definizioni formali di “algoritmo”, per le quali rimando alla relativa voce su Wikipedia, sostanzialmente un algoritmo è un’entità che fa qualcosa e, nella pratica quotidiana, questa entità non è altro che un pezzo di codice.
Concettualmente, un algoritmo è un elenco di istruzioni eseguibili da una macchina in un tempo finito per fare una determinata cosa. Comunque, lo stesso concetto si può estendere anche al di fuori dell'Informatica, ad es. una ricetta per cucinare è un algoritmo. Possiamo anche creare algoritmi scherzosi, come quello qui a lato rappresentanto tramite flowchart, che nello specifico è un algoritmo di decisione.
Per ulteriori riflessioni introduttive sul significato di algoritmo, rimando alle mie slides: “Cosa sono i linguaggi di programmazione”, che preparai nel lontano 2005 e che sono tuttora valide (gli esempi di codice a cui mi riferisco nell'ultima pagina sono a questo link):
L’incertezza e l'arroganza dell’algoritmo: fa quello che vuole lui, quello che vogliamo noi o quello che capita?
Molteplici algoritmi tra loro interagenti, all’interno di una complessità algoritmica spaventosa e quasi inconcepibile per noi persone comuni, rendono utilizzabili le nostre macchine programmabili, ad es. i nostri smartphone.
Per avere un termine di paragone sul livello di complessità delle macchine al nostro servizio, prendiamo un recente e triste caso di cronaca: il 29 ottobre 2018, un aereo della ditta Boeing, nuovissimo, modello “737 MAX”, pochi minuti dopo il decollo da Giacarta, cadde. Morirono tutti (189 persone, più un’altra persona incaricata delle ricerche in mare). La causa fu un errore di programmazione del software di controllo dell’areo, nello specifico un errore di rilevazione della velocità (sto semplificando, in rete si trovano i dettagli di quel che accadde). Orbene, il software di controllo di tale aereo era composto da circa 14 milioni di righe di codice (fonte): un errore fatale purtroppo ci poteva anche stare in così tanto codice… soprattutto, per chi s'è letto i rapporti, se ai piloti, come in questo caso, era stata sottratta ogni possibilità di intervento manuale correttivo (il fatto che il computer del Boeing sia stato programmato per "prevalere sull'uomo" è spiegato in questo articolo). Questo è uno di quei casi in cui, in fase di progettazione, dare più credito alla pseudo-intelligenza senz'anima delle macchine che alla vera intelligenza umana ha comportato una strage. Sempre nel 2018, c'è stata un ulteriore strage aerea per lo stesso motivo. In tutti i casi in cui le decisioni umane vengono delegate a macchine si crea una situazione di stupidità umana da una parte e arroganza dell'algoritmo dall'altra. Ho avuto modo di esprimermi a proposito dell'arroganza dell'algoritmo anche nel caso dei social (link).
Nella normalità della mia vita quotidiana di programmatore, se riuscissi a scrivere cento righe senza fare neanche un errore mi complimenterei con me stesso… ma la realtà è tutt’altra: non mi capita quasi mai che quello che scrivo sia corretto e funzionante come desidero già dal primo tentativo; una tale eventualità può capitarmi per cose estremamente semplici che stanno in poche righe (e a volte neanche in quei casi).
La storia dell’informatica è piena di errori di programmazione che sono stati disastrosi e funerei, errori a volte così subdoli da sfuggire ai migliori programmatori al servizio della NASA o di grandi corporation, errori che hanno comportato danni economici ingenti e morte. In questa pagina c'è riportato il codice che, poco dopo un minuto dopo il lancio (era il 1996), ha fatto esplodere un mezzo spaziale per un bug che, tutto sommato, può capitare a qualunque programmatore (tecnicamente, volendo usare la terminologia di Java di cui ci occuperemo nei prossimi articoli, è stato fatto qualcosa di molto simile a un "casting" errato). Basta cercare in Rete per trovare tracce dei bug che hanno segnato la storia dell'Informatica. Un elenco di disastri della NASA per piccoli bug è a questo link. Nel 1983 abbiamo anche rischiato un olocausto nucleare esteso a tutto il pianeta per via di un bug... la notizia venne resa pubblica nel 1990.
Tornando a noi... qualcuno si è anche preso la briga di fare una stima di quanti errori di programmazioni vengano fatti mediamente. Steve McConnell, nel libro di Ingegneria del Software “Code Complete”, ormai storico (la prima edizione è degli anni '90), riportò una media a livello industriale di circa 15-50 errori ogni 1000 righe di codice e - nel caso specifico di Microsoft - conteggiò una media da 10 a 20 errori ogni 1000 righe di codice in prodotti “released”, cioè messi in commercio (fonte). Sebbene queste stime siano molto vecchie (quella di Microsoft si riferisce al 1992), il problema rimane attuale e, soprattutto, se ciò accade alle grandi aziende multinazionali con sviluppatori super-esperti (o presunti tali), figuriamoci a chi non è così esperto...
La complessità è abnorme: il browser Google Chrome è fatto da circa 7 milioni di righe di codice, il sistema operativo Android da 15 milioni, Facebook lato client (senza considerare il software lato server) da circa 60 milioni, il software di controllo delle recenti smart-car da 100 milioni, i servizi di Google, nel loro complesso, da 2 miliardi (fonti).
Per noi comuni mortali, programmatori “normali”, riuscire a scrivere mille righe di codice in un giorno già potrebbe essere una sfida quasi impossibile: concretamente, a volte può capitare di passare un’intera giornata a cercare di risolvere un problema che alla fine sta dietro ad una sola riga di codice, magari messa nel posto sbagliato. Anzi, questa è una stima che ho trovato su Coralogix, che ritengo molto verosimile e all'incirca applicabile anche a me:
- In media, uno sviluppatore crea 70 bug ogni 1000 linee di codice (o 7 ogni 100, ovvero circa 1 ogni 10).
- 15 bug ogni 1000 linee di codice finiscono nei prodotti destinati ai clienti (che spesso scoprono bug che noi programmatori non avevamo visto o, peggio, che non riusciamo neanche a "riprodurre" sulle nostre macchine, cioè a verificare, a far accedere e quindi a poter indagare).
- Trovare la causa di un bug prende 30 volte più tempo che scrivere una linea di codice (anzi, a volte può richiedere una o più giornate e comunque la causa del bug non viene scoperta...)
- Il 75% del tempo di sviluppo è speso nel debugging, cioè nella ricerca delle "cause" dei bug; da notare che trovare la causa dei bug è di solito un requisito per la loro risoluzione, ma non sempre ciò porta ad una immediata risoluzione, che può rivelarsi anche complessa.
- Solo negli Stati Uniti, 113 miliardi di dollari all'anno sono spesi per identificare e risolvere i bug.
Nel contesto di questa complessità, la programmazione è un’enorme sfida. A te che stai leggendo, se stai per incamminarti in questa avventura di programmatore, bella e tragica allo stesso tempo, vorrei dirti che se dedicherai ore e ore a poche righe di codice che non funzioneranno come tu vorrai, allora ti dirò: «Benvenuto in famiglia!» ;-)
Tieni a mente le stime che ho riportato soprattutto nei momenti di difficoltà: capirai che il problema non è solo tuo, anzi, incontrare problemi durante e dopo il coding (cioè la programmazione) è fisiologico.
Sia ben chiaro, inoltre, che gli errori non necessariamente dipendono dalla capacità di chi programma: magari il nostro programma è corretto, ma il codice sottostante che lo farà girare non lo è. Intendo dire che gli errori non sono necessariamente nei programmi che scriviamo, ma possono essere anche nelle piattaforme e linguaggi di sviluppo che utilizziamo. Come esempio, posso riportare questo bug da me segnalato, in cui dimostrai che una banale concatenazione tra stringhe causava un errore su iPhone non dipendente dal mio codice. Negli ultimi anni, ho scoperto e segnalato vari bug della piattaforma di sviluppo da me usata (qui l'elenco), per fortuna quasi tutti prontamente corretti.
Concludo queste riflessioni sui bug con le parole che Edison scrisse nel 1870 (fonte):
«È stato così per tutte le mie invenzioni. Il primo passo è un'intuizione, e viene come una fiammata, poi le difficoltà crescono... le cose non vanno più ed è allora che i "bachi" – così sono chiamati questi piccoli sbagli e difficoltà – si manifestano, e servono mesi di intensa osservazione, studio e lavoro prima che il successo commerciale oppure il fallimento sia sicuramente raggiunto.»
L’algoritmo agnostico
Mentre in italiano il termine “agnostico” ha un precisa accezione filosofica, nell’inglese tecnico-informatico l’aggettivo “-agnostic” è usato come suffisso in varie parole composte con il significato di “non conoscente, indipendente da, non legato in maniera specifica a, compatibile a prescindere dalla caratteristiche specifiche”, ecc. (ad es.: “device-agnostic”). Significati, questi, che in effetti si ricollegano all’agnosticismo, che parte dal presupposto di “non sapere” e di non “avere la possibilità di sapere”.
Un algoritmo, pur traducendosi in linee di codice, di per sé è un concetto astratto, simile al concetto matematico di “funzione”: dato un input, restituisce un output, in base ad una qualche logica specificata all’interno dell’algoritmo stesso. Se siamo a noi a scrivere, cioè creare, un algoritmo, allora ci interessano i dettagli implementativi (cioè la logica interna), ma se usiamo algoritmi scritti da altri, ci basta sapere a grandi linee “cosa fanno”, a prescindere dai dettagli. Soprattutto quando usiamo algoritmi scritti da altri, di solito li trattiamo come “scatole nere”: sappiamo in maniera astratta cosa fanno, ma non sappiamo concretamente “come” (e di solito neanche ci interessa saperlo).
Nella programmazione ad alto livello di astrazione, multi-piattaforma, gli algoritmi che useremo per programmare con Java + Codename One saranno come minimo:
- device-agnostic, cioè indipendenti dai dispositivi su cui tali algoritmi saranno eseguiti (e quindi indipendenti dall’hardware);
- IDE-agnostic, cioè indipendenti dall’ambiente di sviluppo (IDE significa “ambiente di sviluppo integrato”): Codename One consente infatti di utilizzate ambienti diversi, a nostra preferenza.
In più, ogni algoritmo sarà “language-agnostic” rispetto agli altri algoritmi: non soltanto gli altri algoritmi, pur interfacciandosi tra di loro tramite Java, potranno effettivamente essere implementati in altri linguaggi, ma la comunicazione tra app e server backend sarà indipendente dai rispettivi linguaggi di programmazione.
Provo a spiegare meglio quest’ultimo punto: ogni algoritmo potrà richiamare altri algoritmi per far fare loro certe cose, indipendentemente dal linguaggio effettivo in cui questi saranno implementati (ad es. un pezzo di codice scritto in Java potrà richiamare un altro pezzo di codice scritto in Objective-C, qualora fosse necessario): questo è possibile grazie alle cosiddette “interfacce native” di Codename One, su cui per il momento non mi dilungo. In ogni caso, anche rimanendo all’interno dello stesso linguaggio, cioè Java, ogni algoritmo “conosce” la sua implementazione, ma non quella degli altri algoritmi con cui interagisce. Per quanto riguarda la comunicazione client-server, dove l’app per smartphone è il client e il server è il “backend”, cioè ciò che sta dietro le quinte, ciò che l’utente non vede, tale comunicazione normalmente avviene tramite chiamate “REST” che astraggono completamente i rispettivi linguaggi di programmazione utilizzati. Le chiamate REST non sono altro che richieste con protocollo HTTP, come quando si apre una pagina web: il nostro browser non sa nulla del linguaggio di programmazione usato sul server (ad es. PHP o Java), né del linguaggio che il server usa con il suo database (ad es. Sql), eppure la comunicazione funziona perché gli algoritmi che gestiscono le comunicazioni sono indipendenti dai linguaggi di programmazione del client e del server.
Concludo così questa introduzione sugli algoritmi. Prossimamente preparerò un articolo per mostrare come scrivere i primi algoritmi in Java.
Francesco Galgani,
29 novembre 2019