Jak zlepšit kvalitu kódu pomocí 5 jednoduchých principů
Dost už bylo špagety kódu. Vydejte se vstříc udržitelnosti a mějte své projekty na úrovni s využitím Software Design Principles. Název může znít vznešeně, ale nenechte se zmást. Nejedná se o nic jiného než aplikování selského rozumu v programování.
V nedávném článku Hledá se kodér! Co vše by měl umět? jsme představili vyčerpávající soupis témat, která mohou souviset s prací frontend kodéra. Padla i zmínka o best practices, přesněji řečeno o softwarových návrhových principech jako KISS, DRY nebo YAGNI. V dnešním článku na tuto zmínku navážeme a povíme si o podstatě a aplikaci těchto principů.
Nemusíte se hrozit, že bychom zabíhali do detailů objektově orientovaného programování (s kterým návrhové principy úzce souvisí). Řeč bude o jednoduchých principech, které mnozí z vás aplikují v praxi, aniž by o tom věděli.
Kvalita kódu
Kódování, potažmo stylování, není vyšší dívčí, ale stejně jako vše ostatní, se dá dělat buď dobře, nebo špatně. Kde není čistý kód, nemůže být ani jeho vysoká kvalita.
Obecně lze tvrdit, že dobrý kód je takový, který:
- dělá, co má,
- je konzistentní,
- je snadno pochopitelný,
- je robustní,
- má dobrou dokumentaci,
- můžeme testovat,
- neobsahuje legacy závislosti,
- můžeme snadno udržovat a rozšiřovat,
- nezanáší technický dluh.
Na kvalitní kód bychom neměli pohlížet jako na velkou vstupní investici s nejistou návratností. Dobrý kód má předem jasně danou návratovou hodnotu, která se projeví téměř ihned.
- Urychluje orientaci v projektu, což uvítají především nováčci, ale i vývojáři, kteří se k projektu vrací po delší odmlce.
- Minimalizuje technický dluh.
- S rostoucím časem snižuje náklady spojené s údržbou a rozšiřováním projektu.
Je důležité si uvědomit, že kvalita kódu má zásadní dopad na kvalitu softwaru samotného.
Hodnotíme-li kvalitu kódu, nemůžeme vyvozovat závěry pouze na základě počtu metod či řádků, ze kterých se systém skládá. Ani z výsledků performance auditu. Hodnotí se celek jako takový. Je důležité si uvědomit, že kvalita kódu má zásadní dopad na kvalitu softwaru samotného – ovlivňuje i jeho úroveň zabezpečení, spolehlivost, výkon či použitelnost spjatou s prožitky vývojářů (Developer Experience, DX) a uživatelů (User Experience, UX).
Základní principy
Ke kvalitnímu kódu může pomoci řada pouček. Mezi ně patří Software Design Principles. Jedná se o sadu doporučení, která by měl vývojář zužitkovat při práci. Pomocí těchto doporučení se nám daří psát kód čitelnější, robustnější, snáze udržitelný s možností jednoduchého rozšíření. Principy pomáhají předcházet mrtvému kódu, který mimo jiné zbytečně zabírá místo v repozitáři. Snižují duplicity a šetří tím práci při případném refactoringu. A především přispívají k menší frustraci nás, vývojářů.
Vzpomeňte si – kolikrát jste se vraceli k projektu, na který jste delší dobu nesáhli a při pohledu do kódu se vám div neprotočily panenky? Nebo jste potřebovali rozšířit o jeden řádek metodu, o které jste si mysleli, že se používá jen v daném místě, a rozbili jste tím půlku aplikace?
Díky návrhovým principům jsme schopni včas rozpoznat dobře napsaný projekt od pomalu tikajicí bomby.
Návrhové principy sice nejsou spásným řešením, které můžeme vždy dodržovat (záleží na případu užití, limitech pro časovou či paměťovou složitost, aj.). Rozhodně nás ale navedou správným směrem. Přehledný a udržitelný kód se bude psát takřka sám a navíc nás donutí se nad samotným kódem důkladněji zamyslet. Díky tomu budeme schopni včas rozpoznat dobře napsaný projekt od pomalu tikajicí bomby.
(Poznámka: Některé z níže uvedených principů jsou trochu vágní, takže nemůžeme s jistotou uvést všechny možné strategie a pravidla, které lze aplikovat. Vždy záleží na konkrétním případu užití.)
1. KISS
Název: Keep It Simple, Stupid (Nech to jednoduché, hlupáčku). Alternativně Short and Simple, Simple and Straightforward nebo Smart and Simple.
Myšlenka: Jednoduché řešení je lepší než komplexní, i když může vypadat primitivně.
Popis:
Vývojář se občas nechá svést na scestí používáním sofistikovaných a komplexních řešení, která na první pohled vypadají působivě. Tato volba může zapříčinit, že se kód těžko debuguje, rozšiřuje nebo vyžaduje více času na pochopení.
Nač si komplikovat život, když lze použít jednoduché, snadno pochopitelné a rychlé řešení? KISS si stojí za tím, že je lepší zbytečně se neuchylovat k dědičnosti, polymorfismu nebo třídám, pokud to není vyloženě nutné. Takové přístupy mohou přispět ke zesložitění kódu nebo vyšší provázanosti.
Jak na to:
- Vyhněte se komplikovaným přístupům jako dědičnost nebo polymorfismus. Namísto toho upřednostněte jednoduché konstrukce
if
/else
, v případě stylů pak třeba mixiny. - Vyvarujte se overengineeringu (přetechnizovanosti). V jednoduchosti je krása a v programování to platí dvojnásob.
- Používejte co nejvýstižnější názvy, které jasně reprezentují, co daná proměnná představuje, respektive co přesně daná metoda dělá.
- Udržujte metody co nejkratší. Počet řádků kódu by v ideálním případě neměl být víc než 50.
- Vyvarujte se používání globálních metod a proměnných, pokud to není nutné. Sice jsou přístupnější, zato ale přispívají ke kolizi názvů, složitosti kódu nebo horšímu testování.
Příklad:
Budeme chtít získat jména všech uživatelů. Z toho logicky vyplývá, že budeme muset iterovat nad polem všech uživatelů a předat si získaná jména do nového pole. To bychom mohli udělat například tímto způsobem.
const names = [];
const people = [
{
name: 'Alice',
age: 22,
},
{
name: 'Bob',
age: 29,
},
// …
];
const getName = (item) => {
return item.name;
}
const getNamesFromPeopleArray = (array) => {
for (let i = 0; i < array.length; i += 1) {
const item = array[i];
names[i] = getName(item);
}
}
getNamesFromPeopleArray(people);
Na první pohled se může zdát, že na kódu není co měnit. Vrací nám jména všech uživatelů, jak jsme si přáli. Náš kód jde ale napsat mnohem jednodušeji, a to pomocí funkce map()
.
const people = [
{
name: 'Alice',
age: 22,
},
{
name: 'Bob',
age: 29,
},
// …
];
const names = people.map(person => person.name);
2. DRY
Název: Don't Repeat Yourself (Neopakuj se).
Myšlenka: Nechť má každá část systému své jediné, jednoznačné a autoritativní zastoupení.
Popis:
Pokud jsme nuceni provádět změny v kódu, vykonáme je právě jednou namísto počtu duplicitních výskytů. DRY vyžaduje pouze jednu definitivní reprezentaci objektu, metody nebo proměnné, která tím představuje i jediný zdroj pravdy (tzv. Single Source of Truth), který je dalším z řady návrhových principů.
Díky tomu se vyvarujeme chybám v kódu, šetříme čas strávený údržbou a předcházíme duplicitám, které mohou přispívat ke zmatkům. Vývojáři se navíc nemusí neustále spoléhat na fulltextové vyhledávání.
Jak na to:
- Předcházejte opakujícímu se kódu, například pomocí abstrakce, dobrým přehledem o projektu či pomocí code review.
- Vyhněte se definování proměnných na více místech.
- Omezte copy & paste vlastního kódu v aplikaci. Namísto toho jej přepište do logického a znovupoužitelného celku.
Příklad: Chceme definovat styly pro různé modifikace tlačítka. Každé tlačítko bude mít svou specifickou barvu pozadí i rámečku.
.button--red {
border-color: darken(#ff0000, 10%);
background-color: #ff0000;
}
.button--green {
border-color: darken(#00ff00, 10%);
background-color: #00ff00;
}
Jednotlivé styly se od sebe liší pouze barvou. Redukovat opakující se kód můžeme například tímto způsobem, který je sice o něco upovídanější, zato přispěje ke granularitě kódu, abstrakci a snazšímu rozšíření.
$colors: (
red: #ff0000,
green: #00ff00
);
@mixin button-variants($color) {
border-color: darken($color, 10%);
background-color: map-get($colors, $color);
}
.button--red {
@include button-variants(red);
}
.button--green {
@include button-variants(green);
}
3. YAGNI
Název: You Aren't Going to Need It (Nebudeš to potřebovat).
Myšlenka: Neimplementujte funkcionalitu, dokud není potřeba.
Popis: „Kdybychom to v budoucnu potřebovali, bude to tam připravené.“ Typické programátorské „kdyby – chyby“, které může přispět k nabobtnání mrtvého kódu. Nepotřebný kód zanáší technický dluh do projektu, zvyšuje časové nároky a i jiné náklady s tím spjaté. V tomto případě platí motto:
„Co nemusíš udělat dnes, odlož na později.“
Jak na to:
- Je-li některá funkcionalita opravdu třeba, využívejte výhod verzovacího systému, nebo práci doopravdy odkládejte na dobu, kdy bude třeba.
- Prioritizujte svoji práci na základě aktuálních požadavků a implementujte jen to, co je v danou chvíli nutné.
- Pokud provádíte refactoring kódu, zkontrolujte, že při přepisu odstraňujete všechny nepotřebné kusy kódu.
Příklad: Nebudete potřebovat 🙂.
4. Abstraction
Název: Abstrakce, též zobecnění.
Myšlenka: Jedno generalizované řešení je lepší než více specifických.
Popis:
Princip abstrakce přispívá ke znovupoužitelnému kódu, který se snáze rozšiřuje. Pokud se v kódu vyskytuje podobná funkcionalita, zvažme použití abstrakce. Obecné řešení umožňuje řešit více podobných podúloh prostřednictvím například jedné metody a do ní předaných parametrů. Abstraktní metody nám umožňují nesoustředit se na detaily v místech, kde to není třeba.
Typickým příkladem je dotazování se na položky v databázi. Zajímá nás přesné znění dotazu na databázi, pokud jen potřebujeme vypsat získané údaje na stránku? Nejčastěji ne. Bohatě se spokojíme s tzv. black boxem, ve kterém jsme odstíněni od konkrétní implementace a veškerých detailů a řešíme pouze návratovou hodnotu.
Jak na to:
- Používejte parametrizované metody. Mějte ale na paměti, že všeho moc škodí. Pokud do metody předáváte více jak 5 parametrů, zvažte vhodnost svého řešení a zkuste kód rozdělit na ještě menší samostatné celky.
- V případě stylů dávejte přednost
@mixin
ům před@extend
. Zlepší to přehlednost v kódu a sníží provázanost. - Pokud to daný jazyk umožňuje, pracujte s třídami, případně interfaces nebo abstraktními metodami.
Příklad:
Ukázkou budiž tento jednoduchý příklad @mixin
u s media queries.
Napříč našimi styly potřebujeme definovat různé chování od daného breakpointu. Jedním z možných řešení může být následující.
// _button.scss
.button {
padding: 10px;
width: 100%;
@media (min-width: 576px) {
width: auto;
}
@media (min-width: 768px) {
padding: 16px;
}
}
Kvůli konzistenci a zamezení opakujícího se kódu by bylo vhodnější předat breakpointy do proměnných. S největší pravděpodobností nás při definování media queries ani nebudou zajímat přesné hodnoty těchto mezí, takže i ty můžeme odstínit od zbytku kódu.
Mnohem sofistikovanější by mohla být tato varianta.
// _breakpoints.scss
$breakpoint-values: (
xs: 0,
sm: 36em, // 576 px
md: 48em, // 768 px
);
@mixin breakpoint-up($breakpoint) {
@if (not map-has-key($breakpoint-values, $breakpoint)) or $breakpoint == 'xs' {
@content;
}
@else {
@media (min-width: map-get($breakpoint-values, $breakpoint)) {
@content;
}
}
}
// _button.scss
.button {
padding: 10px;
width: 100%;
@include breakpoint-up(sm) {
width: auto;
}
@include breakpoint-up(md) {
padding: 16px;
}
}
Tímto řešením jsme dozajista ušetřili velké množství duplicitního kódu (DRY), učinili kód přímočařejším a jednodušším (KISS) a navíc jsme jej zobecnili. To nám přináší benefit v podobě škálovatelnosti mixinu, například na ověření retina obrazovky.
// _breakpoints.scss
@mixin breakpoint-up($breakpoint, $retina: false) {
@if (not map-has-key($breakpoint-values, $breakpoint)) or $breakpoint == 'xs' {
@if ($retina) {
@media (min-resolution: 192dpi) {
@content;
}
}
// …
}
// …
}
5. SoC
Název: Separation of Concerns (Oddělení odpovědností).
Myšlenka: Udržování souvisejících komponent pospolu s minimální provázaností se zbytkem kódu.
Popis: Pojem odpovědnost můžeme v tomto kontextu chápat jako funkcionalitu. Vzpomínáte na špagety kód, ve kterém se vše prolínalo se vším a bylo těžké najít přehlednou strukturu v kódu? Oddělení odpovědností navrhuje strukturovat kód podle dílčích odpovědností, resp. funkcionalit.
Krásnou ukázkou mohou být například design patterns (návrhové vzory). Jedním z nejstarších je MVC (Model-View-Controller). Každá z vrstev představuje jakousi komponentu, která má jasně danou funkcionalitu, jež se liší od zbylých komponent. Díky tomu změna v jedné z komponent má minimální vliv na ostatní. Navíc jsme schopni docílit lepší přehlednosti v kódu, jeho znovupoužití, rozšíření a snazšího testování dílčí komponenty.
S tímto principem úzce souvisí principy low coupling (nízká provázanost) a high cohesion (vysoká soudržnost). Provázanost nám říká, jak moc je daná komponenta svázaná s ostatními. Soudržnost určuje, jak moc spolu funkcionality v dané komponentě souvisí.
Jak na to:
- Zavádějte architekturu v kódu. Ať už se jedná o styly, databázovou vrstvu nebo celý projekt. Její přínos vám ušetří spoustu času i nervů. Vhodně sestavená architektura by se měla odrážet i v adresářové struktuře projektu.
- Využívejte enkapsulace (zapouzdření) v podobě tříd či modulů. Každá metoda, která v ní bude, by neměla vykonávat více než jednu věc.
Příklad: Vyvíjíme prezentační firemní web, který obsahuje vysouvací mobilní menu, interaktivní kontaktní formulář a v zápatí mapu z Google Maps s adresou sídla firmy.
// index.js
const nav = document.querySelector('.js-nav');
const inputName = document.querySelector('.js-input-name');
// …
// Metoda pro animaci navigace.
toggleNavigation();
// Validování dat z formuláře a následné zpracování dat.
validateForm();
// Vykreslení Google Mapy s vlastními ukazateli v mapě.
renderCustomMarksInGoogleMap();
Pro přehlednost nejsou jednotlivé metody implementovány. Kdyby byly, kolik řádků kódu tipujete, že by zabíraly? Určitě dost na to, abyste si řádně poscrollovali nebo v jednom kuse používali fulltextové vyhledávání 🙂.
// index.js
import './_contact-form.js';
import './_google-maps.js';
import './_nav.js';
// _nav.js
const nav = document.querySelector('.js-nav');
toggleNavigation();
// _contact-form.js
const inputName = document.querySelector('.js-input-name');
validateForm();
// _google-maps.js
renderCustomMarksInGoogleMap();
I na tomhle řešení by se dalo ještě leccos zlepšit, ale hlavní myšlenku principu se nám podařilo dodržet – rozdělit kód do komponent podle jejich funkcionality, což se i odrazilo ve struktuře souborů.
Závěrem
Existují i další principy, které mohou přispět ke zkvalitnění kódu a jsou stejně tak důležité jako ty výše zmíněné. Řeč je například o GRASP, SOLID, Dependency Injection, či o jednoduchých doporučení týkající se názvu proměnných, počtu řádků metody a mnoha dalších. Moc pěkné zpracování návrhových vzorů a principů můžete najít na Zdroják.cz.
Pouhé používání návrhových principů z kupky hnoje cihlu zlata neudělají. Představuje jeden z dílků skládačky, která zlepšuje kvalitu projektu. Pomáhá se zamýšlením se nad kódem samotným a přispívá tak k jeho čitelnosti a ještě lepšímu porozumění ze strany autora. Pokud se snažíme učinit kód více abstraktní, přímočařejší a jednodušší, v některých situacích musíme řešení přepsat více než jednou. Obrňte se proto trpělivostí a s vidinou lepšího kódu se jej snažte psát tak dobře, jak jen můžete.
Děkuji své druhé polovičce 👫 za zajímavé podněty a konzultace při psaní článku.
Související odkazy
- Principy objektové orientovaného návrhu (Zdroják)
- What Is Code Quality? And how to Improve Code Quality? (Perforce)
- This KISS Principle in Software Development (Everything You Need to Know)
Úvodní obrázek byl inspirován přebalem knihy Spaghetti Code od Christopha C. Cempera.
Další články od autora:
Sdílejte
Líbí se vám článek? Podpořte jej sdílením!
Komentujte
Chcete k článku něco doplnit? Našli jste chybu? Napište e-mail.