Strona główna > Java > Rozwlekła java

Rozwlekła java

Wprowadzenia
Uczenie się (czytanie o) innych języków poszerza horyzonty (tak mówią). Miałem ostatnio okazje coś w tym obszarze doświadczyć. Jako „lucky geek” dostałem do wyboru darmowego ebook_a. Z możliwej listy jakoś żaden z tytułów poświęconych javie nie przykuł mojej uwagi, więc w ramach rozszerzania horyzontów zdecydowałem się na „Clojure in Action”. Mam nadzieje że w taki sposób bardziej zmuszę się do zapoznania z tym językiem.
Czytając pierwszy rozdział trafiłem na jeden z sprawdzonych argumentów dlaczego jakiś język jest lepszy niż java. Jest to owa rozwlekłość (verbose), choć nie dotyczył on tylko javy.

Rozwlekłość
Przykład na rozwlekłość, duplikacje wyglądał mniej więcej tak:

    public List getExpenses(long userId, Date start_date, Date end_date) {
        AuditLogger.logLookup(userId, AuditCode.GET_EXPENSES);
        ExpensesConnection connection = new ExpensesConnection(userId);
        List expenses = connection.findAllBetween(start_date, end_date);
        connection.close();
        return expenses;
    }

    public void addExpense(long userId, Date date, BigDecimal amount) {
        AuditLogger.logLookup(userId, AuditCode.ADD_EXPENSES);
        ExpensesConnection connection = new ExpensesConnection(userId);
        connection.saveNewExpense(date, amount);
        connection.flush();
        connection.close();
    }

No tak, duplikacja rzuca się w oczy (kod do obsługi połączenia do jakiegoś repozytorium i do logowania zdarzeń). Ów przykład był pokazany aby zaprezentować cechę języka pozwalającą na zwijanie takich kawałków kodu do zgrabnych form, co w kodzie wygląda tak:

(defn get-expenses [user-id start-date end-date]
	(with-audited-connection [user-id connection]
		(find-all-between connection start-date end-date)))

(defn add-expense [user-id date amount]
	(with-audited-connection [user-id connection]
		(save-new-expense connection date amount)))

I tu jest pokazywana siła języka, jego zwięzłość, kosztem javy😉. Jest wspomniane że java może użyć coś takiego jak szablon (template method pattern), ale i tak efekt tego nie będzie taki ładny jak tutaj.
No cóż trudno się nie zgodzić. Nie wiem jak Clojure robi tą magie (na razie nie doczytałem tak daleko – termin „macro” musi wystarczyć) ale chwali się że wszystko to jest w stanie załatwić na poziomie samego języka (dzięki jego elastycznej składni), poczytamy zobaczymy.
Ze strony javy próby rozwiązania tego w bardziej elegancki sposób kryją się pod hasłami Spring, EJB3 lub AOP; ale to jest dodawanie zewnętrznych bibliotek, uruchamiania kodu w specjalnych warunków, czy modyfikacja na poziomie bytecode. Ogólny przekaz w javie nie da się zrobić tego tak elegancko jak w Clojure.

Sprawdzenie czy się nie da
No to spróbowałem zobaczyć w kodzie czy rzeczywiście wygląda to tak źle. Na początek definiując co jest problemem mamy zrobić coś takiego (tylko pierwsza metoda):

    public List getExpenses(long userId, Date start_date, Date end_date) {
        ExpensesConnection connection = doBefore(userId, , AuditCode.ADD_EXPENSES);
        try {
            return connection.findAllBetween(start_date, end_date);
        } finally {
            doAfter(connection);
        }
        return expenses;
    }

    private ExpensesConnection doBefore(long userId, String logId) {
        AuditLogger.logLookup(userId, logId);
        ExpensesConnection connection = new ExpensesConnection(userId);
        return connection;
    }

    private void doAfter(ExpensesConnection connection) {
        connection.close();
    }

Dodałem try aby kod był bardziej życiowy. Widać że mam dwie grupy technicznego kodu wykonywanego wokoło biznesowej części metody. Jak nic prowadzi to do szablonu więc sprawdźmy jak to wygląda:

    public List getExpenses(long userId, final Date start_date, final Date end_date) {
        return new ExpensesConnectionTemplet<List>() {
            List doInTry(ExpensesConnection connection) {
                return connection.findAllBetween(start_date, end_date);
            }
        }.execute(userId, AuditCode.ADD_EXPENSES);
    }

    abstract class ExpensesConnectionTemplet<T> {

        abstract T doInTry(ExpensesConnection connection);

        T execute(long userId, String logId) {
            ExpensesConnection connection = doBefore(userId, logId);
            try {
                return doInTry(connection);
            } finally {
                doAfter(connection);
            }
        }
    }

No i wygląda to niezbyt pięknie (dla mnie jednak jest czytelne :)). Dla potrzeb innych użytkowników można by postarać się o lepsze nazwy i komentarze, ale i tak tworzenie anonimowej klasy zaciemnia obraz tego co metoda robi. Czytelność pierwotnej metody jest poświęcona na rzecz unikania duplikacji. Jakoś trzeba wyważyć to; porównując to z pierwszą wersją można dojść do stwierdzenia: „co tam duplikacja w dwóch metodach przynajmniej kod się łatwiej czyta”🙂. Wersja z szablonem potrzebuje opatrzenia i po pewnym czasie staje się także czytelna, dla osób które na co dzień pracują z kodem. Jednak dla stykających się z kodem pierwszy raz wymagać będzie trochę wysiłku na początek.
Jednak spoglądając na piękny przykład z Clojure doszedłem do spostrzeżenie że przy pewnym chytrym zabiegu z kodem można coś jeszcze tu zdziałać. Tak więc na początek odizolowanie części biznesowej od technicznej.

    public List getExpenses(long userId, Date start_date, Date end_date) {
        ExpensesConnection connection = doBefore(userId, , AuditCode.ADD_EXPENSES);
        try {
            return _getExpenses(start_date, end_date, connection);
        } finally {
            doAfter(connection);
        }
        return expenses;
    }

    private List _getExpenses(Date start_date, Date end_date, ExpensesConnection connection) {
        return connection.findAllBetween(start_date, end_date);
    }

Pozornie naprodukowałem dodatkowy kod. Podpierajac się autorytetem SRP mam osobny kod do części biznesowej, a poprzez DI na poziomie metody przekazuje tam wszytko co jest potrzebne sprawiając że kod jest łatwiejszy do testowania. Przy okazji lepiej widać że userId należy tylko do części technicznej.
Dzięki takiej separacji można wprowadzić dodatkowy byt, który można porównać do trochę koślawego szablonu, ale w kodzie jest to już bardziej czytelne.

    public List getExpenses(long userId, final Date start_date, final Date end_date) {
        ConnectionHelper helper = new ConnectionHelper<List>(userId, AuditCode.ADD_EXPENSES);
        return helper.executeInTry(_getExpenses(start_date, end_date, helper.connection));
    }

    class ConnectionHelper<T> {
        ExpensesConnection connection;
        public ConnectionHelper(long userId, String logId) {
            connection = doBefore(userId, logId);
        }
        public T executeInTry(T value) {
            try {
                return value;
            } finally {
                doAfter(connection);
            }
        }
    }

No i już mogłem myśleć że trafiłem na jakiś „święty graal”, że mam konstrukcję tak samo elegancką jak ta z Clojure😉. Ale drugie spojrzenie uświadomiło mi że kod nie będzie działać dobrze kiedy wystąpi wyjątek (wychodzą braki w znajomości języka). Część biznesowa będzie wykonywać się poza try_em.
Wszystko byłoby pięknie gdyby można byłoby symulować konstrukcje try {} finally {} jakoś inaczej (można!!! szczegóły). Na pocieszenie zostaje fakt że to rozwiązanie można stosować kiedy trzeba otoczyć jakiś fragment kodu innym kodem i nie ma finally.

Wniosek
Java nie jest aż tak elastyczna. Można byłoby się pokusić o użycie jakichś mechanizmów np. Dynamic Proxy. Jednak to prowadziłoby do wymyślania koła na nowo; i jeśli rzeczywiście bardzo to jest potrzebne to już lepiej dołączyć parę jarów. Względnie może idea domknięć (closures) w javie rozwiąże tu wszystko i sprawi że całe dywagacje zawarte w tym rozwlekłym pości będą nieaktualne😉.

PS. Co do rozwlekłości w javie trafiłem też na ciekawy post i dyskusje, podejmujący temat z innej perspektywy.

Kategorie:Java Tags:
  1. Brak komentarzy.
  1. No trackbacks yet.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d bloggers like this: