Konvence pro programování v jazyce C
Konvence jsou při programování (v jazyce 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, pak ale 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 |
struktury a výčtové typy ( |
|
položky struktury |
|
položky výčtového typu |
|
pojmenované konstanty |
|
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 také názvům začínajícím _
a malým písmenem, např. _ptr
.
Pokud programujete na UNIXovém systému (např. Linux, MacOS), nedávejte
vlastním typům názvy, které končí _t
(např. stack_t
), protože jsou
rezervované pro POSIX.
/** TAKHLE ANO **/
struct node {
struct node *next;
int value;
};
enum mode {
MODE_EXECUTE = 1,
MODE_WRITE = 2,
MODE_READ = 4
};
int position;
size_t array_size;
const char DELIMITER = ':';
/** TAKHLE NE */
enum flags {
autoCloseFiles = 1, // --> FLAG_AUTO_CLOSE_FILES
verbose = 2, // --> FLAG_VERBOSE
// ...
};
int ARGUMENT, Argument, ArGuMeNt; // --> argument
char lastChar; // --> last_char
void __do_something() ... // --> do_something()
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 pište nejvýše jeden výraz nebo řídící strukturu.
Blokové závorky doporučujeme používat na vyznačených místech i v případě, že blok obsahuje pouze jediný příkaz. |
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í.
Funkce a struktury
int factorial(int x)
{
␣␣␣␣// ...
}
-
Otevírací a uzavírací bloková závorka na novém řádku bez odsazení.
-
Tělo je odsazené.
Řídící struktury obecně
while (condition) {
␣␣␣␣// ...
}
-
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í.
Podmínky (if
, else if
)
if (condition) {
␣␣␣␣// ...
} else {
␣␣␣␣// ...
}
-
else if
ielse
mohou začít na stejném řádku jako ukončovací závorka předchozího bloku.
Psaní mezer
Při psaní mezer dodržujte běžné typografické konvence, tedy mezery (značené ␣
)
pište
-
kolem závorek, ale ne pokud jde o parametry funkce,
if␣(a < b)␣ { // mezery kolem závorek factorial(42); // výjimka: bez mezer
-
kolem binárních operátorů kromě
.
a `→`,x␣+=␣2; // tady ano y␣<␣1; queue->head; // tady ne stack.size;
-
za čárkou,
printf("%d %d %d",␣a,␣b,␣c);
-
za středníkem ve `for`,
for (int i = 0;␣i < 10;␣++i)
Naopak, mezery (␣
) se nepíší
-
před otevírací závorkou seznamu argumentů a odpovídající uzavírací závorkou,
/* ANO */ queue_enq(queue,␣item); /* NE */ queue_enq␣(queue,␣item); /* NE */ queue_enq(queue,␣item)␣;
Speciálně při deklaraci ukazatele je mezera mezi typem a `*, ale ne mezi `*
a identifikátorem (proč?), tedy:
/* ANO */ int␣*ptr;
/* NE */ int*␣ptr;
/* NE */ int*ptr;
/* NE */ int␣*␣ptr;
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 (COND₁ && COND₂ && COND₃
// toto je odsazeno o 8 znaků, protože je to pokračování podmínky
&& COND₄ && COND₅
&& COND₆ && COND₇) {
// toto je odsazeno pouze o 4 znaky, aby bylo zřejmé,
// že začíná tělo podmínky
STATEMENT;
}
doStuff(arg₁, arg₂, arg₃,
arg₄, arg₅, arg₆);
Ukázka formátování — jak psát závorky a mezery
typedef struct node
{
struct node *left;
struct node *right;
void *value;
} node;
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 < array_size; ++i) { // mezery za ;
printf("%d -> %d\n", i, array[i]);
}
}
Příklady, které konvence porušují
enum answer {
yes, // ANSWER_YES
No, // ANSWER_NO
MayBe, // ANSWER_MAYBE
iDontKnow // ANSWER_I_DONT_KNOW
};
if (isStrange(something) && InNeighbourhood(something))
Call(ghostbusters); // is_strange(), in_neighbourhood(), call()
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
. -
Pravdivostní test proměnné pište jako test hodnoty proměnné, nikoliv porovnání hodnoty s nulou.
char *ptr; // Ukazatel, který může a nemusí být nastaven. int flag; // Proměnná s logickým významem. int num; // Proměnná s aritmetickým významem (číslo). /* NE */ if (ptr) // ptr není logická hodnota /* ANO */ if (ptr != NULL) // porovnání ukazatelů /* NE */ if (!*ptr) // *ptr neobsahuje logickou hodnotu /* ANO */ if (*ptr == '\0') // porovnání hodnot znaků /* NE */ if (num) // num nereprezentuje logickou hodnotu /* ANO */ if (num == 0) // porovnání v kontextu čísla /* ANO */ if (!flag) // flag používáme jako logickou hodnotu /* NE */ if (flag != 0) // a tudíž přímé porovnání s 0 nebo false je zbytečné
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
-
-
strukturujte kód tak, aby jej bylo možné snadno změnit v případě doplnění nových funkcí 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.
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ýkonností 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 organizací 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ě efektivní a krátké programy (kvůli úspoře paměti kódu samomodifikující se), se ukázal 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. Finanč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ódu 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.