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