Un’occhiata alla “Google C++ Style Guide” (seconda parte)…

Salve a tutti.

Riprendiamo il discorso da dove l’avevamo interrotto, ossia con la seconda e ultima parte di una piccola analisi che ho condotto sulla Google C++ Style Guide.

Sezione “Other C++ Features”

In questa sezione vengono presentate molte interessanti regola circa varie funzionalità del C++.

Regola: “Function Overloading. Use overloaded functions (including constructors) only in cases where input can be specified in different types that contain the same information. Do not use function overloading to simulate default function parameters.”

Nulla da eccepire. Se due costruttori differiscono per uno o comunque pochi parametri non fondamentali o che cambiano raramente, è consigliabile ricorrere a parametri con valori di default.

// due costruttori distinti
struct Test
{
      Test(int a) 
      {
            cout << a << endl;         
      }
      
      Test(int a, int b) 
      {
            cout << a*b << endl;         
      }   
};

Test a(1, 2); // richiamato il secondo costruttore

// un solo costruttore: dichiarando un parametro con un valore
// default, si gestiscono casi con 1 o 2 parametri
struct Test
{
      Test(int a, int b=1) 
      {
            cout << a*b << endl;         
      }   
};

Test a(1, 2);  // richiamato l'unico costruttore

Nota: parlando di costruttori, è bene ricordare che lo standard attuale del C++ non permette a un costruttore di invocarne direttamente un altro (“delegation“). La prossima edizione dello standard, C++0x, invece lo permetterà (cfr. documento).

Citando:

“C++ does not provide a mechanism by which one constructor can delegate to another.”

Regola: “Exceptions. We do not use C++ exceptions.”

La gestione delle eccezioni in C++ ha la triste nomea di peggiorare significativamente le prestazioni dei programmi, ma a livello progettuale le eccezioni non sono il male.

Parafrasando C++ FAQ Lite:

“What are some ways try / catch / throw can improve software quality?
[...] So compared to error reporting via return-codes and if, using try/catch/throw is likely to result in code that has fewer bugs, is less expensive to develop, and has faster time-to-market.[...]“

Un’altra delle critiche alle eccezioni, almeno per quanto riguarda la gestione in C++, è l’incapacità di rientro da un’eccezione. In termini più semplici, in C++ manca la clausola finally per il blocco try…catch.

C’è chi sostiene che il C++, adottando la “strategia” RAII (inventata fra l’altro proprio dal padre di questo linguaggio, Stroustrup), in qualche modo mitighi gli effetti dell’assenza della finally, ma temo non basti (sempre).

Per capirci, ricordiamo cos’è la RAII (da Wikipedia):

“(RAII) ensures that when resources are acquired they are properly released by tying them to the lifespan of suitable objects: resources are acquired during the initialization of objects, when there is no chance of using them before the resource is available, and released with the destruction of the same objects, which is guaranteed to take place even in case of errors.”

Mancando il blocco finally, si sostiene quindi che, se l’approccio RAII viene seguito scrupolosamente, gli eventuali oggetti verranno comunque correttamente distrutti e le relative risorse liberate.

Sempre da Wikipedia:

“In C++, a resource acquisition is initialization technique can be used to clean up resources in exceptional situations.”

Il problema è che ci sono eccezioni ed eccezioni… Alcune sono critiche/”mortali”, altre gestibili e “recuperabili”. In altre parole, corretta la condizione che ha lanciato l’oggetto-eccezione, si potrebbe decidere di voler riprendere da dove si era verificato il problema. Sfortunatamente senza il blocco finally (o qualcosa del genere) non si riesce a tornare facilmente sui propri passi.

Nota: le eccezioni sono condizioni speciali che non dovrebbero mai verificarsi (si spera). Quindi, almeno sulla carta, non è giustificabile se non nei termini di “praticità” la possibilità di rientrare dalle eccezioni. In altri termini, data la natura delle eccezioni (che non sono errori!) non si può denigrare il C++ per la mancanza dell’istruzione finally.

Più propriamente (da Wikipedia):

“In questo contesto, un’eccezione è un meccanismo per interrompere l’esecuzione di un sottoprogramma e propagare l’errore ad un livello più alto del programma stesso. Figurativamente, l’eccezione viene generata in basso, e trappata, o catturata più in alto.”

Regola: “Run-Time Type Information (RTTI). We do not use Run Time Type Information (RTTI).”

Con il termine RTTI si intende una feature-meccanismo del C++ che permette di mantenere in memoria e a runtime informazioni (metadati) sui dati.

Per quanto questa funzionalità talvolta sia vantaggiosa e utile (es: per alcuni tipi di cast), lasciarla abilitata quando non serve può comportare un overhead.

E’ interessante osservare la nota relativa al flag G++ per disabilitare RTTI, -fno-rtti, nel manuale del compilatore:

“Disable generation of information about every class with virtual functions for use by the C++ runtime type identification features (`dynamic_cast’ and `typeid’). If you don’t use those parts of the language, you can save some space by using this flag. Note that exception handling uses the same information, but it will generate it as needed. The `dynamic_cast’ operator can still be used for casts that do not require runtime type information, i.e. casts to void * or to unambiguous base classes.”

Regola: “Casting. Use C++ casts like static_cast(). Do not use other cast formats like int y = (int)x; or int y = int(x);.”

Nulla da eccepire: il cast “vecchio stile” talvolta è rischioso.

Citando l’apposita pagina su cplusplus.com:

“Traditional explicit type-casting allows to convert any pointer into any other pointer type, independently of the types they point to. The subsequent call to member result will produce either a run-time error or a unexpected result.”

Regola: “Use of const. We strongly recommend that you use const whenever it makes sense to do so.”

Concordo, anche se generalmente dimentico sempre qualche punto in cui metterlo. :D

Regola: “Preprocessor Macros. Be very cautious with macros. Prefer inline functions, enums, and const variables to macros.”

Lo confesso: fino a qualche anno fa ero fissato col preprocessore, almeno fino a quando il nostro prof-guru Fiorentini ci illustrò nel corso “C per supereoi” i rischi connessi all’abuso delle macro.

In particolare il problema dell’uso delle macro sta nello scarso controllo sui tipi, sugli incrementi, sulle conversioni, … La macro è qualcosa che viene espansa ma i comportamenti non sempre sono quelli attesi. Sono da usare con attenzione.

Sempre dalle C++ FAQ Lite (riportano anche un esempio chiaro del problema):

“Unlike #define macros, inline functions avoid infamous macro errors since inline functions always evaluate every argument exactly once. In other words, invoking an inline function is semantically just like invoking a regular function, only faster.
[...]
Also unlike macros, argument types are checked, and necessary conversions are performed correctly.”

Regola: “sizeof. Use sizeof(varname) instead of sizeof(type) whenever possible.”

Corretto, anche se spesso me lo dimentico. :)

Citando due risposte all’eccellente domanda “‘C’ sizeof with a type or variable” su StackOverflow:

1) “If the type of the variable is changed, the sizeof will not require changing if the variable is the argument, rather than the type.”

2) “[...] let me add that sizeof does not evaluate its operand. So you are free to do anything in it. Not only you can use not-yet initialized variables, but you can dereference a null-pointer, call functions not defined but only declared and do any other kind of stuff. I encourage you to always use the expression version for reasons Steve explained greatly.

Also consider that sometimes typenames are really long and unreadable, just think of pointers to functions (especially in C++). Instead of writing sizeof(my_long_type) you just do sizeof t.”

Regola: “Boost. Use only approved libraries from the Boost library collection.”

Anche qui, nulla da eccepire: le Boost C++ Libraries sono molto rinomate per le interessanti feature, per la loro qualità ed affidabilità intrinseca, per l’essere opensource e multipiattaforma, ecc… Sono così pregevoli qualitativamente che molti dei loro “contributor” ora sono membri del comitato ISO che gestisce lo Standard C++. Inoltre le librerie stesse sono state prese come base per il prossimo standard C++0x.

Sezione “Naming”

In questa sezione vengono presentate regole che riguardano i nomi di variabili, costanti, funzioni, ecc…

Regola: “File Names. Filenames should be all lowercase and can include underscores (_) or dashes (-). Follow the convention that your project uses.”

Nulla da eccepire: scrivendo tutto in minuscolo non si hanno problemi di portabilità ed uso su filesystem con differente “case sensivity“. Il ricorso a underscore e dash al posto degli spazi (whitespaces) nei nomi permette di evitare alcuni (ormai teorici) problemi di gestione degli spazi su varie piattaforme.

Ad esempio, da Wikipedia:

“Note 1: Most Unix shells require certain characters such as spaces, , |, \, and sometimes :, (, ), &, ;, as well as wildcards such as ? and *, to be quoted or escaped”

Regola: “Type Names. Type names start with a capital letter and have a capital letter for each new word, with no underscores: MyExcitingClass, MyExcitingEnum.”

Nulla da eccepire: camel case con la prima lettera maiuscola (“UpperCamelCase “).

Regola: “Variable Names. Variable names are all lowercase, with underscores between words. Class member variables have trailing underscores. For instance: my_exciting_local_variable, my_exciting_member_variable_.”

Usando un IDE “moderno” aggiungere un prefisso o un postfisso ai nomi delle varibili diventa un orpello/pleonasmo e nulla più. Ciononostante anch’io preferisco aggiungere un underscore postfisso al nome delle variabili-membro proprio per rimarcarne il significato. Esistono svariate forme alternative, come il più intuitivo ma anche prolisso prefisso m_ (cfr. post di Stefano sul suo blog).

Non mi trovo invece d’accordo sull’usare gli underscore nei nomi delle varibili, considerando poi la regola precedente.

// seguendo Google...
MyClass simple_test_object;

// come farei io...
MyClass simpleTestExample;

In questo caso utilizzo nuovamente il camel case ma con la prima lettera minuscola (“lowerCamelCase“) secondo lo stile normalmente adottato per il codice Java.

Regola: “Constant Names. Use a k followed by mixed case: kDaysInAWeek.”

Personalmente resto fedele al vecchio stile C ossia nomi tutti in maiuscolo e con gli underscore. Aldilà delle reminiscenze del C, preferisco tenere ben distinti i nomi della variabili da quelli delle costanti.

#define THIS_IS_A_CONSTANT 0xDEAFBEEF // o...
const long THIS_IS_A_CONSTANT = 0xDEAFBEEF;

Ciononostante, se preferire una coerenza 100%, il prefisso “k” non è una cattiva idea.

Regola: “Function Names. Regular functions have mixed case; accessors and mutators match the name of the variable: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().”

Questa è una non regola.

Oltre a a quanto detto (di male) in precedenza sull’asimmetricità fra accessori e mutatori, questa regola propone un’assoluta incoerenza di fondo: per le funzioni si usa l’UpperCamelCase (e fin qui, va bene), ma per le funzioni accessori/mutatori si passa a minuscole con underscore. No grazie…

Regola: “Enumerator Names. Enumerators should be all uppercase with underscores between words: MY_EXCITING_ENUM_VALUE.”

In questo caso si va a gusti. Personalmente, visto che il nome del tipo enumerato non viene di fatto usato (unscoped), uso le minuscole per l’enum e le maiuscole per i valori-costanti “all’interno”.

Dal mio esempio di Object Chaining:

enum cend{ END_CHAIN = 0xDEADBEEF };

Da Wikipedia:

“C++ has enumeration types that are directly inherited from C’s and work mostly like these, except that an enumeration is a real type in C++, so the “enum” keyword is only used when declaring the enumeration, but the name of the enumeration properly refers to the type thereafter.”

Si noti che il futuro standard C++0x introdurrà le “enum class/enum struct“, che saranno “scoped”, ossia si dovrà specificare l’identificatore e non ammetteranno conversioni implicite.

Dall’ultimo draft noto del C++0x:

//Note that this implicit enum to int conversion 
//is not provided for a scoped enumeration: 
enum class Col { red, yellow, green }; 
int x = Col::red; // error: no Col to int conversion 
Col y = Col::red; 
if (y) { } // error: no Col to bool conversion 

Regola: “Macro Names. You’re not really going to define a macro, are you? If you do, they’re like this: MY_MACRO_THAT_SCARES_SMALL_CHILDREN.”

Ahahahah… Geniale questa regola… :D

Sezione “Comments”

In questa sezione vengono presentate regole relative a come scrivere commenti.

Regola: “Comment Style. Use either the // or /* */ syntax, as long as you are consistent.”

Concordo. Per praticità uso la sintassi multilinea /* */ per i commenti che superano le 3 righe; inoltre la uso, secondo lo stile Javadoc (supportato anche dall’ottimo Doxygen), per i commenti che poi finiranno nella documentazione.

/** 
 * ...
 */ 
class MyClass {};

Regola: “File Comments. Start each file with a copyright notice, followed by a description of the contents of the file.”

Anche qui concordo e, come mio solito, estendo.

Per i file sorgente (.cpp), uso un formato di header esteso:

/**
 *	FILE     : ...
 *	AUTHOR   : ...
 *	LICENSE  : ...
 *	COMPILE  : ...
 *	PURPOSE  : ...
 *
 *	TESTED ON:
 *	- ...
 *	- ...
 *	- ...
 *	...
 *
 *	REFERENCES:
 *	[1]: ...
 *	[2]: ...
 *	... 
 *	[n]: ...
 */

Per i file header (.h), uso un formato più compatto, che rimanda al file sorgente corrispondente:

/**
 *	FILE     : ...
 *	AUTHOR   : ...
 *	LICENSE  : ...
 *	COMPILE  : ...
 *
 *  NOTE: for more info, please see file ...
 */

Regola: “Punctuation, Spelling and Grammar. Pay attention to punctuation, spelling, and grammar; it is easier to read well-written comments than badly written ones.”

Vero! Quando trovo un commento con degli errori anche banali (es: typo), finisce che la mia attenzione spesso si ferma sull’errore e poi mi tocca rileggere il commento dall’inizio.

Regola: “TODO Comments. Use TODO comments for code that is temporary, a short-term solution, or good-enough but not perfect.”

TODO è uno dei tag di commento speciali, usati come segnaposto per ricordare qualcosa.

Molti IDE moderni riconoscono questi tag e, opportunamente istruiti, sono in grado di evidenziarli nel codice e/o ricordarli agevolmente al programmatore. Ne esistono altri come HACK, FIXME, …

Inutile dire che sono tanto comodi quanto opportuni da usare per tenere sempre presenti le porzioni di codice insicuro che vanno sistemate as soon as possibile.

Sezione “Formatting”

In questa sezione vengono prese le regole di formattazione.

Regola: “Line Length. Each line of text in your code should be at most 80 characters long.”

Ottimo in ottica di stampa o pubblicazione web, particolarmente triste considerando che siamo nel 2009… Diciamo che personalmente mi impongo un limite di 120 massimo, ma molto spesso resto ancorato alle 80 colonne…

Regola: “Spaces vs. Tabs. Use only spaces, and indent 2 spaces at a time.”

Totale disaccordo. Viva i tab! Che rabbia quando vedo il codice indentato a spazi e si finisce sempre con pezzi indentati a caso…

Se e quando serve la conversione tab->spazi va fatta automaticamente da un IDE e solo per l’eventuale stampa.

Aggiungo che molti IDE decenti ora hanno opzioni per il “rendering” dei tab, se proprio occorre: un tab appare cioè come l’equivalente di n spazi ma intrinsecamente rimane un tab.

Capisco che in Google si usi molto codice Python (tanto che l’autore di questo linguaggio, Guido van Rossum ora è un dipendente) ma c’è un limite…

Regola: “Function Declarations and Definitions. Return type on the same line as function name, parameters on the same line if they fit.”

Nulla da eccepire.

Non ho mai capito i fissati del “tipo-di-ritorno-sulla-riga-precedente“:

int // <= che senso ha?
getValue() { ... }

Quanto a spezzare su più linee i parametri, se sforano le n colonne, nulla da eccepire.

Regola: “Function Calls. On one line if it fits; otherwise, wrap arguments at the parenthesis.”

Nulla da eccepire. Se gli argomenti di una funzione superano il numero delle colonne prescelte, è bene andare a capo con gli argomenti, cercando di mantenere una certa leggibilità.

Regola: “Conditionals. Prefer no spaces inside parentheses. The else keyword belongs on a new line.”

Concordo al 100%.

int myFunc( int a, int b ) <= a cosa che servono gli spazi nella parentesi?
{
	// ... 
}

Regola: “Return Values. Do not surround the return expression with parentheses.”

Concordo al 100%.

int getValue() 
{
	return (value_);  // <= che senso hanno le parentesi in questi casi?
}

Regola: “Preprocessor Directives. Preprocessor directives should not be indented but should instead start at the beginning of the line.”

Su questo mi trovo in disaccordo… col mondo: sono solito indentare le direttive.

Detesto vedere gli #ifdef a inizio riga quando poi il codice incluso è altrove…

// secondo Google...
class Test
{
	private: 
		int value_;
		
	public:
		int getValue() 
		{
#ifdef DEBUG // <= orrendo!
			// fai qualcosa
#endif

			return value_;
		}
		
		// ...
};

// come farei io...
class Test
{
	private: 
		int value_;
		
	public:
		int getValue() 
		{
			#ifdef DEBUG // <= è pur sempre un if (e io lo indento)
				// fai qualcosa
			#endif
			
			return value_;
		}
		
		// ...
};

Regola: “Class Format. Sections in public, protected and private order, each indented one space.”

Non ritorno sull’ordine delle sezioni nelle classi quanto piuttosto sul fatto che si indenti di uno spazio. Fosse stato due spazi, almeno avrei apprezzato la coerenza rispetto alla policy dei “due spazi”… Inoltre in questo modo bisogna ricordarsi di due regole al posto di una sola (soluzione incoerente e inefficiente, IMHO)…

// secondo Google...
class Test
{
 public: // sezioni indentate di un singolo spazio
   int a; // ... il resto di due

   void MyPublicMethod()
   {
     cout << "MyPublicMethod() << endl;
   }

 protected:
   int b;

   void MyProtectedMethod() {}	
	   
 private:
   int c;

   void MyPrivateMethod() {}
};

// come farei io...
class Test
{
	public: // sempre 1 tab, ovunque (ho mantenuto l'ordine delle sezioni)
		int a;

		void MyPublicMethod() 
		{
			cout << "MyPublicMethod() << endl;
		}
   
	protected:
		int b;

		void MyProtectedMethod() {}	
	   
	private:
		int c;

		void MyPrivateMethod() {}
};

Regola: “Initializer Lists. Constructor initializer lists can be all on one line or with subsequent lines indented four spaces.”

Premesso che ho sempre odiato il formato delle liste di inizializzazione, di solito le inserisco sulla stessa riga dove si apre la classe/struttura.

Altra regola, altra indentazione: stavolta 4 spazi. Comincio a pensare che ci siano troppe regole… :D

Regola: “Namespace Formatting. The contents of namespaces are not indented.”

Apprezzo lo sforzo di mantenere compatto il codice ma anche in questo caso resto fedele alla mia prassi e quindi tendo a indentare, usando tab, anche il contenuto dei namespace.

In altre parole io indento l’indentabile: namespace, classi/strutture, varibili e funzioni membro, …

Ne faccio un discorso estetico e funzionale: estetico perchè tutto appare uniforme, senza rientri “variabili” a seconda del contesto; funzionale perchè non si devono tenere in mente troppe regole ma solo una.

Sezione “Exceptions to the Rules”

In questa sezione vengono presentate le eccezioni ammissibili alle regole contenute nella guida..

Regola: “Existing Non-conformant Code. You may diverge from the rules when dealing with code that does not conform to this style guide.”

Spesso si ha a che fare con codice “legacy” quindi è naturale doversi conformare a quanto già detto e scritto: nessuna obiezioni.

Regola: “Windows Code. Windows programmers have developed their own set of coding conventions, mainly derived from the conventions in Windows headers and other Microsoft code. We want to make it easy for anyone to understand your code, so we have a single set of guidelines for everyone writing C++ on any platform.”

Notevole osservare come Windows riceva un trattamento “di favore” nonostante questa guida sia per sua natura “platform agnostic”…

Conclusioni!

Beh, direi che con questo concludo la mia mini-analisi della guida di Google.

Noterete che certe tematiche come la “stile di indentazione” non sono state discusse perchè non presenti in questo documento: ne riparleremo più avanti. Posso assicurarvelo. :D

Ciau! ^^

Contrassegnato da tag , , , ,

4 thoughts on “Un’occhiata alla “Google C++ Style Guide” (seconda parte)…

  1. NeXuS scrive:

    Come al solito bel lavoro, io pero’ mi trovo in disaccordo su qualcosa (sia con te, che con le linee guida di Google!).

    Spazi tra le parentesi di una chiamata/definizione di funzione:

    int myFunc( int a, int b ) // <= a cosa che servono gli spazi nella parentesi?
    

    A me servono per migliorare la leggibilita’. So che in tipografia le parentesi non si usano, ma qui non stiamo scrivendo codice per poi pubblicarlo su un libro (e nel caso sed/grep et similia aiutano a sistemare questo genere di errori). Personalmente trovo molto piu’ pulito e leggibile f( int a ) che non f(int a).

    Inoltre non capisco perche’ dici che Windows abbia un trattamento di favore. Da quello che ho capito io, il significato della frase e’: “mi spiace che tu abbia un modo tutto tuo (Microsoft) di scrivere il codice C++, ma noi usiamo uno ed un solo stile indipendentemente dalla piattaforma”.

    Magari sbaglio.

  2. jp scrive:

    1) Io invece lo trovo fastidioso, specie con gli IDE moderni che evidenziano il tipo con un colore diverso dal resto. De gustibus:D

    2) Il senso delle mie parole era fra l’ironico e il rassegnato (avevo messo le virgolette apposta ma rileggendolo mi accorgo che la cosa non è affatto chiara – sorry -)… ^^’

  3. NeXuS scrive:

    Un’altra nota. Se trovo comodi per la lettura gli spazi tra le parentesi, odio invece gli spazi tra il nome di funzione e la lista di parametri. Una cosa del tipo:

    funzione   (type a, type b, ...);
    

    Trovo che renda molto complicato capire che si tratta di una funzione.

    Inoltre mi piace avere i parametri separati da uno spazio (che pure non sarebbe necessario. Ovvero non mi piace una cosa cosi’:

    funzione(type a,type b,type c);
    
  4. jp scrive:

    Nulla da eccepire su questo, Max. :D

Lascia un Commento

Fill in your details below or click an icon to log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Log Out / Modifica )

Foto Twitter

You are commenting using your Twitter account. Log Out / Modifica )

Foto di Facebook

You are commenting using your Facebook account. Log Out / Modifica )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 259 other followers