Konvence pro programování v jazyku C

Konvence jsou při programování (v jazyku C) chápány jako soubor pravidel či doporučení, který firma předepisuje svým programátorům jakožto povinné. Důvodem k tomu je usnadnit či dokonce umožnit, aby někdo mohl pokračovat v práci udělané někým jiným – ať už to znamená dopracovat, odstranit chyby či optimalizovat. Většinou se má na mysli

  • dokumentace (včetně komentářů),
  • způsob typografického odlišovaní různých entit či úrovní zanoření programových struktur,
  • pojmenovávací a jazyková pravidla, či omezení znakových sad.

Následuje shrnutí konvencí, které se budou používat na cvičeních PB071. Kontrola jejich dodržování nebude striktní, nicméně se snažte, aby byl Váš kód přehledný a formátování konzistentní (můžete třeba psát závorky v domácím úkolu jinak než je uvedeno zde, ale pak dodržujte stejný způsob v celém svém řešení).

Názvy identifikátorů

Identifikátory jsou názvy funkcí, proměnných, struktur atd. Používejte anglický jazyk a snažte se, aby název identifikátoru odpovídal jeho účelu.

identifikátor název
funkce, proměnné začínají vždy malým písmenem; pokud je navíc název složen z více slov, používejte camelCase, nikoliv underscore_case
struktury a výčtové typy (enum) stejně jako funkce a proměnné, ale navíc při definici synonyma pomocí typedef může synonymum končit na _t
položky struktury stejně jako proměnné
položky výčtového typu jako proměnné, ale začínají velkým písmenem (CamelCase)
pojmenované konstanty všechna písmena velká, slova oddělena _

Poznámka: dle standardu C99 jsou názvy začínající _ a velkým písmenem nebo dalším _ rezervované (tj. __linux__ nebo _M_node), nicméně vyhýbejte se taky názvům začínajícím _ a malým písmenem (_ptr).

Ukázka:

    /** TAKHLE ANO **/
    typedef struct node
    {
        struct node *next;
        int value;
    } node_t;
 
    typedef enum mode
    {
        Execute = 1,
        Write   = 2,
        Read    = 4
    } mode_t;
 
    int         position;
    size_t      sizeOfArray;
    const char  DELIMITER = ':';
 
    /** TAKHLE NE */
    typedef enum flags
    {
        AUTO_CLOSE_FILES = 1,
        VERBOSE          = 2,
        // ...
    } flags_t;
 
    int  ARGUMENT, Argument, ArGuMeNt; // --> argument
    char last_char;                    // --> lastChar
    void __do_something() ...          // --> doSomething()
    int  pom;                          // --> ne nutně špatně, ale raději temp

Formátování zdrojového kódu

Délka každého řádku by měla být nanejvýš 80 znaků. Tento limit není striktní, nicméně pokud by měl řádek „přetéct za okraj obrazovky“, je vhodné ho raději rozdělit (viz níže).

Na každém řádku píšte nejvýše jeden výraz nebo řídící strukturu.

Psaní blokových závorek

Závorky pište podle K&R style. V dalším textu se odsazením myslí odsazení +4 mezery doprava vzhledem k odsazení v aktuálním bloku kódu, bez odsazení znamená, že se použije stejné odsazení jako v původním bloku. Následující tabulka obsahuje shrnutí formátování, kde ␣␣␣␣ znázorňuje odsazení.

co popis příklad
funkce a struktury * otevírací a uzavírací bloková závorka na novém řádku bez odsazení,
* tělo je odsazené
int factorial(int x)
{
␣␣␣␣//...
}
řídící struktury (obecně) * otevírací bloková závorka na stejném řádku,
* tělo začíná samostatným řádkem a je odsazené,
* uzavírací bloková závorka na samostatném řádku bez odsazení
while (condition) {
␣␣␣␣// ...
}
else if, else* else if i else můžou začít na stejném řádku jako ukončovací závorka předchozího bloku if (...) {
␣␣␣␣// ...
} else if (...) {
␣␣␣␣// ...
} else {
␣␣␣␣// ...
}
do ... while * stejně jako výše, while může být za ukončovací blokovou závorkou do {
␣␣␣␣// ...
} while (condition);
switch * case a default mají stejné odsazení jako switch,
* těla větví jsou na samostatných řádcích s odsazením
* těla větví nejsou v blokových závorkách
switch (cardinal) {
case 0:
␣␣␣␣// ...
␣␣␣␣break;
default:
␣␣␣␣// ...
}

Blokové závorky používejte na vyznačených místech i v případě, že blok obsahuje pouze jediný příkaz!

Psaní mezer

Při psaní mezer dodržujte běžné typografické konvence, tedy mezery (značené ) pište

kde ukázka
kolem závorek
* kromě funkcí
* uvnitř závorek
if␣(a < b)␣{
* factorial(42);
* (a < b)
kolem binárních operátorů x␣+=␣2;
za čárkou printf(str,␣a,␣b,␣c);
za středníkem ve for for␣(int i = 0;␣i < 10;␣++i) {

Naopak, mezery se nepíšou

před otevírací závorkou seznamu argumentů
před ukončovací závorkou
factorial(15);
před indexovací závorkou
před čárkou
matrix[1,␣2];

Ukázky viz níže.

Speciálně při deklaraci ukazatele je mezera mezi typem a *, ale ne mezi * a identifikátorem (proč?) , tedy:

    int*ptr;   // ANO
    int*␣ptr;   // NE
    int*ptr;    // NE
    int*␣ptr;  // NE

Rozdělení dlouhého příkazu

Místo, na kterém rozdělit dlouhý příkaz, se těžko určuje obecně. Nejlépe je příkaz rozdělit tak, aby každý řádek obsahoval sémanticky významný celek. Vhodná místa k rozdělení jsou často za čárkou v argumentech volání funkce nebo před operátorem složeného výrazu. Další řádky se odsadí o 8 znaků nebo tak, aby bylo zřejmé, že jde o pokračování prvního řádku.

    if (condition1 && condition2 && condition3
            // toto je odsazeno o 8 znaků, protože je to pokračování podmínky
            && condition4 && condition5
            && condition6 && condition7) {
        // toto je odsazeno pouze o 4 znaky aby bylo zřejmé,
        // že začíná tělo podmínky
        statement;
    }
 
    doStuff(argument1, argument2, argument3,
            argument4, argument5, argument6);

Ukázka formátování - jak psát závorky a mezery

typedef struct node
{
    struct node *left;
    struct node *right;
    void        *value;
} node_t;
 
int main(int argc, char **argv)         // mezera za , a před *
{
    while (player->health > 0) {        // mezery kolem operátoru
        search(player, map);            // pište { } i kolem jediného příkazu
    }
 
    if (temperature < 80) {
        printf("temperature is OK\n");
    } else if (temperature < 1000) {
        printf("a bit too hot\n");
    } else {
        printf("MELTDOWN\n");
        system("rm -rf /");             // nezkoušet doma ;)
    }
 
    switch (it->type) {
    case BIRD:
        printf("It's a bird!\n");
        break;
    case PLANE:
        printf("It's a plane!\n");
        break;
    default:
        printf("It's a birdplane!\n");
    }
 
    do {
        printf("Enter zero: ");
        scanf("%d", &x);
    } while (x != 0);                       // while může pokračovat za blokovou závorkou
 
    for (int i = 0; i < sizeOfArray; ++i) { // mezery za ;
        printf("%d -> %d\n", i, array[i]);
    }
}

Příklady, které konvence porušují:

    typedef enum answer {
        YES,
        NO,
        MAYBE,
        I_DONT_KNOW
    } answer_t;
 
    if (isStrange(something) && inNeighbourhood(something))
        call(ghostbusters);     // chybí blokové závorky
 
    while(problems>0)           // chybí mezery
 
    if(condition){              // chybí mezera " (" a ") "
    if ( condition ) {          // mezery navíc "( " a " )"
 
    array [index];              // mezera navíc " ["
 
    printf ("%d", 25);          // mezera " (" tady být nemá
    printf("%d",25);            // chybí mezera za čárkou ", "
    printf("%d" , 25);          // tohle se taky občas vyskytuje v úkolech

Pravidla pro konverze

Dodatečná pravidla pro zlepšení čitelnosti kódu.

  • pokud konstanta v programu má reprezentovat znak, zapište ji jako znakovou konstantu, nikoliv číslo:
int c = getchar();
if (c == '\n') {           // ANO
    // ...
} else if (c == 97) {      // NE
    // ...
}
  • nepište do kódu magická čísla, ale používejte pro ně pojmenované konstanty; výjimkou jsou čísla se zřejmým významem, např. circ = 2 * PI * r,
  • test nulové hodnoty pište jako test hodnoty proměnné, nikoliv porovnání hodnoty s nulou (platí pro if, for i while):
ANO NE
if (chr) { if (chr != NULL) {
if (!num) { if (num == 0) {

ale if (*chr != '\0') je správně, protože test kontroluje znakovou konstantu.

Stále platné zásady efektivního programování

Dobří programátoři a vývojáři aplikací se drží několika prověřených zásad tvorby zdrojového kódu. Tyto principy provázejí programování už několik desítek let, přesto jsou dodnes platné a uznávané.

  • Neduplikujte kód. Uložení a sdílení částí kódu ve společných metodách je mnohem čistší a efektivnější řešení než jejich kopírování a vkládání na různá místa ve zdrojovém kódu.
  • Vytvářejte kód, který bude snadno čitelný a pochopitelný. Jeho struktura by měla být konzistentní s ostatními částmi systému nebo aplikace.
  • Dokumentujte hodnoty konstant. Konstanty objevující se zázračně v kódu bez vysvětlení kontextu zatemňují strukturu a myšlenku softwaru.
  • Volte dostatečně vysvětlující názvy proměnných. Názorné názvy usnadní orientaci v kódu, a pokud jsou doplněné o dostatečně informativní komentáře, umožní i dalším vývojářům snadno se zapojit do práce na kódu.
  • Při pojmenování konstant, proměnných, funkcí a dalších prvků kódu buďte konzistentní. Pokud zavedete do názvů určitou strukturu (nejlépe odpovídající obecným zvyklostem), držte se jí v celém kódu.
  • Udržujte jednotlivé funkce malé a jednoznačné. Příliš velký záběr znepřehledňuje kód a znejasňuje účel, ke kterému má daná funkce opravdu sloužit v případě, že nefunguje tak, jak bylo zamýšleno.
  • Vytvářejte kód s ohledem na výkonnost. Při kódování využívejte profilační a diagnostické aplikace, které upozorní na možná zpomalení běhu výsledné aplikace.
  • Vytvářejte takovou strukturu kódu, aby jej bylo možné snadno změnit v případě doplnění nových funkci nebo přepracování existujících.
  • Často a důkladně testujte.

S dobře strukturovaným kódem se následně lépe pracuje. Příprava před kódováním je sice náročnější, ale o to snazší je pak samotná tvorba. Pokud úsilí o dobrou čitelnost, udržovatelnost a výkonnost kódu zanedbáme se slovy „skoro nikdo ve skutečnosti nebude tuto část aplikace používat“ nebo „nechceme být perfektní, potřebujeme něco vypustit co nejdříve“, skončíme u hromady nekvalitního a neudržovatelného kódu, se kterým se nikdo další nebude chtít zabývat.

PETRJANOŠ, Vít. Aplikace, apíčka, apky! Computerworld, 2016/06, roč. XXVII, str. 17.

Povídání starého zbrojnoše

Už v šedesátých letech se začala projevovat „krize programování“ - to byla situace, kdy poptávka po programování (psaní nových či údržba dosavadních) vysoce překračovala nabídku - tedy počet a produktivitu programátorů. Situace byla oproti dnešku ještě komplikována drahotou, nevýkoností a nespolehlivostí tehdejších počítačů, tím, že se programy psaly v mnoha často specifických jazycích: strojových kódech nebo assemblerech jednotlivých procesorů a také nekomfortností tehdejších OS a vstupů - děrné štítky a pásky.

Jeden z důsledků onoho stavu bylo, že dobří programátoři měli velmi vysoké platy a špatní nebyli vyhazováni. Často se u jednotlivých organisací stávalo, že poměr produktivity mezi nejlepšími a nejhoršími programátory byl 10 : 1. K nápravě krize programování částečně vedlo strukturované programování, tvorba nových programovacích jazyků, metodik, paradigmatu objektově orientovaného programování. Programy měly být univerzálnější, opětovně použitelné a zejména snadno udržovatelné.

Ze začátku, kdy programovali jen opravdoví programátoři (originál), kteří psali neuvěřitelně efektní a krátké programy (kvůli úspoře paměti kódu samomodifikující se), se ukázalo jako velký problém nedostatek těch, kdo ty geniální programy měl udržovat - opravovat v nich chyby, rozšiřovat jejich funkčnost a upravovat pro nové OS či stroje. Finační nákladnost údržby rostla. A čím dražší, protože komplikovanější, byl prvotní vývoj souboru programů, tím větší byly požadavky na prodloužení životnosti - tedy udržování. Začalo se mluvit o ceně za celou životnost - tedy pořízení a udržování. A udržování mnohdy ve finančních nárocích převyšovalo pořízení. Když si uvědomíme, že mnohý opravdový programátor údržbu prováděl záplatami ve strojovém kódů přímo na disk a nikde to nedokumentoval, pak se nemůžeme divit, že odchod takového opravdového programátora znamenal odepsání programu, ať už byl drahý, jak chtěl.

Způsob jak problém řešit je nabíledni: opravy a úpravy se musí dělat výhradně do dostatečně přehledného a komentovaného zdrojového textu ve vyšším jazyce - typicky C a velmi podrobná a pečlivá dokumentace. Záhy se ale projevilo, že když sice všichni píší texty programů dostatečně přehledně a podrobně a pečlivě je dokumentují, ale každý úplně jinak, tak to situaci nezlepší příliš a začaly se vytvářet konvence pro přehlednost textu programu a pro jejich dokumentaci a také nástroje, které tomu mohou být nápomocny.

QR Code
QR Code public:pb071_konvencec (generated for current page)