Programovanie

Log4j ortogonálnosť príkladom

Ortogonálnosť je pojem, ktorý sa často používa na popis modulárneho a udržiavateľného softvéru, ale je ľahšie pochopiteľný prostredníctvom prípadovej štúdie. V tomto článku Jens Dietrich demonštruje ortogonalitu a niektoré súvisiace princípy návrhu demonštráciou ich použitia v populárnej knižnici nástrojov Log4j. Diskutuje tiež o tom, ako Log4j v niekoľkých prípadoch porušuje ortogonálnosť, a diskutuje o možných riešeniach nastolených problémov.

Koncept ortogonality je založený na gréckom slove ortogóniovi, čo znamená „v pravom uhle“. Často sa používa na vyjadrenie nezávislosti medzi rôznymi dimenziami. Keď sa objekt pohybuje pozdĺž X-osa v trojrozmernom priestore, jeho r a z súradnice sa nemenia. Zmena v jednej dimenzii nespôsobí zmenu v inej dimenzii, čo znamená, že jedna dimenzia nemôže spôsobiť vedľajšie účinky pre ostatných.

To vysvetľuje, prečo sa koncept ortogonality často používa na popis modulárneho a udržiavateľného dizajnu softvéru: premýšľanie o systémoch ako o bodoch vo viacrozmernom priestore (vytvorených nezávislými, ortogonálnymi rozmermi) pomáha vývojárom softvéru zabezpečiť, aby naše zmeny jedného aspektu systému nebude mať vedľajšie účinky pre iného.

Stáva sa, že Log4j, populárny open source protokolovací balík pre Javu, je dobrým príkladom modulárneho dizajnu založeného na ortogonálnosti.

Rozmery Log4j

Ťažba dreva je iba obľúbenejšou verziou servera System.out.println () vyhlásenie a Log4j je obslužný balík, ktorý abstrahuje od mechaniky protokolovania na platforme Java. Funkcie Log4j okrem iného umožňujú vývojárom vykonávať nasledujúce činnosti:

  • Prihláste sa do rôznych príloh (nielen do konzoly, ale aj do súborov, umiestnení v sieti, relačných databáz, obslužných programov denníka operačného systému atď.)
  • Prihláste sa na niekoľkých úrovniach (napríklad CHYBA, VÝSTRAHA, INFO a DEBUG)
  • Centrálne riadte, koľko informácií sa zaznamená na danej úrovni protokolovania
  • Použite rôzne rozloženia na definovanie spôsobu vykreslenia udalosti protokolovania do reťazca

Aj keď Log4j má ďalšie funkcie, zameriam sa na tieto tri dimenzie jeho funkčnosti, aby som preskúmal koncept a výhody ortogonality. Upozorňujeme, že moja diskusia je založená na Log4j verzie 1.2.17.

Log4j na JavaWorld

Získajte prehľad Log4j a naučte sa, ako písať svoje vlastné vlastné dodatky Log4j. Chcete viac Java tutoriálov? Dostať Newsletter Enterprise Java doručené do vašej doručenej pošty.

Považovanie typov Log4j za aspekty

Prílohy, úroveň a rozloženie sú tri aspekty Log4j, ktoré možno považovať za nezávislé dimenzie. Používam výraz aspekt tu ako synonymum pre znepokojenie, čo znamená zaujímavý bod alebo zameranie na program. V takom prípade je ľahké definovať tieto tri obavy na základe otázok, ktorým sa každá z nich venuje:

  • Dodatok: Kam by sa mali odosielať údaje udalosti protokolu na zobrazenie alebo uloženie?
  • Rozloženie: Ako by sa mala prezentovať denníková udalosť?
  • Úroveň: Ktoré udalosti denníka by sa mali spracovať?

Teraz skúste zvážiť tieto aspekty spoločne v trojrozmernom priestore. Každý bod v tomto priestore predstavuje platnú konfiguráciu systému, ako je to znázornené na obrázku 1. (Upozorňujeme, že ponúkam mierne zjednodušený pohľad na Log4j: Každý bod na obrázku 1 v skutočnosti nie je globálnou konfiguráciou celého systému, ale konfiguráciou pre jeden. konkrétny záznamník. Samotné záznamníky možno považovať za štvrtú dimenziu.)

Zoznam 1 je typický útržok kódu implementujúci Log4j:

Zoznam 1. Príklad implementácie Log4j

// nastav logovanie! Logger logger = Logger.getLogger ("Foo"); Appender appender = nový ConsoleAppender (); Rozloženie rozloženia = nový org.apache.log4j.TTCCLayout () appender.setLayout (rozloženie); logger.addAppender (appender); logger.setLevel (Level.INFO); // spustiť logovanie! logger.warn („Hello World“);

Čo si želám, aby ste si všimli na tomto kóde, je to, že je ortogonálny: mohli by ste zmeniť prílohu, rozloženie alebo aspekt úrovne bez porušenia kódu, ktorý by zostal úplne funkčný. V ortogonálnom prevedení je každý bod v danom priestore programu platnou konfiguráciou systému. Nie je dovolené obmedzovať, ktoré body v priestore možných konfigurácií sú platné alebo nie.

Ortogonalita je silný koncept, pretože nám umožňuje vytvoriť relatívne jednoduchý mentálny model pre prípady použitia zložitých aplikácií. Najmä sa môžeme zamerať na jednu dimenziu a ignorovať ďalšie aspekty.

Testovanie je bežný a známy scenár, v ktorom je užitočná ortogonálnosť. Funkčnosť úrovní protokolov môžeme otestovať pomocou vhodnej pevnej dvojice pripájača a rozloženia. Ortogonalita nám zaručuje, že nás nečakajú žiadne prekvapenia: úrovne denníka budú fungovať rovnako s ľubovoľnou kombináciou doplnku a rozloženia. Nielen, že je to pohodlné (je toho ešte dosť potrebné vykonať), ale je to aj nevyhnutné, pretože by nebolo možné testovať úrovne protokolov s každou známou kombináciou prílohy a rozloženia. Platí to najmä vzhľadom na to, že Log4j je rovnako ako mnoho softvérových nástrojov a nástrojov navrhnutý tak, aby ho mohli rozširovať tretie strany.

Zníženie zložitosti, ktoré ortogonálnosť prináša softvérovým programom, je podobné tomu, ako sa používajú dimenzie v geometrii, kde sa komplikovaný pohyb bodov v n-dimenzionálnom priestore rozkladá na relatívne jednoduchú manipuláciu s vektormi. Celé pole lineárnej algebry je založené na tejto silnej myšlienke.

Navrhovanie a kódovanie ortogonality

Ak sa teraz pýtate, ako navrhnúť a kódovať ortogonalitu do svojich programov, potom ste na správnom mieste. Kľúčovou myšlienkou je použiť abstrakcia. Každá dimenzia ortogonálneho systému sa zameriava na jeden konkrétny aspekt programu. Takúto dimenziu bude obvykle predstavovať a typu (trieda, rozhranie alebo výpočet). Najbežnejším riešením je použiť abstraktný typ (rozhranie alebo abstraktná trieda). Každý z týchto typov predstavuje dimenziu, zatiaľ čo inštancia typu predstavuje body v rámci danej dimenzie. Pretože abstraktné typy nemožno priamo vytvoriť inštanciou, sú potrebné aj konkrétne triedy.

V niektorých prípadoch sa bez nich zaobídeme. Napríklad nepotrebujeme konkrétne triedy, keď je typ iba značením a nezakrýva správanie. Potom môžeme iba vytvoriť inštanciu typu predstavujúceho samotnú dimenziu a často preddefinovať pevnú množinu inštancií, a to buď pomocou statických premenných, alebo pomocou typu explicitného výčtu. V zozname 1 by sa toto pravidlo vzťahovalo na dimenziu „úroveň“.

Obrázok 3. Vo vnútri dimenzie Level

Všeobecným pravidlom ortogonality je vyhnúť sa odkazom na konkrétne konkrétne typy predstavujúce ďalšie aspekty (dimenzie) programu. Toto vám umožňuje písať všeobecný kód, ktorý bude fungovať rovnako pre všetky možné inštancie. Takýto kód môže stále odkazovať na vlastnosti inštancií, pokiaľ sú súčasťou rozhrania typu definujúceho dimenziu.

Napríklad v Log4j abstraktný typ Rozloženie definuje metódu ignoresThrowable (). Táto metóda vracia logickú hodnotu, ktorá označuje, či rozloženie môže vykresliť stopy zásobníka výnimiek alebo nie. Ak prihlasovateľ použije rozloženie, bolo by úplne v poriadku napísať podmienený kód ignoresThrowable (). Napríklad prihlasovateľ súboru mohol vytlačiť stopy zásobníka výnimiek System.err pri použití rozloženia, ktoré nedokáže spracovať výnimky.

Podobným spôsobom a Rozloženie implementácia by sa mohla vzťahovať na konkrétny prípad Úroveň pri vykresľovaní udalostí protokolovania. Napríklad, ak bola úroveň protokolu Úroveň. CHYBA, implementácia rozloženia na základe HTML by mohla zabaliť správu protokolu do značiek, ktoré ju vykreslia červenou farbou. Ide opäť o to, že Úroveň. CHYBA je definované Úroveň, typ predstavujúci dimenziu.

Mali by ste sa však vyhnúť odkazom na konkrétne implementačné triedy pre iné dimenzie. Ak prihlasovateľ používa rozloženie, potom to nemusí vedieť aký druh rozloženia to je. Obrázok 4 zobrazuje dobré a zlé referencie.

Niekoľko vzorov a rámcov uľahčuje predchádzanie závislostiam od typov implementácie, vrátane vkladania závislostí a vzoru vyhľadávača služieb.

Porušovanie ortogonality

Celkovo je Log4j dobrým príkladom použitia ortogonality. Niektorý kód v Log4j však tento princíp porušuje.

Log4j obsahuje prílohu s názvom JDBCAppender, ktorý slúži na prihlásenie do relačnej databázy. Vzhľadom na škálovateľnosť a popularitu relačnej databázy a skutočnosť, že vďaka tomu je možné ľahko vyhľadávať udalosti protokolu (pomocou dotazov SQL), JDBCAppender je dôležitý prípad použitia.

JDBCAppender je určený na riešenie problému s prihlásením do relačnej databázy premenou udalostí protokolu na SQL VLOŽTE Vyhlásenia. Tento problém rieši pomocou a PatternLayout.

PatternLayout používa šablónu, aby používateľovi poskytla maximálnu flexibilitu pri konfigurácii reťazcov generovaných z udalostí protokolu. Šablóna je definovaná ako reťazec a premenné použité v šablóne sú inštancované z udalostí protokolu za behu, ako je uvedené v zozname 2.

Výpis 2. PatternLayout

Reťazcový vzor = "% p [@% d {dd MMM rrrr HH: mm: ss} v% t]% m% n"; Rozloženie rozloženia = nový org.apache.log4j.PatternLayout (vzor); appender.setLayout (rozloženie);

JDBCAppender používa a PatternLayout so vzorom, ktorý definuje SQL VLOŽTE vyhlásenie. Nasledujúci kód je možné použiť najmä na nastavenie použitého príkazu SQL:

Výpis 3. Príkaz vloženia SQL

public void setSql (String s) {sqlStatement = s; if (getLayout () == null) {this.setLayout (nový PatternLayout (s)); } else {((PatternLayout) getLayout ()). setConversionPattern (s); }}

V tomto kóde je zabudovaný implicitný predpoklad, že rozloženie, ak je nastavené pred použitím setLayout (rozloženie) metóda definovaná v Dodatok, je v skutočnosti inštanciou PatternLayout. Z hľadiska ortogonality to znamená, že zrazu veľa bodov v 3D kocke, ktoré používajú JDBCAppender s iným rozložením ako PatternLayout už nepredstavujú platné konfigurácie systému! To znamená, že akékoľvek pokusy o nastavenie reťazca SQL s iným rozložením by mali za následok výnimku za behu (obsadenie triedy).

Obrázok 5. JDBCAppender porušuje ortogonalitu

Existuje ešte ďalší dôvod JDBCAppenderdizajn je otázny. JDBC má vlastné vyhlásenia pripravené pre šablónový modul. Používaním PatternLayout, šablónový engine je však obídený. Je to poľutovaniahodné, pretože predkompilované súbory JDBC pripravovali vyhlásenia, ktoré viedli k významnému zlepšeniu výkonu. Bohužiaľ to nie je ľahké vyriešiť. Zjavným prístupom by bolo určiť, v akom rozložení sa dá použiť JDBCAppender zrušením nastavovača nasledujúcim spôsobom.

Výpis 4. Prepísanie setLayout ()

public void setLayout (rozloženie rozloženia) {if (rozloženie instanceOf PatternLayout) {super.setLayout (rozloženie); } else {hodiť novú IllegalArgumentException ("Rozloženie je neplatné"); }}

Tento prístup má, bohužiaľ, aj problémy. Metóda v zozname 4 vrhá runtime výnimku a aplikácie volajúce túto metódu nemusia byť pripravené ju zachytiť. Inými slovami, setLayout (rozloženie rozloženia) metóda nemôže zaručiť, že nebude vyvolaná žiadna runtime výnimka; oslabuje preto záruky (dodatočné podmienky) poskytnuté metódou, ktorá je nadradená. Ak sa na to pozrieme z hľadiska predpokladov, setLayout vyžaduje, aby rozloženie bolo inštanciou PatternLayout, a preto má silnejšia predpoklady ako metóda, ktorú prepíše. Tak či onak, porušili sme základný objektovo orientovaný princíp návrhu, ktorým je liskovský substitučný princíp používaný na ochranu dedičstva.

Alternatívne riešenia

Skutočnosť, že neexistuje jednoduché riešenie, ktoré by opravilo návrh JDBCAppender naznačuje, že v práci existuje hlbší problém. V tomto prípade je úroveň abstrakcie zvolená pri navrhovaní základných abstraktných typov (najmä Rozloženie) je potrebné doladiť. Základná metóda definovaná v Rozloženie je formát (udalosť LoggingEvent). Táto metóda vráti reťazec. Pri prihlásení do relačnej databázy však nemusí byť vygenerovaná n-tica hodnôt (riadok), a nie reťazec.

Jedným z možných riešení by bolo použitie sofistikovanejšej dátovej štruktúry ako návratového typu pre formát. To by však znamenalo ďalšiu réžiu v situáciách, keď by ste skutočne chceli vygenerovať reťazec. Museli by sa vytvoriť ďalšie medziľahlé objekty a potom by sa zbierať odpadky, čo by ohrozilo výkon rámca protokolovania. Používanie sofistikovanejšieho typu návratu by tiež sťažilo pochopenie Log4j. Jednoduchosť je veľmi žiaducim dizajnovým cieľom.

Ďalším možným riešením by bolo použitie „vrstvenej abstrakcie“ pomocou dvoch abstraktných typov, Dodatok a CustomizableAppender ktorá sa rozširuje Dodatok. Iba CustomizableAppender by potom definoval metódu setLayout (rozloženie rozloženia). JDBCAppender by iba realizoval Dodatok, zatiaľ čo iné implementácie appenderu ako napr ConsoleAppender by realizoval CustomizableAppender. Nevýhodou tohto prístupu je zvýšená zložitosť (napr. Spôsob spracovania konfiguračných súborov Log4j) a skutočnosť, že vývojári musia urobiť informované rozhodnutie o tom, ktorú úroveň abstrakcie použiť skôr.

Na záver

V tomto článku som použil Log4j ako príklad na demonštráciu tak princípu návrhu ortogonality, ako aj občasného kompromisu medzi dodržiavaním princípu návrhu a dosiahnutím atribútu kvality systému, napríklad škálovateľnosti. Aj v prípadoch, keď nie je možné dosiahnuť úplnú ortogonálnosť, sa domnievam, že kompromis by mal byť vedomým rozhodnutím a mal by byť dobre zdokumentovaný (napríklad ako technický dlh). V časti Zdroje sa dozviete viac o konceptoch a technológiách diskutovaných v tomto článku.

$config[zx-auto] not found$config[zx-overlay] not found