Zásady programování
Proč psát čistý kód
- udržitelnost – další funkcionalita se přidává tím hůř a pomaleji, čím je kód v horším stavu
- je těžší číst a porozumět starému nebo cizímu kódu, než napsat nový, vlastní kód
- špatný kód vede ke špatnému nápadu celý produkt od začátku přepsat – to má malou šanci vyjít, protože tříštíte síly a neexistuje důvod, proč věřit, že byste napodruhé neopakovali stejné chyby a proč by měl být nový produkt automaticky lepší (viz Things You Should Never Do)
- přepisování od začátku je zcela proti myšlence agilního vývoje
- nejlepší je prostě se snažit psát rozumně a čistit starý kód během toho, co kolem něj děláte nové věci
- když už je potřeba něco přepsat, měla by to být relativně malá část kódu, která se dá zvládnout rozumně rychle a hned zakomponovat do hlavního programu
Moto programátora pracujícího na velkém programu
- Po každé návštěvě opouštěj tábor čistší, než jsi ho našel. (Skautské heslo)
- Každý hlupák umí napsat kód, kterému rozumí počítač. Dobrý programátor musí psát kód, kterému budou rozumět lidé.
Jména proměnných, funkcí, struktur, tříd, atd.
- vyjadřují co je v nich uloženo, nebo co dělají
- třídy podstatné jméno, funkce slovesa
- jsou anglicky, vlastně celý kód včetně komentářů a názvů souborů by měl být v angličtině (když narazíte na kód s čínskými komentáři, tak asi taky hned skočíte přes palubu)
- krátká, ale ne přehnaně (
int d;
vsint day;
), jednopísmenné leda jako počítadlo v cyklu - radši přímo název než komentář:
int d; // Day in week
- tím delší/přesnější, čím větší má scope (proměnná ve funkci
days
, proměnná v objektudaysOfWork
) - měly by jít vyslovit (
mod_ymdhms
vsmodificationTimestamp
), lépe se nad tím přemýšlí a lépe se to někomu vysvětluje - není na škodu dělat meziproměnné pro vysvětlení (
int key = getArg(1); int value = getArg(2); update(key, value);
) - neobsahují typ ani jiné informace, které vám snadno sdělí IDE nebo jiné nástroje
- neobsahují předpony dle programu (
MAAMainClass
– My Awesome Application), pokud je to potřeba tak použít prostory jmen (namespace
) - nepoužívat
o
al
v krátkých proměnných:l1
,o0
- nepoužívat slova která mají jiný význam a mohla by způsobit zavádějící pojmenování (např.
std::vector<Account> accountList;
, přitom to očividně není spojový seznam (list)) - nepoužívejte vtipné názvy
- pozor na názvy, které by mohly někoho urazit (např. „blacklist“, „whitelist“, atd.), viz Linux kernel
Pro větší projekty je vhodné udržovat jednotný styl pojmenování proměnných (např. old_date
vs oldDate
) a obecně celý styl formátování kódu. Velké projekty typicky mají svá pravidla pro formátování kódu (coding guidelines), kterých se programátoři musí držet, např.:
Existují nástroje pro automatické formátování C/C++ kódu, např. ClangFormat.
Členění kódu do funkcí, struktur a tříd
- SRP (Single Responsibility Principle)
- Každá funkce by měla dělat právě jednu věc.
- Každá třída nebo struktura má mít pouze jednu zodpovědnost – mít jen jeden důvod ke změně. Místo supertříd co umí vše, je lepší dělat malé, specializované.
- DRY (Don‘t Repeat Yourself)
- Pokud už něco dělá jedna funkce, ostatní by ji mely používat a ne opakovat její kód.
- Když vidíte opakující se kód, snažte se ho odstranit, jinak je těžké ho měnit (je třeba najít všechny výskyty) a zvyšuje komplexnost.
- Oddělení datových struktur od algoritmů
- Datové struktury = objekty (třídy, struktury), algoritmy = samostatné funkce.
- Algoritmy využívající nějakou datovou strukturu by neměly být svázané s daným objektem, měly by používat jen jeho veřejné rozhraní.
- Rozumná délka kódu (čím menší, tím lepší)
- Funkce: optimálně cca 10 řádků, max stránka rozumně velkého monitoru, pro více už je potřeba opravdu dobrý důvod
- Struktury, třídy: pokud jsou delší, definice metod by měly být v samostatném souboru (ne přímo v definici třídy).
Praktické tipy:
- pokud je ve funkci několik bloků kódu, kde každý má komentář, co dělá, zřejmě by se dala rozdělit
- neměla by mít dlouhé if/while/switch bloky, každý tento ‚blok‘ by mel byt jen jeden jednoduchý příkaz nebo volání další funkce
- pokud funkce dela právě jednu věc, neměl by být problém ji podle toho pojmenovat
- pokud vás nenapadá dobré jméno, pravděpodobně dělá víc věcí
- funkce a metody by neměly mít vedlejší efekty (např. funkce
checkPassword
, která vás přihlásí, pokud projde – není to dobré jméno a dělá několik věcí) - funkce by měly používat jednu úroveň abstrakce (např. funkce
parseCppCode
, by neměla zároveň resit chybějící středník (chyba na úrovni textu) a neexistující proměnnou (chyba na úrovni programu)) switch
v podstatě vždy dělá víc věcí, takže pokud je potřeba, měl by být někde dole (nízkoúrovňové funkce) a pouze jednou s tím, že ostatní funkce volají tuto nízkoúrovňovou obálku- malý počet parametrů (pokud má funkce více než 3 parametry, nejspíš dělá více než jednu věc)
- počet parametrů lze snížit tím, že předáme strukturu (samozřejmě dává-li to smysl, třeba rectangle místo čtyř bodů)
- čím je funkce větší, tím víc se vyplatí dodržovat nepoužívání return/break/continue (ale obecně funkce by neměly být velké, viz výše)
- existují nástroje, které vyhodnocují kognitivní složitost jednotlivých funkcí
Třídy vs struktury:
- Struktura by měla být jednoduchý kontejner na data, který buď nemá žádné metody, nebo má pouze metody pro nějakou jednoduchou operaci s těmito daty. Typicky umožňuje přímý přístup ke všem svým datům.
- Třída se naopak snaží mít svá data schovaná jen sama pro sebe a vůči zbytky programu vystupuje pouze přes své veřejné metody, ty mohou dělat i velmi složité operace a volat další metody, ke kterým má daná třída přistup.
- Třída by měla mít co nejmenší veřejné API, které ale dává hodně možností (takže spíš menší kombinovatelné metody, než jedna velká
DoItAll()
, pak se stejně najde někdo kdo bude chtít něco trochu jinak). - Vyplatí se důsledně odlišovat třídy a struktury a nedělat z vašeho objektu hybrida. V C++ k tomu máme dvě různá klíčová slova (
class
,struct
). - Hlavní výhodou množství malých tříd je, že se snadno mění/přidávají další, kdežto když máte jednu velkou třídu, může být snaha něco v ní měnit docela nebezpečná.
- Pokud třída závisí na jiných třídách, měla by být vázána jen na obecný interface a ne na konkrétní implementaci. Programátor by měl mít možnost podhodit jí jinou třídu, která toto rozhraní implementuje (to se hodí například pro testování).
- Na druhou stranu vytvářejte rozhraní spíš až když opravdu potřebuje, aby ho implementovaly alespoň dvě třídy.
Psaní komentářů
- Kód by měl být ve většině případů tak snadno pochopitelný, že komentáře nepotřebuje. Pokud není, komentář to nespraví.
- Nepoužívejte komentáře zbytečně, když je kód jasný:
index++; // Move to next index
void CreateNewWaypoint(); //Creates new waypoint
- Nenechávejte v kódu ani zakomentované řádky, nikdo je nikdy nebude chtít smazat, protože nebude vědět, proč tam jsou. Přitom máme git, takže kód se vám rozhodně neztratí.
- Mají problémy, že je málokdo mění, když se mění kód a pak jen matou nebo lžou.
- Pište je srozumitelně, aby je jasně pochopil i ten, kdo do kódu nevidí tolik jako vy.
- Hodí se tam, kde dávají nějakou informaci navíc, která se do kódu vložit nedá (třeba název algoritmu a stránka odkud jste ho převzali, nebo obecně myšlenka toho, co se v kódu děje).
- Opravdu se hodí pro dokumentaci při deklarací funkcí: např. informace o tom v jakých rozsazích funkce očekává parametry, co znamená její návratová hodnota, jak funkce reaguje na chybné vstupy, apod.
- Případně pomocí komentářů můžete upozorňovat na problémy:
void func(); //This function is not threadsafe ensure that it is used only from one thread!
- TODO a FIXME komentáře, většina IDEček je umí automaticky najít a zobrazit seznam.