Le attività si svolgono su copie dei programmi. A chiede di prevedere e spiegare un comportamento, C di correggere o completare un caso, G di riconoscere il confine tecnico, D di decidere in una variante. Le soluzioni indicano il ragionamento osservabile; l'esecuzione positiva dei programmi non sostituisce le prove sui casi di errore. I gate delle esercitazioni restano aperti fino alla lettura con utenti.
C25-A01 – Byte o testo?
Consegna. Un file contiene la sequenza di byte di una fotografia e un altro contiene titoli in UTF-8. Scegli per ciascuno fra InputStream e BufferedReader, spiegando il ruolo del charset. Nel programma di importazione prevedi cosa accade se una riga contiene libro;errore.
Soluzione ragionata. La fotografia rimane una sequenza di byte; tentare di interpretarla come testo può cambiarla o far fallire la decodifica. I titoli richiedono la conversione UTF-8 dichiarata. La riga con separatore produce IOException prima della pubblicazione finale. Per dimostrare il requisito, il test deve partire da una destinazione già esistente e verificare che il suo contenuto non cambi.
C25-C01 – Una prova del fallimento
Consegna. Modifica una copia del programma affinché il messaggio indichi il numero fisico della riga, comprese quelle vuote. Prepara un input con due righe vuote prima del dato errato. Verifica anche che non rimangano file provvisori se la validazione fallisce.
Soluzione ragionata. Serve un contatore incrementato a ogni readLine, distinto dal conteggio dei titoli importati. Il finally rimuove il provvisorio sul fallimento; il test deve controllare la directory, non soltanto l'eccezione. Se anche la pulizia fallisce, occorre conservare la causa principale e segnalare il problema di pulizia separatamente.
C25-D01 – Milioni di righe e duplicati
Consegna. L'archivio cresce oltre la memoria disponibile e non può contenere titoli duplicati. Disegna una soluzione che mantenga la pubblicazione completa o nulla. Specifica come scopri i duplicati e qual è il limite della tua scelta. Ripeti la decisione se la sorgente arriva da una risorsa dentro un JAR.
Soluzione ragionata. La lettura e la scrittura restano progressive. Una Set in memoria funziona solo se gli elementi distinti ci stanno; altrimenti serve una strategia esterna, per esempio partizionamento o ordinamento su disco. Il file nel JAR è una sorgente impacchettata, non una destinazione aggiornabile con Files.move; il risultato va pubblicato in un percorso esterno. La soluzione deve nominare il limite del provider per lo spostamento atomico.
C26-A01 – Due volte le 02:30
Consegna. Nel programma della fattura spiega perché 2026-10-25T02:30 a Roma produce due offset. Indica quali due istanti sono rappresentati e perché LocalDateTime da solo non basta per una prenotazione irrevocabile.
Soluzione ragionata. Durante il ritorno all'ora standard l'ora civile viene ripetuta. Gli offset +02:00 e +01:00 portano a istanti separati da sessanta minuti. Una prenotazione deve conservare l'offset scelto o l'istante, oltre alla zona necessaria per la presentazione e le regole future.
C26-C01 – Arrotondare nel posto giusto
Consegna. Calcola l'imposta del 22% su tre righe da 0.03 euro. Confronta arrotondamento HALF_UP a due decimali per ciascuna riga e arrotondamento una sola volta dopo la somma delle basi. Non usare double come sorgente dei valori.
Soluzione ragionata. Ogni riga produce 0.0066, quindi 0.01 dopo arrotondamento; la somma delle imposte arrotondate è 0.03. La base complessiva è 0.09, la sua imposta è 0.0198, arrotondata a 0.02. La differenza dimostra che il punto dell'arrotondamento è una regola di dominio, non un dettaglio di sintassi.
C26-D01 – Trenta giorni o 720 ore?
Consegna. Una biblioteca concede un prestito «per trenta giorni civili»; un servizio concede una finestra di «720 ore effettive». Scegli tipi e operazioni per entrambe le regole. Spiega che cosa cambierebbe attraversando un cambio d'ora legale e come rendi ripetibile un test.
Soluzione ragionata. La prima regola usa LocalDate.plusDays(30) nel contesto civile della biblioteca. La seconda usa un Instant e Duration.ofHours(720). Le due scadenze possono mostrare ore civili diverse perché le regole di zona cambiano l'offset. Un Clock.fixed elimina la dipendenza dall'ora corrente nei test.
C27-A01 – Assenza o guasto?
Consegna. Nel client locale prevedi cosa il chiamante ottiene se il server risponde 404 con un corpo di testo, e cosa accade se nessun processo ascolta sulla porta. Quale dei due casi può diventare Optional.empty() per una ricerca di libro?
Soluzione ragionata. Un 404 è una risposta HTTP con status e corpo; il client la riceve e il contratto del servizio può definirla come assenza normale. Una connessione rifiutata è un fallimento di comunicazione e non dimostra che il libro manchi. La distinzione va mantenuta anche se l'interfaccia utente mostra una frase semplice.
C27-C01 – Un retry controllato
Consegna. Cambia il server locale affinché la prima GET /libri restituisca 503 e la seconda 200. Implementa al massimo un retry per questa sola richiesta e registra il numero di tentativi. Verifica separatamente che un POST che crea una prenotazione non venga ripetuto senza un contratto di idempotenza.
Soluzione ragionata. Il test deve osservare due chiamate nel caso temporaneo e una nel caso positivo immediato. Dopo due fallimenti la logica restituisce un errore esplicito; non entra in un ciclo indefinito. Per il POST il timeout lascia incerto se il server abbia già creato la prenotazione: serve una regola applicativa, per esempio una chiave di idempotenza supportata dal server.
C27-D01 – Dieci richieste, tre posti
Consegna. Dieci richieste HTTP partono da thread virtuali, ma il servizio remoto consente tre lavori simultanei. Descrivi dove applicare il limite e come controllarlo. Confronta la decisione con sendAsync se il processo deve comporre i risultati senza occupare un thread per ogni attesa.
Soluzione ragionata. Un Semaphore(3) attorno all'accesso al servizio limita le richieste in corso, indipendentemente dal numero di thread virtuali. Il test misura il massimo di richieste attive sul server locale, non soltanto il tempo totale. sendAsync restituisce future componibili; anche quel modello richiede un limite di capacità. La scelta riguarda la struttura del programma, non una promessa di maggiore velocità per la singola richiesta.
C28-A01 – Limite e durata
Consegna. Nel programma FFM alloca due interi e prova a scriverne tre, in una copia isolata. In un'altra copia conserva il segmento dopo la chiusura dell'arena e prova a leggerlo. Classifica i due errori.
Soluzione ragionata. Il primo accesso supera il limite spaziale del segmento; il secondo usa una regione la cui durata è terminata. La prova deve anticipare dove fallirà il programma. Non va proposta la global arena solo per evitare di ragionare sulla chiusura.
C28-G01 – Che cosa dice strlen?
Consegna. Sostituisci "Java" con "città" nel programma. Prevedi se strlen conta cinque caratteri o il numero dei byte della stringa C in UTF-8. Spiega perché il layout del risultato size_t va verificato sulla piattaforma.
Soluzione ragionata. strlen conta i byte prima dello zero finale; à richiede più di un byte in UTF-8, quindi il risultato supera cinque. size_t segue l'ABI della piattaforma, non il tipo Java long per definizione. Se cambiano architettura o libreria, un descrittore copiato senza verifica può essere errato anche se il codice Java compila.
C28-D01 – Un puntatore trattenuto dalla libreria
Consegna. Una libreria C conserva il puntatore ricevuto e lo usa in un callback successivo. Progetta durata dell'arena, condivisione fra thread e punto di chiusura. Specifica quale informazione manca se conosci soltanto il nome della funzione C.
Soluzione ragionata. L'arena deve restare viva almeno fino all'ultimo uso nativo del puntatore e dello stub di callback. Se thread diversi accedono alla regione, una confined arena non è sufficiente; serve una scelta di condivisione e sincronizzazione coerente. Senza firma C, layout, ABI e contratto di proprietà del puntatore non è possibile costruire un binding corretto. La risposta valida sa fermarsi prima di inventare queste garanzie.
Prova integrata – Una ricevuta che arriva da fuori
Situazione. Un programma della biblioteca interroga un servizio HTTP locale per ottenere la ricevuta di un prestito. Il servizio restituisce un corpo testuale con codice del libro, data di scadenza, importo della cauzione e valuta. Il programma deve salvare una copia leggibile dal personale. Alcune richieste possono fallire; il file precedente non deve diventare una ricevuta parziale. Questa prova riunisce i confini dei capitoli 25, 26 e 27. Il capitolo 28 resta un approfondimento: la presenza di una libreria C nel sistema non è, da sola, un motivo per introdurre FFM.
Consegna. Disegna prima il contratto dei dati ricevuti: quale charset, quale formato per la data, quale unità monetaria, quali campi obbligatori e quale dimensione massima? Indica quali errori avvengono prima di avere una risposta HTTP, quali sono status HTTP da interpretare e quali sono errori di validazione del corpo. Scegli il tipo Java per la scadenza e per l'importo, poi descrivi come pubblichi il file finale. Scrivi almeno quattro prove: risposta valida, 404 secondo il contratto del servizio, corpo malformato e connessione interrotta dopo l'invio della richiesta. Per ogni prova specifica lo stato atteso del file precedente.
Soluzione ragionata. La scadenza civile è un LocalDate se il contratto dice «valido fino a quel giorno» e non fissa un istante preciso. L'importo viene interpretato da testo decimale con BigDecimal, conservando separatamente una Currency o un codice di valuta validato. Il formato di scambio dichiara UTF-8, una data ISO e un limite alla dimensione del corpo. Un parser controlla presenza e grammatica dei campi prima di trattare il dato come ricevuta. Non basta uno status 200: il corpo può essere incompleto o non valido.
Il client imposta timeout coerenti con il servizio e distingue fallimento di trasporto, status e validazione. 404 può significare «ricevuta assente» soltanto se il contratto remoto lo dice; una connessione interrotta non equivale ad assenza. Il contenuto valido viene scritto in un file provvisorio nella directory della destinazione e chiuso prima del tentativo di pubblicazione. Se lo spostamento atomico richiesto non è disponibile, l'operazione fallisce lasciando il file precedente; il prodotto può scegliere un'altra politica, ma deve dichiararla e provarla separatamente. La prova positiva controlla contenuto e metadati essenziali; i tre casi negativi controllano che il file precedente sia identico byte per byte e che il provvisorio sia rimosso.
Variante di trasferimento. Il servizio comincia a fornire ricevute da molti megabyte. Una soluzione che usa BodyHandlers.ofString e poi costruisce un'altra stringa completa può consumare troppa memoria. Passiamo a una gestione progressiva del corpo e imponiamo un limite di byte mentre leggiamo; la validazione e la pubblicazione restano due passi distinti. Se il formato obbliga a vedere il documento intero prima di validarlo, si può conservare il provvisorio e rileggerlo, evitando comunque di esporlo come finale. La scelta non cambia la semantica degli errori HTTP.
Criterio di valutazione. La risposta collega ogni rischio al suo confine: trasporto, status, formato, tempo, numero o file system. Motiva la scelta delle API e prova che l'archivio precedente resta protetto. FFM non serve senza una necessità nativa.
Estensione avanzata: il calcolo esiste soltanto in una libreria C. In una seconda versione la biblioteca eredita una libreria nativa che calcola una firma di controllo del file. La funzione riceve puntatore ai byte e lunghezza, restituisce un codice numerico e non conserva il puntatore dopo il ritorno. Prima di scrivere un binding FFM, il lettore deve ottenere l'header C, conoscere il tipo esatto della lunghezza nell'ABI usata e sapere che cosa indicano i codici di ritorno. Il file è stato già ricevuto e validato; l'integrazione nativa interviene soltanto per calcolare la firma, prima della pubblicazione. Se la funzione restituisce un errore, il file precedente resta intatto.
Una soluzione motivata alloca un segmento abbastanza grande da contenere i byte da elaborare, vi copia il blocco secondo l'API scelta e lo mantiene valido per tutta la chiamata. Poiché la libreria non conserva il puntatore, un'arena chiudibile attorno alla chiamata può essere sufficiente; se il contratto cambiasse e il puntatore venisse trattenuto, la durata andrebbe riprogettata. Il FunctionDescriptor riflette la firma della libreria su quella piattaforma, non una supposizione ricavata da un esempio trovato altrove. Il codice di ritorno viene tradotto in un risultato di dominio che distingue firma calcolata e fallimento nativo. Il valore della firma, il nome della libreria e la sua versione entrano nelle evidenze di verifica, perché un aggiornamento del binario può cambiare il risultato anche se il sorgente Java è identico.
Le prove dell'estensione eseguono almeno tre casi: un file noto con firma attesa, un file vuoto e una chiamata che restituisce un codice di errore. La prova del file vuoto chiarisce se la funzione accetta un puntatore a zero byte o richiede comunque un indirizzo valido; la risposta non può essere inventata dal programma Java, deve provenire dal contratto C. Il caso d'errore conferma che il provvisorio non viene pubblicato. Queste prove si eseguono nella piattaforma nativa dichiarata, idealmente in un processo separato dalle altre verifiche, perché un binding errato può terminare la JVM. La scelta di FFM è giustificata solo dal nuovo vincolo della libreria C: il percorso precedente con API Java rimane la soluzione più semplice finché quel vincolo non esiste.