Getting Bit By BigDecimal

I recently wrote some Java code to a port an existing feature into a newer architecture. In a nutshell, what this code did was take a fixed total price and recalculate what the unit price and discount should be at different quantities. Being the well meaning, responsible developer that I am, I decided to refactor the logic to make it easier to read for the next poor sap who would have to come behind me. As is so often the case, that poor sap turned out to be me.

One of the things I decided to do was put the data in a real object. Like much of the data in the legacy application that I battle with on a daily basis, this data was “Stringly typed”. The whole app is littered with List<String>, Map<String,String>, and the abomination of desolation that is the List<Map<String,String>>. This code was no better, and half the logic was just converting between Strings and BigDecimal objects. So I decided to be the hero that Gotham deserves and replaced these horrid beasts with real data types and real objects.

I confidently released my code into production. Unfortunately, I also released a bug into production. In the original implementation, an error was thrown if the amount was less than $0.01, but not if the unit price was $0.00. In my buggy implementation, an error was being thrown when the unit price was $0.00. I was confused. I was presented with the holy grail of unchanging, well-defined requirements. All I had to do was make the code do what it did before. How could this happen? After digging into the code the problem was obvious. I had gotten bit by BigDecimal.

First, the fix. I had to replace the red line with the green line like so :

At first this had me scratching my head. Why didn’t zero equal zero? Well, according to the documentation , the equals() method considers two BigDecimal objects equal only if they are equal in value and scale. The compareTo() method, on the other hand, will not consider scale when evaluating equality.

Luckily the fix was simple, but I was frustrated that I didn’t catch it before release. The problem reminded me of a few valuable lessons in crafting software.

The first is to read the manual. I’m a big believer in writing self-documenting code and using comments sparingly to document ideas that you can’t express in code. However, if you are going to use code that somebody else wrote, you better know what it does. In my case that was the Java standard library, but the same principle applies to any third-party dependencies that you are leveraging. Unlike most closed source internal software, major libraries and frameworks have nice documentation that is kept up to date. Reading the documentation for those libraries is how you learn to use them effectively. On top of that, read the code itself.

The second is to write automated tests. I didn’t do it because I was working with legacy code and it would have been a pain in the butt. I should have done it anyway.

The third is that building software is rarely as simple as it seems. Doing it professionally for a few years will pound humility into you pretty quickly. Still, as you see the same problems repeated over and over, it is easy to become cocky. The devil is in the details, and we often stumble over the simplest stuff because we don’t pay close enough attention. On many occasions I have seen large, risky projects released with a few minor bugs and small projects released with SLA violations and data loss. I think this is because we do a better job of testing and code reviewing when we feel the risk is high. We would do well to remember this phenomenon when we begin to think we are God’s gift to programming.

One Reply to “Getting Bit By BigDecimal”

Comments are closed.