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_ymdhmsvsmodificationTimestamp), 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
oalv 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)) switchv 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 indexvoid 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.