poznáme.it Programovanie Vývoj softvéru ako stavba babylonskej veže

Vývoj softvéru ako stavba babylonskej veže

Vývoj softvéru ako stavba babylonskej veže 1

Podľa príbehu z Biblie babylonskú vežu ľudia nedostavali preto, lebo sa nemohli dohovoriť. Keď stavitelia hovoria rôznymi jazykmi, akýkoľvek komplikovaný projekt skončí nezdarom. Tento článok ukazuje, ako je možné postupovať pri implementácii domény tak, aby bola zachovaná hlavná myšlienka Domain Driven Designu: spoločný jazyk.

Hoci doslovný preklad pojmu „ubiquitous language“ je „všadeprítomný jazyk“, pojem „spoločný jazyk“ v slovenčine lepšie zodpovedá zamýšľanému významu.

Spoločný jazyk

Domain Driven Design je rozšírený prístup k navrhovaniu objektového modelu domény. Obvykle sa diskutujú jeho stavebné bloky ako „repository“, „factory“, „value object“, „aggregate“ atď. Všetky stavebné bloky však vychádzajú z jednej základnej myšlienky: „ubiquitous language“ – t.j. z existencie spoločného (všadeprítomného) jazyka. Spoločný jazyk je množina pojmov, ktoré používajú analytici, programátori, testeri, používatelia aplikácie aj doménoví experti, keď hovoria o doméne. Pričom pre všetkých má každý pojem rovnaký význam.

Vývoj softvéru ako stavba babylonskej veže 3

Technológie nie sú súčasťou domény

Pri písaní zdrojového kódu sa musia programátori vysporiadať s množstvom technologických konceptov, ktoré s doménou nesúvisia, ale pre beh programu sú nevyhnutné (databáza, prenos údajov po sieti, messaging, logovanie, bezpečnosť). Tieto koncepty je možné držať v samostatnej vrstve tak, aby do doménového modelu nezasahovali. Veľmi často sa využíva na tieto účely technika otočenia závislostí – „dependency inversion“.

Reprezentácia pojmov

Pri implementácii pojmov z domény je dôležité nájsť čo najvernejšiu reprezentáciu pojmu domény v analýze a v zdrojovom kóde. Objektovo-orientovaný prístup má v tomto smere výhodu v tesnej väzbe medzi analýzou a implementáciou. Ak analytik nájde správnu reprezentáciu pojmu pomocou tried, objektovo-orientovaný jazyk umožňuje túto reprezentáciu preniesť do zdrojového kódu. Analytik zodpovedá za to, aby špecifikovaná trieda čo najvernejšie odrážala doménu, t.j. chápanie pojmu používateľom. Programátor zase zodpovedá za to, aby implementácia triedy mala tesnú väzbu na analýzu.

Príklad: analýza rizík

V doméne „analýza rizík“ bude existovať pojem „Riziko“ s požiadavkou na výpočet miery rizika:

„Miera rizika sa počíta na základe pravdepodobnosti a dopadu podľa nasledujúcej tabuľky:“

Miera rizika Pravdepodobnosť Dopad
Vysoká Vysoká Veľký
Stredná Vysoká Malý
Nízka Nízka Veľký, Malý

 

Vývoj softvéru ako stavba babylonskej veže 5Použitie novej technológie na projekte vývoja softvéru je typickým rizikom. Ak vývojársky tím ide prvýkrát použiť databázu „MongoDB“, projekt čelí riziku technologických problémov, ktoré má vysokú pravdepodobnosť a veľký dopad. A teda aj miera rizika je vysoká.

Analýza domény

Spoločný jazyk je v tomto diagrame zachovaný. Analytik narába s pojmami „Riziko“, „Pravdepodobnost“ a „Dopad“, pričom sa snaží pochopiť ich vzájomné vzťahy a závislosti.

Implementácia

public final class Riziko {
	
  private String nazov;
  private Pravdepodobnost pravdepodobnost;
  private Dopad dopad;
	
  public MieraRizika vypocetMieryRizika() {
    if ( pravdepodobnost == Pravdepodobnost.VYSOKA ) {
	return dopad == Dopad.MALY ? MieraRizika.NIZKA : MieraRizika.STREDNA;
    } else {
	return MieraRizika.NIZKA;
    }
  }
}

Spoločný jazyk je zachovaný aj v implementácii na úrovni pojmov. Ale metóda na výpočet miery rizika nepoužila spoločný jazyk, pretože použila algoritmický výpočet tam, kde bolo zadanie špecifikované deklaratívne vo forme tabuľky.

Táto implementácia má dva zásadné problémy:

  1. Zdrojový kód nezodpovedá zadaniu od analytika (hoci je implementovaný správne).
  2. Trieda Riziko má zodpovednosť za výpočet miery rizika.

Oba zásadné problémy sa naplno prejavia pri prvej požiadavke na zmenu.

Požiadavka na zmenu

„Malé rozšírenie tabuľky pre výpočet miery rizika:“

Miera rizika Pravdepodobnosť Dopad
Vysoká Vysoká Veľký
Stredná Stredná Veľký
Stredná Stredná Malý
Nízka Nízka Malý

 

V porovnaní s pôvodnou implementáciou navrhol teda používateľ zaviesť strednú pravdepodobnosť, ktorej zodpovedá aj stredná miera rizika.

Dopady na existujúci kód

Takáto požiadavka na zmenu má dva nepríjemné dopady na existujúci zdrojový kód:

  1. Vedie k modifikácii triedy Riziko, čo je v doméne „Analýza rizík“ zásadný pojem, na ktorý budú mať závislosti iné triedy. Akákoľvek zmena tejto triedy môže zasiahnuť všetky ostatné, ktoré na nej závisia. Dôsledkom je nutnosť komplexne pretestovať celý systém.
  2. Keďže pôvodná požiadavka nebola implementovaná v zmysle analýzy, bude potrebné celý algoritmus znovu premyslieť a prerobiť. V tomto príklade je algoritmus triviálny a teda ani jeho opätovné napísanie nebude náročné.

Refactoring, alebo ako to malo byť správne

Obom neželaným dopadom na existujúci kód sa dalo vyhnúť správnym návrhom modelu. Princíp jednoduchej zodpovednosti („single responsibility principle“) v tomto príklade hovorí, že trieda Riziko nemôže mať metódu vypocetMieryRizika, lebo riziko tým získava novú zodpovednosť: vie vypočítať mieru rizika. Správny model teda vyzerá takto:

Vývoj softvéru ako stavba babylonskej veže 7Trieda VypocetMieryRizika je funkcia, ktorá zo vstupných parametrov Pravdepodobnost a Dopad vypočíta výsledok MieraRizika. V tomto modeli trieda Riziko vôbec nepozná pojem MieraRizika, pretože nie je zaťažená logikou ohľadom výpočtu.

Druhá zásadná úprava spočíva v zmene zdrojového kódu:

public enum VypocetMieryRizika {
	VYSOKA ( MieraRizika.VYSOKA, Pravdepodobnost.VYSOKA, Dopad.VELKY ),
	STREDNA ( MieraRizika.STREDNA, Pravdepodobnost.VYSOKA, Dopad.MALY ),
	NIZKA1 ( MieraRizika.NIZKA, Pravdepodobnost.NIZKA, Dopad.VELKY ),
	NIZKA2 ( MieraRizika.NIZKA, Pravdepodobnost.NIZKA, Dopad.MALY );

	private final MieraRizika miera;
	private final Pravdepodobnost pravdepodobnost;
	private final Dopad dopad;
	
	private VypocetMieryRizika( MieraRizika miera, Pravdepodobnost pravdepodobnost, Dopad dopad ) {
		this.miera = miera;
		this.pravdepodobnost = pravdepodobnost;
		this.dopad = dopad;
	}

	public static Optional<MieraRizika> vypocet(Pravdepodobnost pravdepodobnost, Dopad dopad ) {
		return Arrays.stream(	VypocetMieryRizika.values() )
			.filter( vypocet -> vypocet.dopad == dopad && vypocet.pravdepodobnost == pravdepodobnost )
			.map( vypocet -> vypocet.miera )
			.findAny();
	}

Výhodnou takto napísaného zdrojového kódu je presný obraz analytického zadania. Analytik sa s používateľom dohodol na tabuľkovom vyhodnocovaní výpočtu miery rizika, čiže použili deklaratívny prístup k analýze. Pre zachovanie spoločného jazyka je vhodné udržať deklaratívny prístup aj v implementácii. Kontrola kódu oproti zadaniu je veľmi jednoduchá. Cenou za to je však komplikovanejšia metóda „vypocet“. Prvá implementácia pomocou jedného príkazu „if“ pôsobí ďaleko jednoduchšie.

Výhodnou takto napísaného zdrojového kódu je presný obraz analytického zadania. Analytik sa s používateľom dohodol na tabuľkovom vyhodnocovaní výpočtu miery rizika, čiže použili deklaratívny prístup k analýze. Pre zachovanie spoločného jazyka je vhodné udržať deklaratívny prístup aj v implementácii. Kontrola kódu oproti zadaniu je veľmi jednoduchá. Cenou za to je však komplikovanejšia metóda „vypocet“. Prvá implementácia pomocou jedného príkazu „if“ pôsobí ďaleko jednoduchšie.

Požiadavka na zmenu ešte raz

Ako by vyzerala požiadavka na zmenu, ak by bol model navrhnutý správne?

V správnom modeli by vôbec nezasiahla triedu Riziko, čiže by nemala nečakané dopady na iné triedy, ktoré s objektami typu Riziko pracujú. Prípadné chyby v implementácii by mohli ovplyvniť spôsob výpočtu miery rizika, ale iné nechcené dôsledky by nastať nemohli.

A v druhom rade by verne kopírovala zmenu analytických podkladov:

        VYSOKA (MieraRizika.VYSOKA, Pravdepodobnost.VYSOKA, Dopad.VELKY),
	STREDNA1 (MieraRizika.STREDNA, Pravdepodobnost.STREDNA,Dopad.VELKY),
	STREDNA2 (MieraRizika.STREDNA, Pravdepodobnost.STREDNA,Dopad.MALY),
	NIZKA1 (MieraRizika.NIZKA, Pravdepodobnost.NIZKA, Dopad.VELKY),
	NIZKA2 (MieraRizika.NIZKA, Pravdepodobnost.NIZKA, Dopad.MALY)

V uvedenej tabuľke chýba kombinácia pre vysokú pravdepodobnosť a malý dopad. Rovnakou chybou teda trpí aj zdrojový kód. Avšak chyby takéhoto typu sú ľahko komunikovateľné: keď si analytik, používateľ a programátor sadnú za spoločný stôl, budú mať v predstave rovnaký model a budú používať spoločný jazyk.

Desivý záver: nová požiadavka na zmenu

Namiesto tradičného happy endu končí tento článok desivou požiadavkou na zmenu. Pretože iné oddelenie sa rozhodlo úspešný softvér na analýzu rizík používať tiež.

  • Používateľ: „U nás sa miera rizika počíta veľmi jednoducho: pravdepodobnosť krát dopad.“
  • Analytik: „Krát?“
  • Používateľ: „Veď pravdepodobnosť je číslo od nula do jedna. A dopad je od 0 do 10.“

A úlohou analytika a programátora bude nájsť s používateľom opäť spoločný jazyk. Lebo veď stringy sa násobiť nedajú.


Dobrý článok? Chceš dostávať ďalšie?

Už viac ako 6 200 ITečkárov dostáva správy e-mailom. Nemusíš sa báť, nie každé ráno. Len občasne.



Súhlasím so spracovaním mojich osobných údajov. ( Viac informácií. )

Tvoj email neposkytneme 3tím stranám. Posielame naňho len informácie z robime.it. Kedykoľvek sa môžeš odhlásiť.

Zdeno Jašek
Zdeno Jašek
Pracujem ako Solution Architect vo firme PosAm a programovaním sa zaoberám takmer 30 rokov. Prešiel som jazykmi Basic, Assembler, Pascal, Object Pascal, Lisp, Prolog, Magic, MUMPS, Clipper, Paradox a Java, z ktorých najmilšia mi je Java. Pracoval som hlavne ako softvérový architekt, ale aj ako programátor, analytik, dizajnér a projektový manažér. Pri vývoji softvéru sa mi najviac páči navrhovanie objektového dizajnu – obzvlášť pre zložité aplikácie. Svoje blogy chcem zamerať na postupy pri vytváraní objektového návrhu aplikácie a ich technologickej realizácii v podobe hexagonálnej architektúry a microservices.

Senior Python Developer/ka

Úspešný americký startup, ktorý umožňuje vývojárom vytvárať aplikácie a rozhrania API bez akýchkoľvek časových, priestorových a cloudových architektúr hľadá...

C#/.NET & React Fullstack Developer/ka

FullStack Developer/ka | Vývoj nového produktu | Security Pracoval/a by si na TPP s platom 2400 - 2800 EUR/brutto mesačne...

Data Visualization Senior Analyst

Pre medzinárodnú IT spoločnosť hľadáme Senior Data Visualization Analyst. Pracujú na veľkých projektoch pre top 100 svetových spoločností. Ide o...

Splunk Data Engineer

Chcel by si dlhodobú spoluprácu, dobré pracovné podmienky, seriózny prístup? Medzinárodná IT spoločnosť hľadá Splunk Data Engineera. Pracujú na veľkých...

Senior Java Developer

Úspešný americký startup, ktorý umožňuje vývojárom vytvárať aplikácie a rozhrania API bez akýchkoľvek časových, priestorových a cloudových architektúr hľadá...

Backend/Node.js Developer / REMOTE

Pre mladý startup vyvíjajúci softvér, ktorý prispieva hráčom k lepšiemu zážitku z hrania, hľadáme Backend/Node.js Developera. Založili ho dvaja...

Julia Developer / REMOTE

Projekt pre nadšencov Julia a machine learning. Pre spoločnosť, ktorá používa matematické metódy a metódy AI / ML na...

Máte vo firemných stretnutiach chaos? Česká firma našla riešenie, teraz s aplikáciou dobývajú svet

Vyvíjajú ju v Českej republike, pracujú na nej aj hendikepovaní kolegovia a používajú ju firmy v rôznych krajinách sveta....

Ondrej Kubovič – Digitálna bezpečnosť a riziká na internete

Tentokrát sme sa porozprávali s Ondrejom Kubovičom, špecialistom v spoločnosti ESET na populárnu tému digitálnej bezpečnosti. Ondrej nám uviedol...

Slovenskí tvorcovia hier dosiahli v roku 2020 rekordný obrat 72 miliónov EUR, zamestnávali 870 ľudí

Slovenský herný priemysel nezastavila ani pandémia, v roku 2020 opäť výrazne rástol. Podľa štatistík Slovak Game Developers Association prekonal...

Čítaj ďalej:

Dobrý článok? Chceš dostávať ďalšie?

Už viac ako 6 200 ITečkárov dostáva správy e-mailom. Nemusíš sa báť, nie každé ráno. Len občasne.

Súhlasím so spracovaním mojich osobných údajov. ( Viac informácií. )

Tvoj email neposkytneme 3tím stranám. Posielame naňho len informácie z robime.it. Kedykoľvek sa môžeš odhlásiť.