pondělí 21. listopadu 2016

Peníze v Javě


Tentokrát bych chtěl rozebrat možnosti a moje zkušenosti, jak v kódu nakládat s penězi.

Datový typ - BigDecimal


Pro ukládání částky je rozhodně nutné používat datový typ BigDecimal  Jedině ten zaručuje, že nebude docházet k nečekanému zaokrouhlování za desetinou čárkou. BigDecimal snese v podstatě libovolnou přesnost a velikost čísla. Rozhodně nepoužívejte datové typy typu Float nebo Double.

Setkal jsem se i s aplikací, kde částky byly uchovávány jako celé číslo. Pro zobrazení se pak musela desetinná čárka uměle posouvat, tak aby dávala obsahově smysl. Fungovalo to dobře, ale myslím, že už je to přežité. Použil bych tento přístup jen v programovacím jazyku, který nemá BigDecimal alternativu.

Třída BigDecimal je immutable, takže se s instancemi ve vícevláknovém běhu pracuje bezpečně. Poskytuje všechny základní operace jako je sčítání, násobení a zaokrouhlování.

Identifikace měny


Peníze jsou ale ve skutečnosti dvojicí částky a měny.  Třídu, která by ale skládala dohromady částku a měnu přímo v JDK nemáme. Pokud máte částku a víte že jde o nějaké peníze, ale nevíte v jaké měně, tak je to docela problém.

public class Product {
    private final Long productId;
    private final String name;
    private final BigDecimal price; // What currency is it ?

    public Product(final Long productId, 
                   final String name, 
                   final BigDecimal price) {

        this.productId = productId;
        this.name = name;
        this.price = price;
    }
    // geters …
}

Třídu pro měnu máme v Javě už od verze 1.4 -  Currency. Rozšířit entitu o další atribut by neměl být problém.

public class Product {
    private final Long productId;
    private final String name;
    private final BigDecimal price;
    private final Currency currency;

    public Product(final Long productId, 
                   final String name, 
                   final BigDecimal price, 
                   final Currency currency) {

        this.productId = productId;
        this.name = name;
        this.price = price;
        this.currency = currency;
    }

    // geters …
}

Pokud v jedné entitě máte více cen - např. prodejní, nákupní, apod. Duplikují se vám dvojice částka a měna a poměrně snadno při použití může dojít k tomu, že částky a měny poplete. Musíte také ohlídat, aby byla vždy zadaná celá dvojice. Pokud dovolíte zadat částku bez měny, opět je to problém.

Joda Money

Problém neexistence třídy pro peníze vyřešil Stephen Colebourne, mimochodem autor populární knihovny JodaTime. Naimplementoval knihovnou pod názvem Joda-Money. Ta obsahuje třídu jak pro měnu - CurrencyUnit tak pro magickou dvojici částka, měna - a to hned ve dvou různých třídách Money a BigMoney

Money je třída, která agreguje  dvojici částka (jako BigDecimal) a měna (jako CurrencyUnit). Navíc hlídá počet desetinných míst pro danou měnu. Třída BigMoney poskytuje vše co Money, ale nehlídá počet míst za desetinou čárkou pro danou měnu. To se hodí, pokud vám částky přicházejí ze zdroje, který nemáte úplně pod kontrolou. Konverze mezi oběma typy je samozřejmě přímočará.  Obě třídy jsou immutable, takže opět bezpečné pro práci ve vícevláknovém prostředí. 

Upravený příklad vypadá takto.

public class Product {
    private final Long productId;
    private final String name;
    private final Money price;
    
    public Product(final Long productId, 
                   final String name, 
                   final Money price) {

        this.productId = productId;
        this.name = name;
        this.price = price;
    }

    // geters ... 
}

Závěr

Pokud pracujete v rámci programu s penězi, doporučuji určitě zahrnout do projektu o jednu závislost navíc a důsledně používat datový typ Money resp. BigMoney, místo dvou nezávislých atributů pro částku a pro měnu. Předcházíte tak riziku, že atributy prohodíte. Navíc je kód mírně přehlednější. Líbí se mi i že místo obecného typu pro číslo použit naprosto konkrétní pro peníze.