Úvod Mapa webu Kontakty EN

Jan Ringoš, Tringi.TRIMCORE.cz

Vývoj a programování  »  Dokumenty a papery  » 

Standard pro zápis C++

Pravidla zápisu a poznámky ke stylu psaní kódu v jazyce C a odvozených. Dokument popisuje preferovaný způsob zápisu a organizaci kódu pro nově vyvíjené kódy. Popsány jsou především nejdůležitější koncepty, a tento dokument by měl sloužit také jako jakýsi úvod do problematiky profesionálního programování.

Upozornění: Tento dokument vychází ze standardu, který jsem dříve připravoval pro své pracoviště. Ve variantě, kterou prezentuji zde, ale v maximální možné míře odpovídá mému vkusu a mým osobním preferencím.

Odůvodnění, názory a osobní poznámky jsou psány stejným stylem jako tento odstavec a typicky méně formálním tónem.

Obecná vývojová doporučení

  • [A1] Kompilace bez varování při –Wall a -Wextra

    Kompilace zdrojového kódu proběhne bez varování na zvýšené úrovni kontroly. Pro kompilátory nevycházející z GCC se použije obdoba uvedených parametrů.

    Odůvodnění: Varování kompilátoru často odhalí nenápadné chyby. Planý poplach lze typicky odvrátit standardní syntaktickou úpravou, která je v kódu snadno rozpoznatelná a explicitně vyjadřuje, že se nejedná o chybu či překlep.

  • [A2] Nejméně jeden testovací program ke každé unitě

    Ke každému zdrojovému souboru nebo logickému celku musí existovat alespoň jeden testovací program, který reprezentuje jeho kontrakt. Ten by měl obsáhnout a ověřit co možná nejširší část funkce příslušného celku.

    Odůvodnění: Po provedení úpravy nebo interní změny v příslušné části musí překompilování a spuštění testovacího programu odhalit případné regrese.

  • [A3] Oddělení logických sad do zvlášť použitelných knihoven

    Je-li při vývoji vytvořen systém unit zajišťující určitou logicky separátní funkci, je tento od projektu oddělen v podobě samostatné knihovny, použitelné bez vazeb na původní projekt.

    Odůvodnění: Modularizace projektů usnadňuje oddělené ladění a umožňuje hotovu součást snadno použít v jiném projektu.

  • [A4] Idiomatické a standardní vzory a postupy

    Standardní knihovna jazyka C++ i další odborné publikace zmiňují ustálené způsoby aplikace jednotlivých vlastností jazyka, tzv. idiomy. Některé z nich jsou zmíněny i dále v tomto dokumentu. Viz [1] a [2]. Je vyžadováno aby se programátor těchto technik držel.

    Odůvodnění: Tyto spolehlivé techniky jsou mezi profesionály známé a zažité, tedy redukují čas potřebný novým programátorem k pochopení logiky a struktury kódu.

  • [A5] Svaté války ohledně GOTO, SE-SE, atd. nejsou dovoleny!

    V jazycích C a C++ lze mnoho věcí napsat více různými způsoby. Preferován je ten, který je pro daný problém nejjednodušší a především posléze nejčitelnější.

    Goto nebo return pro ukončení tří vnořených smyček je jednoduše lepší než sada bool proměnných a podmínek. Přeneste se přes to!

    Podobně existuje mnoho syntaktických drobností, jako např. zápis úvodní složené závorky, které jsou při zachování konzistentního odsazování ekvivalentně čitelné.

    Přesto autor preferuje K&R styl, tedy otevřená složená závorka hned za if, while, for, a při definici třídy, funkce nebo struktury, kde to při vyhledávání umožňuje okamžitě rozlišit zda se jedná o deklaraci nebo definici.

  • [A6] Striktní konzistence!

    V rámci projektu, přinejmenším pak knihovny (nebo unity, není-li součástí knihovny) je striktně dodržován styl původního autora projektu.

    To však neznamená, že psal-li původní autor jako prase, že budu psát také jako prase.

Konceptuální pravidla

  • [B1] Minimum globálních a sdílených dat

    Globální a veřejně sdílená data mohou být použity pouze v omezených případech, je-li pro jejich použití dostatečný argument.

    Odůvodnění: Globální data jsou přežitým způsobem komunikace mezi unitami, nedoporučují se jelikož neumožňují kontrolu a/nebo omezení přístupu.

    Netýká se lokálních (static nebo lokální namespace) a konstantních dat a také prostředků programu „okolo“ vstupního bodu (main).

  • [B2] Jedna entita má na starost jednu operaci

    Každá funkce, třída nebo šablona má jednu, dobře definovanou, funkci s jednoznačným výsledkem. Je-li k provedení operace zapotřebí dvou nebo více logicky odlišných činností, jsou tyto odděleny do separátních entit a původní funkce je přepsána na jejich základě.

    Odůvodnění: Klasický idiom, snižuje se cyklomatická složitost, zvyšuje přehlednost.

    http://en.wikipedia.org/wiki/Cyclomatic_complexity [cíl odkazu vede mimo tento web]

  • [B3] Žádný zbytečný nebo duplicitní kód

    Produkční sestavení zdrojových kódů projektu neobsahuje nepoužité nebo nepoužitelné větve kódu, nepoužité funkce, nadbytečné členské funkce tříd, třídy ani jiné jazykové konstrukty.

    Druhý výskyt kódu stejného kódu nebo kódu dostatečně podobného již existujícímu je vždy přepsán do obecné funkce nebo šablony a původní výskyty jsou nahrazeny příslušným voláním.

    Odůvodnění: Minimalizace nároků na správu kódu, minimalizace možnosti výskytu chyb, maximalizace přehlednosti, urychlení kompilace.

    Existuje-li předpoklad na rozšíření kódu o nějakou funkcionalitu, práce probíhají na separátní kopii kódu.

  • [B4] Skrytí implementace, PIMPL idiom

    U tříd implementujících složitější funkcionalitu, je pro data a privátní členské funkce použit tzv. PIMPL idiom, tedy skrytí za ukazatelem na vnořenou strukturu/třídu. Podobně, lze-li to v konkrétním případě použít, je konsolidována funkcionalita u šablon tříd. Viz [1].

    Odůvodnění: Mimo skrytí implementačních detailů vytváří kompilační bariéru, čímž umožňuje změnit implementaci bez nutnosti překompilovat všechny kódy které objekt používají.

    http://en.wikipedia.org/wiki/Opaque_pointer [cíl odkazu vede mimo tento web]

  • [B5] Inicializace je alokace, RAII idiom

    Prostředky pro provedení akce zprostředkovávané objektem jsou získány při inicializaci (v konstruktoru) a uvolněny v destruktoru. Nepodaří-li se prostředky získat, konstruktor je ukončen výjimkou. Viz [1].

    Odůvodnění: Existence objektu je vázána na dostupnost prostředků, pomocí kterých naplňuje svůj účel. Jedná se o klíčový koncept pro korektní správu zdrojů a pro psaní kódu bezpečného vůči výjimkám.

    http://en.wikipedia.org/wiki/RAII [cíl odkazu vede mimo tento web]

  • [B6] Destruktor a operace swap

    Operace swap je implementována technikami, které nevyvolají výjimku, viz [1]. Destruktor ani dealokační prostředky nesmí vyvolat výjimku, viz [2]. V případě chyby, je-li to nutné, je třeba použít alternativní mechanizmus hlášení.

    Odůvodnění: Nedodržení těchto podmínek může v krizové situaci způsobit náhlé ukončení programu. Viz [1], [2] a Standard C++.

    Pokud se nepodaří ani uvolnit paměť, pak je něco hodně špatně, např. poškozena struktura haldy. V takovém případě se typicky nezdaří ani žádný alternativní chybový mechanizmus použít a program dřív nebo později stejně neočekávaně havaruje.

  • [B7] Omezení funkcí na 200 LLOC a maximálně 7 parametrů

    Žádná z funkcí v projektu nepřesáhne svou délkou 200 logických řádků kódu. Počet parametrů žádné funkce nepřesáhne 7.

    Odůvodnění: Snížení složitosti a zvýšení přehlednosti kódu.

    Obvykle se doporučuje držet délku každé funkce tak, aby se vešla na obrazovku editoru, tj. cca 60 LLOC. Taktéž cyklomatická složitost by neměla překročit rozumnou úroveň.

  • [B8] Naturální sémantika a kanonická definice operátorů

    Přetížené operátory zůstávají ve shodě s původním matematickým nebo logickým významem. Operátory &&, || a , (čárka) nesmí být přetíženy. Není-li závažný důvod pro opak, operátory u kterých je to možné jsou definované kanonicky (např. operátor + lze implementovat ve smyslu operátoru +=, nebo opačně, operátor != lze implementovat pomocí operátoru ==, taktéž všechny operátory porovnání lze implementovat pomocí operátoru < (menší než).

    Odůvodnění: Přehlednost a pochopitelnost kódu. Už samotný akt přetížení zmíněných zakázaných operátorů mění zažitou sémantiku kódu, viz [1]. Pro kanonické definice platí stejná odůvodnění jako pro B3.

  • [B9] Zpracování všech potenciálních bodů selhání

    Každý možný bod selhání (funkce API vracející chybu, výjimka při konstrukci nebo alokaci, chybová odpověď volané funkce, neplatný vstup, …) je zajištěn a zotaven nebo předán vyšší úrovni v rozumné podobě.

    Odůvodnění: Selhat může téměř cokoliv. Program by měl dát administrátorovi dostatek informací aby problém vyřešil, a běžet do vyřešení problému v omezeném režimu, je-li to alespoň trochu možné.

    Viz známý zákon pojmenovaný dle kpt. Edwarda Aloysia Murphyho, Jr.

Komentování kódu

  • [C1] Nekomentuje se CO, ale PROČ!

    Je-li potřeba okomentovat řádek nebo blok kódu, komentuje se především důvod pro jeho přítomnost. Výstižný název třídy, funkce nebo parametrů je vždy lepší než komentář. Komentáře také neopakují kód, který komentují ani jeho větší části. Viz [2].

    Odůvodnění: Kvalitní kód nepotřebuje komentář aby vysvětlil co dělá nebo co by měl dělat. Důvod proč někde čte komentáře je aby zjistil proč tam ten řádek nebo blok vůbec je.

  • [C2] Žádné komentářové dekorace

    Nejsou přípustné žádné oddělovací čáry, rámečky ani jiné dekorace vytvářené pomocí komentářů.

    Odůvodnění: Jedná se o zastaralý způsob dekorování, který nenapomáhá přehlednosti, naopak působí rušivě, především při prohlížení kódu bez zvýraznění syntaxe. Pro více viz [1].

  • [C3] Vzorová hlavička zdrojového souboru

    Uvádí se hned za úvodní direktivu #include v případě souboru s implementací, v případě hlavičkového souboru pak hned za úvodní pár direktiv #ifndef - #define. Obsahuje informace o příslušnosti souboru, původní název, jméno autora a dostatečně univerzální kontakt, popis souboru, a seznam změn (changelog).

    Konkrétní syntaxe zápisu není podstatná. Je-li v týmu (nebo projektu) standardem používání dokumentačního nástroje, např. Doxygen, použije se samozřejmě odpovídající způsob komentování. #ifndef PROJEKT_SOUCAST_SOUBOR_HPP
    #define PROJEKT_SOUCAST_SOUBOR_HPP

    /* Nazev projektu - Delsi nazev soucasti – Funkcionalita
    // soubor.hpp
    //
    // Author: Aaa Bbbbbb, aaa.bbbbbb@firma.cz
    // Description: Conversion between UNICODE and ASCII strings
    //
    // Changelog:
    //     23.11.2007 - initial version
    //     06.12.2008 – modified to be included in this document :-)
    */
    ...

    Prvotní nástřel hlavičky, jak ji používám já, lze doplnit o další informace.

  • [C4] Komentář k funkci popisuje kontrakt

    Mimo zřejmých operátorů a dostatečně jednoduchých nebo standardně typizovaných funkcí (size, clone, …) je deklarace každé funkce, třídy nebo šablony komentována názvem a kontraktem. Tj. funkcí, popisem parametrů, možnými výsledky a chybovým chování.

    /* convert
    //  - converts a wide-character string to char string
    //  - parameters:
    //     - text - input string to convert
    //     - cs – code page identifier, optional
    //          - see MSDN: "Code Page Identifiers"
    //  - returns the resulting converted string
    */
    std::string convert (const std::wstring & text, unsigned int cs = 0);

    Komentář u definice funkcí se používá, je-li potřeba k upřesnění algoritmu nebo detailů implementace nezávislých na kontraktu.

    Odůvodnění: Moderní způsob komentování, zvyšuje přehlednost a usnadňuje komunikaci mezi vývojáři. Tzv. kontraktní programování.

    Detaily implementace jsou VŽDY nezávislé na kontraktu a vice versa.

  • [C5] TODO komentáře

    Je-li nezbytně nutné použít nedokončený kód do produkční verze projektu, všechny nedokončené části je třeba kvalifikovat TODO komentářem, který popisuje situaci, kterou je třeba programově vyřešit. Taktéž je nutné tyto komentáře zapsat do kódu, na kterém bude práce na více než jeden den přerušena.

        ...
    } else {
        // TODO: Provide fix-ups for out of range condition
    };

    Odůvodnění: TODO komentáře jsou nepsaným standardem, se kterými dokáže řada vývojových prostředí přímo pracovat.

  • [C6] Komentáře k prázdným blokům

    Smyčky, jejichž tělo neobsahuje žádný kód, jsou přípustné pouze je-li důvod pro tento stav zdokumentován komentářem.

    Konstrukce catch (...) stejně jako další prázdná těla (funkce, struktury, …) bez vysvětlujícího komentáře nejsou dovoleny.

    Odůvodnění: Přítomnost uvedených konstrukcí mnohdy indikuje chybu v sémantice nebo nedokonalost v designu algoritmu.

    Zda je komentář k prázdné smyčce v těle, nad smyčkou nebo vedle záleží na konkrétním případě a není třeba to předepisovat.

Preferovaná stylistika

  • [D1] Odsazování a délka řádků

    Každá úroveň vnoření bude odsazena o 4 znaky a vždy se bude jednat o mezery. Znaky tabulátor nejsou přípustné.

    Programátor je povinen nastavit své IDE tak aby nepoužívalo tabulátory a na klávesu TAB generovalo čtyři mezery. U všech rozumných editorů to jde, žádné že ne!

    Délka řádků nepřekračuje 100 znaků v případě C++. V případě ANSI C je pak tento limit 76 znaků.

    Úrovní vnoření je myšlen každý otevřený blok kódu, příkaz za/uvnitř příkazem větvení, nový řádek těla složeného výrazu a první a vnořená kvalifikace šablony. Novou úroveň vnoření uvozuje také návěští (včetně public/private/protected a case/default).

    Odůvodnění: Při délce řádků nad 100 znaků je čtení namáhavé a při návratu na začátek řádku oči snadno ztratí aktuální řádek. 76 znaků umožňuje zobrazení v editorech v textovém režimu bez nutnosti horizontálního posuvu.

    Ve skutečnosti nezáleží na počtu odsazujících mezer jako především na konzistenci odsazování.

  • [D2] Oddělování mezerami a členění prázdnými řádky

    Logické části algoritmu jsou od sebe oddělovány prázdnými řádky. Za každým ukončeným celkem (blok, smyčka, větvení, …) následuje prázdný řádek. Výjimkou je „tabulkování“ série syntakticky i logicky podobných příkazů, kde se naopak prázdné řádky nepoužívají.

    Mezi identifikátory a symboly se zapisují mezery. Nejsou vyžadovány u rozlišovacích operátorů, jako je tečka, čtyř-tečka ::, šipka -> a dále u unárních operátorů (!, *, & a další). Mezery dále nejsou vyžadovány po obou stranách závorky, před čárkami, před středníky a dalšími symboly reflektující větnou stavbu.

    if (a == 1)
        a = d->xyz + sizeof (std::ptrdiff_t) * (&e – &b);

    Odůvodnění: Vizuální oddělení souvisejících částí zvyšuje přehlednost, mezery mezi identifikátory a symboly usnadňují čitelnost. Tabulkování napomáhá rychlejšímu porozumění rozhodovacím hierarchiím.

  • [D3] Pojmenovávání

    Názvy identifikátorů musí být v první řadě výstižné a přímo nebo metaforicky pojmenovávat k čemu daný element slouží nebo o co se jedná. Z názvu musí být zřejmá funkce, nelze-li snadno zvolit vhodný název, musí být přítomen vysvětlující komentář. Důraz je kladen na nalezení vhodného jednoslovného identifikátoru, ale lze použít i více slov, ty je ale nutné vizuálně oddělit.

    1. Z názvu souborů příslušné komponenty je vždy zřejmé o jakou součást se jedná. ANSI C soubory mají příponu .c, hlavičkové soubory pak .h. Zdrojové soubory C++ mají příponu .cpp, hlavičkové pak .h nebo .hpp. Soubory s implementací šablony pak mají příponu .tcc.
    2. Entity jsou nazývány technickou angličtinou.
    3. Pro názvy tříd, funkcí a proměnných je doporučen zápis malými písmeny, slova oddělena podtržítkem. Je-li to jen trochu možné, pak jednoslovný. Tento způsob není povinný, pro nové knihovny nebo logické součásti programu lze použít i PascalCasing nebo camelCasing, dle preference programátora.
      Vždy je však závazný existující styl knihovny do které jsou prováděny změny!
    4. V případě členských funkcí je preferováno de-facto standardní pojmenování ve stylu STL kontejnerů a utilit. Neexistuje-li vhodný zažitý název, pak se pro triviální nastavovací, čtecí a rozhodovací funkce použije prefixů set_, get_ nebo is_.

      Tyto dvě věty si zčásti navzájem odporují, první má však prioritu. Např. test na prázdnost bude funkce .empty (), jelikož se jedná o standardní název, nikoliv is_empty.

    5. Názvy typových aliasů (typedef), lokálních a pomocných typů mají příponu _type.
    6. Názvy substitutů (typový parametr šablony nebo parametr makra) začínají velkým písmenem, případně podtržítkem a velkým písmenem.
    7. Makra preprocesoru jsou vždy POUZE_VELKÝMI_PÍSMENY, slova oddělena podtržítky.
    8. Maďarská notace není přípustná. Výjimkou jsou zásahy do kódu již psaném v tomto stylu, kde by zavedení jiného stylu vedlo k narušení konzistence.
    9. Méně důležité, pouze dopředu předávané parametry, nebo inicializační parametry konstruktoru mohou začínat podtržítkem.
    10. Není povoleno zavádět proměnné, třídy, apod. začínající dvěma podtržítky.
    11. Jednopísmenné identifikátory jsou povoleny pouze pro lokální proměnné, a to za dodržení typizovaného významu. Totéž platí pro zkratky. Odchylky oproti obecně zažitým významům je nutné vysvětlit komentářem. Pro definované typizované významy viz D4.
    12. Série jednopísmenných identifikátorů proměnných, tedy ‘a’, ‘b’, ‘c’, …až dle potřeby, jsou povoleny pouze jako homogenní parametry (stejného typu) pomocných výpočetních funkcí.

    Odůvodnění: Tento bod vychází ze zavedené praxe zdokumentované autory v referencích [1] a [2]. Identifikátory začínající dvěma podtržítky jsou vyhrazeny pro budoucí revize standardu C++ a pro interní funkce kompilátoru.

  • [D4] Typizované (obecně známé a zažité významy) identifikátory

    Tento bod je informativní. Následují popisy obecně známých a zažitých významů jednopísmenných a zkratkových identifikátorů. Je doporučeno se těchto držet.

    • a – alpha; hodnota alfa složky údaje o barvě, případně vizuální interpolace
    • b – blue; hodnota modré složky údaje o barvě (pouze ve spojení s ‚r‘ a ‚g‘, jinak slovem)
    • c – carry; příznak přetečení
    • e – epsilon, end iterator; rozdílová hodnota, iterátor konce rozsahu
    • f, fptr – function pointer; ukazatel na funkci, nebo soubor typu std::FILE *
    • g – green; hodnota zelené složky údaje o barvě (pouze ve spojení s ‚r‘ a ‚b‘, jinak slovem)
    • h – height; údaj o výšce (pouze ve spojení s ‚w‘, v opačném případě celým slovem)
    • i, j – iterator; pomocné iterátory nebo iterační proměnné (j – druhá vnořená úroveň)
    • id – identifier; obecný identifikátor, název typu
    • n – number, count; počet
    • p, ptr – pointer; pomocný ukazatel použitý jen krátce (např. pro iteraci)
    • pid – process identifier; identifikátor, číslo, procesu
    • r – red; hodnota červené složky údaje o barvě (pouze ve spojení s ‚g‘ a ‚b‘, jinak slovem)
    • rv, retval, result – return value; pomocná proměnná pro návratovou hodnotu funkce
    • t – timer, ticks; pomocná proměnná časovače, okamžitý rozdílový časovač
    • w – width; údaj o šířce (pouze ve spojení s ‚h‘, nebo x-y-z, v opačném případě slovem)
    • x, y – souřadnice
    • z – z-index, údaj o úrovni nebo třetí prostorové souřadnici

    Odůvodnění: Vždy je efektivnější využívat zažitých významů zkratek, především pro čtenáře zdrojového kódu.

    Tento seznam není kompletní …prakticky asi nikdy nebude.

  • [D5] Jeden příkaz nebo výraz na řádek

    Každý příkaz je vždy na samostatném řádku. Toto platí především pro definice a deklarace proměnných. Není přípustné deklarovat více proměnných na jednom řádku.

    Odůvodnění: Zvýšení přehlednosti kódu. Umožňuje jednodušší komentování je-li třeba.

  • [D6] Není třeba šetřit

    Jazyk C++ je poměrně volný co se týče syntakticky nadbytečných prvků. Tento dokument dále doporučuje následující:

    1. Středník za koncovou složenou závorkou ke zvýraznění závěru bloku.
    2. Redundantní return i na konci void-vracející funkce ke zdůraznění konce funkce.

    Odůvodnění: Zvýšení přehlednosti kódu, více znaků obklopeno mezerami je vždy viditelnější než jeden.

    K doplnění, pravděpodobně by se sem hodilo ještě několik drobností.

Mandatorní syntaktická pravidla

  • [E1] Digrafy a Trigrafy nejsou dovoleny

    Standard C++ umožňuje místo některých znaků použít tzv. digrafy (<:, <%, %:, …) nebo trigrafy (??=, ??/, ??<, …). Tyto nejsou dovoleny. Bez výjimek.

    Odůvodnění: Použití těchto konstrukcí výrazně znepřehledňuje kód.

    Je tohle pravidlo vůbec třeba?

  • [E2] Omezení použití preprocesoru

    Použití preprocesoru je omezeno pouze na standardní způsob hlídání proti opakovanému vkládání hlavičkových souborů (include), případně k nastavení vlastností knihoven operačního systému nebo třetích stran.

    Zcela zapovězena je pak direktiva #pragma a to především včetně nestandardní deklarace #pragma once.

    Preprocesor je dovoleno použít tam, kde by to jinak znamenalo duplikaci kódu, řetězců nebo nepřiměřené zvýšení složitosti programu.

    Odůvodnění: Preprocesor nerespektuje žádné aspekty kódu jako rozsah prostorů jmen a kolize definic může způsobit nečekané problémy.

    Ačkoliv populární, není direktiva #pragma once definována žádnou revizi C++ standardu, a kompilátory ji tedy mohou zcela ignorovat. Chování kompilátorů (a inteligentních IDE) pak nebývá jednotné co se týče symbolických odkazů, hardlinků souborů a hardlinků adresářů (ve Windows také junction pointů). A navíc, optimalizace tohoto typu jsou již dnes u moderních kompilátorů implementovány i pro dvojici #ifndef/#define.

  • [E3] Zapovězené funkce C knihovny

    Tyto funkce není dovoleno použít: setlocale, setjmp, longjmp, abort, exit, getenv, system.

    Je zapovězeno C makro offsetof, které je taktéž závislé na konkrétním kompilátoru. Dále není dovoleno používat chybový kód errno jinak, než pro odstínění pro platformu specifického chování vůči zbytku programu, a kód by měl být psán tak, aby se chyby obvykle pochycené tímto nevyskytovaly.

    Systémové signály (funkce signal) mohou být použity pouze hlavním řízením procesu.

    Odůvodnění: Zmíněné funkce typicky nemají přenositelné chování, jsou závislé na konkrétní implementaci a tudíž je jejich použití náchylné k chybám.

    Místo setjmp a longjmp je třeba použít C++ výjimky, abort a exit jsou zakázány protože pokud program neskončí opuštěním main, není garantováno volání destruktorů. Getenv, system a setlocale jsou funkce závislé na nastavení systému, programem typicky neovlivnitelné.

  • [E4] Magické konstanty

    Vyjma znakových literálů a konstant -1, 0 a 1 musí být všechny konstanty pojmenovány. Magická konstanta je dovolena, vyskytuje-li se v konkrétním významu v celém programu pouze jednou a je-li její účel, způsob a důvod zvolení dostatečně zdokumentován komentářem. Preferováno je static const, pro ANSI C je možné použít i definice preprocesoru.

    Odůvodnění: Volně se objevující číselná konstanta v kódu nedává čtenáři žádnou informaci ani pojítko, kde by tuto informaci nalezl. Obvykle stačí pouhé pojmenování výstižným názvem.

  • [E5] Minimum optimalizací, maximum vhodných technik

    Optimalizace se provádí až po získání důkazu že program nefunguje dostatečně efektivně, po vyprofilování úzkého hrdla, pouze zde a nejlépe volbou jiného algoritmu nebo struktury dat.

    Mikro-optimalizace nejsou dovoleny, viz [1] a [2].

    Výběr vhodné techniky jako prefixová inkrementace, preferování inicializace před přiřazením, vhodný výběr způsobu předávání parametru (referencí, hodnotou, ukazatelem), a další vhodné techniky popsané v [1] a [2], ačkoliv mnohdy považovány také za mikro-optimalizace, naopak jsou doporučovány.

    Odůvodnění: Schopnost rozlišit výhodu prefixových operátorů před postfixovým nebo kdy je vhodné předávat objekty referencí a kdy hodnou patří ke kultuře programování a nejedná se o optimalizace v pravém slova smyslu. Historické mikro-optimalizace u moderních, agresivně optimalizujících, kompilátorů naopak mnohdy znemožní použití jiných, silnějších optimalizací.

    Program, který funguje dobře pomalu je lepší než program který běží rychle ale špatně. Většina jednoduchých optimalizací dneska nemá smysl, optimizér ví o kódu daleko více než samotný programátor a dokáže optimalizovat daleko za představivost programátora. Mikro-optimalizace obvykle způsobí, že optimizér „nevidí“ pravý účel kódu, a jeho optimalizace pak nemůžou mít kýžený účinek.

  • [E6] Const-korektnost

    Parametry předávané ukazatelem nebo referencí, do kterých se nezapisuje jsou kvalifikovány const. Členské funkce (metody), které nemění z vnější pozorovatelný stav objektu jsou kvalifikovány const.

    Parametry funkcí předávané hodnotou naopak nejsou kvalifikovány const i když je tak s nimi nakládáno, přinejmenším v deklaraci funkce.

    Odůvodnění: Const-korektnost objektů zjednodušuje ochranu proti omylům a nechtěným změnám. Doporučováno jako prověřená a efektivní technika proti latentním chybám, viz [1]. U deklarací funkcí to naopak snižuje přehlednost kontraktu funkce.

  • [E7] Maximalizace lokality proměnných

    Proměnné jsou deklarovány v nejmenším možném kontextu ve kterém jsou použity, nejlépe až v okamžiku, kdy je možné je inicializovat. Není vyžadováno pokud by to snížilo čitelnost kódu, např. je-li několik podobných proměnných s podobnou funkcí deklarováno o úroveň výše.

    Odůvodnění: Maximalizace lokality proměnných zvyšuje přehlednost a snižuje náchylnost k chybám způsobených neinicializovanými proměnnými.

  • [E8] Polymorfizmus místo typových kódů

    Mimo prostředky pro komunikaci mimo rámec programu, není dovoleno rozhodování na základě typových kódů. Místo toho bude použito odvození od společného rozhraní.

    Odůvodnění: Využití prostředků jazyka, vyjasnění závislostí kódu a v neposlední řadě efektivita, kdy je virtuální dispatch u moderních kompilátorů mnohdy rychlejší než rozhodovací strom.

    Viz vaše oblíbená kniha o OOP.

  • [E9] Deklarace místo #include kde je to možné

    V hlavičkových souborech, kde je použita pouze reference nebo ukazatel na třídu, a kde tento ukazatel není operandem příkazu delete, bude použita dopředná deklarace třídy, místo plnohodnotného připojení příslušného hlavičkového souboru.

    Odůvodnění: Snižuje závislosti, urychluje kompilaci při zásahu do některého z hlavičkových souborů.

  • [E10] Omezené přetypovávání

    Klasické přetypování ve stylu jazyka C není pro C++ dovoleno, lze však použít jeho inicializační formu.

    reinterpret_cast je dovoleno pouze pro volání API systému, pro zobrazení a výjimečně pro přetypování mezi ukazateli na typy o velikosti 1 (tedy char, signed char a unsigned char).

    const_cast je dovoleno pouze pro volání const-nekorektního kódu mimo dosah programátora.

    static_cast nad polymorfními hierarchiemi je dovoleno pouze je-li jeho použití doplněno komentářem vysvětlujícím důvod.

    Odůvodnění: Přetypování pomocí reinterpret_cast je zcela závislé na implementaci kompilátoru a jeho chování se může neočekávaně změnit, dále jeho použití nad ukazateli na jiné než zmíněné typy porušuje standard C++ co se týče překrývání typů, což může vyústit v nesprávné chování.

  • [E11] Explicitní this kvalifikace

    Ačkoliv je redundantní, explicitní kvalifikace proměnných a členských funkcí třídy v jiných funkcích zvyšuje přehlednost kódu a snižuje šanci na mylné použití např. globální proměnné při překlepu.

    bool dataset::empty () const {
        return this->size () == 0;
    };
    std::size_t dataset::size () const {
        return this->allocated_size
             / this->item_size;
    };

    Odůvodnění: Napomáhá čitelnosti a orientaci v kódu. Preferován experty (např. vývojáři GCC, viz: http://gcc.gnu.org/wiki/CppConventions [cíl odkazu vede mimo tento web]) při konverzi projektů z C do C++. Čtenář okamžitě ví, zda funkce nebo data pochází z hierarchie dané třídy nebo se jedná o volnou funkci z vnějšku.

    Zmíněno i v [1]. Pravděpodobně kontraproduktivní není-li dodržováno konzistentně.

  • [E12] Using namespace pouze lokálně

    Konstrukce using namespace pro zavedení názvů všech entit daného prostoru jmen do aktuálního kontextu může být použita pouze lokálně v těle funkce, výjimečně na úrovni souboru s implementací, nikdy však v hlavičkovém souboru.

    Zvláště užití entit z prostoru jmen std musí být vždy explicitně kvalifikováno.

    Odůvodnění: Pravidla C++ pro vyhledávání symbolů jsou docela komplikovaná a volná injektáž názvů snadno způsobí nejednoznačnosti a s tím spojené problémy. V neposlední řadě se tímto pravidlem sníží kompilační náročnost projektu.


Referenční dokumenty

  1. Addison Wesley Professional, ISBN: 0-321-11358-6, October 25, 2004
    Herb Sutter, Andrei Alexandrescu: C++ Coding Standards: 101 Rules, Guidelines, and Best Practices
  2. NO STARCH PRESS, ISBN: 1-59327-119-0, December 2006
    Pete Goodliffe: Code Craft, The Practice of Writing Excellent Code
portfolio, knihovna programů, nástrojů a zajímavého software