EuraStudy
Appunti/Informatica/Linguaggi di programmazione e sintassi
Appunti · InformaticaIT · Maturità

Linguaggi di programmazione e sintassi

Un linguaggio di programmazione è il sistema formale con cui traduciamo un algoritmo in un programma eseguibile dal calcolatore: ha una «sintassi» (le regole di forma per scrivere istruzioni corrette) e una «semantica» (il significato, cioè ciò che ogni istruzione fa eseguire alla macchina). In questo argomento si parte dal linguaggio macchina per arrivare ai linguaggi ad alto livello, distinguendo «compilazione» e «interpretazione», e si studiano gli elementi costitutivi del linguaggio — tipi di dato, variabili, operatori, strutture di controllo, sottoprogrammi — fino alla diagnosi e correzione degli errori. L'obiettivo è saper tradurre un algoritmo in un programma corretto, leggibile e modulare in un linguaggio ad alto livello (ad esempio Python), e saperlo verificare con prove ed esempi.

5 sezioni·~27 min di lettura·3 competenze·Livello Base 1 · Standard 3 · Approfondimento 1·Verificato · 07/2026

T·0222 / 14
Profilo d’esame
Acquisire la padronanza degli strumenti dell'informatica e utilizzarli per la soluzione di problemi significativiTradurre un algoritmo in un programma corretto, leggibile e modulare in un linguaggio ad alto livelloVerificare e validare un programma attraverso prove ed esempi (testing e debugging)
Operatori:spiegadescriviconfrontaclassificaanalizzatraduciimplementagiustificainterpretaverifica

livello base

È richiesto saper distinguere compilatore e interprete, riconoscere e usare tipi, variabili, operatori e le tre strutture di controllo, e scrivere semplici programmi corretti con funzioni in un linguaggio ad alto livello.

livello avanzato

L'indirizzo Scienze Applicate approfondisce la modularità (progettazione di sottoprogrammi con passaggio dei parametri ben scelto), la classificazione sistematica degli errori e una pratica consapevole di testing e debugging su problemi più articolati, anche in raccordo con Matematica.

Profondità

Profondità di lettura: Approfondimento

Testo

Dimensione del testo: Standard

Contenuti · 5 sezioni▾
  1. Linguaggi di programmazione e sintassi
    • 01Dal linguaggio macchina ai linguaggi ad alto livello○
    • 02Sintassi e semantica: tipi, variabili, operatori◐
    • 03Strutture di controllo e istruzioni di ingresso/uscita◐
    • 04Sottoprogrammi e modularità◐
    • 05Errori, testing e debugging●
§ 01

Dal linguaggio macchina ai linguaggi ad alto livello#

●○○BaseLPOSA-informatica-fondamenti-linguaggi — Dal linguaggio macchina ai linguaggi ad alto livello; compilatori e interpreti

Dal sorgente all'esecuzione: compilazione e interpretazione

Compilazione e interpretazionefigura a più pannelli, 2 pannelli, Dati: Compilazione — Grafo, 4 nodi, 3 archi; Interpretazione — Grafo, 3 nodi, 3 archiCompilazione e interpretazionecodice sorgentecompilatoreeseguibileesecuzioneCompilazionecodice sorgenteinterpreteesecuzioneriga per rigaInterpretazione
Fig. 1Il compilatore traduce tutto il sorgente in un eseguibile che poi viene eseguito; l’interprete legge ed esegue il sorgente istruzione per istruzione.

Punti chiave

Il calcolatore esegue direttamente soltanto il «linguaggio macchina», cioè sequenze di numeri binari che codificano operazioni elementari sul processore; questo linguaggio è velocissimo per la macchina ma praticamente illeggibile per l'essere umano e strettamente legato alla specifica architettura. Per rendere la programmazione umanamente sostenibile si è saliti per livelli di astrazione: prima l'«assembly», una notazione simbolica uno-a-uno del linguaggio macchina (con mnemonici come ADD, MOV), e poi i «linguaggi ad alto livello» (Python, C, Java), vicini al modo in cui ragioniamo sul problema e in larga misura indipendenti dalla macchina sottostante.
Un linguaggio ad alto livello non può essere eseguito così com'è: il suo «codice sorgente» va tradotto in istruzioni che la macchina comprende. Esistono due strategie di traduzione fondamentali. Il «compilatore» traduce una volta per tutte l'intero programma sorgente in un programma in linguaggio macchina (l'«eseguibile»), che viene poi lanciato autonomamente; è il modello tipico del C. L'«interprete» invece legge ed esegue il sorgente istruzione per istruzione, senza produrre un eseguibile separato; è il modello tipico di Python.
Le due strategie hanno conseguenze pratiche diverse. La compilazione tende a produrre programmi più veloci in esecuzione (la traduzione è già stata fatta) e individua molti errori di sintassi prima dell'esecuzione, durante la fase di «compilazione»; per contro il ciclo «modifica → compila → esegui» è più rigido e l'eseguibile è legato alla piattaforma. L'interpretazione offre maggiore flessibilità e portabilità (basta avere l'interprete) e un ciclo di prova più rapido, al prezzo di un'esecuzione tipicamente più lenta e di errori che possono emergere solo quando si raggiunge l'istruzione difettosa. Molti linguaggi moderni, come Java, adottano un modello ibrido: il sorgente è compilato in un «bytecode» intermedio, poi eseguito da una macchina virtuale (la JVM).
Indipendentemente dalla strategia di traduzione, il «programma sorgente» resta il documento centrale: è il testo, scritto secondo le regole del linguaggio, che esprime in modo leggibile l'algoritmo. Programmare significa anzitutto scrivere un sorgente corretto e chiaro; la traduzione in linguaggio macchina è un passaggio meccanico delegato a compilatore o interprete. Per questo l'attenzione alla «sintassi» (la forma) e alla «semantica» (il significato) del sorgente è il primo dovere di chi programma.
Esempio svolto

Confronto guidato tra compilazione e interpretazione

Un programma viene scritto in linguaggio ad alto livello. Descrivi che cosa accade, passo per passo, se viene compilato e che cosa accade se viene interpretato, evidenziando dove e quando emergono gli errori di sintassi.

  1. 01Punto di partenza comune

    In entrambi i casi si parte dal codice sorgente, cioè il testo scritto dal programmatore secondo le regole sintattiche del linguaggio.

  2. 02Caso compilazione

    Il compilatore analizza l'intero sorgente: se trova errori di sintassi li segnala tutti in questa fase e non produce alcun eseguibile. Se il sorgente è corretto, genera l'eseguibile in linguaggio macchina, che potrà essere lanciato anche molte volte senza ritradurre.

  3. 03Caso interpretazione

    L'interprete legge ed esegue il sorgente un'istruzione alla volta: non crea un eseguibile separato e gli errori (anche di sintassi su una riga non ancora raggiunta) possono manifestarsi solo quando l'esecuzione arriva a quella riga.

  4. 04Conseguenza pratica

    La compilazione anticipa la diagnosi degli errori e premia la velocità di esecuzione; l'interpretazione premia la rapidità del ciclo di prova e la portabilità.

Risultato: Nella compilazione la traduzione è anticipata e completa (eseguibile + errori segnalati in fase di compilazione); nell'interpretazione la traduzione è contestuale all'esecuzione (nessun eseguibile, errori rilevati al momento dell'esecuzione della riga).

Obiettivo Maturità

  • Saper confrontare compilatore e interprete indicando in modo preciso le differenze (momento della traduzione, presenza o assenza di un eseguibile, prestazioni, portabilità, rilevamento degli errori) è una richiesta tipica del colloquio e delle domande teoriche.
  • Distinguere con chiarezza i livelli — linguaggio macchina, assembly, linguaggio ad alto livello — e motivare perché si è saliti in astrazione (leggibilità, portabilità, produttività) è considerato un prerequisito teorico irrinunciabile.

Errori frequenti

  • Confondere «codice sorgente» ed «eseguibile»: il sorgente è il testo leggibile scritto dal programmatore, l'eseguibile è il risultato (binario) della compilazione — non sono la stessa cosa.
  • Affermare che un linguaggio «è» compilato o interpretato come se fosse una proprietà assoluta del linguaggio: in realtà compilazione e interpretazione sono modalità di esecuzione, e uno stesso linguaggio può avere implementazioni diverse (esistono interpreti C e compilatori Python).

Approfondimento

La traduzione operata da un compilatore non è un unico passaggio, ma una catena di fasi ben distinte, oggetto di studio della teoria dei compilatori. L'analisi lessicale (scanner) spezza il testo in token elementari (parole chiave, identificatori, numeri, operatori); l'analisi sintattica (parser) verifica che i token rispettino la grammatica del linguaggio e ne costruisce l'albero sintattico; l'analisi semantica controlla la coerenza dei tipi e la dichiarazione delle variabili; seguono la generazione del codice intermedio, l'ottimizzazione e la generazione del codice macchina; infine il collegamento (linking) unisce il codice alle librerie usate. Il modello ibrido «bytecode + macchina virtuale» merita attenzione particolare: Java compila il sorgente in «bytecode», un linguaggio intermedio indipendente dalla piattaforma, che la JVM esegue interpretandolo o — per le porzioni più eseguite — compilandolo al volo in linguaggio macchina (compilazione JIT, just-in-time), conciliando così portabilità e prestazioni. Anche Python compila internamente in bytecode («.pyc») eseguito dalla propria macchina virtuale, il che mostra come «compilato» e «interpretato» non siano categorie nette ma estremi di un continuo. Sul piano concettuale i linguaggi si raggruppano poi in paradigmi — imperativo/procedurale, orientato agli oggetti, funzionale e logico — ciascuno con un diverso modo di descrivere il calcolo, dalla sequenza di comandi che modificano lo stato alla valutazione di funzioni senza effetti collaterali.

Ripasso attivo

Spiega in che cosa differiscono compilazione e interpretazione di un programma scritto in un linguaggio ad alto livello. Descrivi il percorso che porta dal codice sorgente all'esecuzione nei due casi, e indica almeno due vantaggi e due svantaggi di ciascuna strategia.

Richiamo attivo

Ricorda i punti chiave — poi rivela.

Fonti: Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento (Ministero dell'Istruzione e del Merito (MIM))

§ 02

Sintassi e semantica: tipi, variabili, operatori#

●●○StandardLPOSA-informatica-fondamenti-linguaggi — Elementi di un linguaggio: sintassi, semantica, parole chiave, tipi di dato; variabili, costanti, operatori ed espressioni

Tipi primitivi e operatori applicabili

Tipi primitivi e operatoriTabella con 3 colonne e 4 righe, Dati: Tipo · Esempio · Operatori; int · 42 · + − * // % **; float · 3.14 · + − * /; bool · True / False · and or not; str · "ciao" · + (concat.) *TIPOESEMPIOOPERATORIint42+ − * // % **float3.14+ − * /boolTrue / Falseand or notstr"ciao"+ (concat.) *
Fig. 2Il confronto (== != < > <= >=) restituisce sempre un valore di tipo bool.

Punti chiave

Ogni linguaggio di programmazione si descrive su due piani complementari. La «sintassi» è l'insieme delle regole formali che stabiliscono quali sequenze di simboli costituiscono istruzioni ben formate: come si scrive un'assegnazione, dove vanno i due punti o le parentesi, quali sono le «parole chiave» riservate (come if, while, def). La «semantica» è invece il significato di ciò che è ben formato, cioè l'effetto che l'istruzione produce quando viene eseguita. Una frase può essere sintatticamente corretta ma semanticamente sbagliata rispetto all'intenzione del programmatore: per questo entrambi i piani vanno controllati.
Il «tipo di dato» indica la natura di un valore e le operazioni lecite su di esso. I tipi primitivi più comuni sono gli interi (int), i numeri reali in virgola mobile (float), i valori logici o booleani (bool: vero/falso) e le stringhe di caratteri (str). Il tipo determina sia l'insieme dei valori ammessi sia gli operatori applicabili: si possono sommare due interi, ma «sommare» una stringa e un intero o è un errore o ha un significato diverso (la ripetizione, in alcuni linguaggi). Conoscere i tipi è quindi indispensabile per scrivere espressioni corrette.
La «variabile» è un nome associato a una cella di memoria che custodisce un valore e che può cambiare nel tempo; la «costante» è un valore che, per scelta o per regola del linguaggio, non deve cambiare durante l'esecuzione. L'operazione fondamentale è l'«assegnazione»: in Python si scrive «x = 5», che significa «metti nella variabile x il valore 5». È cruciale non confondere l'assegnazione con l'uguaglianza matematica: «x = x + 1» non è un'equazione impossibile, ma l'istruzione «prendi il valore attuale di x, aggiungi 1, e rimetti il risultato in x».
Gli «operatori» combinano valori e variabili in «espressioni» che, una volta valutate, producono un risultato. Si distinguono operatori aritmetici (+, -, , /, // per la divisione intera, % per il resto, * per la potenza), operatori relazionali o di confronto (==, !=, <, >, <=, >=) che producono un valore booleano, e operatori logici (and, or, not) che combinano valori booleani. La valutazione di un'espressione segue regole di «precedenza» (la potenza prima del prodotto, il prodotto prima della somma) che si possono forzare con le parentesi; tipico errore d'esame è scrivere «==» dove serve «=» o viceversa.
x←x+1x \leftarrow x + 1x←x+1

Assegnazione

L'assegnazione non è un'uguaglianza: prende il valore attuale di x, vi somma 1 e rimette il risultato in x. La freccia rende esplicito il verso «da destra verso la variabile a sinistra».

Esempio svolto

Valutazione di espressioni con tipi e precedenza

Con a = 7, b = 2 e c = 3, calcola valore e tipo di: (1) a // b, (2) a % b, (3) a / b, (4) (a > b) and (b > c). Giustifica ogni passaggio.

  1. 01Divisione intera a // b

    7 diviso 2 dà quoziente intero 3; il tipo è int.

  2. 02Resto a % b

    Il resto della divisione di 7 per 2 è 1; il tipo è int.

  3. 03Divisione reale a / b

    In Python l'operatore / dà sempre un float: 7 diviso 2 vale 3.5; il tipo è float.

  4. 04Espressione logica

    Si valutano prima i confronti (a > b è 7 > 2 = True; b > c è 2 > 3 = False), poi l'and. True and False dà False; il tipo è bool.

Risultato: a // b = 3 (int); a % b = 1 (int); a / b = 3.5 (float); (a > b) and (b > c) = False (bool).

Obiettivo Maturità

  • Distinguere errore di sintassi (forma) ed errore di semantica/logica (significato) con un esempio chiaro è una domanda ricorrente; mostra di avere compreso i due piani del linguaggio.
  • Saper classificare i tipi primitivi e i tre gruppi di operatori (aritmetici, relazionali, logici), e prevedere il «tipo del risultato» di un'espressione (un confronto produce un booleano), è esplicitamente valutato.

Errori frequenti

  • Confondere l'operatore di assegnazione «=» con l'operatore di confronto «==»: «x = 5» mette 5 in x, «x == 5» chiede se x vale 5 e restituisce vero/falso.
  • Trascurare il tipo nelle divisioni: in molti linguaggi «7 / 2» dà 3.5 (float) mentre «7 // 2» dà 3 (divisione intera); usare l'operatore sbagliato falsa il risultato.

Approfondimento

La sintassi di un linguaggio si descrive rigorosamente con una grammatica formale, spesso scritta in notazione BNF (Backus-Naur Form): un insieme di regole di produzione che generano tutte e sole le frasi ben formate — la stessa nozione di grammatica che ritroveremo nella teoria della computazione e nei linguaggi di markup. Sul versante dei tipi, i linguaggi si distinguono lungo due assi indipendenti. La tipizzazione può essere statica (il tipo delle variabili è fissato e controllato alla compilazione, come in C e Java) oppure dinamica (il tipo è associato al valore e determinato a tempo di esecuzione, come in Python); ed è forte quando il linguaggio vieta conversioni implicite azzardate, debole quando le concede silenziosamente. Un concetto operativo importante è la valutazione in corto circuito (short-circuit) degli operatori logici: in «A and B», se A è falsa, B non viene nemmeno valutata perché il risultato è già deciso; ciò consente scritture idiomatiche e sicure come «k ≠ 0 and n/k > 1», dove il secondo test si esegue solo quando è lecito. Va infine ricordato che i tipi numerici hanno rappresentazione finita: gli interi a dimensione fissa possono andare in overflow oltre un certo intervallo, e i float approssimano i reali (0,1 non è rappresentabile esattamente in binario, così «0,1 + 0,2» non dà esattamente «0,3»), tema centrale ripreso nel capitolo di calcolo numerico. Distinguere il valore dal suo tipo e dalla sua rappresentazione in memoria è la chiave per prevedere il comportamento delle espressioni.

Ripasso attivo

Data la dichiarazione di tre variabili a = 7, b = 2 e c = 3, determina passo per passo il valore e il tipo del risultato delle espressioni a // b, a % b, a / b e (a > b) and (b > c), giustificando ogni passaggio con la precedenza degli operatori.

Richiamo attivo

Ricorda i punti chiave — poi rivela.

Fonti: Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento (Ministero dell'Istruzione e del Merito (MIM))

§ 03

Strutture di controllo e istruzioni di ingresso/uscita#

●●○StandardLPOSA-informatica-fondamenti-linguaggi — Implementazione delle strutture di controllo in un linguaggio reale; istruzioni di ingresso/uscita

Le tre strutture di controllo come diagrammi di flusso

Le tre strutture come diagrammi di flussofigura a più pannelli, 3 pannelli, Dati: Sequenza — Grafo, 2 nodi, 1 archi; Selezione — Grafo, 3 nodi, 2 archi; Iterazione — Grafo, 3 nodi, 3 archiLe tre strutture come diagrammi di flussoistr Aistr BSequenzacond?ramo Vramo FsìnoSelezionecond?corpoescisìripetinoIterazione
Fig. 3Sequenza, selezione (sì/no) e iterazione: l’iterazione ripete il corpo finché la condizione resta vera.

Punti chiave

Le tre strutture di controllo della programmazione strutturata — «sequenza», «selezione» e «iterazione» — sono universali (teorema di Böhm-Jacopini, ripasso) e ogni linguaggio ad alto livello le fornisce con una propria sintassi. La sequenza è la semplice esecuzione ordinata delle istruzioni, una dopo l'altra. La selezione sceglie un ramo di esecuzione in base a una condizione booleana. L'iterazione ripete un blocco di istruzioni finché una condizione resta vera (o per un numero stabilito di volte). Tradurre un algoritmo significa esprimerlo combinando queste tre strutture nel linguaggio adottato.
La «selezione» si realizza con l'istruzione if. La forma semplice esegue un blocco solo se la condizione è vera; la forma if/else sceglie tra due alternative; la forma if/elif/else gestisce più casi mutuamente esclusivi. La condizione è un'espressione booleana (per esempio «voto >= 6»). In Python l'appartenenza di un'istruzione a un ramo è indicata dall'«indentazione» (il rientro): istruzioni allo stesso livello di rientro appartengono allo stesso blocco. Questo rende l'indentazione parte della sintassi, non un semplice abbellimento.
L'«iterazione» ha due forme principali. Il ciclo «while» (ciclo a condizione) ripete il blocco finché una condizione resta vera ed è adatto quando non si conosce a priori il numero di ripetizioni; richiede che qualcosa, dentro il ciclo, faccia prima o poi diventare falsa la condizione, altrimenti si ha un «ciclo infinito». Il ciclo «for» (ciclo enumerativo) ripete il blocco per ogni elemento di una sequenza o per un numero noto di volte (in Python tipicamente con range), ed è adatto quando il numero di iterazioni è determinato.
Un programma comunica con l'esterno tramite le «istruzioni di ingresso/uscita» (I/O). L'ingresso acquisisce dati, per esempio dalla tastiera, con un'istruzione di lettura (in Python input(), che restituisce una stringa: per usarla come numero va convertita con int() o float()). L'uscita comunica i risultati, per esempio a schermo, con un'istruzione di stampa (in Python print()). L'organizzazione «leggi i dati → elabora → mostra i risultati» è lo scheletro di moltissimi programmi e va sempre tenuta presente nel tradurre un algoritmo.
Esempio svolto

Pari o dispari (selezione + I/O)

Scrivi un programma che legge un numero intero da tastiera e stampa «pari» se è divisibile per 2, altrimenti «dispari».

  1. 01Ingresso e conversione

    Si legge il dato e lo si converte in intero: n = int(input("Inserisci un intero: ")). La conversione è necessaria perché input() restituisce una stringa.

  2. 02Condizione

    Un numero è pari se il resto della divisione per 2 è zero. La condizione corretta è n % 2 == 0.

  3. 03Selezione

    Si usa if/else: if n % 2 == 0: print("pari") else: print("dispari"). I due rami sono mutuamente esclusivi e coprono tutti i casi.

  4. 04Verifica con casi di prova

    Con n = 4 il resto è 0 -> «pari»; con n = 7 il resto è 1 -> «dispari». Anche n = 0 dà resto 0 -> «pari», come corretto.

Risultato: Il programma legge n, valuta n % 2 == 0 e stampa «pari» o «dispari»; testato su 4, 7 e 0 fornisce rispettivamente «pari», «dispari», «pari».

Obiettivo Maturità

  • Saper tradurre un piccolo algoritmo (per esempio: leggere un numero e dire se è pari o dispari, oppure sommare i primi N numeri) nelle strutture di controllo di un linguaggio reale, con I/O corretto, è il cuore delle prove pratiche.
  • Scegliere consapevolmente tra ciclo while (numero di ripetizioni non noto) e ciclo for (numero noto) e saper riconoscere/evitare il ciclo infinito sono competenze esplicitamente attese.

Errori frequenti

  • Dimenticare di aggiornare, dentro un ciclo while, la variabile che ne controlla la condizione: il risultato è un ciclo infinito (la condizione resta sempre vera).
  • Usare il dato letto da input() come numero senza convertirlo: input() restituisce una stringa, quindi confronti e operazioni aritmetiche danno errore o risultati inattesi finché non si applica int() o float().

Approfondimento

Le forme di iterazione di un linguaggio reale sono, sul piano teorico, interscambiabili: un ciclo «for» su un intervallo è zucchero sintattico per un «while» con inizializzazione, condizione e aggiornamento resi espliciti («i <- 0; while i < n: corpo; i <- i + 1»). I costrutti «break» (esci dal ciclo) e «continue» (salta all'iterazione successiva) sono comodi ma vanno usati con misura: introducono uscite multiple e allontanano dall'ideale «un ingresso, un'uscita» della programmazione strutturata, quindi spesso conviene una condizione scritta meglio. Saper negare correttamente una condizione composta è cruciale e si fonda sulle leggi di De Morgan: la negazione di «A and B» è «(not A) or (not B)», quella di «A or B» è «(not A) and (not B)»; sbagliare questa trasformazione, per esempio invertendo un «and» in un «or», è una tipica fonte di errori logici nei rami di selezione. Sul fronte dei cicli enumerativi, l'errore per scarto di uno (off-by-one) nasce dai confini dell'intervallo: «range(n)» in Python genera 0, 1, ..., n−1 (n valori, ultimo escluso), mentre spesso si vorrebbe includere n. I cicli annidati, infine, moltiplicano i costi: due cicli uno dentro l'altro su n elementi eseguono il corpo interno «n²» volte — prima avvisaglia concreta della complessità quadratica «O(n²)» che analizzeremo negli algoritmi di ordinamento elementari.

Ripasso attivo

Scrivi (in pseudocodice o in Python) un programma che legge un numero intero dalla tastiera e stampa se è «pari» oppure «dispari». Indica con chiarezza la struttura di controllo usata e perché la condizione scelta è corretta.

Richiamo attivo

Ricorda i punti chiave — poi rivela.

Fonti: Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento (Ministero dell'Istruzione e del Merito (MIM))

§ 04

Sottoprogrammi e modularità#

●●○StandardLPOSA-informatica-fondamenti-linguaggi — Sottoprogrammi: funzioni e procedure, parametri, valore di ritorno; modularità

Passaggio dei parametri tra chiamante e funzione

Passaggio dei parametriGrafo, Chiamante · massimo(4, 9) → Funzione massimo(a, b), Funzione massimo(a, b) → Chiamante · massimo(4, 9)Chiamante ·massimo(4, 9)Funzionemassimo(a, b)argomenti 4, 9return 9
Fig. 4Gli argomenti (4, 9) si legano ai parametri formali (a, b); la funzione esegue e con return riporta il valore 9 al chiamante. def massimo(a, b): if a > b: return a; else: return b.

Punti chiave

Un «sottoprogramma» è un blocco di istruzioni, dotato di un nome, che svolge un compito ben definito e può essere richiamato (invocato) più volte da punti diversi del programma. Scomporre un programma in sottoprogrammi è il principio della «modularità»: invece di un unico, lungo elenco di istruzioni, si costruisce il programma componendo unità più piccole, ciascuna responsabile di una cosa sola. I vantaggi sono concreti: il codice si riusa (si scrive una volta e si richiama tante volte), si legge meglio (ogni unità ha un nome che dice cosa fa) e si verifica più facilmente (si prova un pezzo alla volta).
Si distinguono tradizionalmente la «funzione», che calcola e restituisce un valore al chiamante (per esempio una funzione che restituisce il massimo tra due numeri), e la «procedura», che esegue un'azione senza restituire un valore (per esempio stampare un menu). In linguaggi come Python entrambe si definiscono con def: una funzione termina con return seguito dal valore, una procedura semplicemente non ha un return (o ha un return senza valore). La distinzione concettuale resta utile: chiedersi «questo sottoprogramma calcola un risultato o compie un'azione?» guida la scrittura.
La comunicazione tra il «programma chiamante» e il sottoprogramma avviene tramite i «parametri». Nella definizione si dichiarano i «parametri formali» (i nomi che il sottoprogramma userà internamente); nella chiamata si forniscono gli «argomenti» o parametri attuali (i valori effettivi). All'invocazione gli argomenti vengono associati ai parametri formali, il sottoprogramma viene eseguito e, se è una funzione, il valore prodotto da return viene riportato al punto di chiamata, dove sostituisce l'invocazione nell'espressione. Questo «andata-e-ritorno» è il meccanismo centrale del passaggio dei parametri.
Un concetto correlato è la «visibilità» (o scope) delle variabili. Le variabili definite dentro un sottoprogramma sono tipicamente «locali»: esistono solo durante la sua esecuzione e non interferiscono con variabili omonime altrove. Questo isolamento è ciò che rende i sottoprogrammi componibili e affidabili: chi richiama una funzione deve preoccuparsi solo di che cosa essa restituisce, non di come è fatta dentro. Progettare buone «interfacce» (nome, parametri, valore di ritorno chiari) è la chiave per scrivere programmi modulari, leggibili e manutenibili.
Esempio svolto

Funzione massimo con parametri e valore di ritorno

Definisci massimo(a, b) che restituisce il maggiore tra due numeri e usala per stampare il massimo tra 4 e 9.

  1. 01Definizione

    def massimo(a, b): qui a e b sono i parametri formali, cioè i nomi locali che useremo dentro la funzione.

  2. 02Corpo della funzione

    if a > b: return a; else: return b. La funzione confronta i due valori e restituisce, con return, il maggiore.

  3. 03Chiamata

    m = massimo(4, 9): gli argomenti 4 e 9 (parametri attuali) si associano ad a e b. Poiché 4 > 9 è falso, viene eseguito return b, cioè 9.

  4. 04Uso del risultato

    Il valore restituito (9) sostituisce l'invocazione, viene assegnato a m e poi stampato con print(m).

Risultato: massimo(4, 9) restituisce 9; il programma stampa 9. Parametri formali: a, b; argomenti (parametri attuali): 4, 9.

Obiettivo Maturità

  • Saper definire una funzione con parametri e valore di ritorno e poi invocarla correttamente, distinguendo parametri formali e attuali, è una competenza pratica esplicitamente valutata.
  • Saper motivare i vantaggi della modularità (riuso, leggibilità, verificabilità) e distinguere funzione da procedura è una richiesta teorica frequente al colloquio.

Errori frequenti

  • Confondere il momento della definizione con quello della chiamata: scrivere la def non esegue il codice della funzione; il codice viene eseguito solo quando la funzione viene invocata.
  • Dimenticare il return in una funzione che dovrebbe restituire un valore: senza return il chiamante non riceve nulla di utile (in Python ottiene None), pur sembrando che «il calcolo sia stato fatto».

Approfondimento

L'approfondimento naturale dei sottoprogrammi è la ricorsione: una funzione può richiamare sé stessa su un'istanza più piccola del problema, purché siano definiti un caso base (che arresta la discesa) e un passo ricorsivo (che riconduce il problema a uno più semplice). Il fattoriale ne è l'esempio canonico: «fatt(n) = 1 se n = 0, altrimenti n·fatt(n−1)». Ogni chiamata in sospeso è custodita in un record di attivazione (stack frame) impilato sulla pila delle chiamate (call stack), che conserva parametri, variabili locali e indirizzo di ritorno; quando si raggiunge il caso base la pila si svuota risalendo e componendo i risultati parziali. È chiaro allora perché una ricorsione senza caso base — o che non si avvicina mai ad esso — provochi il traboccamento della pila (stack overflow), l'esatto analogo del ciclo infinito per l'iterazione. Il passaggio dei parametri avviene poi secondo due modalità di fondo: per valore, dove il sottoprogramma riceve una copia dell'argomento e non può modificarne l'originale, e per riferimento, dove riceve un riferimento alla variabile del chiamante e può alterarla — distinzione che spiega molti «effetti collaterali» inattesi (per esempio quando si passa una lista). Le funzioni prive di effetti collaterali, che dipendono solo dai propri parametri e non modificano lo stato esterno, si dicono pure: sono le più facili da testare, riusare e comporre, e sono il cuore del paradigma funzionale.

Ripasso attivo

Definisci una funzione massimo(a, b) che restituisce il maggiore tra due numeri, poi mostra una chiamata che ne stampa il risultato per a = 4 e b = 9. Indica con precisione quali sono i parametri formali e quali gli argomenti.

Richiamo attivo

Ricorda i punti chiave — poi rivela.

Fonti: Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento (Ministero dell'Istruzione e del Merito (MIM))

§ 05

Errori, testing e debugging#

●●●ApprofondimentoLPOSA-informatica-fondamenti-linguaggi — Diagnostica e correzione degli errori (di sintassi, di esecuzione, logici); testing

Le tre famiglie di errori e quando emergono

Quando emerge l’erroreGrafo, Sintassi · non parte → Run-time · si interrompe, Run-time · si interrompe → Logico · output erratoSintassi · nonparteRun-time · siinterrompeLogico · outputerratoavvioesecuzione
Fig. 5L’errore di sintassi emerge prima dell’avvio (il programma non parte); il run-time durante l’esecuzione (si interrompe); il logico a esecuzione conclusa (output errato), rivelato soprattutto dal testing.

Punti chiave

Tradurre un algoritmo in un programma corretto richiede di saper riconoscere e correggere gli errori, che si classificano in tre famiglie nette. Gli «errori di sintassi» violano le regole di forma del linguaggio (una parentesi non chiusa, i due punti dimenticati dopo un if, un'indentazione sbagliata in Python): il programma non può nemmeno partire perché compilatore o interprete lo rifiutano. Sono i più facili da individuare, perché lo strumento li segnala esplicitamente, spesso indicando riga e tipo.
Gli «errori di esecuzione» (errori run-time) si manifestano mentre il programma gira, quando incontra una situazione che non può gestire: una divisione per zero, l'accesso a un elemento inesistente, la conversione in numero di una stringa che non lo è. Il programma è sintatticamente corretto e si avvia, ma si interrompe con una segnalazione (un'eccezione) nel punto critico. Per gestirli in modo robusto si verificano le condizioni problematiche prima di operare (per esempio controllare che il divisore non sia zero) o si usano i meccanismi di gestione delle eccezioni offerti dal linguaggio.
Gli «errori logici» (o semantici) sono i più insidiosi: il programma è sintatticamente corretto, gira senza interrompersi, ma produce un risultato sbagliato perché l'algoritmo o la sua traduzione non corrispondono all'intenzione. Un classico è usare l'operatore sbagliato (somma invece di prodotto, < invece di <=) o sbagliare la condizione di un ciclo. Non vengono segnalati da alcuno strumento: si scoprono solo confrontando l'output atteso con quello effettivo, cioè attraverso il «testing».
Il «testing» è la prova sistematica del programma su casi scelti per cui si conosce il risultato atteso; serve a far emergere gli errori, soprattutto quelli logici. Si scelgono casi «normali», casi «limite» (i confini, per esempio lo zero o il valore minimo/massimo) e casi «particolari». Quando un test fallisce, inizia il «debugging»: il processo di localizzazione e correzione dell'errore, per esempio inserendo stampe di controllo per osservare i valori intermedi o eseguendo il programma passo passo. È importante ricordare un principio metodologico: il testing può rivelare la presenza di errori, ma non può dimostrare la loro assenza.
Esempio svolto

Classificazione di tre errori tipici

Classifica e giustifica: (1) «if x > 0» senza i due punti finali; (2) calcolo di 10 / k con k = 0; (3) media di N voti calcolata dividendo la somma per N+1.

  1. 01Caso 1

    Manca un elemento richiesto dalla forma del linguaggio (i due punti dopo la condizione dell'if). L'interprete rifiuta il programma prima di eseguirlo: è un errore di SINTASSI.

  2. 02Caso 2

    Il programma è ben scritto e si avvia, ma durante l'esecuzione tenta una divisione per zero, operazione non definita, e si interrompe con un'eccezione: è un errore di ESECUZIONE (run-time).

  3. 03Caso 3

    Il programma è sintatticamente corretto e gira senza interrompersi, ma la formula è sbagliata: dividere per N+1 anziché per N dà un valore errato. Nessuno strumento lo segnala: è un errore LOGICO.

  4. 04Come si scopre il caso 3

    Con un test a risultato noto: per i voti 6 e 8 la media è 7; se il programma divide per 3 invece che per 2 restituisce circa 4.67, valore diverso da 7, e l'errore emerge.

Risultato: (1) errore di sintassi; (2) errore di esecuzione; (3) errore logico, individuabile solo con il testing su un caso a risultato noto.

Obiettivo Maturità

  • Saper classificare un errore nelle tre famiglie (sintassi, esecuzione, logico) a partire da un frammento di codice o da una descrizione del comportamento è una domanda ricorrente e molto discriminante.
  • Saper proporre casi di test sensati — compresi i casi limite — per verificare un programma, e descrivere una strategia di debugging, è una competenza pratica attesa nelle prove e al colloquio.

Errori frequenti

  • Pensare che «se il programma gira, allora è corretto»: un programma può eseguire senza errori e tuttavia produrre risultati sbagliati per un errore logico; solo il testing lo rivela.
  • Trascurare i casi limite nel testing (lo zero, il valore minimo, una lista vuota): molti errori si annidano proprio sui confini, dove la condizione del ciclo o del confronto cambia comportamento.

Approfondimento

Il testing si organizza con metodo, non a caso. Nel testing a scatola nera (black-box) i casi si scelgono guardando solo alla specifica (input attesi → output attesi), ignorando il codice interno; nel testing a scatola bianca (white-box) si guarda invece al codice per esercitarne tutti i cammini, misurando la copertura (coverage) raggiunta dai test. Poiché gli input possibili sono in genere infiniti, si usa il partizionamento in classi di equivalenza — si raggruppano gli input che il programma tratta nello stesso modo e se ne prova un rappresentante per classe — affiancato dall'analisi dei valori limite, che prova proprio i confini fra le classi (lo zero, il minimo, il massimo, la lista vuota), dove si annidano gli errori più frequenti. Conviene distinguere tre termini spesso confusi: lo sbaglio umano (error) introduce un difetto (fault) nel codice, che a sua volta può provocare un malfunzionamento (failure) osservabile; il debugging risale dal failure al fault. La programmazione difensiva contrasta i difetti a monte: si inseriscono asserzioni (assert) che verificano invarianti e precondizioni e fanno fallire subito, in modo evidente, il programma quando un'ipotesi è violata, anziché lasciarlo proseguire producendo dati errati. Vale infine, come principio metodologico, il monito di Dijkstra: il testing può rivelare la presenza di errori, mai dimostrarne l'assenza — solo una dimostrazione formale di correttezza potrebbe garantirla.

Ripasso attivo

Per ciascuna di queste situazioni, classifica l'errore (sintassi, esecuzione o logico) e giustifica: (1) manca il carattere due punti dopo «if x > 0»; (2) il programma calcola 10 / k con k che vale 0; (3) per calcolare la media di N voti il programma divide la somma per N+1 invece che per N.

Richiamo attivo

Ricorda i punti chiave — poi rivela.

Fonti: Esame di Stato del secondo ciclo — quadri di riferimento e griglie di valutazione (Ministero dell'Istruzione e del Merito (MIM))

Contenuti

Sezione -- / 05

    • 01Dal linguaggio macchina ai linguaggi ad alto livello○
    • 02Sintassi e semantica: tipi, variabili, operatori◐
    • 03Strutture di controllo e istruzioni di ingresso/uscita◐
    • 04Sottoprogrammi e modularità◐
    • 05Errori, testing e debugging●

0/5 Letti

Dagli appunti all'allenamento

Linguaggi di programmazione e sintassi

Consolida questo argomento con domande dalla banca dati.

~27
min
3
Competenze
Esercitati

Riferimenti e fonti

Fonti

Ministero dell'Istruzione e del Merito (MIM)

  • Indicazioni Nazionali per i Licei (DPR 89/2010, DM 211/2010) — Obiettivi Specifici di Apprendimento
  • Esame di Stato del secondo ciclo — quadri di riferimento e griglie di valutazione

Argomento precedente

Algoritmi, problem solving e pseudocodifica

Argomento successivo

Strutture dati e loro implementazione

EuraStudy·Appunti T·02·MMXXVI

Continua con l'argomento successivo: il percorso viene conservato.