Psaní čistého a přehledného kódu je věc, která se vždy řešila, řeší a řešit bude. Samozřejmě, že každý vývojář má motivaci, aby jeho kód byl jako ze škatulky, krásně čitelný i pro další kolegy nebo nové vývojáře, byl strukturovaný a znovupoužitelný. Pokud pracujete v týmu, většinou máte nastavené nějaké interní standardy pro psaní a něž kód “pošlete” na CR, před mergnutím, měl by tyto nálěžitosti splňovat. Ale i když pracujete sami, je dobré se držet nějakých pravidel, jelikož v budoucnu strávíte méně času přemýšlením, co jsem to vlastně napsal :)
Ale pojdmě se už podívat na několik tipů, jak napsat přehledné a znovupoužitelné funkce.
Dobře pojmenované funkce
Pokud vytváříme zcela novou funkci, máme štěstí, můžeme si ji totiž pojmenovat tak jak chceme. Při pojmenování funkce bychom měli mít na paměti, že kód s nějaoku pravděpodobností nebudeme číst jen my, ale i někdo další a proto by funkce měla být exaktně pojmenovaná.
Příklad: Chci vytvořit funkci, která mi vrátí všechny uživatele.
function users()
Název funkce mi říká, že se bude nejspíše jednat o funkci, která má něco společného s uživatelem. Ale co? Je to dotaz na API, pokud ano, tak jaká je to metoda? Nebo je to transformace dat do jiné struktury?
Pokud bude funkce využita v kódu, musel bych se proklikat na její definici a podívat se do těla funkce, abych zjistil, co dělá. Ano, jde to, ale je to zdlouhavé.
Funkce by tedy měla svým názvem jasně vyjadřovat kontext. V tomto případě by to mohlo vypadat následovně:
function getAllUsers()
function getUser()
function updateUser()
function deleteUser()
Dobrý způsob, jak nedělat v pojmenovávání funkcí zmatek, je také sjednocení operací pod jeden název.
Parametry
Příliš mnoho parametrů ve funkcí může způsobit, že funkce se bude hůře využívat a snadno se zapomene na doplnění parametrů. Zvyšuje se tím tedy složitost samotné funkce.
function createBook(
string bookName,
number pages
sting coverPhoto
string authorName
string category
)
Je vhodné se zamyslet, jaké parametry jsou třeba a zda nebude lepší místo 6 argumentů mít jen jeden jako objekt.
interface Book {
bookName: string;
pages: number;
coverPhoto: string;
authorName: string;
category: string;
}
function createBook(book: Book): void {
}
Magická čísla a hodnoty
Pokud definujeme funkci, měli bychom mzslet na to, abzchom nevytvořili parametr, který bude validní pro funkci, ale hodně obecný pro vývojáře.
Máme funkci s definicí:
const TAX_RATE = 0.2;
function calculateTotalPrice(price) {
return price + price * TAX_RATE;
}
Na první pohled je jasné, že se zde počítá nějaká celková cena. Ale je otázkou, zda parametr price
představuje cenu bez DPH nebo s DPH. Pro tuto informaci musíme nahlédnout do těla funkce. Pro lepší orientaci by parametr price
mohl být pojmenován jako priceWithoutTax
. Tím by bylo jasné, že cena, která vstupuje do funkce, je bez DPH.
Další možností, jak zlepšit čitelnost a udržitelnost kódu, by bylo také přejmenovat samotnou funkci například na calculateTotalPriceWithTax
, což ještě více zdůrazňuje, že výstupem funkce je cena s DPH.
const TAX_RATE = 0.2;
function calculateTotalPriceWithTax(priceWithoutTax) {
return priceWithoutTax + priceWithoutTax * TAX_RATE;
}
Samozřejmě, že v reálném případě máme lepší kontext využití funkce, ale je vždy dobré uvádět parametry s odpovídajícími popisnými názvy a předcházet tak případným nejasnostem při použití funkce
Izolovanost funkce
Funkce by měla být závislá pouze na vstupních parametrech, neměla by záviset na nějaké globální proměnné a měnit její stav.
Problém s neizolovanou funkcí
Když funkce přímo používá globální proměnnou, jako je například stavSkladu
, může dojít k nežádoucím vedlejším účinkům. Tato funkce tak mění stav, který může být ovlivněn i jinými částmi kódu, což vede k nepředvídatelnému chování.
// Globální proměnná pro stav skladu
let stavSkladu = 100;
function pridatDoSkladu(pridano) {
// Funkce přímo mění stavSkladu
stavSkladu += pridano;
return stavSkladu;
}
// Příklad použití
pridatDoSkladu(50);
console.log(stavSkladu); // Výstup je 150
Problémy:
- Závislost na globální proměnné: Funkce
pridatDoSkladu
mění globální proměnnou, což může vést k nečekaným výsledkům, pokud jiná část kódu tuto proměnnou změní. - Vedlejší účinky: Jakékoliv volání této funkce ovlivní
stavSkladu
, což může způsobit chyby, pokud se funkce zavolá nesprávně nebo v nevhodný čas. - Nepředvídatelnost: Stav skladu se může kdykoliv změnit jinde v aplikaci, což může vést k nečekanému chování.
Řešení: Izolovaná funkce
Izolovaná funkce nedělá změny přímo, ale vrací novou hodnotu. Tím se vyhneme vedlejším efektům a zajišťujeme lepší předvídatelnost.
function pridatDoSkladu(stavSkladu, pridano) {
return stavSkladu + pridano;
}
// Příklad použití
let novyStavSkladu = pridatDoSkladu(stavSkladu, 50);
console.log(novyStavSkladu); // Výstup je 150, ale původní stavSkladu zůstává beze změny
Tímto způsobem je funkce pridatDoSkladu
nezávislá na globální proměnné, nemění stav aplikace a její výstup je vždy předvídatelný.
Bool hodnota jako parametr
Při vymýšlení parametrů funkce je dobré myslet na to, aby byly jasně identifikovatelné. Například pokud chci do funkce poslat jméno a věk, je z parametrů name
a age
hned zřejmé, co funkce očekává. Toto však neplatí pro boolean (pravdivostní) hodnoty. Uvažujme funkci s definicí:
function stringTransformation(string, boolean)
Z názvu víme, že funkce transformuje řetězec, a je jasné, že první parametr bude string
. Co však znamená boolean
. Ano, je to pravdivostní hodnota, ale co ovlivňuje? Abychom to zjistili, musíme nahlédnout do těla funkce, což snižuje čitelnost kódu.
Lepší způsob je použít popisné názvy parametrů nebo v některých jazycích zvažovat použití pojmenovaných parametrů či objektů. Například:
function stringTransformation(text, shouldCapitalize)
Nyní je z parametrů ihned zřejmé, že druhý parametr shouldCapitalize
určuje, zda se má text převést na velká písmena. Tím zvyšujeme čitelnost kódu, aniž bychom museli analyzovat tělo funkce. Zde bzs se to dalo vyřešit i tak, že dáme kontext do pojmenování funkce, například capitalizeString, ale i tak má smysl pojmenovat bool hodnotu.
Délky funkcí
Pokud píšeme funkci a najednou zjistíme, že máme funkci o 200 řádcích, je dobré se zamyslet nad tím, jestli by nešla zjednodušit. Rozdělit ji do více funkcí, případně zkontrolovat, jestli funkce dělá opravdu jednu věc a neovlivňuje jiná data, která jsou mimo kontext funkce.
Ošetření errorů
Ošetření chyb ve funkci je také důležitá část, na kterou je třeba mzslet, Pokud například načítáme data z nějakého API, je důležité vědět informaci, jestli se to povedlo, či nikoli a poté zobazit příslušnou hlášku uživateli.
Ošetření chyb je důležité, ale ne každá funkce musí mít explicitně ošetřené chyby. Jsou ale případy, kdy je to potřeba
- Funkce pracující s vnějšími zdroji nebo daty - práce s API, databázemi, soubory nebo uživatelskými vstupy
- Kritické a core funkce aplikace - pokud pracujeme s peněžními transakcemi (napojení na platební brány, využití věrnostních programů atd..) a při zabezpečení aplikace (autorizace a autentizace)
- Asynchronní funkce - při využívání async/await, fetch a promises. Pro tyto funkce je nezbytné ošetření výjimek, jelikož může nastat více chybových stavů (síťové, časové, formátové…)
- Zanořené a opakované funkce - pokud voláme funkci uvnitř jiné funkce, je dobré ošetřit její chybové stavy, například pomocí try-catch
Následující funkce načítá data z url a vrací JSON. Pokud nastane v průběhu nějaká chyba, funkce nám o tam dá vědět.
async function fetchData(url) {
try {
let response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Failed to fetch data:", error);
return null;
}
}
Čisté funkce jsou základem dobře strukturovaného, čitelného a snadno udržovatelného kódu. Dodržováním osvědčených postupů, jako je minimalizace vedlejších efektů, používání izolovanosti a zaměření na jednoduchost, si nejen zjednodušíme práci, ale také přispíváme ke kvalitě a spolehlivosti celého projektu. Pamatujte, že čistý kód není jen o technice – je to o zvyku a dá se říci, že i o určité míře disciplíny.