Informatica per script kiddies 12 – I formati per lo scambio dei dati

Eccoci un po’ in ritardo con Informatica per script kiddies, il dodicesimo numero! Qualche settimana fa, un bell’articolo del Post mi ha fatto venire voglia di parlare di questo argomento che è molto parte del mio lavoro, e che purtroppo non è chiarissimo non solo a molti che all’informatica ci si avvicinano, ma anche a tantissimi che si trovano a dirigere, più o meno direttamente, branche Information technology di aziende.

Non sono un data analyst, e mi piacerebbe il parere di chi si occupa di dati per lavoro. Mi occupo però – e molto – della produzione, della gestione e della comunicazione di discrete moli di dati, soprattutto in ambito pubblico (ma non solo), e mi ritrovo purtroppo molto spesso a discutere con persone che – in perfetta buona fede – non hanno la più pallida idea di come funzionino queste cose. Da oggi posso linkargli questo articolo, grazie alla pigrizia propria di noi informatici: fare le cose bene una volta per non farle mai più. Vale per gli algoritmi, vale per gli spiegoni.

Cosa è un dato

La prima cosa da capire prima ancora di parlare di formati, è che un dato in ambito informatico è una cosa ben specifica, ovvero un’informazione prima di tutto significativa (e questo vale pure fuori dall’ambito informatico), e poi elaborabile da un computer.

Un’informazione che non rappresenta la realtà sufficientemente bene da essere significativa (come i dataset di Immuni che usano numeri esatti se il valore è maggiore di cinque e “-1” altrimenti) non è un dato di buona qualità, e un’informazione inquinata da cose non necessarie o strutturata in una maniera da essere difficilmente comprensibile da un computer, come ad esempio un foglio Excel (ma ci torniamo) non è un dato di buona qualità.

Queste considerazioni ci portano a due cose determinanti e spesso sottovalutate:

  • Un dato deve essere prodotto bene: è determinante fare in maniera che la fonte dei dati, che sia una persona o un sistema di sensori, introduca una quantità di errori misurabile, nota e più piccola del margine oltre il quale il dato è inservibile. Se l’inserimento di dati anagrafici è affidato a un operatore che frequentemente marca telefoni fissi come telefoni cellulari, quando dovrai inviare degli sms avrai problemi. Se al posto del gruppo sanguigno può mettere l’età, non ne parliamo.
  • Un dato che deve rilevare una situazione di stress deve essere, per quanto possibile, preso da un sistema che non risente del medesimo stress. Se devo misurare la temperatura di un oggetto che può arrivare a scaldarsi più di quanto i materiali di un termometro sopportano, uso un pirometro. Se devo contare i malati, non chiedo di farlo al medico che deve salvare la vita a un malato ogni poco tempo. Se i dati che devono misurare lo stress di un sistema peggiorano di qualità all’aumento dello stress, il loro margine di errore li rende progressivamente inutili.
  • Un dato deve essere memorizzato bene. Buttare un dato da qualche parte una volta acquisito non garantisce in nessun modo che sia poi ripescabile in così da ricostruirlo completamente. Nella mia carriera, su questo punto, ho visto cose orribili. Quasi sempre, il problema principale è che la progettazione delle banche dati è una scienza complessa, ma allo stesso tempo è un lavoro affidato a chi a malapena sa che si tratta di una scienza. Se vi apprestate a creare un database, guardate come sono fatti quelli simili. Se è complesso, affidatevi a chi sa farlo. Se è complesso, avete tempo e siete curiosi, comprate un buon libro di fondamenti (Elmasri – Navathe, ad esempio).

Come non si trasmettono i dati

Una delle grandi fissazioni si molti è l’idea che i dati da trasmettere tra due diversi software debbano essere human readable, leggibili da operatori umani. Questa idea è generalmente molto sbagliata, e non offre nessunissimo vantaggio pratico.

Il primo grande problema di questo approccio è che tipicamente un dato human readable non è un dato machine readable: esseri umani e macchine hanno logiche di lettura molto differenti, e una cosa facile da leggere per uno dei due è tipicamente difficile da leggere per l’altro.

Il secondo problema, che probabilmente è il maggiore, è il motivo di questa idea: tipicamente quello che si vuole poter fare è intercettare i dati lungo il loro percorso per modificarli a mano prima che arrivino a destinazione, facendo danni nella quasi totalità dei casi.

L’esempio base di questo problema è il famigerato “export in Excel”. L’idea che un software debba esportare dati in formato Excel, un formato assolutamente non pensato per la memorizzazione dei dati (non ne gestisce quasi per nulla la semantica, non ne garantisce l’integrità, li mescola a valanghe di informazioni di formattazione, cambia nel tempo, esiste in due varianti con quella peggiore come default…), è la causa di una mole incredibile di macroscopici errori. Lasciamo perdere poi quando ad essere fatto in Excel è il database.

Cose simili si fanno con formati analoghi e analogamente non concepiti per gestire dati, come l’onnipresente CSV, ottimo se i dati sono stringhe e pessimo in qualsiasi altro caso. E per onnipresente intendo davvero onnipresente. Ho perso il conto delle persone per le quali “carica un csv su un ftp” è un buon metodo di trasferimento di dati. Con il COVID-19 i data analyst si sono ben abituati al fatto che “carica un csv zeppo di errori su GitHub” sia una cosa.

Come si trasmettono i dati

Come un database deve essere ben strutturato, anche un formato di trasmissione dei dati deve essere ben strutturato. Deve poter memorizzare il dato in maniera più semplice possibile, ma non eccessivamente semplice. Se un dato è un valore numerico, il formato di trasmissione deve ad esempio:

  • dare il valore numerico
  • comunicare in qualche maniera il fatto che si tratta di un valore numerico
  • comunicare in qualche maniera l’unità di misura

Per fare questa cosa, oggi, si tende ad usare su larghissima scala due meta-formati, peraltro abbastanza human readable (più di quanto vorrei, diciamo) piuttosto interessanti.

Il primo è davvero nato a questo scopo, e si chiama XML. Ha diversi aspetti molto complessi che lo rendono flessibile e quindi davvero potente e versatile, ma anche potenzialmente utilizzabile in maniera disastrosa. Si tratta comunque di un buon esempio di cosa intendo. Ad esempio, i dati di una persona, in CSV, potrebbero essere:

 name,surname,birth_date,phone_no_1,phone_no_2
 Lorenzo,Breda,1988-07-04,00000492,00000898

In XML posso fare di molto meglio:

<person>
     <name>Lorenzo</name>
     <surname>Breda</surname>
     <birthDate format='Y-m-d'>1988-07-04</birthDate>
     <phones>
         <phone type='landline'>00000492</phone>
         <phone type='mobile'>00000898</phone>
     </phones>
 </person>

Il formato mi permette di marcare semanticamente ogni elemento con qualche informazione in più rispetto alla mera definizione semantica (il formato della data, il tipo di numero di telefono) e di dare una grande flessibilità sul contenuto informativo (in CSV posso mettere una quantità limitata di numeri di telefono, mentre il formato XML se ben strutturato non mi pone limiti non necessari). XML, in aggiunta, permette di definire quali marcatori siano accettabili e quale no all’interno di ogni marcatore, grazie ad un apposito sistema di definizione detto DTD. Permette, insomma, di definire formati adatti a quello che vuoi fare, e di discriminare un documento valido da uno non valido.

Il secondo meta-formato di cui vorrei parlare, invece, è il JSON, al momento persino più diffuso. Il JSON ha tutt’altra finalità, essendo pensato per linearizzare (ovvero sostanzialmente tradurre in testo) gli oggetti JavaScript. Gli oggetti però molto spesso contengono dati, e quindi risulta facile – per quanto inappropriato – usare il formato per la trasmissione di dati. Si stanno diffondendo varianti, come il JSON-LD, specificamente pensate per la trasmissione di dati e associate a sistemi di definizione dei formati analoghi a DTD. In JSON, il dato di cui sopra, potrebbe essere una cosa di questo tipo:

{
     "name": "Lorenzo",
     "surname": "Breda",
     "birthDate": {
         "format": 'Y-m-d',
         "value": "1988-04-07",
     },
     "phones": [
         {
             "type": "landline",
             "value": "00000492"
         },
         {
             "type": "mobile",
             "value": "00000898"
         }
     ]
 }

Come vedete il contenuto informativo è equivalente, ma c’è qualche necessaria porcheria, come i metadati allo stesso livello dei dati. Ovviamente in fase di ingegnerizzazione del formato si può benissimo lavorare a cucirlo su ciò che JSON offre, ad esempio vincolando le date ad avere uno specifico formato, o inserendo al posto di phones due diversi subsets, landlinePhones e mobilePhones, ma insomma, l’idea mi pare chiara.

Ovviamente si possono creare (e si creano) innumerevoli formati che non siano derivazioni di questi meta-formati, e alcuni di questi formati di trasmissione li utilizziamo moltissimo. HTTP, ad esempio, così come il grosso dei protocolli di comunicazione via Internet, utilizza sistemi molto più rudimentali per trasmettere le informazioni. XML e JSON, però, sono un ottimo esempio di cosa manchi a cose come “il foglio Excel” (la marcatura semantica solo per dirne una) e “il CSV” (la potenza di rappresentazione).

Formato non significa file

Quando parliamo di comunicazione di dati tra software, per quanto a molti possa sembrare strano, non è necessario passare per un file. In genere, due software che si trasmettono dati tra loro comunicano con un sistema di API: il software che riceve espone una serie di comandi utili a ricevere dati, e il software che invia utilizza tali comandi passando direttamente il flusso di dati.

Le considerazioni sul formato, però, rimangono valide: anche il flusso di dati trasmesso deve avere un formato, e le proprietà necessarie sono le medesime. Delle API in ambito web parleremo di sicuro in un futuro articolo.

Storage e trasmissione sono cose diverse

Un’altra cosa non sempre chiarissima è che lo storage di un dato e la sua trasmissione sono cose profondamente diverse, e hanno quindi spessissimo strategie di definizione molto diverse.

Un formato di trasmissione, ad esempio, ha spesso l’esigenza di essere facilmente interrompibile senza eccessivi danni, mentre un formato di storage ha di solito la necessità di rendere facile e veloce la ricerca di uno specifico dato del dataset.

Un buon formato per salvare dati, quindi, raramente è un buon formato per trasmetterli, e vice versa. Un errore che spesso si fa è quello di rendere la trasmissione dei dati (o addirittura la struttura stessa di una API) più simile possibile al database, adducendo esigenze di velocità di acquisizione e storage. Ebbene, non ha senso. Quando si trasmettono dati è sempre fondamentale avere bene in mente che si vogliono trasmettere dei dati, e dimenticare completamente la forma in cui sono memorizzati.

Conclusioni

Insomma, chiudendo, per trasmettere decentemente dei dati si deve:

  • Descrivere il dato il meno possibile, ma abbastanza da renderlo effettivamente interpretabile e ricostruibile in tutte le sue parti
  • Non puntare a renderlo necessariamente human readable, perché è più dannoso che utile, e impedire in ogni maniera in vostro potere che la gente modifichi i file
  • Avere sempre in testa che la finalità è inviare i dati, e non salvarli su file o altrove.

Buona progettazione!