Dnes budu zase psát o takové samozřejmosti, že ani nevím jestli s tím mám někoho obtěžovat. Navíc už jsem to před rokem předváděl na jedné konferenci. Možná ale nepatříte mezi těch deset šťastlivců, kteří mě tam viděli, tak to asi přeci jen napíšu. Začněme mojí oblíbenou ukázkou kódu:
public class PureJdbcBankDao implements Bank { public void setBalance(String accountNumber, BigDecimal balance) { Connection conn = null; PreparedStatement ps = null; try { conn = ConnectionFactory.getConnection(); ps = conn.prepareStatement("UPDATE ACCOUNT SET BALANCE = ? WHERE ACCOUNT_NUMBER = ?"); ps.setBigDecimal(1, balance); ps.setString(2, accountNumber); ps.execute(); } catch (SQLException e) { throw new ApplicationException("JDBC failure", e); } finally { if (ps != null) { try { ps.close(); } catch (SQLException e) { // what to do here? } } if (conn != null) { try { conn.close(); } catch (SQLException e) { // what to do here? } } } }
Je to metoda pro zápis do databáze pomocí čistého JDBC. Toto je nádherná ukázka míchání dvou druhů kódu. Jednomu druhu říkám hezky česky „business kód”. To je kód, který je užitečný pro zákazníka. V našem příkladu po nás někdo chce, abychom změnili hodnotu v databázi. To obvykle děláme pomocí SQL příkazu, který vidím jenom na jednom řádku. Zbytek je „infrastrukturní kód”. Tak říkám kódu, který někdo udělat musí, ale zákazníkovi je v podstatě k ničemu. V našem příkladě to jsou všechny řádky kromě jednoho. Ano opravdu, zákazníkovi je naprosto jedno, že potřebuji připojení do databáze, je mu dokonce jedno, že ho musím po sobě zavřít, on po mě chce abych něco zapsal do databáze. Popravdě řečeno, často po mě nechce ani to, chce jen, aby jeho důležitá data byla dobře uložena.
Ale zpátky k věci. Když to přeformuluji tak mohu říci, že business kód je ten, co dělá něco zajímavého pro zákazníka, infrastrukturní kód je ten, který zajímá jen programátora. Samozřejmě potřebujeme oba druhy, je ale důležité si uvědomit který je který a přistupovat k nim rozdílně. Úplně nejdůležitější je držet oba druhy odděleně. Pokud máte JDBC kód v business vrstvě nebo nedej bože v kontroleru, tak vám nepomůže ani svěcená voda. Pokud ho máte schovaný v DAO vrstvě, tak jste v trochu lepší situaci. Ale i tak je obtížné takový moloch testovat a měnit. Představte si, že bych si vzpomněl, že potřebuji transakce. Musel bych upravit všechny metody v DAO vrstvě. To nezní jako dobrý nápad.
Existují i další argumenty proč infrastrukturní kód nepsat. Nejenže je takový kód nečitelný, on je navíc náchylný na chybu. Když náhodou zapomenu zavřít PreparedStatement nemusí to nějaký čas vadit. Po změně databáze ale můžu začít dostávat veselé chyby a může mi trvat pěkných pár dní než najdu jejich zdroj.
Já jsem obecně přesvědčen, že programátoři, kteří píší kód pro zákazníky, by se měli infrastrukturnímu kódu co nejvíce vyhýbat. Myslím si že je mnohem lepší psát kód takto:
public class JdbcBankDao extends SimpleJdbcDaoSupport implements Bank { public void setBalance(String accountNumber, BigDecimal balance) { getSimpleJdbcTemplate(). update("UPDATE ACCOUNT SET BALANCE = ? WHERE ACCOUNT_NUMBER = ?", balance, accountNumber); } }
Nejen, že rovnou vidím, co daná metoda dělá, já tam ani nemám moc co zkazit. Téměř všechen infrastrukturní kód zmizel. Samozřejmě že tam stále je, ale napsal ho za mě někdo jiný. V tomto případě autoři Springu. Ano mohl jsem si napsat vlastní framework, ale tím bych si moc práce neušetřil. Navíc bych do něj musel přidat podporu transakcí, podporu testování a podobně. Takže bych víc času strávil vývojem frameworku než něčím, co přináší zákazníkovi užitek.
Příklad s JDBC je samozřejmě trošku extrémní, málokdy se nám stane, že bychom motali JDBC kód s business kódem. Často ale vyvíjíme jiné typy infrastruktury a často nás ani nenapadne, že bychom to dělat neměli. Nebo nás to napadne, ale najdeme si nějakou výmluvu. Ze své praxe pamatuji několik případů, které jsem spáchal já nebo některý z mých kolegů.
- Asi nejčastější jsou všelijaké implementace vyrovnávací paměti (cache). Znáte to, máte číselníková nebo jiná málo se měnící data v databázi. Místo toho abyste je četli při každém dotazu tak si je přednačtete do mapy a pak je už jen taháte z paměti. Klasické míchání infrastrukturního a business kódu a také klasický kandidát na vlastní framework popřípadě knihovnu. Přitom stačí vhodně použít cache přes AOP (spring-modules nebo AOP cache).
- Načítání konfigurace je také oblíbené. S nástupem Springu už se s tím tak často nesetkáte, ale doby kdy si každý implementoval svoji variantu konfigurační knihovny neminuly zas tak dávno. Občas ještě v business kódu tu a tam zahlédnu načítání properties souboru a vytahování příslušné hodnoty.
- Logování je další klasický případ. Zde bych udělal výjimku z pravidla a řekl bych, že míchání logování a business kódu není na škodu, někdy může být i užitečné. Logy mohou pěkně zastupovat komentáře. Když ale říkám že logování samotné škodlivé není, psaní si vlastní knihovny na logování už škodlivé je. Myslím si, že zrovna logovacích knihoven máme v Javě dost.
- Skoro vždy se setkáme s vlastními utility třídami. Tedy třídami, které usnadňují věci, které v Javě nejsou zrovna jednoduché. Ať už se jedná o práci s časem a datem, kopírování input streamu do output streamu, načítání obsahu souboru do stringu a podobně. Ano, standardní knihovny jsou v tomto ohledu neohrabané, to ale neznamená, že si to musíme psát sami. Jakarta commons, Joda nebo i Spring nám v tomto ohledu rozhodně dokáží pomoci.
- Jednou jsem strávil krásný týden implementací skoro transparentního šifrování dat na úrovni Hibernate. Tzn. v Javě se s daty pracovalo normálně, ale i při ukládání a načítání do databáze se data automagicky šifrovala a dešifrovala. Měl jsem z toho projektu docela radost. Tu mi zkazily dvě věci. Týden po tom, co jsem to dopsal jsem zjistil, že už podobné řešení existuje. Druhou vadou na kráse bylo, že kolegové začali přes Hibernate dávkově zpracovávat desetitísíce záznamů a moje geniální knihovna na to nebyla stavěná a dost celé zpracování zpomalovala.
- Jsem si jist, že další příklady z praxe si doplníte sami.
Já jsem přesvědčen, že v dnešní době už člověk sežene knihovnu skoro na všechno. Pokud má to štěstí a knihovna mu umožní zbavit se infrastrukturního kódu, měl by ji použít. Pokud štěstí nemá a musí si opravdu napsat infrastrukturní kód, měl by ho důsledně oddělit od business kódu. Ale jak říkával kolega, každý, kdo se rozhodne napsat si vlastní knihovnu, by měl před tím desetkrát oběhnout barák, aby si to dobře rozmyslel. Ono to totiž není o tom ji jen napsat. Ono je potřeba ji zdokumentovat, otestovat, verzovat, donutit kolegy aby se ji naučili, dávat si pozor jestli máme dobře nadefinované API a spoustu dalších věcí.
Podle mě je jen jeden případ, kdy se vyplatí psát si vlastní knihovnu. A to v případě kdy do ní dávám kód specifický pro daného zákazníka. Například nějaké specifické výpočty, které chci sdílet mezi různými moduly, kód pro volání jeho backendu a podobně. Ale i tak je potřeba počítat s tím, že údržba takové knihovny zabere neuvěřitelné množství času a vaši kolegové z jejího používaní nebudou moc nadšení.
Takže abych to shrnul. Ať už píšete cokoliv, je dobré uvědomit si, který typ kódu to je. Pokud se jedná o business kód, tak je vše v pořádku. Pokud zrovna píšete infrastrukturní kód a není to hlavní náplní vaší práce (zdravím chlapce ze Sunu), zamyslete se nad tím, jestli je to opravdu potřeba. Zamyslete se jestli to už náhodou někdo nevyřešil za vás. Já vím, naučit se novou knihovnu je mnohem menší legrace než si ji napsat. Ale připomíná mi to jedno staré rčení, které říkalo něco v tom smyslu, že “Půl roku v laboratoři vás dokáže uchránit od deseti minut v knihovně.”