Dva druhy kódu

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ů.

  1. 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).
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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ě.”

17 thoughts on “Dva druhy kódu

  1. Tomas Herman

    Hmm zrovna se chystam psat DAO pro Pgsql do semestralky tak bych mohl neco v tomhle smyslu pouzit :] ikdyz si to asi budu muset napsat sam, nevim jestli by byly radi ze k tomu pouzivam neco jako swing :]

  2. v6ak

    No konkrétně pro parsování komandlajny jsem hledal knihovnu. Našel jsem jich spoustu, ale ta kvalita! Typově bezpečná asi jedna a mnohým nevadily vynechané parametry a nešlo to nastavit. To abych si napsal vlastní … která bude špatná v něčem jiném a někdo se pak zase naštval tak jako já…

  3. v6ak

    @Dagi: Třeba ta typová bezpečnost.
    Představoval jsem si to buď ve formě nějakých generických placeholderů (portHolder.getValue().intValue() nebo result.getValue(portOpt).intValue()) s možností si napsat vlastní nebo v podobě anotací. Tady je už typová bezpečnost o něco menší, ale zas je to jednosušší na použití. JewelCli je celkem OK.

  4. kve

    Souhlasim az na detaily. Je otazka jak dlouho mi zabere najit spravnou knihovnu, jak zjistit jak dana metoda vlastne funguje, jak pripojit do sve aplikace a kolik vlastne dana knihovna zabira. Aneb videl jsem projekty, kde samotny kod mel cca 10kB a pripojene knihovny, ze kterych se pouzivali asi dve metody (vytvoreni toString, porovnani dvou objektu s tim ze dva nully jsou si rovnocene) zabrala nekolik mega.
    Navic zjistovat, hledat, zkouset je pro programatora nudna prace .. ale kdyz si ten kod sam napise, a otestuje … to uz je zajimavejsi, muze se o tom kodu pobavit s kolegama a vsichni se tak obohatit (dobra tohle asi zakaznikovi nevysvetlite :-)… ale je to proste tak).

    Jen jsem tim chtel rici ze to neni az tak cernobile, jak by se mohlo zdat :-).

  5. Martin

    kve:myslím, že jeden programátor může psát oba druhy kódu, ale neměl by je míchat do jedné třídy, ale udržovat je oddělené.

  6. v6ak

    @kve: To jo. Ale na druhou stranu: naštvu je, vytvořím svoji knihovnu, která bude umět to, co chci já, a přispěju do moře knihoven, které bude někdo zbytečně procházet při hledání té pravé, protože jsem na něj nemyslel. Mince má zkrátka dvě strany.

  7. lzap

    No pouzivam commons-cli uz tak v 5 projektech a zadny typy jsem nepotreboval. Proste to umi dobre zparsovat commandlinu, nic vic.

    Jinak knihovna vsech knihoven je pochopitelne GNU getopt, ze ktere CLI castecne vychazi. Je to defacto standard.

  8. IA

    Je fakt, jak pise kve, ze ty knihovny jsou nekdy osklive velke. Kdyz ma samotny projekt 50kB, tak docela boli davat k tomu 3 pul megove knihovny :-|.
    Ale jinak samozrejme souhlas s autorem…

  9. Petr Juza

    Plně souhlasím – já bych ještě více zdůraznil, že čím více toho sám napíšu, tím více chyb tam také budu mít. Když to tedy obrátím, tak čím méně toho sám naprogramuji (ideálně jen bussines kód) a čím více využiji již ověřeného existujícího (infrastrukturního) kódu, tím lépe.

    Sám věnuji pár hodin měsíčně procházením různých RSS, abych se podíval jaké nové knihovny nebo verze jsou, ty si pak uložím a mám je v hledáčku pro případ možného řešení.

  10. kve

    Jeste pridam komentar :-).
    Asi jsem mel smulu, nebo jsem hledal spatne ale tak ve 30% kdyz hledam nejakou knihovnu a funkcnost nebo cely framework a najdu … tak zjistim ze uplne nedela to co bych potreboval, zacnu do nej rypat, kolikrat i dekompilovat a prepisovat, nekdy se jen pokrizuju s pohledem nahoru a se slovy kdo tohle napsal :-O? zahodim a napisu si sve :-).
    (Ted nemluvim o Common, Log4J, Spring ani Hibernate, ale napriklad do GWT-cka jsem uz vrtal 😛 – ovsem tam je ten kdo celkem pekny…).
    Jinak prednost knihoven je jasna … nemusi se psat :-), nemusi se testovat, nemusi se dokumentovat, staci je jen najist a zjistit jak pouzit (jo a precist si licenci …):-).

  11. Anonymous

    Skutecne stoji za uvahu, kolik casu zabere evaluace knihoven (protoze alespon trochu pouzitelna bude podle Murphyho zakonu az ta posledni, pokud vubec) oproti napsani vlastni knihovny + testy, docs etc…

    A take je otazka, nakolik oboje oceni klient/nadrizeny 🙂

  12. Pingback: Java crumbs » Blog Archive » Sláva abstrakci

Comments are closed.