layout: post
title: “Calculator Walkthrough: Part 3”
description: “Adding the services and user interface, and dealing with disaster”
categories: [“Worked Examples”]
seriesId: “Annotated walkthroughs”

seriesOrder: 3

In this post, I’ll continue developing a simple pocket calculator app.

In the first post, we completed a first draft of the design, using only types (no UML diagrams!).
and in the previous post, we created an initial implementation that exercised the design and revealed a missing requirement.

Now it’s time to build the remaining components and assemble them into a complete application

Creating the services

We have a implementation. But the implementation depends on some services, and we haven’t created the services yet.

In practice though, this bit is very easy and straightforward. The types defined in the domain enforce constraints
such there is really only one way of writing the code.

I’m going to show all the code at once (below), and I’ll add some comments afterwards.

  1. // ================================================
  2. // Implementation of CalculatorConfiguration
  3. // ================================================
  4. module CalculatorConfiguration =
  5. // A record to store configuration options
  6. // (e.g. loaded from a file or environment)
  7. type Configuration = {
  8. decimalSeparator : string
  9. divideByZeroMsg : string
  10. maxDisplayLength: int
  11. }
  12. let loadConfig() = {
  13. decimalSeparator =
  14. System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator
  15. divideByZeroMsg = "ERR-DIV0"
  16. maxDisplayLength = 10
  17. }
  18. // ================================================
  19. // Implementation of CalculatorServices
  20. // ================================================
  21. module CalculatorServices =
  22. open CalculatorDomain
  23. open CalculatorConfiguration
  24. let updateDisplayFromDigit (config:Configuration) :UpdateDisplayFromDigit =
  25. fun (digit, display) ->
  26. // determine what character should be appended to the display
  27. let appendCh=
  28. match digit with
  29. | Zero ->
  30. // only allow one 0 at start of display
  31. if display="0" then "" else "0"
  32. | One -> "1"
  33. | Two -> "2"
  34. | Three-> "3"
  35. | Four -> "4"
  36. | Five -> "5"
  37. | Six-> "6"
  38. | Seven-> "7"
  39. | Eight-> "8"
  40. | Nine-> "9"
  41. | DecimalSeparator ->
  42. if display="" then
  43. // handle empty display with special case
  44. "0" + config.decimalSeparator
  45. else if display.Contains(config.decimalSeparator) then
  46. // don't allow two decimal separators
  47. ""
  48. else
  49. config.decimalSeparator
  50. // ignore new input if there are too many digits
  51. if (display.Length > config.maxDisplayLength) then
  52. display // ignore new input
  53. else
  54. // append the new char
  55. display + appendCh
  56. let getDisplayNumber :GetDisplayNumber = fun display ->
  57. match System.Double.TryParse display with
  58. | true, d -> Some d
  59. | false, _ -> None
  60. let setDisplayNumber :SetDisplayNumber = fun f ->
  61. sprintf "%g" f
  62. let setDisplayError divideByZeroMsg :SetDisplayError = fun f ->
  63. match f with
  64. | DivideByZero -> divideByZeroMsg
  65. let doMathOperation :DoMathOperation = fun (op,f1,f2) ->
  66. match op with
  67. | Add -> Success (f1 + f2)
  68. | Subtract -> Success (f1 - f2)
  69. | Multiply -> Success (f1 * f2)
  70. | Divide ->
  71. try
  72. Success (f1 / f2)
  73. with
  74. | :? System.DivideByZeroException ->
  75. Failure DivideByZero
  76. let initState :InitState = fun () ->
  77. {
  78. display=""
  79. pendingOp = None
  80. }
  81. let createServices (config:Configuration) = {
  82. updateDisplayFromDigit = updateDisplayFromDigit config
  83. doMathOperation = doMathOperation
  84. getDisplayNumber = getDisplayNumber
  85. setDisplayNumber = setDisplayNumber
  86. setDisplayError = setDisplayError (config.divideByZeroMsg)
  87. initState = initState
  88. }

Some comments:

  • I have created a configuration record that stores properties that are used to parameterize the services, such as the decimal separator.
  • The configuration record is passed into the createServices function, which in turn passes the configuration on those services that need it.
  • All the functions use the same approach of returning one of the types defined in the design, such as UpdateDisplayFromDigit or DoMathOperation.
  • There are only a few tricky edge cases, such as trapping exceptions in division, or preventing more than one decimal separator being appended.

Creating the user interface

For the user interface, I’m going to use WinForms rather than WPF or a web-based approach. It’s simple and should work on Mono/Xamarin as well as Windows.
And it should be easy to port to other UI frameworks as well.

As is typical with UI development I spent more time on this than on any other part of the process!
I’m going to spare you all the painful iterations and just go directly to the final version.

I won’t show all the code, as it is about 200 lines (and you can see it in the gist), but here are some highlights:

  1. module CalculatorUI =
  2. open CalculatorDomain
  3. type CalculatorForm(initState:InitState, calculate:Calculate) as this =
  4. inherit Form()
  5. // initialization before constructor
  6. let mutable state = initState()
  7. let mutable setDisplayedText =
  8. fun text -> () // do nothing

The CalculatorForm is a subclass of Form, as usual.

There are two parameters for its constructor.
One is initState, the function that creates an empty state, and calculate, the function that transforms the state based on the input.
In other words, I’m using standard constructor based dependency injection here.

There are two mutable fields (shock horror!).

One is the state itself. Obviously, it will be modified after each button is pressed.

The second is a function called setDisplayedText. What’s that all about?

Well, after the state has changed, we need to refresh the control (a Label) that displays the text.

The standard way to do it is to make the label control a field in the form, like this:

  1. type CalculatorForm(initState:InitState, calculate:Calculate) as this =
  2. inherit Form()
  3. let displayControl :Label = null

and then set it to an actual control value when the form has been initialized:

  1. member this.CreateDisplayLabel() =
  2. let display = new Label(Text="",Size=displaySize,Location=getPos(0,0))
  3. display.TextAlign <- ContentAlignment.MiddleRight
  4. display.BackColor <- Color.White
  5. this.Controls.Add(display)
  6. // traditional style - set the field when the form has been initialized
  7. displayControl <- display

But this has the problem that you might accidentally try to access the label control before it is initialized, causing a NRE.
Also, I’d prefer to focus on the desired behavior, rather than having a “global” field that can be accessed by anyone anywhere.

By using a function, we (a) encapsulate the access to the real control and (b) avoid any possibility of a null reference.

The mutable function starts off with a safe default implementation (fun text -> ()),
and is then changed to a new implementation when the label control is created:

  1. member this.CreateDisplayLabel() =
  2. let display = new Label(Text="",Size=displaySize,Location=getPos(0,0))
  3. this.Controls.Add(display)
  4. // update the function that sets the text
  5. setDisplayedText <-
  6. (fun text -> display.Text <- text)

Creating the buttons

The buttons are laid out in a grid, and so I create a helper function getPos(row,col) that gets the physical position from a logical (row,col) on the grid.

Here’s an example of creating the buttons:

  1. member this.CreateButtons() =
  2. let sevenButton = new Button(Text="7",Size=buttonSize,Location=getPos(1,0),BackColor=DigitButtonColor)
  3. sevenButton |> addDigitButton Seven
  4. let eightButton = new Button(Text="8",Size=buttonSize,Location=getPos(1,1),BackColor=DigitButtonColor)
  5. eightButton |> addDigitButton Eight
  6. let nineButton = new Button(Text="9",Size=buttonSize,Location=getPos(1,2),BackColor=DigitButtonColor)
  7. nineButton |> addDigitButton Nine
  8. let clearButton = new Button(Text="C",Size=buttonSize,Location=getPos(1,3),BackColor=DangerButtonColor)
  9. clearButton |> addActionButton Clear
  10. let addButton = new Button(Text="+",Size=doubleHeightSize,Location=getPos(1,4),BackColor=OpButtonColor)
  11. addButton |> addOpButton Add

And since all the digit buttons have the same behavior, as do all the math op buttons, I just created some helpers that set the event handler in a generic way:

  1. let addDigitButton digit (button:Button) =
  2. button.Click.AddHandler(EventHandler(fun _ _ -> handleDigit digit))
  3. this.Controls.Add(button)
  4. let addOpButton op (button:Button) =
  5. button.Click.AddHandler(EventHandler(fun _ _ -> handleOp op))
  6. this.Controls.Add(button)

I also added some keyboard support:

  1. member this.KeyPressHandler(e:KeyPressEventArgs) =
  2. match e.KeyChar with
  3. | '0' -> handleDigit Zero
  4. | '1' -> handleDigit One
  5. | '2' -> handleDigit Two
  6. | '.' | ',' -> handleDigit DecimalSeparator
  7. | '+' -> handleOp Add
  8. // etc

Button clicks and keyboard presses are eventually routed into the key function handleInput, which does the calculation.

  1. let handleInput input =
  2. let newState = calculate(input,state)
  3. state <- newState
  4. setDisplayedText state.display
  5. let handleDigit digit =
  6. Digit digit |> handleInput
  7. let handleOp op =
  8. Op op |> handleInput

As you can see, the implementation of handleInput is trivial.
It calls the calculation function that was injected, sets the mutable state to the result, and then updates the display.

So there you have it — a complete calculator!

Let’s try it now — get the code from this gist and try running it as a F# script.

Disaster strikes!

Let’s start with a simple test. Try entering 1 Add 2 Equals. What would you expect?

I don’t know about you, but what I wouldn’t expect is that the calculator display shows 12!

What’s going on? Some quick experimenting shows that I have forgotten something really important —
when an Add or Equals operation happens, any subsequent digits should not be added to the current buffer, but instead start a new one.
Oh no! We’ve got a showstopper bug!

Remind me again, what idiot said “if it compiles, it probably works”.*

* Actually, that idiot would be me (among many others).

So what went wrong then?

Well the code did compile, but it didn’t work as expected, not because the code was buggy, but because my design was flawed.

In other words, the use of the types from the type-first design process means that I do have high confidence that the code I wrote is a correct implementation of the design.
But if the requirements and design are wrong, all the correct code in the world can’t fix that.

We’ll revisit the requirements in the next post, but meanwhile, is there a patch we can make that will fix the problem?

Fixing the bug

Let’s think of the circumstances when we start a new set of digits, vs. when we just append to the existing ones.
As we noted above, a math operation or Equals will force the reset.

So why not set a flag when those operations happen? If the flag is set, then start a new display buffer,
and after that, unset the flag so that characters are appended as before.

What changes do we need to make to the code?

First, we need to store the flag somewhere. We’ll store it in the CalculatorState of course!

  1. type CalculatorState = {
  2. display: CalculatorDisplay
  3. pendingOp: (CalculatorMathOp * Number) option
  4. allowAppend: bool
  5. }

(This might seem like a good solution for now, but using flags like this is really a design smell.
In the next post, I’ll use a different approach which doesn’t involve flags)

Fixing the implementation

With this change made, compiling the CalculatorImplementation code now breaks everywhere a new state is created.

Actually, that’s what I like about using F# — something like adding a new field to a record is a breaking change, rather than
something that can be overlooked by mistake.

We’ll make the following tweaks to the code:

  • For updateDisplayFromDigit, we return a new state with allowAppend set to true.
  • For updateDisplayFromPendingOp and addPendingMathOp, we return a new state with allowAppend set to false.

Fixing the services

Most of the services are fine. The only service that is broken now is initState, which just needs to be tweaked to have allowAppend be true when starting.

  1. let initState :InitState = fun () ->
  2. {
  3. display=""
  4. pendingOp = None
  5. allowAppend = true
  6. }

Fixing the user interface

The CalculatorForm class continues to work with no changes.

But this change does raise the question of how much the CalculatorForm should know about the internals of the CalculatorDisplay type.

Should CalculatorDisplay be transparent, in which case the form might break every time we change the internals?

Or should CalculatorDisplay be an opaque type, in which case we will need to add another “service” that extracts the buffer from the CalculatorDisplay type so that the form
can display it?

For now, I’m happy to tweak the form if there are changes. But in a bigger or more long-term project,
when we are trying to reduce dependencies, then yes, I would make the domain types opaque as much as possible to reduce the fragility of the design.

Testing the patched version

Let’s try out the patched version now (you can get the code for the patched version from this gist).

Does it work now?

Yes. Entering 1 Add 2 Equals results in 3, as expected.

So that fixes the major bug. Phew.

But if you keep playing around with this implementation, you will encounter other bugs undocumented features too.

For example:

  • 1.0 / 0.0 displays Infinity. What happened to our divide by zero error?
  • You get strange behaviors if you enter operations in unusual orders. For example, entering 2 + + - shows 8 on the display!

So obviously, this code is not yet fit for purpose.

What about Test-Driven Development?

At this point, you might be saying to yourself: “if only he had used TDD this wouldn’t have happened”.

It’s true — I wrote all this code, and yet I didn’t even bother to write a test that checked whether you could add two numbers properly!

If I had started out by writing tests, and letting that drive the design, then surely I wouldn’t have run into this problem.

Well in this particular example, yes, I would probably would have caught the problem immediately.
In a TDD approach, checking that 1 + 2 = 3 would have been one of the first tests I wrote!
But on the other hand, for obvious flaws like this, any interactive testing will reveal the issue too.

To my mind, the advantages of test-driven development are that:

  • it drives the design of the code, not just the implementation.
  • it provides guarantees that code stays correct during refactoring.

So the real question is, would test-driven development help us find missing requirements or subtle edge cases?
Not necessarily. Test-driven development will only be effective if we can think of every possible case that could happen in the first place.
In that sense, TDD would not make up for a lack of imagination!

And if do have good requirements, then hopefully we can design the types to make illegal states unrepresentable
and then we won’t need the tests to provide correctness guarantees.

Now I’m not saying that I am against automated testing. In fact, I do use it all the time to verify certain requirements, and especially for integration and testing in the large.

So, for example, here is how I might test this code:

  1. module CalculatorTests =
  2. open CalculatorDomain
  3. open System
  4. let config = CalculatorConfiguration.loadConfig()
  5. let services = CalculatorServices.createServices config
  6. let calculate = CalculatorImplementation.createCalculate services
  7. let emptyState = services.initState()
  8. /// Given a sequence of inputs, start with the empty state
  9. /// and apply each input in turn. The final state is returned
  10. let processInputs inputs =
  11. // helper for fold
  12. let folder state input =
  13. calculate(input,state)
  14. inputs
  15. |> List.fold folder emptyState
  16. /// Check that the state contains the expected display value
  17. let assertResult testLabel expected state =
  18. let actual = state.display
  19. if (expected <> actual) then
  20. printfn "Test %s failed: expected=%s actual=%s" testLabel expected actual
  21. else
  22. printfn "Test %s passed" testLabel
  23. let ``when I input 1 + 2, I expect 3``() =
  24. [Digit One; Op Add; Digit Two; Action Equals]
  25. |> processInputs
  26. |> assertResult "1+2=3" "3"
  27. let ``when I input 1 + 2 + 3, I expect 6``() =
  28. [Digit One; Op Add; Digit Two; Op Add; Digit Three; Action Equals]
  29. |> processInputs
  30. |> assertResult "1+2+3=6" "6"
  31. // run tests
  32. do
  33. ``when I input 1 + 2, I expect 3``()
  34. ``when I input 1 + 2 + 3, I expect 6``()

And of course, this would be easily adapted to using NUnit or similar.

How can I develop a better design?

I messed up! As I said earlier, the implementation itself was not the problem. I think the type-first design process worked.
The real problem was that I was too hasty and just dived into the design without really understanding the requirements.

How can I prevent this from happening again next time?

One obvious solution would be to switch to a proper TDD approach.
But I’m going to be a bit stubborn, and see if I can stay with a type-first design!

In the next post, I will stop being so ad-hoc and over-confident,
and instead use a process that is more thorough and much more likely to prevent these kinds of errors at the design stage.

The code for this post is available on GitHub in this gist (unpatched)
and this gist (patched).