The Problem With Embeds, Part 6: The CalcValues Code
This article is the sixth in a series on what Clarion developers (yours truly included) do wrong with embeds, and how to do embeds right. In a nutshell, Clarion's application architecture encourages an all-in-one approach, where the user interface, the business logic, and the data access code are all lumped together.
This is a problem.
Actually it's not a huge problem for the generated code, inasmuch as that code tends to actually work as expected. It's tried and true, and if the architecture leaves something to be desired, well, so what?
It's when we start throwing our own code into embed points that things tend to get really ugly.
In Part 1 of this series I examined the architecture of Clarion applications; in Part 2 and, Part 3  I examined some of my own code for parsing TXAs to extract embed code and showed how to make that code into testable, reusable classes. In Part 4 I reused my classes to create an embed viewer utility app.
In Part 5 I reintroduced the Invoice.app that ships with the Clarion examples and I demonstrated the buggy nature of the invoice line item calculation code.Â
In this instalment I'm going to rework that buggy code into a testable, reusable class. And in the next instalment I'll plug that code back into the original Invoice.app.Â
The Invoice app
The specific code I'm interested in is in the UpdateDetail procedure in the Invoice app. It's a routine that's used to calculate various values for each line item that's added to an invoice. As I'm sure you'll agree, this is critical business logic. And as I showed in Part 5, it's buggy code.
Let's take a look at what this embed code is actually trying to accomplish. Here again is the code:
CalcValues ROUTINE IF DTL:TaxRate = 0 THEN IF DTL:DiscountRate = 0 THEN DTL:TotalCost = DTL:Price * DTL:QuantityOrdered ELSE LOC:RegTotalPrice = DTL:Price * DTL:QuantityOrdered DTL:Discount = LOC:RegTotalPrice * DTL:DiscountRate / 100 DTL:TotalCost = LOC:RegTotalPrice - DTL:Discount DTL:Savings = LOC:RegTotalPrice - DTL:TotalCost END ELSE IF DTL:DiscountRate = 0 THEN LOC:RegTotalPrice = DTL:Price * DTL:QuantityOrdered DTL:TaxPaid = LOC:RegTotalPrice * DTL:TaxRate / 100 DTL:TotalCost = LOC:RegTotalPrice + DTL:TaxPaid ELSE LOC:RegTotalPrice = DTL:Price * DTL:QuantityOrdered DTL:Discount = LOC:RegTotalPrice * DTL:DiscountRate / 100 LOC:DiscTotalPrice = LOC:RegTotalPrice - DTL:Discount DTL:TaxPaid = LOC:DiscTotalPrice * DTL:TaxRate / 100 DTL:TotalCost = LOC:DiscTotalPrice + DTL:TaxPaid DTL:Savings = LOC:RegTotalPrice - DTL:TotalCost END END
First, there are a number of different states, which can be expressed as a matrix:
 | Not Discounted | Discounted |
Not Taxed | price * qty | price * qty - discount |
Taxed | price * qty + tax | price * qty - discount + tax |
Any item can have a discount, and it can have a tax, and it can have both a discount and a tax. If a taxed item is discounted, I'll assume that the tax is applied after the discount (although in real life this isn't always the case, as with manufacturer's rebates in which the tax may apply to the non-discounted total - but that's beyond the scope of this article).
One of the problems with the embedded code is terminology. It isn't especially clear on the concept of an extended price, which is the unit price (price for one item) times the quantity. Instead, there's a local variable called LOC:RegTotalPrice
, which is calculated in three of the four matrix elements. For the simplest case (no taxes, no discount) it isn't used at all; instead the calculation just sets the total price to the price times the quantity.
There are four values that care calculated:
- Total Cost - the actual amount paid by the customer
- Discount - the dollar value of the discount
- Tax Paid - the dollar value of the tax
- Savings - this one is a little unclear, but it appears to be the extended price minus the total cost (yielding a negative number, with a lower number meaning greater savings). That doesn't make a whole lot of sense to me when taxes are involved, since the higher the tax the greater the perceived savings! I can sooner see the savings as including any tax savings realized by the application of the discount (and so being the same as the discount amount if the tax rate is zero), but as written the code isn't going to produce that value.
So to start with let's use a formal definition of an extended price:
extended = price * qty
Now, how about discount amount? That's simply the extended price times the discount rate:
discount = extended * discountrate
Similarly, the tax amount (assuming no discount) is the extended price times the tax rate:
tax = extended * taxrate
And if there is a tax and a discount, the tax is the discounted amount times the tax rate:
tax = (extended - discount) * taxrate
And the grand total is simply the extended amount, less any discount, plus the tax:
total = extended - discount + tax
Here's the matrix with the updated pseudocode:
 | Not Discounted | Discounted |
Not Taxed | extended = price * qty total = extended | extended = price * qty discount = extended * discountrate total = extended - discount |
Taxed | extended = price * qty | extended = price * qty discount = extended * discountrate tax = (extended - discount) * taxrate total = extended - discount + tax |
So far, so good. But how do you translate this logic into a class that can be tested and reused?
Class design and test driven development
Back when I first started writing object-oriented code, I often found the most difficult part of the job was imagining what the class would look like. Which methods should the class have? What properties? Would I need one class or multiple classes?
And at some point I realized that it was a whole lot easier if I approached class design not from the perspective of what I wanted to include in the class (the methods, properties), but how I wanted to use that class. So I'd imagine some code that called the class. And that often made the class design task much, much easier.
And then I came across Test Driven Development, or TDD. It all looked very familiar to me, because in a way that's what I'd been trying to with my "how will I use this class" visualizations.
TDD isn't something you hear about a lot on the Win32 platform; I came across it while doing .NET development (Clarion# and C#). The basic idea is you begin writing code by writing the tests first. For instance, I might have a test that looks something like this:
detail InvoiceDetail code detail.Init(12.50,3) ! 3 items at $12.50 each extended = detail.GetExtended() if extended <> 37.50 ! Report an error end
I have an instance of an InvoiceDetail
class that models one invoice line item, and I'm initializing it with a price (12.50) and a quantity (3). I'm then calling a GetExtended()
method on the class to get the extended price, and if that price isn't what I think it should be I'm going to report an error. For the moment I'm leaving out how I report that error.
Note that at this point my code will not compile. That's critical, and is due to test-driven aspect of TDD. I'm using the test as my starting point. I haven't written the class yet. So the next step will be to write a stub class so my test compiles, but of course the test will fail because I won't have any logic in the class yet. It's only once I have a failing test that I actually start writing the class code.
You don't have to use a test-driven approach to get the benefit of unit testing. You can always write your tests after you've written the code. But I think you'll find that the more you use unit tests, the more you use the tests as a starting point.
But even before I start writing any code I have to back up a bit further, because the one thing still missing from this picture is a unit testing framework.
A unit testing framework
A unit testing framework is a software tool that makes it easy to unit test your code. But what's a unit test?
Wikipedia defines unit testing as
a software verification and validation method in which a programmer tests if individual units of source code are fit for use. A unit is the smallest testable part of an application.
In practice, unit testing often involves fairly complex processes, but each individual test should only test one thing. And if you start with the smallest, simplest testable units and work your way up, you can achieve a high degree of confidence that your code is working as expected. In the above example I'm testing the ability of the class to return a correct extended price; I'll go on to write tests that verify the class's ability to calculate taxes, discounts and totals.
The entry goes on to explain that unit testing is commonly automated and run in the context of a framework:
Under the automated approach, to fully realize the effect of isolation, the unit or code body subjected to the unit test is executed within a framework outside of its natural environment, that is, outside of the product or calling context for which it was originally created. Testing in an isolated manner has the benefit of revealing unnecessary dependencies between the code being tested and other units or data spaces in the product. These dependencies can then be eliminated.
Using an automation framework, the developer codes criteria into the test to verify the correctness of the unit. During execution of the test cases, the framework logs those that fail any criterion. Many frameworks will also automatically flag and report in a summary these failed test cases. Depending upon the severity of a failure, the framework may halt subsequent testing.
As a consequence, unit testing is traditionally a motivator for programmers to create decoupled and cohesive code bodies. This practice promotes healthy habits in software development. Design patterns, unit testing, and refactoring often work together so that the best solution may emerge.
Unit testing under .NET is a treat - there are some terrific tools out there that make it easy to create, run, and log tests. You can run tests manually or have them run automatically as part of your build process (typically via a continuous integration server, if you take that approach).
But .NET unit testing tools benefit from some .NET language features, particularly reflection. A .NET unit test framework can inspect an assembly, locate test methods inside test classess, run those test methods and report on the results. That's a lot more difficult to accomplish in Win32.
Unit testing in Clarion Win32
I couldn't find any third party unit testing tools for Clarion, so I went ahead and wrote one. Actually this tool is in two parts: an executable application (called ClarionTest) that will search a DLL looking for unit tests to run, and a set of supporting templates you use to create test procedures in a test DLL that is compatible with ClarionTest.
Figure 1 shows the ClarionTest application with a test DLL loaded and several tests having been run (this screen shot is from Part 2). As you can see, ClarionTest is just a single window application that lists the available tests, lets you run those tests, and shows you the results.
Figure 1. The ClarionTest test runner
As I mentioned earlier in this series, ClarionTest is included in the freely available DevRoadmaps Clarion Library (DCL).Â
To recap, here are the stages I've completed:
- I've analyzed the embed code enough to figure out more or less what it's supposed to do
- I've come up with a rough idea for my first unit test
Now I'm about to:
- Create a test DLL
- Write my first unit test procedure
- Create a skeleton class so the unit test will compile
- Run the unit test using the ClarionTest framework
- Fix the class so it returns the expected result
I realize that this seems like a lot of work for just one wee bit of embedded code. But a lot of this work is also a one-time effort which you can leverage when you write additional tests for other code/classes. Once you understand the basic concepts of unit testing, creating new tests will come very easily.
Having said that, even after you become proficient in creating unit tests you almost certainly will spend more time up front creating your code than you did writing embed code. It takes time to write good tests. But there's also a huge payback in the form of fewer bugs, more confidence and better reusability.