Passa al contenuto principale

Capitolo 01

La stessa informazione scritta due volte

A man with a watch knows what time it is. A man with two watches is never sure.

Legge di Segal (1961)

Nella lezione precedente il registro dei punti delle case ha mostrato due limiti: ogni domanda nuova implica la scrittura di nuovo codice, e ogni risposta richiede la lettura dell’intero file. Una domanda però era facile: il totale di Grifondoro si può calcolare senza aprire il file studenti.json, perché il registro dei punti ripete in ogni assegnazione la casa dello studente. La stessa informazione, la casa di appartenenza, è scritta anche in studenti.json. Ogni studente ha la propria casa scritta in due posti diversi. In questa lezione vediamo qual è il costo nascosto di questa comodità.

Due conteggi, due classifiche​

Aggiungiamo al registro della lezione precedente una nuova assegnazione: cinque punti dati a Neville dalla professoressa Sprout. Nella riga, però, la casa di Neville è sbagliata. Al posto di Grifondoro è stato inserito erroneamente Tassofrasso:

{
"studente": "Neville Longbottom",
"casa": "Tassofrasso",
"punti": 5,
"professore": "Sprout"
}

Il programma qui sotto calcola i punti di ogni casa in due modi. Il primo usa la casa scritta nel registro dei punti, come faceva il conteggio di McGonagall. Il secondo modo invece ignora quel campo e prende la casa da studenti.json, con lo stesso incrocio che nella lezione precedente serviva per gli anni. Prima di eseguire il codice, prova a prevedere quanti punti ha totalizzato Grifondoro secondo ciascun conteggio.

Nessuno dei due conteggi contiene un errore: ognuno somma correttamente i dati che legge. Il problema sta nei dati: i due file memorizzano due case diverse per la stessa persona. Noi sappiamo che Neville è un Grifondoro perché conosciamo la storia; il programma ha due file che si contraddicono e nessun criterio per scegliere. Con il registro di tutta la scuola, 20.000 assegnazioni di studenti che non conosciamo, saremmo nella stessa posizione del programma.

La casa nel registro è una copia: un’informazione sullo studente, che ha già il suo posto in studenti.json, scritta una seconda volta dove faceva comodo. In informatica un’informazione scritta in più posti si chiama ridondanza. Il suo difetto più evidente è lo spazio occupato, ma non è il più grave: finché le due copie coincidono, l’informazione è soltanto ripetuta; la prima volta che non coincidono, l’archivio ha due risposte diverse alla stessa domanda.

Il disaccordo, almeno, puoi trovarlo. Scrivi un controllo che confronti, per ogni assegnazione, la casa scritta nel registro con quella di studenti.json, e stampi solo le assegnazioni in cui le due case non coincidono. Il programma qui sotto prepara già il dizionario casa_di e scorre il registro, ma per ora stampa tutte le assegnazioni.

Esercizio: quando esegui il codice, viene controllato.

Copie che invecchiano​

La casa di Neville era sbagliata dal momento in cui è stata scritta e inserita nell’archivio. Una copia però può diventare falsa anche se nessuno sbaglia l’inserimento.

Nella lezione precedente abbiamo lasciato in sospeso una scorciatoia: copiare l’anno dello studente dentro ogni assegnazione, come il registro fa già con la casa, per calcolare i punti di ogni anno senza incrocio.

{
"studente": "Neville Longbottom",
"casa": "Grifondoro",
"anno": 1,
"punti": -50,
"professore": "McGonagall"
}

Per un anno scolastico la scorciatoia funziona. Il 1° settembre ogni studente passa all’anno successivo e studenti.json va aggiornato. Le copie nel registro però memorizzano ancora l’anno vecchio, tutte quante. Per riallinearle, la funzione che apre l’anno nuovo deve scorrere anche il registro:

def nuovo_anno(studenti, registro):
for studente in studenti:
studente["anno"] += 1
for assegnazione in registro:
assegnazione["anno"] += 1

Il primo ciclo aggiorna nove righe, una per studente. Il secondo esiste solo perché l’anno è stato copiato: aggiorna 18 assegnazioni nel registro dell’esempio, più di 20.000 in quello di tutta la scuola, e ogni anno qualcuna di più, perché il registro cresce e le assegnazioni vecchie restano. Un programma che aggiorna studenti.json e dimentica il registro, o che si interrompe fra i due cicli, lascia l’archivio con due file che danno due classifiche diverse, come è successo con la casa di Neville. Questa volta però nessuno ha scritto un valore sbagliato. Ogni copia era vera quando è stata scritta, ed è diventata falsa quando è cambiato l’originale.

Le copie dell'anno invecchianoIn studenti.json Neville Longbottom è al primo anno, e il registro ne copia l'anno in ognuna delle sue due assegnazioni (-50 e +10). Il 1° settembre studenti.json passa a 2, le copie restano a 1, e alla domanda «in che anno è Neville?» i due file danno due risposte diverse.1° settembrestudenti.jsonnomecasaannoNeville LongbottomGrifondoro1Neville LongbottomGrifondoro2punti.jsonstudentepuntiannoNeville Longbottom-501Neville Longbottom+101Neville Longbottom-501Neville Longbottom+101In che anno è Neville?studenti.json → 2punti.json → 1

Alla stessa domanda i due file danno due risposte diverse.

  1. Il registro copia l'anno di Neville da studenti.json in ognuna delle sue assegnazioni.
  2. Il 1° settembre Neville passa al secondo anno in studenti.json.
  3. Nessuno aggiorna le copie: restano a 1, e da vere diventano false.
  4. Alla stessa domanda i due file danno due risposte diverse.

Un valore ripetuto non è sempre una copia​

Prima di chiamare “copia” un campo, chiediti quale informazione contiene. La funzione nuovo_anno tratta l’anno nel registro come l’anno attuale dello studente: un’informazione sullo studente, che sta già in studenti.json.

Lo stesso campo potrebbe però voler dire un’altra cosa: l’anno che lo studente frequentava quando ha ricevuto quei punti. Questa informazione riguarda l’assegnazione, non lo studente, e il 1° settembre non deve cambiare: i 50 punti persi da Neville nella notte fuori dal dormitorio restano punti del primo anno anche quando lui è al secondo. Su un campo con questo significato, nuovo_anno trasformerebbe dati giusti in dati falsi.

Oggi i due significati danno lo stesso numero, 1; da settembre in poi divergono. Il valore ripetuto è identico, l’informazione no. Quale dei due significati abbia il campo lo decide chi progetta l’archivio, e il file JSON non lo registra da nessuna parte.

Mettiti alla prova

Quale campo di un’assegnazione del registro è una copia di un’informazione che appartiene a qualcos’altro?

Una riga, tre fatti​

Il registro ha un altro difetto, meno visibile, e riguarda i professori. Prendiamo l’assegnazione con cui il professor Flitwick dà dieci punti a Padma Patil, studentessa di Corvonero, e scomponiamola nelle informazioni che contiene:

Che cosa registra la rigaDi chi è l’informazioneDove altro è scritta
Padma ha ricevuto dieci punti da Flitwickdell’assegnazioneda nessuna parte
Padma è di Corvonerodi Padmain studenti.json
Flitwick insegna a Hogwartsdi Flitwickda nessuna parte

Nessun file elenca i professori: per l’archivio un professore esiste solo se compare in qualche assegnazione. Il blocco qui sotto ricava dal registro l’elenco dei professori, poi annulla l’assegnazione di Padma, che è la decima, e ricava l’elenco di nuovo. Prima di eseguire, prova a prevedere che cosa cambia nel secondo elenco.

La casa di Padma, invece, è sopravvissuta all’annullamento, perché ha un posto suo in studenti.json. Le due informazioni cancellate insieme alla riga erano quelle che non stavano scritte da nessun’altra parte.

Il registro come unico elenco dei professori crea un problema anche quando un nuovo professore arriva. Supponiamo che Lupin cominci a insegnare a Hogwarts due anni dopo i fatti del registro: finché non assegna o toglie punti a qualcuno, l’archivio non sa che esiste. Ci entrerebbe alla sua prima lezione, con i dieci punti dati a Neville per aver vestito Piton con gli abiti della nonna. Per registrarlo subito servirebbe un’assegnazione finta:

{ "studente": "", "casa": "", "punti": 0, "professore": "Lupin" }

Ogni programma che lavora sul registro dovrebbe poi sapere che quella riga, zero punti dati da Lupin a nessuno, non è un’assegnazione. Chi conta le assegnazioni ne troverebbe una in più, senza nessun avviso. Il calcolo dei punti per anno, invece, si fermerebbe con un KeyError, perché in anno_di non c’è uno studente senza nome.

Un fatto, un posto​

Le tre anomalie hanno un rimedio comune: scrivere ogni informazione una volta, nel posto che le appartiene. La casa e l’anno stanno in studenti.json; i professori in un file dedicato, professori.json; il registro tiene solo ciò che riguarda l’assegnazione, cioè chi ha ricevuto i punti, quanti e da chi.

{ "studente": "Padma Patil", "punti": 10, "professore": "Flitwick" }

Le righe dei tre file si collegano attraverso i nomi, come anno_di collegava il registro all’elenco degli studenti. Ogni informazione è scritta una volta sola, quindi non c’è un’altra copia che possa contraddirla. Flitwick resta nell’elenco dei professori anche quando un’assegnazione viene annullata, e Lupin ci entra il giorno in cui arriva.

La regola ha un prezzo, e lo conosciamo dalla lezione precedente. Senza la casa nel registro, anche il totale di Grifondoro richiede l’incrocio con studenti.json, scritto a mano, e quasi ogni domanda ne chiede uno. In più il legame fra i file resta affidato al codice: niente impedisce di scrivere nel registro un professore che non compare nell’elenco dei professori, o uno studente con il nome sbagliato come “Logbottom”. Togliendo le copie abbiamo scambiato dati che si contraddicono con dati che possono riferirsi a qualcuno che non esiste.

Con i file, quindi, la scelta è fra copie da tenere allineate e incroci da scrivere. Un DBMS tiene ogni informazione al suo posto, ti lascia chiedere un incrocio invece di programmarlo e, se glielo chiedi, rifiuta un nome che non compare nell’elenco a cui deve riferirsi. È l’argomento della prossima lezione.

Mettiti alla prova

La biblioteca tiene un solo file, prestiti.json, in cui ogni prestito riporta lo studente, il titolo del libro, l’autore e la data. Un libro è stato prestato una sola volta, e quel prestito viene cancellato. Che cosa perde l’archivio?