Let's Talk About Money

11-Jul-2013 Like this? Dislike this? Let me know

Money
It's a gas
Grab that cash with both hands and make a stash
- from "Money", "Dark Side of the Moon", © 1973 Pink Floyd
  Money
It's a class
Make it so it's safe inside a hash
- Buzz Moschetti, with apologies to Roger Waters and programmers everywhere

Having just spent some number of weeks doing a financial services coding effort, I have reached the following conclusions about how to represent and manipulate Money in object-oriented software. I will use Java in the examples but the concepts extend to pretty much anything else out there, strict OO or otherwise.

  1. Money is a simple class with amount and currency code
    There is no such thing as Money without currency code. You can't have 100 nothings. You have 100 dollars, 100 euros, or 100 pounds. If you don't need to know currency, you're not dealing with money. It is unlikely that a system being built as of 2013 does not need to know about currency. If you're dealing with currency, you're not dealing with a single scalar value for amount. You need a Money class to capture amount and currency. Don't take the shortcut and assume things have been converted to USD (or worse, deliberately design it that way). Make it easy for everyone in the present and the future. Just use a Money class everywhere you deal with money. This is basically all you need in Money:
        public class Money {
            private BigDecimal amt;  
            private String ccode;
            // ..
        }
    
    We'll explore BigDecimal, String, constructors, and the conspicuous absence of most operations in just a bit.

  2. Money amount is not a floating point number. Use BigDecimal or similar
    You have all read the 100s of posts and papers on the dangers/pitfalls of using native single and double precision floating point representations. Here is but one of the standard snafus:
        double qq = 1.0 - .9;
        System.out.println("qq " + qq);
    
        // qq 0.09999999999999998<
    
    Avoid the issues by not getting into doubles in the first place. Use BigDecimal and Money everywhere you need to deal with money.

  3. Money can be made into an immutable class
    If you eliminate setters, provide only getAmount and getCurrencyCode, and properly override both equals and hashCode, then you can use an instance of Money safely as an immutable object, just like String. This is an extremely valuable (no pun intended) property for a low-level object that shows up everywhere and can get passed around. Immutability means you have confidence that no one else beyond in the call stack will mess with your money.
        public class Money {
    
            private Money() {} // hide default constructor
        
            public Money(int        amt, String ccode) { // .. }
            public Money(long       amt, String ccode) { // .. }
            public Money(String     amt, String ccode) { // .. }
            public Money(BigDecimal amt, String ccode) { // .. }
    
            // NO constructor for doubles!   double cannot be reliably
            // ingested to BigDecimal, so just avoid them altogeher.
        
            // ...
        }
    

  4. Put all Money functions (add, subtract, etc.) into Utility classes
    This is probably the most interesting aspect of Money software design. Many existing implementations of Money have a whole suite of add(), subtract() etc. functions. Maybe even currency conversion functions! This approach is flawed for these reasons:
    1. You cannot perform basic functions on Money of differing currency codes without a currency conversion table. Providing a whole suite of functions that work only on a single currency and throwing a mismatched currency exception is a waste of coding effort because ultimately you're going to have to code those currency-normalized functions anyway. Don't duck the issue and let the subtle but important aspects of currency conversion and money operations bubble up into the application space where multiple and often conflicting (if not outright broken) implementations could be crafted.
    2. Currency conversion is exogenous to money and has a very strong point-in-time aspect to it. The conversion table is therefore more closely associated with a runtime context and a set of Money instances operating in that context. Thus, burying any sort of currency conversion IN Money is wrong. And because the resources required to craft the conversion table are well beyond the simple amount+code nature of Money, it needs to live in a separate class altogether to properly organize dependency.
    3. The number of potential functions we might wish to perform with Money is essentially unbounded. Therefore, we do not want the class to blossom into something with dozens or more methods. This is the Winnebago Class Design Flaw (acknowledgements to John Lakos). Every time we add a new method, Money has to be rigorously backtested and the effort is non-linear as the class and state grow larger. Money "shows up everywhere" so every piece of code that imports Money will feel the change/release impact. But if you put the functions into, say, MoneyUtils, then only those pieces of code that actually do something with Money need to be tested. And you can create more than one package of utils, e.g. MoneyUtils2 and mix them as needed. Some utils might require more exogenous resources than others; why burden the developer with dragging in every possible dependency just to call add()? Observe good dependency design & and management! Factor heavier-weight functions into their own utils package with their own build/test packaging.

  5. Money utilities create new Money
    Building on the themes above, one does not add Money "directly" to Money. Instead, all utilities return new Money. Since the operations have no state, they can and should be static
        public class MoneyUtils {
            /**
             *  Caller's choice if they want to risk the exception...
             */
            public static Money add(Money a, Money b)
                throws UnconvertibleCurrencyException { // ... }
        
            /**
             *  Fancier versions -- but it can STILL throw an exception in case
             *  the table does not have the necessary conv!
             */
            public static Money add(Money a, Money b, CurrencyConv cc)
                throws UnconvertibleCurrencyException { // ... }    
            public static Money subtract(Money a, Money b, CurrencyConv cc)
                throws UnconvertibleCurrencyException { // ... }
        }
    
        // In use:
        import Money;
        import MoneyUtils;
    
        Money a = new Money(100, "USD");
        Money b = new Money(200, "USD");
        Money c = MoneyUtils.add(a, b);
    
        Money d = new Money(200, "GBP");
        CurrencyConv currtbl = factory_or_otherwise_get_it_from_somewhere();
        Money e = MoneyUtils.add(c, d, currtbl);
    
    Note that CurrencyConv should be a lightweight carrier of currency exchange rates only. It is not connected to a database or a screen or anything else. Something else is responsible for calling its public set() functions to set up rate pairs.

  6. Don't be pedantic about String vs. Currency as a class
    Specifically WRT Java, yes, there is a class defined as Currency. I avoid it in the Money class for these reasons:
    1. Everything is STILL based on ISO 4217 codes and those are Strings.
    2. The docs say Currency is designed so there is never more than one instance of Currency for any given currency. That's neat, but Strings are immutable so creating one and then creating new Money over and over again will use the same instance of that String, too.
      That said, as a marker more than anything else, the following is pretty handy to add to the Money class:
          public class Money {
              public static final String USD = "USD";
              public static final String JPY = "JPY";
              public static final String EUR = "EUR";
              public static final String GBP = "GBP";
              //..,
          }
      
          // In use:
          Money a = new Money(100, Money.USD);
          Money b = new Money(200, Money.USD);
          Money c = MoneyUtils.add(a, b);
      
          Money d = new Money(200, Money.GBP);
      
    This set of four static final Strings will cover many of your needs and still leave the door open for other String codes.

  7. Don't be pedantic about Money being negative
    In theory, you cannot have negative money. Money is a GTE(0) kind of thing. Negative money comes about only when the subtraction operation is brought into play; add, multiply, and divide of a positive number will always yield GTE(0). By convention, negative money has come to mean that money is owed somewhere, somehow. Of course, the Money class has no idea what owing means and to whom.
    As an exercise, let's examine what happens if Money is defined to never be negative. We would have to convert Money to standalone BigDecimal and interpret the results of the subtraction indepently of Money, e.g.:
        Money m_a = new Money(100, Money.USD);
        Money m_b = new Money(100, Money.GBP);
    
        // Need monies normalized first....
        Money m_ba = MoneyUtils.convertTo(m_b, Money.USD, currtbl);
    
        // ... before we pull out the actual amount:
        BigDecimal v_a  = m_a.getAmount();
        BigDecimal v_ba = m_ba.getAmount();
    
        BigDecimal diff_v = v_a.subtract(v_ba);
        if(diff_v.compareTo(BigDecimal.ZERO) < 0) {
            // Do something with a negative amount...
        } else {
            // Not negative; rebuild a new Money:
            BigDecimal c_v = new Money(diff_v, Money.USD);
        }
    
    We can get even more pedantic. Assuming currency conversion is independent of the magnitude of the monies being converted, this means we can keep the conversion out of the MoneyUtils picture entirely:
        Money m_a = new Money(100, Money.USD);
        BigDecimal v_a  = m_a.getAmount();
    
        Money m_b = new Money(100, Money.GBP);
        BigDecimal v_b = m_b.getAmount();
    
        BigDecimal rate = currtbl.getConvRate(m_a.getCurrencyCode(),m_b.getCurrencyCode());
        BigDecimal x = v_b.multiply(rate); // conv GBP to USD
    
        BigDecimal diff_v = v_a.subtract(x);
        if(diff_v.compareTo(BigDecimal.ZERO) < 0) {
            // Do something with a negative amount...
        } else {
            // Not negative; rebuild a new Money:
            BigDecimal c_v = new Money(diff_v, Money.USD);
        }
    
    The practical problem, however, is that once you start pulling out the amount and currency codes, you lose "context" of these things and the amount of support logic (tracking which BigDecimal is paired with which Money, currency tables, tests for GTE(0), etc.) piles up, clouding the main purpose. And in general, the effort to deal with this for just the subtract() case isn't worth it. Plus, at some point you have to use the CurrencyConv class anyway to do any real multicurrency work, so there's no getting around the fact that at some point you have to construct it.
    The "good thing" is to permit negative money and bury some of the conversion logic and dependency in MoneyUtils (note: NOT Money!)
        Money m_a = new Money(100, Money.USD);
        Money m_b = new Money(100, Money.GBP);
    
        Money m_diff = MoneyUtils.subtract(m_a, m_b, currtbl);
        if(m_diff.getAmount().compareTo(BigDecimal.ZERO) < 0) {
            // (Maybe) do something with negative Money...
        } else {
            // NO need to build new Money; m_diff is already good to go!
        }
    

  8. Adopt and clearly document conventions for currency handling operations
    You must adopt conventions about what the currency code of the resultant Money object should be and clearly document them, e.g.:
        /**
         *  Add a,b and return a new Money.
         *
         *  In the case where currencies are not equal, the CurrencyConv
         *  object will be used to try to perform the conversion.  The 
         *  currency of Money a is the target and the specific conversion of
         *  the currency of Money b to that of Money a will be used.
         *  If the conversion exists, 
         *  the new Money will be returned with currency code
         *  set to that of Money a.   For example,
         *      10 USD + 5 GBP = 17.5 USD   (assuming USD<->GBP .6667)
         *  The UnconvertibleCurrencyException will be thrown if a specific
         *  conversion [from B to A] does not exist.
         */
        public static Money add(Money a, Money b, CurrencyConv cc)
          throws UnconvertibleCurrencyException 
          { // .. }
    

  9. Currency conversion from B to A is not automatically the inverse of A to B
    Many times it is, but some times it is not, depending on context. Make sure your CurrencyConv will do the right thing; for example:
    
        public void addConvRate(String from, String to, BigDecimal rate, boolean permitInverse) 
    
    
Like this? Dislike this? Let me know
Site copyright © 2013-2024 Buzz Moschetti. All rights reserved