Můj kolega mě osočil, že dávám přednost rychlému, snadnému řešení před hezkým a kvalitním. Z reakce, která mi v hlavě rostla však vznikl celý následující text, protože si myslím, že se na celou věc nelze podívat zběžně ale důsledně, aby neutekl detail, který je velmi důležitý.
Citát:
Tvůj stance je většinou minimální množství abstrakce a co nejrychlejc ven. Když jde o to dělat radost Mirkovi, není to špatný. Ale nevyžíváš se ve stavění továren na tužky. Globální callback sem, stejnojmennou třídu nebo dvě tam, a je to. Občas bych chtěl být v údivu nad elegancí nějakýho řešení a rozebírat hlavu člověku co to napsal. A vím, že kód je věc, která většinou zraje jako kvalitní nepasterovaný mlíko. Pokud teda zrovna nejsi tex.
Mám pocit, že FW si píše daleko víc abstrakcí, ať už z donucení, nebo jen tak. Ne že bych jim to C++ záviděl :-)
Než začnu "trhat" jeho argumenty na cucky, rád bych navedl Váženého čtenáře, aby na můj text koukal kritickýma očima, a zároveň aby vše nebral příliš doslovně. Každý z nás jsme jedinečný, prošel si různými cestami, zažil různé situace, má jiné zkušenosti které nás formují. Je to v pořádku, a je to tak správně. Pokud se budete o tomto tématu bavit, pozorně poslouchejte starší, mají těch zkušeností víc, to ale neznamená, že musí mít pravdu :-)
Abychom mohli odpovědět na otázku, jaký je dobrý kód, je třeba znát celý kontext. Myslím si, že na kód nelze koukat paušálně, není určen k tomu aby byl vystaven na obdiv okolí, ale má plnit svůj účel. Kód může vypadat všelijak, může být neefektivní, nebo optimální, může být zastaralý nebo absolutně moderní, ale pokud neplní svůj účel je zbytečný a neměl by existovat. Mnoho programátorů trpí tím, autor tohoto textu nebyl výjimkou, že potřebují zdokonalovat již existující kód. Ale zapomínají, že účelem kódu je jeho funkčnost. Je to čistě užitná komodita. Ano kód má svá specifika a vztahují se na něj další kritéria, ale pokud neplní svojí funkci, postrádá nárok na existenci. Alespoň do té doby, dokud nevznikne muzeum krásného kódu.
Účel kódu se může dělit do několika kategorií. Samozřejmě jich může být více, a při psaní článku jsem jistě na nějaké okrajové užití zapomněl, ale ty nejběžnější jsou následující.
Jednorázový
Některý kód slouží pouze pro opravení existujících dat, nebo k jednorázovému převodu mezi systémy. I když tvrdit, že kód opravdu znovu nebude nikdy použitý je přehnané, opravdu takový kód tu a tam vznikne, a jeho účel byl splněn právě jedním spuštěním. Další osud může trávit v hlubokém /dev/null .
Prototyp
Prototyp je něco velmi zvláštního. Má často potvrdit domněnku, ověřit koncept, vyzkoušet technologii. Není explicitně jednorázový, ale četnost jeho spuštění je limitně malá, dokud se ho někdo neodváží nasadit do produkce. To je zcela proti myšlence prototypu, ale nikoli proti myšlení manažerů.
Na kód prototypu je třeba pohlížet jako na něco, nad čím se nemá trávit čas. Někteří programátoři se explicitně zaměřují na psaní prototypů. To neznamená že jejich kód musí být nečitelný nebo ošklivý, ale na trojúhelníku rychle, kvalitně a levně je důraz na rychlost a cenu. U prototypu se počítá, že bude obsahovat chyby, je zaměřen na optimistický přístup, tedy ošetřuje absolutní minimum okrajových stavů, pokud vůbec.
Takový kód může sloužit jako příklad, jak nějakou technologii použít, nebo ukázat jiný způsob provedení. Často se prototyp používá při hledání optimálního způsobu použití nějaké další komponenty, nebo nalezení slabého místa. Znamená to, že kód může být studován. Účel jeho kódu je co dělá a jak to dělá. A je to jeho jediné kritérium pro zachování.
Kritický
Zvláštní kategorií je kritický kód. Tedy takový, který má na celou komponentu zásadní vliv. Zásadní z hlediska výkonu, bezpečnosti, nebo třeba robustnosti. Kvalitu takového kódu je nutné posuzovat právě podle dotyčných kritérií. Zaměření na rychlost vede velmi často k omezení čitelnosti kódu. Což v dostatečně kritických místech může být oprávněné, pokud daná věc nelze napsat čitelněji na úkor rychlosti.
Ukázkový
Pokud by nějaký kód mohl být vystaven na obdiv, mohl by to možná být právě ukázkový kód. Kód ukazující příklad použití nějaké knihovny, nějakého návrhového vzoru, prostě kód, jehož účelem je ukazovat se. Je ale třeba pracovat s kritériem, aby takový kód byl dostatečně malý, jednoduchý a výstižný, aby umožnil čtenáři soustředit se na podstatu věci.
Takový kód často nebude ošetřovat krajní situace, bude počítat spíš s optimistickým průběhem. Kód má ukázat jak něco dělat, nebo naopak jak něco nedělat. To je jeho podstata věci. Kód může být prostou ukázkou libovolné zde popsané kategorie nebo jejich kombinací.
Běžný
Do kategorie běžného kódu pak patří vše ostatní. Prostě standardní kód. Neznamená to, že nemá být rychlý! Nebo, že by neměl být bezpečný, nebo snad nemá ošetřovat krajní situace. Je třeba na něj koukat jako na něco, co by mělo být optimální ze všech hledisek. Má být rychlý a robustní, ale ne na úkor čitelnosti. Má být dobře dokumentovaný, aby bylo možné ho dobře udržovat, opravovat a vylepšovat, tak jak si to jeho provoz žádá.
Pro každou skupinu následujících platí jiná pravidla, ať se nám to líbí nebo ne, a je třeba k němu tak i přistupovat. Samozřejmě že kód patřící do kterékoli z těchto skupin, může být napsán krásně, zajímavě, může být zdrojem obdivu. Je třeba se ale koukat na jeho účel.
Vedle hlediska účelu je vhodné se na kód koukat i z hlediska ceny. Každý kód něco stojí, každý! Neexistuje kód, který by nic nestál, žádný! Alespoň ne ten počítačový.
Cena vývoje
Každý kód má svého autora, nebo skupinu autorů. Tito autoři jej psali na počítači (ten něco stál), napájený elektřinou (ta něco stála), a strávili u toho nějaký čas (ten se dá přepočítat na peníze) a spálili u toho kalorie získané z jídla a pití (to něco stálo). To že autoři pak tento kód poskytnou dál zdarma neznamená, že nemá svou pořizovací cenu. Ve firemním prostředí je pak relativně snadné spočítat, pořizovací cenu nějakého kódu. Firma má k dispozici kompletní náklady na zaměstnance, jeho vybavení i provozní náklady. To vše se musí do pořizovací ceny započítat.
Cena údržby
Pokud účelem kódu není jednorázové spuštění, po němž je nemilosrdně smazán. Nebo pokud jeho účel nemá z nějakého důvodu omezenou hranici, která nikdy nebude překročena (většinou vždy bude překročena), bude kód udržovaný. Údržba kódu je ošemetná věc. A protože cena údržby se dá počítat stejně snadno, jako cena vývoje, firmám se za ní nechce platit. Cena údržby tak často vstupuje do rozhodovacího vzorce, jehož výstupem může být jiné řešení než kód udržovat. A pokud manažer řekne, že se něco nikdy nebude upravovat, je 99.99% šance, že se to stane do jednoho týdne.
Cena provozu
Každý kód, který je spuštěn má své náklady. Tyto náklady se dají relativně snadno spočítat cenou za stroj, a jeho provoz, na kterém kód běží. Je ale nutné připočítat i cenu za obsluhu kódu, případně jeho servis. To je totiž také velmi důležitý faktor, na který je často zapomínáno. To že je kód optimální, neznamená, že je optimální jej instalovat a konfigurovat, nebo obsluhovat. Pokud při psaní kódu na toto nebyl brán ohled, cena provozu jen kvůli obsluze může být mnohem dražší, než kdyby byl kód neoptimální, ale snadno obsluhovatelný.
Zde si dovolím malý povzdech. Někteří vývojáři se bohužel nezajímají o cenu provozu, zejména pak tvůrci aplikací běžící na klientském stroji. Díky tomu pak nové verze nedělají nic jinak, ale jsou výrazně pomalejší a vyžadují více paměti. Myslím si, že všichni ti, kteří prohlásí že si prostě zákazník koupí lepší počítač mají vlastní speciální oddělení v pekle, ve kterém budou muset provozovat jejich nenažraný software, a každou vteřinu čekání na odezvu budou mučivě cítit. Protože i to, že zákazník kód provozuje, znamená, že on bude mít své náklady. Jistě, tyto náklady bude mít zákazník, ale to neznamená, že cena provozu neexistuje. Jen ji platí někdo jiný.
Optimálnost kódu je posuzována podle hledisek. Pokud je něco optimální, je to dokonale vyvážené. U kódu je tím většinou myšlena rychlost a využití zdrojů počítače. Jak jsem ale naznačil, na kód lze koukat z celé další škály různých hledisek. Dokonalý vyvážený bod mezi těmito hledisky se pak hledá o to hůř.
Většina programátoru zná zmiňovaný trojúhelník rychlost - cena - kvalita.
"Trojúhelník" Rychle - Levně - Kvalitně
V nedávných diskuzích s kolegy, jsem si ale uvědomil jiný trojúhelník. Pokud se bavíme o běžném kódu, vyvíjí se. Tedy vyvíjí ho vývojáři, ale v čase má kód různé charakteristiky, které se mohou, zejména v agilním vývoji, měnit. Kód má v počátku vlastnosti spíše prototypu. Je třeba rychle vytvořit minimální produkt, který v ideálním případě nebude přepracován, ale doplněn o další funkcionalitu, nicméně riziko přepracování je velmi vysoké. V počátku je tedy na snadně neřešit optimálnost kódu, ale ověření že koncept bude fungovat. A tento koncept může být nasazen do produkce. Vývojář tak v souladu s podmínkami sáhne ke generickému řešení, které mu rychle pomůže poskládat aplikaci jako skládačku.
V nějakou dobu, kdy se aplikace dostane do relativně stabilní podoby stran funkcionalit, a začne být dobře odhadnutelné, jakým směrem se bude dál vyvíjet, přijde čas na optimalizaci. Tu známou programátorskou optimalizaci stran výkonu. Zejména pro serverové aplikace je výkon velmi důležitým aspektem, pokud tedy aplikaci nebude využívat jen velmi omezené množství uživatelů, pro které by výkonová optimalizace nedávala smysl. Kód musí být ale dostatečně čitelný a jasný, aby jeho následující údržba byla levná, a dovolovala kód dále rozšiřovat dle požadavků.
U velký systémů ale nastane ještě jeden bod, který jsem si příliš neuvědomoval. V určitou chvíli je systém - skupina kódů natolik komplexní a složitá, že přestává být snadné, se v něm orientovat. Programátoři pak často musí skákat po různých vrstvách kódu, kdy se v jejich hlavách mísí pohled shora na celou architekturu, a pohled na detail konkrétní části. I když velmi nerad, musím připustit, že to je správné místo pro abstrakci, tak, aby programátor nepřišel o nadhled, ale nekomplikoval si jej detailem.
Osobně mám k abstrakci odpor, ale víc než k abstrakci, mám odpor ke generičnosti. Což není to samé. Proto jsem si dovolil nakreslit ještě jeden trojúhelník generický - abstraktní - optimální.
"Trojúhelník" Generický - Abstraktní - Optimální
Pro pochopení tohoto pohledu je třeba vydefinovat rozdíly mezi tím, co je generické, a co je abstraktní. Protože podle mého názoru, tento rozdíl není zcela zřejmý.
Generický
Za generický kód považuji takový, který umí "vše" a velmi pohodlně. Typickým příkladem generické knihovny jsou ORM (Object–relational mapping ). Je to neuvěřitelně svůdná věc, kdy bez znalosti databázového jazyka, typicky SQL, programátor založí tabulky včetně vazeb, snadno provádí vytváření nových záznamů, jejich modifikaci i vyčítání. A to vše ideálně v syntaxi jazyka, jenž používá. Pokud Vám to připadá, že popisuji abstrakci tak je to správně. Zejména ORM knihovny jsou na abstrakci založené.
V určitý moment se ale abstrakce zvrhne, protože dřív nebo později začnou tyto knihovny řešit i specifické věci. ORM často pracuje s relací, bez ohledu na to, že některé databáze relační nejsou. V nějakém momentě začne podporovat různé datové typy, bez ohledu na to, že i ty se mezi jednotlivými databázemi liší. Místo toho aby pak existovala definice číselného sloupečku, dřív nebo později začne programátor používat definici konkrétního čísla, jeho velikosti nebo tvaru. To už ale není abstraktní, je to generické.
Celá řada podobných knihoven, které mají za úkol odstínit programátora od konkrétních řešení nakonec nabízí konkrétní, specifické přístupy, atributy a metody, které lze používat jen s konkrétní technologií. Protože podporované technologie nejsou stejné. A použití abstraktní vrstvy nemusí být optimální.
Abstraktní
Abstraktní kód by měl být odstíněn od všeho generického. To ale sužuje jeho možnosti, což je správně. Abstraktní kód neřeší detail, neřeší konkrétní specifikaci, ta je o stupeň jinde. V ideálním případě tak abstraktní kód nemusí být neoptimální, protože implementace konkrétní technologie je od abstraktní vrstvy oddělená a do abstraktní vrstvy se nepropisuje.
To samo o sobě se ale lépe řekne, než provede. Po pravdě sem se nesetkal s knihovnou, která by zaštiťovala více technologií a stále byla jen abstraktní. To ale neznamená, že neexistuje! Naopak jsem viděl hezké malé kusy kódu, které byly abstraktní, které sjednocovali přístup a umožňovali věnovat se detailům bez ztráty pochopení celku nebo naopak.
Největším úskalím generického kódu je čitelnost. Často využívá magických konstrukcí jazyka, kdy se samo, někde, nějak. To může být velmi nečitelné. Pokud pak vznikne požadavek toto "samo se" specifikovat, ohnout, začne se hackovat způsob používání, protože to standardním způsobem nejde. To v abstraktním kódu není potřeba, protože abstrakce má být jednoduchá, prostá a neřeší vše, jen základní obrysy.
I přes to si ale myslím, že abstraktní kód má sklony ovlivnit výkon. Protože pokud abstrakce není dostatečně malá (pak přestává dávat smysl), je snadné provádět operace, které nejsou optimální, ale z hlediska abstrakce jsou zcela nezajímavé. Abstrakce má tedy vliv na cenu provozu aplikace, ale i údržbu, a je třeba si spočítat, jak zvolit hlediska k posuzování, zda kód má být víc abstraktní - tedy čitelný i kdyby to bylo na úkor výkonu.
Čitelnost má totiž velmi zásadní vliv na údržbu kódu. Ne nadarmo se říká, že programátor by měl psát kód tak, aby jej dokázal pochopit i on sám po několika měsících. Pokud má problémy se v něm vyznat sám autor, jaké to pak může být pro nováčka, nebo dokonce mladšího juniornějšího kolegu, který staré triky nezná, v lepším případě zná nové - moderní. Pokud z kódu není jasně vidět co dělá, je to špatně. Komentář to nespraví.
Osobně jsem velmi kritický k čitelnosti kódu, zaslouží si to vlastní článek. Nicméně všechny magické samo-se, někde, někdy, nějak jsou neprůhledné, nečitelné, špatně se ladí, špatně se chápou. Programátor, který má kód upravit se pak soustředí na magii kódu, místo aby odbavil požadavky na změnu, které jsou lidsky stravitelné. Čitelnost je pak v přímém ohrožení na rozdíl od jiných hledisek, a v kvalitě kódu hraje zásadní roli.
Pětiúhelník: Genericky, Rychle, Levně, Abstraktně, Kvalitně.
V souvislosti se všemi aspekty které jsem zde zmínil, ceny kódu, rychlost vývoje, účel kódu, použití abstrakce a generického přístup, optimální využití zdrojů, jde o mix, který určuje kritéria dobrého kódu. Dalo by se říci, že kód je optimální ze všech těchto hledisek. Protože rychlý prototyp vyžaduje generickou knihovnu, dlouhodobě udržitelný kód by měl udržovat rovnováhu mezi abstrakcí a specifickým přístupem.
Neznamená to tedy, že jsem odpůrce dobrého a kvalitního kódu, naopak! Jen posuzuji kvalitu kódu podle všech dostupných hledisek, protože dobrý kód je dobrý jen pokud je jeho struktura a styl optimální vůči těmto kritériím.
© 2023 Ondřej Tůma McBig. Ondřej Tůma | Based on: Morias | Twitter: mcbig_cz | RSS: články, twitter