SOLID princípy a JavaScript
2026-10
SOLID princípy a JavaScript
Milan Herda
2026-10
SOLID princípy a TypeScript
Milan Herda
2026-10
Čo je SOLID?
Skratka, pod ktorou sa skrýva
päť základných princípov
dobrej softvérovej architektúry
Čo je SOLID?
Päť základných princípov = mali by sme všetci poznať
Poznáme? 😅
- Single Responsibility Principle
- Open-Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Koľkí z vás by vedeli kolegovi vysvetliť:
Čo je SOLID?
Nemusíme poznať definíciu na to, aby sme niečo úspešne používali.
Avšak, keď tú definíciu poznáme, tak:
- premýšľame jasnejšie
- pýtame sa a hľadáme presnejšie
- vidíme ďalej
1. Single Responsibility Principle
Princíp jedinej zodpovednosti
Definícia
Modul by mal mať jeden (a iba jeden) dôvod pre svoju zmenu.
💡Pomôcka pre uvažovanie:
Veci, ktoré sa menia z rovnakých dôvodov, dávame k sebe.
Tie, ktoré sa menia z odlišných dôvodov, patria do odlišných modulov.
📝 Príklady:
- zmena dizajnu by nemala ovplyvniť databázovú štruktúru
- výmena frameworku nesmie vyžadovať zmeny v biznis modeli firmy
Definícia
Modul by sa mal zodpovedať jednému (a iba jednému) aktérovi.
🤔 Kto je aktér?
Aktér je jedna alebo viac osôb,
ktoré chcú rovnakú zmenu v systéme.
📝 Príklady:
- finančné oddelenie
- právne oddelenie
- UX oddelenie
- IT bezpečnosť
- technický architekt
- používatelia
SRP
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
const topTenBooks = [];
const result = await fetch("/api/books.json");
if (result.ok) {
const data = await result.json();
topTenBooks.push(...data.items);
}
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App topTenBooks={topTenBooks} />
</StrictMode>,
);Koľko zodpovedností alebo dôvodov na zmenu vidíte?
🔍
- zodpovednosť: získanie dát
- dôvod zmeny: spôsob získavania dát
- zodpovednosť: inicializácia aplikácie
- dôvod zmeny: výmena frameworku
Ako refaktorovať?
🤔
Oddelenie do samostatných modulov
// src/main.ts
import loadTopTenBooks from './books/repo/loadTopTen';
import mountApp from './infra/fw/mountApp';
const books = loadTopTenBooks();
mountApp(books);SRP
// src/book/repo/loadTopTen.ts
export async function loadTopTenBooks() {
const topTenBooks: Book[] = [];
const result = await fetch("/api/books.json");
if (result.ok) {
const data = await result.json();
topTenBooks.push(...data.items);
}
return topTenBooks;
}🎉
// src/infra/fw/mountApp.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { type Book } from "./dto/Book";
export function mountApp(
topTenBooks: Book[]
) {
const root = createRoot(
document.getElementById("root")!
);
root.render(
<StrictMode>
<App topTenBooks={topTenBooks} />
</StrictMode>
),
};Po oddelení do vlastných modulov
// src/main.ts
import loadTopTenBooks from './books/repo/loadTopTen';
import mountApp from './infra/fw/mountApp';
const books = loadTopTenBooks();
mountApp(books);👍
Čo sme týmto získali?
- jasnejšie oddelenie hraníc
- bezpečnejšie zmeny
- potenciál na jednoduchšie výmeny modulov alebo celých aplikačných vrstiev
2. Open-Closed Principle
Princíp otvorenej uzavretosti
Definícia
Každý softvérový artefakt (trieda, modul, funkcia) by mal byť otvorený pre rozširovanie, ale uzatvorený pre úpravy.
💡Pomôcka pre uvažovanie:
Píšme kód tak, aby sme vedeli modifikovať jeho správanie aj bez jeho prepisu.
📝 Príklady:
- používajme parametre a konfiguráciu
- vyhýbajme sa detailom a konkrétnostiam v kóde (adresy, texty, hodnoty...)
K tomu potrebujeme oddeliť konkrétne časti od všeobecných.
// src/book/repo/loadTopTen.ts
export async function loadTopTenBooks() {
const data = await loadData("/api/books.json");
return data.items.map(
(bookData) => createBook(bookData)
);
}🔍
Vidíte konkrétnosti, ktoré vieme oddeliť?
// src/book/repo/loadTopTen.ts
export async function loadTopTenBooks(sourceUrl: string = "/api/books.json") {
const data = await loadData(sourceUrl);
return data.items.map(
(bookData) => createBook(bookData)
);
}🎉
Oddeľme túto konkrétnosť od všeobecného kódu
// src/book/repo/loadTopTen.ts
export async function loadTopTenBooks(sourceUrl: string = "/api/books.json") {
const data = await loadData(sourceUrl);
return data.items.map(
(bookData) => createBook(bookData)
);
}Čo sme týmto získali?
👍
- teraz už dokážeme načítať zoznam kníh aj z inej URL
- takže už vieme rozšíriť správanie funkcie bez dodatočnej zmeny jej kódu
3. Liskov Substitution Principle
Liskovej princíp nahraditeľnosti
Definícia
💡Pomôcka pre uvažovanie:
Potomok musí byť dobrou náhradou svojho rodiča.
Ak z nejakého kódu odoberiem rodiča a na to isté miesto dosadím jeho potomka, kód musí fungovať bez chyby.
Nemôžeme sprísňovať požiadavky na argumenty a nemôžeme rozširovať typy návratovej hodnoty.
interface Encoder {
encode: (value: string | number) => string;
}
export async function saveEncrypted(
encoder: Encoder
) {
const secrets = readRawSecrets();
const encoded: string[] = secrets.map(
(secret) => {
return encoder.encode(secret);
}
);
// ...
}🔍
Ktoré objekty sú dobrým potomkom typu Encoder?
const encoderA: Encoder {
encode(value: string | number): string {
// ...
}
}
const encoderB: Encoder {
encode(value: string): string {
// ...
}
}
const encoderC: Encoder {
encode(
value: string | number | boolean
): string {
// ...
}
}✅
✅
❌
🔍
Ktoré funkcie sú dobrým potomkom typu Encode?
type Encode = (value: string | number) => string;
function encode(value: string | number): string {
// ...
}
function encode(value: string): string {
// ...
}
function encode(
value: string | number | boolean
): string {
// ...
}
function encode(value: string | number): string | number {
// ...
}
function encode(value: string | number): "foo" | "bar" {
// ...
}✅
✅
❌
✅
❌
Totožný podpis
Sprísnený vstupný kontrakt
Zvoľnený vstupný kontrakt
Zvoľnený výstupný kontrakt
Sprísnený výstupný kontrakt
Čo sme týmto získali?
👍
- časti kódu sú navzájom ľahko vymeniteľné
- vieme jednoducho upraviť správanie výmenou modulu (triedy, objektu, funkcie)
4. Interface Segregation Principle
Princíp oddeľovania rozhraní
Definícia
Žiaden kód by nemal závisieť na niečom, čo nepoužíva.
function isUnitEngaged(unit: Unit, gameState: GameState) {
return (
gameState.engagedUnitIds.find(id => id === unit.id) !== undefined
);
}interface Unit {
id: number;
typeId: UnitTypeId;
label: string;
sideId: SideId;
position: Position;
occupiedHexes: Position[];
isInColumn: boolean;
rotation: CounterRotation;
// ...
}interface GameState {
stateName: PossibleStates;
round: number;
phase: PhaseName;
activeSide: SideId;
units: Scenario['units'];
leaders: Scenario['leaders'];
engagedUnitIds: Unit['id'][];
// ...
}Čo v skutočnosti potrebuje funkcia isUnitEngaged?
🔍
function isUnitEngaged(unit: Pick<Unit, 'id'>, gameState: Pick<GameState, 'engagedUnitIds'>) {
return (
gameState.engagedUnitIds.find(id => id === unit.id) !== undefined
);
}Čo v skutočnosti potrebuje funkcia isUnitEngaged?
🔍
Vypýtajme si iba tie property a metódy, s ktorými pracujeme
Čo sme týmto získali?
👍
- kód závisí iba na tom, čo potrebuje
- jednoduchšie testovanie
- moduly vieme ľahšie vymieňať, lebo je medzi nimi menej závislostí a tieto sú viditeľnejšie
5. Dependency Inversion Principle
Princíp obrátenia závislostí
Definícia
Vysokoúrovňové moduly by nemali importovať nič z nízkoúrovňových. Oboje by mali závisieť na abstrakciách.
Abstrakcie by nemali závisieť na detailoch. Detaily by mali závisieť na abstrakciách.
Sme zvyknutí závisieť na detailoch
import prismaClient from '@local/infrastructure/prisma/client';
async function BookDetailPage({ bookId }: { bookId: string}) {
// ...
const book = await prismaClient.book.findUnique({
where: {
id: bookId,
}
});
// ...
}- UI vrstva má priamu vedomosť o databáze
- vie s ňou priamo komunikovať
- stávajú sa tak pevne prepojené
- a nič z toho nie je dobré
Odstráňme implementačný detail
import { findBook } from '@local/infrastructure/book/repository';
async function BookDetailPage({ bookId }: { bookId: string}) {
// ...
const book = await findBook(bookId);
// ...
}- zrušili sme závislosť priamo na databáze
- repozitárová funkcia findBook skrýva svoju implementáciu
- medzi UI a databázovým konektorom tak neexistuje priame prepojenie
Skúsme ešte lepšie
type FindBookFunc = (bookId: string) => Promise<Book | undefined>;
async function BookDetailPage(
{ bookId, findBook }: { bookId: string, findBook: FindBookFunc }
) {
// ...
const book = await findBook(bookId);
// ...
}- Funkcia teraz závisí iba na type
- Typ je abstraktný
- Zmizlo aj nepriame prepojenie na databázu
Skúsme ešte lepšie
type FindBookFunc = (bookId: string) => Promise<Book | undefined>;
async function BookDetailPage(
{ bookId, findBook }: { bookId: string, findBook: FindBookFunc }
) {
// ...
const book = await findBook(bookId);
// ...
}Kde ale teraz naimportujeme repozitár a pošleme ho ako argument do BookDetail Page?
- To je starosť volajúceho kódu
- controller, funkcia "main" a pod.
- Dependency Injection Container
🤔
Čo sme týmto získali?
👍
- jednotlivé časti kódu sú izolované
- jednoduchšie testovanie
- moduly vieme ľahšie vymieňať, lebo je jednoduchšie vyrobiť implementáciu niečoho abstraktného, ako kopírovať detailné API
Záver
Čo je SOLID?
- Single Responsibility Principle
- modul sa zodpovedá iba jednému aktérovi a má iba jeden dôvod pre zmenu
- Open-Closed Principle
- modul je otvorený pre rozširovanie bez potreby zmeny svojho kódu
- Liskov Substitution Principle
- modul je dobrým potomkom svojich rodičov
- Interface Segregation Principle
- modul závisí iba na tom, čo používa
- Dependency Inversion Principle
- modul nezávisí na konkrétnostiach
Päť základných princípov dobrej softvérovej architektúry:
Výhody
- v kóde sa nám zrazu samé od seba objavujú konštrukcie známe ako Návrhové vzory
- v architektúre aplikácie vznikajú viditeľné a nezávislé vrstvy
- kód sa ľahko testuje
- moduly sa jendoducho nahradzujú
- aplikácia sa ľahko udržiava a rozširuje
Pri dôslednej aplikácii všetkých princípov:
Ďakujem za pozornosť
Milan Herda
- programátor
- Alma Career
- Lead Engineer
- Vedúci "Expertného tímu pre frontend a JavaScript"
- školím a vzdelávam
- programujem webové hry
- mám rád spoločenské hry
- https://perunhq.org/
Dva krátke bonusy
SOLID a OOP
SOLID princípy sú často vysvetľované na triedach.
Takže vzniká dojem, že sa týkajú iba OOP programovania.
Sú však aplikovateľné aj mimo tried a objektov.
Potrebujeme toto vedieť v dobe AI?
🤔
🤔
- závisí od vás
- čo máte na programovaní radi?
- remeslá nevymierajú príchodom efektívnejšej technológie, ale stratou záujmu u remeselníkov
Copy of SOLID princípy a JavaScript, 2026-09-13 - po zmenách
By Milan Herda
Copy of SOLID princípy a JavaScript, 2026-09-13 - po zmenách
- 2