[ Suggerimenti
] [ Soluzioni
degli Esercizi] [ Volume
2 ] [ Newsletter
Gratuita ]
[ Seminari
] [ Seminari
su CD ROM ] [ Consulenza]
[ Capitolo Precedente ] [ Indice Generale ] [ Indice Analitico ] [ Prossimo Capitolo ]
traduzione italiana e adattamento a cura di Marco Arena
Questa appendice non tratta dell'indentazione
e posizionamento di parentesi tonde e graffe, sebbene ciò sarà
menzionato.E' dedicato alle linee guida generali usate in questo libro per
l'organizzazione dei listati del codice.
Sebbene molti di questi problemi sono stati introdotti
in tutto il libro, questa appendice compare alla fine del libro in modo che
si possa presumere che ogni argomento è aperto a critiche e se qualcosa
non è chiaro ci si può rimandare alla sezione appropriata.
Tutte le decisioni riguardanti lo stile di codifica
in questo libro sono state deliberatamente considerate e prese, qualche volta
lungo un periodo di anni. Naturalmente, ognuno ha le proprie ragioni per organizzare
il codice nel modo che vuole ed io proverò solo a dire come sono arrivato
al mio considerando i vincoli e i fattori ambientali che mi hanno guidato
a queste decisioni.
Nozioni generali
Nel testo di questo libro, gli identificatori (funzioni,
variabili e nomi di classi) sono scritte in grassetto. Molte parole
chiavi saranno scritte in grassetto, eccetto per quelle usate moltissimo per
cui il grassetto può diventare tedioso, come "class" e "virtual".
Uso un particolare stile di codifica per gli esempi
in questo libro. Questo è' stato sviluppato su un certo numero di anni
ed è stato parzialmente ispirato dallo stile di Bjarne Stroustrup nel
suo originale The C++ Programming Language.[64]
Lo stile di formattazione è un buon tema per ore di dibattito, per
cui dirò solo che non sto tentando di dettare il corretto stile con
i miei esempi; ho le mie motivazioni per usare lo stile che uso. Poiché
il C++ è un linguaggio di programmazione a stile libero, si può
continuare ad usare qualsiasi stile con cui ci si trova comodi.
Detto questo, noterò che è importante
avere uno stile di formattazione coerente all'interno di un progetto. Cercando
in Internet, si troveranno un certo numero di tools che si possono usare per
riformattare tutto il codice in un progetto per ottenere questa preziosa coerenza.
I programmi in questo libro sono file che vengono automaticamente
estratti dal testo del libro, il che permette ad essi di essere testati per
assicurarsi che funzionino correttamente. Quindi il codice stampato nel libro
funzionerà senza errori di compilazione quando è compilato con
un'implementazione conforme allo Standard C++ (da notare che non tutti i compilatori
supportano tutte le caratteristiche del linguaggio). Gli errori che dovrebbero
causare problemi di compilazione sono descritti con il commento //! per
cui possono essere facilmente scoperti e testati usando gli strumenti automatici.
Errori scoperti e segnalati all'autore compariranno prima nella versione elettronica
del libro (su www.BruceEckel.com) ed in seguito aggiornati sul libro.
Uno degli standard in questo libro è che tutti
i programmi saranno compilati e linkati senza errori (sebbene qualche volta
causeranno dei warning). A questo fine, qualcuno dei programmi, che dimostra
solo un esempio di codifica e non rappresenta un programma a se stante, avrà
una funzione main( ) vuota, come questo
int main() {}
Questo permette al linker di finire
senza errori.
Lo standard per main( ) è di restituire
un int, ma lo Standard C++ stabilisce che se non c'è un istruzione
di return all'interno di main( ), il compilatore genererà
automaticamente il codice return 0. Questa opzione (niente return
nel main( )) sarà usata in questo libro (alcuni
compilatori potranno ancora generare dei warning per ciò, ma questi
non sono compatibili con lo Standard C++).
Nomi di file
In C, è tradizione chiamare gli header file (contenenti
le dichiarazioni) con l'estensione .h e i file di esecuzione ( che
fanno sì che la memoria sia allocata e il codice generato) con l'estensione
.c. Il C++ è passato attraverso un evoluzione. Fu prima sviluppato
su Unix, dove il sistema operativo distingueva tra minuscole e maiuscole nei
nomi di file. I nomi di file originali erano semplicemente la versione maiuscola
dell'estensione C: .H e .C. Questo naturalmente non funziona
per sistemi operativi che non distinguono minuscole e maiuscole, come il DOS.
I venditori C++ sotto DOS usano estensioni come hxx e cxx per
i file header e esecutivi, rispettivamente, hpp e cpp. Più
tardi, qualcuno riuscì a capire che l'unica ragione per cui bisognava
avere differenti estensioni per un file era perchè il compilatore potesse
determinare se compilarlo come file C o C++. Poichè il compilatore
non compilava mai gli header file direttamente, solo l'estensione dei file
esecutivi doveva essere cambiata. La consuetudine, attraversando virtualmente
tutti i sistemi, ha portato ad usare ora cpp per i file esecutivi e
h per gli header file. Da notare che quando includiamo i file header
standard di C++, si usa l'opzione di non avere estensione per i file, ad esempio:
#include <iostream>.
Tag di inizio e fine commento
Un problema molto importante di questo libro è
che tutto il codice che si vede in esso deve essere verificato per essere
corretto ( con almeno un compilatore). Questo è realizzato estraendo
automaticamente il file dal libro. Per facilitare ciò, tutti i listati
che sono proposti per essere compilati ( al contrario dei frammenti di codice,
dei quali ci sono solo alcuni) hanno tag di commento all'inizio e alla fine.
Questi tag sono usati dal tool di estrazione del codice ExtractCode.cpp
nel Volume 2 di questo libro ( che si può trovare sul sito www.BruceEckel.com)
per estrarre ogni listato dalla versione ordinaria di testo ASCII del libro.
Il tag di fine listato dice ad ExtractCode.cpp
che è alla fine del listato, ma il tag di inizio listato è seguito
da informazioni quali la sotto-directory a cui il file appartiene (generalmente
organizzate in capitoli, così un file appartenente al Capitolo 8 avrà
un tag tipo C08), seguito da due punti ed il nome del listato.
Poichè ExtractCode.cpp crea anche un makefile
per ogni sotto-directory, vengono anche incorporate nel listato informazioni
su come un programma è fatto e la linea di comando usata per testarlo.
Se un programma è a se stante ( non necessita di essere linkato con
nessun altro cioè) non ha informazioni extra. Questo vale anche per
gli header file. Comunque, se non contiene un main( ) e si intende
che deve essere linkato con qualcos'altro, allora esso ha uno {O} dopo
il nome del file. Se per il listato si intende che deve essere il programma
principale ma necessita di essere linkato con altre componenti, c'è
una linea separata che inizia con //{L} e continua con tutti i file
che necessitano di essere linkati (senza estensioni, dal momento che questi
possono variare da piattaforma a piattaforma).
Si possono trovare esempi in tutto il libro.
Se un file deve essere estratto ma i tag di inizio e
fine non devono essere inclusi nel file che ne deriva (per esempio se esso
è un file di dati di test) allora il tag di inizio è immediatamente
seguito da un ‘!’.
Parentesi, graffe e indentazione
Si può notare che lo stile di formattazione in
questo libro è differente da molti stili tradizionali di C. Naturalmente,
ognuno pensa che il proprio stile sia il più razionale. Comunque, lo
stile qui usato ha dietro una logica semplice, che sarà presentata
qui insieme alle idee sul perchè sono stati sviluppati altri stili.
Lo stile di formattazione è motivato da una cosa:
la presentazione, sia nella forma stampata che per i seminari. Si può
avere la sensazione che i propri bisogni siano differenti perchè non
si fanno molte presentazioni. Comunque, il codice operativo viene letto più
volte di quanto è scritto e quindi deve essere facile per il lettore
comprenderlo. I miei due importanti criteri sono la "scansione"
( come è facile per il lettore capire il significato di una singola
linea) e il numero di linee che può adattarsi su una pagina. Quest'ultimo
può suonare strano, ma quando si sta tenendo una presentazione in pubblico,
si può far distrarre molto la platea se il presentatore deve spostarsi
avanti e indietro tra le slide e ciò può essere causato da poche
linee sparse.
Tutti sembrano concordare
che il codice tra parentesi graffe dovrebbe essere indentato. Ciò su
cui la gente non concorda - ed è il punto dove c'è maggior inconsistenza
tra gli stili di formattazione - è questo: Dove vanno messe le parentesi
aperte? Questa domanda, penso, è quella che causa tante variazioni
tra stili di codifica (per un enumerazione di stili di codifica, vedi C++
Programming Guidelines, di Tom Plum e Dan Saks, Plum Hall 1991). Proverò
a convincerti che molti degli stili odierni derivino dai vincoli del pre-Standard
C (prima dei prototipi di funzione) e ora sono quindi inappropriati.
Prima, la mia risposta a quella domanda chiave: le parentesi
graffe aperte dovrebbero sempre andare sulla stessa linea come il "precursore"
( per questo intendo " se il corpo è del tipo: una classe, funzione,
definizioni di oggetti, istruzioni if, etc.."). Questa è una semplice,
coerente regola che applico a tutto il codice che scrivo e rende la formattazione
molto più semplice. Rende la "scansione" più semplice
quando si guarda questa linea:
int func(int a);
si sa, dal punto e virgola alla fine della linea, che questa è una dichiarazione e non ha seguito, ma quando si vede la linea:
int func(int a) {
immediatamente si sa che è una definizione perchè
la linea finisce con una parentesi aperta e non il punto e virgola. Usando
questo approccio, non c'è differenza su dove mettere le parentesi di
apertura per una definizione su più linee:
int func(int a) { int b = a + 1; return b * 2; }
e per una definizione su linea singola spesso usata
per l'inline:
int func(int a) { return (a + 1) * 2; }
In modo simile per una classe:
class Thing;
è una dichiarazione di un nome di classe e
class Thing {
è una definizione di classe. Si può dire
guardando la linea singola in tutti i casi se è una dichiarazione o
una definizione. E naturalmente mettendo le parentesi aperte sulla stessa
linea, invece che su una linea a parte, è possibile inserire più
linee nella stessa pagina.
E quindi perchè abbiamo tanti altri stili? In
particolare, si noterà che molte persone creano classi seguendo lo
stile di sopra (il quale è usato da Stroustrup in tutte le edizioni
del suo libro The C++ Programming Language ed. Addison-Wesley) ma creano
definizioni di funzioni mettendo la parentesi aperta su una linea a sé
(il che genera molti differenti stili di indentazione). Stroustrup fa questo
eccetto che per brevi funzioni inline. Con l'approccio che descrivo qui, tutto
è coerente - si può dire di cosa si tratta (class, funzione,
enum, etc.) e sulla stessa linea si mette la parentesi aperta per indicare
che il corpo per questa cosa è scritto a seguire. Inoltre, la parentesi
aperta è la stessa sia per brevi funzioni inline che per le definizioni
di funzioni ordinarie.
Sostengo che lo stile delle definizioni di funzioni
usato da molte persone, derivi dal prototipo C pre-funzioni, nel quale non
si dovevano dichiarare gli argomenti all'interno delle parentesi tonde, ma
invece tra la parentesi tonda chiusa e la parentesi graffa aperte (questo
mostra le radici assembly del linguaggio C):
void bar() int x; float y; { /* il corpo qui */ }
Qui, sarebbe completamente inopportuno mettere le parentesi
graffe aperte sulla stessa linea, per cui nessuno lo fece. Comunque, si presero
diverse decisioni in merito alla questione se le graffe dovrebbero essere
indentate con il corpo o se esse dovrebbero essere al livello del "precursore".
Per questo abbiamo differenti stili di formattazione.
Ci sono altri argomenti sul posizionamento delle graffe
sulla linea immediatamente seguente la dichiarazione (di una classe, struct,
funzione, etc.). La seguente viene da un lettore, ed è presentata in
modo che si possa conoscere quale siano i problemi:
Utenti esperti di 'vi' sanno che schiacciando
il tasto ']' due volte porterà alla prossima occorrenza di "{"
(o ^L) in colonna 0. Questa caratteristica è estremamente utile per
muoversi nel codice (per saltare alla prossima funzione o definizione di classe).
[Il mio commento: quando stavo inizialmente lavorando sotto Unix, apparvero
le GNU Emac e rimasi intrappolato tra esse. Come conseguenza, non ho mai raggiunto
una piena conoscenza di vi e quindi non penso in termini di "locazione
di colonna 0". Comunque, c'è un ampio gruppo di utenti di vi
, ed essi sono colpiti da tale questione.]
Mettendo la "{" sulla linea successiva, si
eliminano un po di confusione dal codice con istruzioni condizionali complesse,
favorendo la "scansione". Esempio:
if(cond1 && cond2 && cond3) { istruzione; }
Il codice precedente [afferma il lettore] ha una scansione
limitata. Comunque,
if (cond1 && cond2 && cond3) { istruzione; }
spezza l'if dal corpo, ma ha la conseguenza di
una migliore leggibilità. [le nostre opinioni sul fatto che ciò
sia vero varieranno in dipendenza da cosa siamo abituati a fare.]
Alla fine, è più facile allineare visualmente
le graffe quando esse sono allineate nella stessa colonna. Esse visivamente
"emergono" meglio. [fine del commento del lettore.]
Il problema di dove mettere le parentesi graffe aperte è probabilmente il più discordante. Ho imparato a scandire entrambe le forme e alla fine ne viene fuori quella con cui ci si trova più comodi. Comunque, noto che lo standard ufficiale Java ( trovato sul sito Java della Sun) è effettivamente lo stesso di quello che presento qui - da quando molte persone stanno iniziando a programmare in entrambi i linguaggi, la coerenza tra stili di codifica può essere utile.
L'approccio che uso rimuove tutte le eccezioni e casi
speciali, e logicamente fornisce uno stile singolo di indentazione. Perfino
all'interno del corpo di una funzione, la coerenza rimane, come in:
for(int i = 0; i < 100; i++) { cout << i << endl; cout << x * i << endl; }
Lo stile è semplice da insegnare e ricordare
- si usa una singola, coerente regola per tutte le nostre formattazioni del
codice, non una per le classi, due per le funzioni (una per quelle inline
e un'altra quelle non), e possibilmente altre per cicli for, istruzioni
if, ecc. La sola coerenza, penso, è meritevole di considerazione.
Soprattutto, C++ è un linguaggio più nuovo che C, e sebbene
dobbiamo fare molte concessioni al C, non dovremmo portare anche troppi manufatti
con noi che causano problemi in futuro. Piccoli problemi moltiplicati per
molte linee di codice diventano grandi problemi. Per un completo esame della
materia, sebbene per il C, vedi C Style: Standards and Guidelines,
di David Straker (Prentice-Hall 1992).
L'altro vincolo sotto il quale ho lavorato è la larghezza della linea, infatti il libro ha una limitazione a 50 caratteri. Cosa succede quando qualcosa è più lungo di una linea? Bene, di nuovo mi sono sforzato per avere una politica coerente per il modo in cui spezzare le linee, così che esse possano facilmente essere identificate. Finché qualcosa è parte di una singola definizione, lista di definizione, etc. la linea di continuazione dovrebbe essere indentata di un livello dentro dall'inizio della definizione, lista di argomenti, ecc.
Nomi degli identificatori
Coloro che hanno familiarità con Java noteranno
che ho cambiato nell'usare lo stile standard di Java per tutti i nomi degli
identificatori. Comunque, non posso essere completamente coerente qui perchè
gli identificatori nelle librerie standard C e C++ non seguono questo stile.
Lo stile è abbastanza lineare. Solo la prima
lettera di un identificatore è maiuscola se tale identificatore è
una classe. Se si tratta di una funzione o variabile allora la prima lettera
è minuscola. Il resto degli identificatori consistono di un o più
parole, legate insieme ma distinte mettendo la maiuscola per ogni inizio parola.
Così una classe appare in questo modo:
class FrenchVanilla : public IceCream {
Un identificatore di un oggetto appare così:
FrenchVanilla myIceCreamCone(3);
e una funzione invece:
void eatIceCreamCone();
(sia per una funzione membro che per una regolare funzione).
La sola eccezione riguarda le costanti compile-time
(const o #define), per le quali tutte le lettere nell'identificatore
sono maiuscole.
Il pregio dello stile è che scrivere con le maiuscole
ha un suo significato - si può vedere dalla prima lettera se si sta
parlando di una classe o di un oggetto/metodo. Ciò è particolarmente
utile nell'accesso a classi membro di tipo static.
Ordine di inclusione dei file header
Gli header file sono inclusi in ordine "dal più
specifico al più generale". Cioè ogni file header nella
directory locale viene incluso per primo, poi ogni header del mio "tool"
come require.h, successivamente vengono inclusi
i file header di librerie di terzi, poi gli header della libreria Standard
C++ e finalmente i file header della libreria C.
La giustificazione per questo viene da John
Lakos nel libro Large-Scale C++ Software Design (Addison-Wesley, 1996):
Errori d'uso nascosti possono essere evitati assicurando che i file .h di una componente facciano l'analisi di se stessi - senza dichiarazioni o definizioni fornite dall'esterno.... Includendo i file .h nella prima linea del file .c ci si assicura che nessuno pezzo critico di informazione intrinseca all'interfaccia fisica del componente manchi dal file .h ( o, se c'è, ciò che si scoprirà su di esso non appena si proverà a compilare il file .c).
Se l'ordine di inclusione dei file header va "dal più specifico al più generale" allora è più verosimile che il proprio header non si analizzi da se, lo si scoprirà ben presto prevenendo fastidi lungo la strada.
Include guard nei file header
Gli include guard sono sempre usati all'interno dei file header per prevenire inclusioni multiple di un file header durante la compilazione di un singolo file .cpp. Le include guard sono implementate usando la direttiva del preprocessore #define controllando per vedere che un nome non sia già definito. Il nome usato per guard è basato sul nome del file header, con tutte le lettere del nome del file in maiuscolo e rimpiazzando il "." con un trattino di sottolineatura. Per esempio:
// IncludeGuard.h #ifndef INCLUDEGUARD_H #define INCLUDEGUARD_H // Il corpo dell'header qui... #endif // INCLUDEGUARD_H
L'indentificatore sull'ultima linea è incluso
per chiarezza. Sebbene alcuni preprocessori ignorino ogni carattere dopo un
#endif, ciò
non è un comportamento standard per questo l'identificatore è
commentato.
Uso dello spazio nei nomi
Nei file header, ogni violazione della spaziatura nei
nomi nel quale è inclusa l'header deve essere scrupolosamente evitata.
Cioè se si cambia la spaziatura fuori da una funzione o classe, si
avrà che quel cambiamento si ritroverà per ogni file che include
il nostro header, portando ad una serie di problemi. Nessuno dichiarazione
using di qualsiasi tipo è permessa fuori dalla definizione di
funzione, e nessuna direttiva using è permessa nei file header.
Nei file cpp, ogni direttiva using globale
riguarderà solo quel file, per cui in questo libro esse sono generalmente
usate per produrre codice più leggibile, specialmente in piccoli programmi.
Uso di require( ) e assure( )
Le funzioni require( ) e assure( )
definite in require.h sono usate in modo consistente in gran parte
del libro, per cui potrebbero riportare errori. Se si ha familiarità
con i concetti di precondizioni e postcondizioni (introdotti
da Bertrand Meyer) si riconoscerà che l'uso di require( )
e assure( ) più o meno fornisce precondizioni (tipicamente)
e postcondizioni (occasionalmente). Quindi all'inizio di una funzione, prima
che parte del "nucleo" della funzione sia eseguito, vengono verificate
le precondizioni per assicurarsi che ogni cosa sia giusta e che tutte le condizioni
necessarie siano corrette. Poi il "nucleo" della funzione viene
eseguito, e qualche volta vengono verificate alcune postcondizioni per essere
sicuri che il nuovo stato dei dati rientri tra i parametri definiti. Si noterà
che le verifiche delle postcondizioni sono are in questo libro, e assure( )
è principalmente usata per sincerarsi che i file siano aperti con successo.
[64]
Ibid.