Pensare da informatico/Imparare a programmare

Indice del libro

L'obiettivo di questo libro è insegnarti a pensare da informatico. Questo modo di pensare combina alcune delle migliori caratteristiche della matematica, dell'ingegneria e delle scienze naturali. Come i matematici, gli informatici usano linguaggi formali per denotare idee (nella fattispecie elaborazioni). Come gli ingegneri progettano cose, assemblano componenti in sistemi e cercano compromessi tra le varie alternative. Come gli scienziati osservano il comportamento di sistemi complessi, formulano ipotesi e verificano previsioni.

La più importante capacità di un informatico è quella di risolvere problemi. Risolvere problemi significa avere l'abilità di schematizzarli, pensare creativamente alle possibili soluzioni ed esprimerle in modo chiaro ed accurato. Da ciò emerge che il processo di imparare a programmare è un'eccellente opportunità di mettere in pratica l'abilità di risolvere problemi.

Da una parte ti sarà insegnato a programmare, già di per sé un'utile capacità. Dall'altra userai la programmazione come un mezzo rivolto ad un fine. Mentre procederemo quel fine ti diverrà più chiaro.

Il linguaggio di programmazione PythonModifica

Il linguaggio di programmazione che imparerai è il Python. Python è un esempio di linguaggio di alto livello; altri linguaggi di alto livello di cui puoi aver sentito parlare sono il C, il C++, il Perl ed il Java.

Come puoi immaginare sentendo la definizione "linguaggio di alto livello" esistono anche linguaggi di basso livello, talvolta chiamati "linguaggi macchina" o "linguaggi assembly". In modo non del tutto corretto si può affermare che i computer possono eseguire soltanto programmi scritti in linguaggi di basso livello: i programmi scritti in un linguaggio di alto livello devono essere elaborati prima di poter essere eseguiti. Questo processo di elaborazione impiega del tempo e rappresenta un piccolo svantaggio dei linguaggi di alto livello.

I vantaggi sono d'altra parte enormi. In primo luogo è molto più facile programmare in un linguaggio ad alto livello: questi tipi di programmi sono più veloci da scrivere, più corti e facilmente leggibili, ed è più probabile che siano corretti. In secondo luogo i linguaggi di alto livello sono portabili: portabilità significa che essi possono essere eseguiti su tipi di computer diversi con poche o addirittura nessuna modifica. I programmi scritti in linguaggi di basso livello possono essere eseguiti solo su un tipo di computer e devono essere riscritti per essere trasportati su un altro sistema.

Questi vantaggi sono così evidenti che quasi tutti i programmi sono scritti in linguaggi di alto livello, lasciando spazio ai linguaggi di basso livello solo in poche applicazioni specializzate.

I programmi di alto livello vengono trasformati in programmi di basso livello eseguibili dal computer tramite due tipi di elaborazione: l'interpretazione e la compilazione. Un interprete legge il programma di alto livello e lo esegue, trasformando ogni riga di istruzioni in un'azione. L'interprete elabora il programma un po' alla volta, alternando la lettura delle istruzioni all'esecuzione dei comandi che le istruzioni descrivono:

Un compilatore legge il programma di alto livello e lo traduce completamente in basso livello, prima che il programma possa essere eseguito. In questo caso il programma di alto livello viene chiamato codice sorgente, ed il programma tradotto codice oggetto o eseguibile. Dopo che un programma è stato compilato può essere eseguito ripetutamente senza che si rendano necessarie ulteriori compilazioni finché non ne viene modificato il codice.

Python è considerato un linguaggio interpretato perché i programmi Python sono eseguiti da un interprete. Ci sono due modi di usare l'interprete: a linea di comando o in modo script. In modo "linea di comando" si scrivono i programmi Python una riga alla volta: dopo avere scritto una riga di codice alla pressione di Invio (o Enter, a seconda della tastiera) l'interprete la analizza subito ed elabora immediatamente il risultato, eventualmente stampandolo a video:

$ python 
Python 1.5.2 (#1, Feb 1 2000, 16:32:16) 
Copyright 1991-1995 Stichting Mathematish Centrum, Amsterdam 
>>> print 1 + 1 
2

La prima linea di questo esempio è il comando che fa partire l'interprete Python in ambiente Linux e può cambiare leggermente a seconda del sistema operativo utilizzato. Le due righe successive sono semplici informazioni di copyright del programma.

La terza riga inizia con >>>: questa è l'indicazione (chiamata "prompt") che l'interprete usa per indicare la sua disponibilità ad accettare comandi. Noi abbiamo inserito print 1 + 1 e l'interprete ha risposto con 2.

In alternativa alla riga di comando si può scrivere un programma in un file (detto script) ed usare l'interprete per eseguire il contenuto del file. Nell'esempio seguente abbiamo usato un editor di testi per creare un file chiamato pippo.py:

print 1 + 1

Per convenzione, i file contenenti programmi Python hanno nomi che terminano con .py.

Per eseguire il programma dobbiamo dire all'interprete il nome dello script:

$ python pippo.py 
2

In altri ambienti di sviluppo i dettagli dell'esecuzione dei programmi possono essere diversi.

La gran parte degli esempi di questo libro sono eseguiti da linea di comando: lavorare da linea di comando è conveniente per lo sviluppo e per il test del programma perché si possono inserire ed eseguire immediatamente singole righe di codice. Quando si ha un programma funzionante lo si dovrebbe salvare in uno script per poterlo eseguire o modificare in futuro senza doverlo riscrivere da capo ogni volta. Tutto ciò che viene scritto in modo "linea di comando" è irrimediabilmente perso nel momento in cui usciamo dall'ambiente Python.

Il primo programmaModifica

Per tradizione il primo programma scritto in un nuovo linguaggio è chiamato "Hello, World!" perché tutto ciò che fa è scrivere le parole Hello, World! a video e nient'altro. In Python questo programma è scritto così:

print "Hello, World!"

Questo è un esempio di istruzione di stampa, che in effetti non stampa nulla su carta limitandosi invece a scrivere un valore sullo schermo. In questo caso ciò che viene "stampato" sono le parole Hello, World!. Le virgolette segnano l'inizio e la fine del valore da stampare ed esse non appaiono nel risultato.

Alcune persone giudicano la qualità di un linguaggio di programmazione dalla semplicità del programma "Hello, World!": da questo punto di vista Python sembra essere quanto di meglio sia realizzabile.

Cos'è un programma?Modifica

Un programma è una sequenza di istruzioni che specificano come effettuare una elaborazione. L'elaborazione può essere sia di tipo matematico (per esempio la soluzione di un sistema di equazioni o il calcolo delle radici di un polinomio) che simbolico (per esempio la ricerca e sostituzione di un testo in un documento).

I dettagli sono diversi per ciascun linguaggio di programmazione, ma un piccolo gruppo di istruzioni è praticamente comune a tutti:

  • input: ricezione di dati da tastiera, da file o da altro dispositivo.
  • output: scrittura di dati su video, su file o trasmissione ad altro dispositivo.
  • matematiche: esecuzione di semplici operazioni matematiche, quali l'addizione e la sottrazione.
  • condizionali: controllo di alcune condizioni ed esecuzione della sequenza di istruzioni appropriata.
  • ripetizione: ripetizione di un'azione, di solito con qualche variazione.

Che ci si creda o meno, questo è più o meno tutto quello che c'è. Ogni programma che hai usato per quanto complesso possa sembrare (anche il tuo videogioco preferito) è costituito da istruzioni che assomigliano a queste. Possiamo affermare che la programmazione altro non è che la suddivisione di un compito grande e complesso in una serie di sotto-compiti via via più piccoli, finché questi sono sufficientemente semplici da essere eseguiti da una di queste istruzioni fondamentali.

Questo concetto può sembrare un po' vago, ma lo riprenderemo quando parleremo di algoritmi.

Cos'è il debug?Modifica

La programmazione è un processo complesso e dato che esso è fatto da esseri umani spesso comporta errori. Per ragioni bizzarre gli errori di programmazione sono chiamati bug ed il processo della loro ricerca e correzione è chiamato debug.

Sono tre i tipi di errore nei quali si incorre durante la programmazione: gli errori di sintassi, gli errori in esecuzione e gli errori di semantica. È utile distinguerli per poterli individuare più velocemente.

Errori di sintassiModifica

Python può eseguire un programma solo se il programma è sintatticamente corretto, altrimenti l'elaborazione fallisce e l'interprete ritorna un messaggio d'errore. La sintassi si riferisce alla struttura di un programma e alle regole concernenti la sua struttura. In italiano, per fare un esempio, una frase deve iniziare con una lettera maiuscola e terminare con un punto. questa frase contiene un errore di sintassi. E anche questa

Per la maggior parte dei lettori qualche errore di sintassi non è un problema significativo, tanto che possiamo leggere le poesie di E.E.Cummings (prive di punteggiatura) senza "messaggi d'errore". Python non è così permissivo: se c'è un singolo errore di sintassi da qualche parte nel programma Python stamperà un messaggio d'errore e ne interromperà l'esecuzione, rendendo impossibile proseguire. Durante le prime settimane della tua carriera di programmatore probabilmente passerai molto tempo a ricercare errori di sintassi. Via via che acquisirai esperienza questi si faranno meno numerosi e sarà sempre più facile rintracciarli.

Errori in esecuzioneModifica

Il secondo tipo di errore è l'errore in esecuzione (o "runtime"), così chiamato perché l'errore non appare finché il programma non è eseguito. Questi errori sono anche chiamati eccezioni perché indicano che è accaduto qualcosa di eccezionale nel corso dell'esecuzione (per esempio si è cercato di dividere un numero per zero).

Gli errori in esecuzione sono rari nei semplici programmi che vedrai nei primissimi capitoli, così potrebbe passare un po' di tempo prima che tu ne incontri uno.

Errori di semanticaModifica

Il terzo tipo di errore è l'errore di semantica. Se c'è un errore di semantica il programma verrà eseguito senza problemi nel senso che il computer non genererà messaggi d'errore durante l'esecuzione, ma il risultato non sarà ciò che ci si aspettava. Sarà qualcosa di diverso, e questo qualcosa è esattamente ciò che è stato detto di fare al computer.

Il problema sta nel fatto che il programma che è stato scritto non è quello che si desiderava scrivere: il significato del programma (la sua semantica) è sbagliato. L'identificazione degli errori di semantica è un processo complesso perché richiede di lavorare in modo inconsueto, guardando i risultati dell'esecuzione e cercando di capire cosa il programma ha fatto di sbagliato per ottenerli.

Debug sperimentaleModifica

Una delle più importanti abilità che acquisirai è la capacità di effettuare il debug (o "rimozione degli errori"). Sebbene questo possa essere un processo frustrante è anche una delle parti più intellettualmente vivaci, stimolanti ed interessanti della programmazione.

In un certo senso il debug può essere paragonato al lavoro investigativo. Sei messo di fronte agli indizi e devi ricostruire i processi e gli eventi che hanno portato ai risultati che hai ottenuto.

Il debug è una scienza sperimentale: dopo che hai avuto un'idea di ciò che può essere andato storto, modifichi il programma e lo provi ancora. Se la tua ipotesi era corretta allora puoi predire il risultato della modifica e puoi avvicinarti di un ulteriore passo all'avere un programma funzionante. Se la tua ipotesi era sbagliata devi ricercarne un'altra. Come disse Sherlock Holmes "Quando hai eliminato l'impossibile ciò che rimane, per quanto improbabile, deve essere la verità" (A.Conan Doyle, Il segno dei quattro)

Per qualcuno la programmazione e il debug sono la stessa cosa, intendendo con questo che la programmazione è un processo di rimozione di errori finché il programma fa ciò che ci si aspetta. L'idea è che si dovrebbe partire da un programma che fa qualcosa e facendo piccole modifiche ed eliminando gli errori man mano che si procede si dovrebbe avere in ogni momento un programma funzionante sempre più completo.

Linux, per fare un esempio, è un sistema operativo che contiene migliaia di righe di codice, ma esso è nato come un semplice programma che Linus Torvalds usò per esplorare il chip 80386 Intel. Secondo Larry Greenfields, "uno dei progetti iniziali di Linus era un programma che doveva cambiare una riga di AAAA in BBBB e viceversa. Questo in seguito diventò Linux." (The Linux Users' Guide Beta Version 1)

I capitoli successivi ti forniranno ulteriori suggerimenti sia per quanto riguarda il debug che per altre pratiche di programmazione.

Linguaggi formali e naturaliModifica

I linguaggi naturali sono le lingue parlate, tipo l'inglese, l'italiano, lo spagnolo. Non sono stati "progettati" da qualcuno e anche se è stato imposto un certo ordine nel loro sviluppo si sono evoluti naturalmente.

I linguaggi formali sono linguaggi progettati per specifiche applicazioni.

Per fare qualche esempio, la notazione matematica è un linguaggio formale particolarmente indicato ad esprimere relazioni tra numeri e simboli; i chimici usano un linguaggio formale per rappresentare la struttura delle molecole; cosa più importante dal nostro punto di vista, i linguaggi di programmazione sono linguaggi formali che sono stati progettati per esprimere elaborazioni.

I linguaggi formali tendono ad essere piuttosto rigidi per quanto riguarda la sintassi: 3+3=6 è una dichiarazione matematica sintatticamente corretta, mentre 3=div6$ non lo è. H2O è un simbolo chimico sintatticamente corretto contrariamente a 2Zz.

Le regole sintattiche si possono dividere in due categorie: la prima riguarda i token, la seconda la struttura. I token sono gli elementi di base del linguaggio (quali possono essere le parole in letteratura, i numeri in matematica e gli elementi chimici in chimica). Uno dei problemi con 3=div6$ è che $ non è un token valido in matematica; 2Zz non è valido perché nessun elemento chimico è identificato dal simbolo Zz.

Il secondo tipo di regola riguarda la struttura della dichiarazione, cioè il modo in cui i token sono disposti. La dichiarazione 3=div6$ è strutturalmente non valida perché un segno div non può essere posto immediatamente dopo un segno =. Allo stesso modo l'indice nelle formule chimiche deve essere indicato dopo il simbolo dell'elementi chimico, non prima, e quindi l'espressione 2Zz non è valida.

Come esercizio crea quella che può sembrare una frase in italiano con dei token non riconoscibili. Poi scrivi un'altra frase con tutti i token validi ma con una struttura non valida.

Quando leggi una frase in italiano o una dichiarazione in un linguaggio formale devi capire quale sia la struttura della dichiarazione. Questo processo (chiamato parsing) in un linguaggio naturale viene realizzato in modo inconscio e spesso non ci si rende conto della sua intrinseca complessità.

Per esempio, quando senti la frase "La scarpa è caduta", capisci che "la scarpa" è il soggetto e che "è caduta" è il verbo. Quando hai analizzato la frase puoi capire cosa essa significa (cioè la semantica della frase). Partendo dal presupposto che tu sappia cosa sia una "scarpa" e cosa significhi "cadere" riesci a comprendere il significato generale della frase.

Anche se i linguaggi formali e quelli naturali condividono molte caratteristiche (token, struttura, sintassi e semantica) ci sono tuttavia molte differenze:

Ambiguità
i linguaggi naturali ne sono pieni ed il significato viene ottenuto anche grazie ad indizi ricavati dal contesto. I linguaggi formali sono progettati per essere completamente non ambigui e ciò significa che ciascuna dichiarazione ha esattamente un significato, indipendente dal contesto.
Ridondanza
per evitare l'ambiguità e ridurre le incomprensioni i linguaggi naturali impiegano molta ridondanza. I linguaggi formali sono meno ridondanti e più concisi.
Letteralità
i linguaggi naturali fanno uso di paragoni e metafore, e possiamo parlare in termini astratti intuendo immediatamente che ciò che sentiamo ha un significato simbolico. I linguaggi formali invece esprimono esattamente ciò che dicono.

Anche se siamo cresciuti apprendendo un linguaggio naturale, la nostra lingua madre, spesso abbiamo difficoltà ad adattarci ai linguaggi formali. In un certo senso la differenza tra linguaggi naturali e formali è come quella esistente tra poesia e prosa, ma in misura decisamente più evidente:

Poesia
le parole sono usate tanto per il loro suono che per il loro significato, e la poesia nel suo complesso crea un effetto o una risposta emotiva. L'ambiguità è non solo frequente, ma spesso addirittura cercata.
Prosa
il significato delle parole è estremamente importante, con la struttura che contribuisce a fornire maggior significato. La prosa può essere soggetta ad analisi più facilmente della poesia, ma può risultare ancora ambigua.
Programmi
il significato di un programma per computer è non ambiguo e assolutamente letterale, può essere compreso nella sua interezza con l'analisi dei token e della struttura.

Qui sono esposti alcuni suggerimenti per la lettura di programmi e di altri linguaggi formali.

  • Ricorda che i linguaggi formali sono molto più ricchi di significato dei linguaggi naturali, così è necessario più tempo per leggerli e comprenderli.
  • La struttura dei linguaggi formali è molto importante e solitamente non è una buona idea leggerli dall'alto in basso, da sinistra a destra, come avviene per un testo letterario: impara ad analizzare il programma nella tua testa, identificandone i token ed interpretandone la struttura.
  • I dettagli sono importanti: piccole cose come errori di ortografia e cattiva punteggiatura sono spesso trascurabili nei linguaggi naturali, ma possono fare una gran differenza in quelli formali.

GlossarioModifica

Soluzione di problemi
il processo di formulare un problema, trovare una soluzione ed esprimerla.
Linguaggio ad alto livello
un linguaggio di programmazione tipo Python che è progettato per essere facilmente leggibile e utilizzabile dagli esseri umani.
Linguaggio di basso livello
un linguaggio di programmazione che è progettato per essere facilmente eseguibile da un computer; è anche chiamato "linguaggio macchina" o "linguaggio assembly".
Portabilità
caratteristica di un programma di poter essere eseguito su computer di tipo diverso.
Interpretare
eseguire un programma scritto in un linguaggio di alto livello traducendolo ed eseguendolo immediatamente, una linea alla volta.
Compilare
tradurre un programma scritto in un linguaggio di alto livello in un programma di basso livello come preparazione alla successiva esecuzione.
Codice sorgente
un programma di alto livello prima di essere compilato.
Codice oggetto
il risultato ottenuto da un compilatore dopo aver tradotto il codice sorgente.
Eseguibile
altro nome per indicare il codice oggetto pronto per essere eseguito.
Script
programma memorizzato in un file, solitamente destinato ad essere interpretato.
Programma
serie di istruzioni che specificano come effettuare un'elaborazione.
Algoritmo
processo generale usato per risolvere una particolare categoria di problemi.
Bug
errore in un programma (detto anche "baco").
Debug
processo di ricerca e di rimozione di ciascuno dei tre tipi di errori di programmazione.
Sintassi
struttura di un programma.
Errore di sintassi
errore in un programma che rende impossibile la continuazione dell'analisi del codice (il programma non può quindi essere interpretato interamente o compilato).
Errore in esecuzione
errore che non è riconoscibile finché il programma non è stato eseguito e che impedisce la continuazione della sua esecuzione.
Eccezione, errore runtime
altri nomi per indicare un errore in esecuzione.
Errore di semantica
errore nel programma che fa ottenere risultati diversi da quanto ci si aspettava.
Semantica
significato di un programma.
Linguaggio naturale
ognuno dei linguaggi parlati evoluti nel tempo.
Linguaggio formale
ognuno dei linguaggi che sono stati progettati per scopi specifici, quali la rappresentazione di idee matematiche o programmi per computer (tutti i linguaggi per computer sono linguaggi formali).
Token
uno degli elementi di base della struttura sintattica di un programma analogo alla parola nei linguaggi naturali.
Parsing
esame e analisi della struttura sintattica di un programma.
Istruzione di stampa
istruzione che ordina all'interprete Python di scrivere un valore sullo schermo.