Welcome to the 9th iteration of WebCalc. Previously I managed to extract billing module from the app module and started differentiating users from one another. This time I’ll enable the users to define their own functions. To IDEs then.
But first I must apologize for what I’ve done last time. I coupled my
User class with Spring’s
UserDetails. It’s obvious to me now, in my face even. Apparently, it wasn’t back then. What I should have done is to create my
User class free of any Spring influences and then create a
SpringUserAdapter class that implements Spring’s
UserDetails and keeps internally an instance of my
User class. That way my application can use
User class wherever it needs to oblivious of Spring’s existence, but when necessary it has a mechanism to talk to Spring in its own language. Luckily the mistake is easy to fix, though it takes a few changes, see commit 5230203c.
From now on I’ll also be skipping all upgrade information, it’s necessary but doesn’t bring anything in this series. Still, I’ll be doing the upgrades. This time it was a bigger one, after upgrading Gradle I also switched to the modern plugins DSL and unified the script where possible, see commit c590bccb if you’re interested.
Now to the actual coding exercise. At the end I want my users to be able to define their own functions. My example would be calculating the area of a circle. In reverse polish notation that would look like this:
^2 π *. This assumes, that the radius of the circle is already on top of the stack I’m using. To define a function like this I need one more element, functions name. I’ll just prefix functions definition with it. Now I can write my test:
As expected, it fails. I’ll be going baby steps, I’ll try to stay almost constantly in green and only push the functionality to desired places by adding new tests. The answer to the question “what is the simplest thing that could possibly work” with my currently red test is this:
That satisfies the tests, but just them. I’m now left with two calls that need to be passed down to the
Calculator. The problem is that they need to go together. The only way to verify that defining custom function works is to first define a function and then do calculations with it. Otherwise, I’d need to test some internal state of
Calculator and thus couple my tests to the chosen implementation. That would be wrong. The test for the
Calculator itself looks like this:
It doesn’t even compile, so:
Now it fails because “circle_area” function is not defined. No surprise here. Again, using baby steps I’m going to hardcode the result just to make the test pass.
I think I can now fix the
CalculatorController and just call
I just used obvious implementation here and pushed all the responsibility to the
Calculator. I don’t expect to revisit the controller again in this iteration, but we’ll see.
Now the real troubles begin. If I take a look at the definition of my function,
^2 π *, I see two issues. First of all, I don’t have a square (
^2) function, but even more troublesome is the fact that it takes just one argument! So far all my functions were taking 2 arguments. So refactoring is waiting ahead. The second problem I’ll have is
π. It’s not a regular digit, I cannot just call
new BigDecimal("π"). What I can do is to use a fact that Java has a constant
Math.PI and make
π a 0-argument function. That would align very well with what I need to do anyway with
Like mentioned already my main problem now is that in
Calculator my code assumes that the functions are always taking 2 arguments. Let’s remove that assumption and let each function decide on its own how many arguments it takes. For now, I don’t need any new tests, I’ll be refactoring in green.
Now all my function get the
Stack and take whatever they need, all by themselves. That allows me to try to implement a
π function. The test is simple:
The implementation even simpler:
Square function comes next, it’s also trivial:
I have now all the individual pieces I need to calculate my
circle_area function. I’m also getting an actual definition of
circle_area function through
defineCustomFunction. I could now replace
circle_area in the input string with the definition, and it should work.
Indeed, it does the trick. However, now my integration test failed. After digging deeper I found out, that my
CalculatorController.defineCustomFunction() function is getting as a parameter this:
circle_area ^2 ? *, it looks like
π character it not passed through properly. I must say it’s interesting and unexpected.
The error took a bit of time to be found, but is, as usual, on me. What I did wrong this time is forgetting to set character encoding in Rest Assured. By default, it uses “ISO-8859-1” which doesn’t contain
π. The fix is easy:
To push me further with defining and using custom functions I need another example of a function. This follows ZOMBIES approach, I support currently just one custom function and now I want to support many. To keep it simple the function with its definition would be
It now works, but I don’t like the solution. It’s not only about that it’s wrong. If I’d define two functions and in one I’d use the other, successful calculation would then depend on the order in which
customFunctions would return me the entries. There’s something else. But let’s first try to create a test for the scenario I described.
It fails, I was right, and I need to do something about it.
What bothers me is that there’s currently no way of executing partial computation. Let’s take an example:
2 3 + circle_area 3 *. If I had ignored replacing a function name with its definition, after applying a few steps I would arrive at a place where I’d have
5 on the stack, and I’d have
circle_area 3 * still to process. It is now that I’d like to take the definition of
circle_area and calculate it using the stack I got so far. I do not seem to have a function in my
Calculator class that could do it.
After looking closely at my
eval function I can see that it does two things. One is parsing and tokenizing the input, creating a new stack and formatting the result, so it’s translating between input/output format and the structures I need inside
Calculator. The other one is looping through the tokens and performing the actual computation. I always knew that there’s something wrong with it. It looks to me that if I separate those two responsibilities, I should get a function I need.
It looks better, but the number of parameters I had to pass worries me a bit. I’ll try to deal with that later. I can finally stop replacing function names with their definitions and just try to evaluate them once I encounter them, like so:
It looks far better now, I’m using custom function only if I encounter them, and the problem with the order of replacing names with definitions of custom functions is gone, the test verifying that is green.
I believe I have now a fully functioning system with the support of defining custom functions. Time to make it right. Let’s start with something small. All functions take their parameters from the stack by themselves, but they let someone else put the result back on the stack, that’s not really correct.
The second thing that bothers me for some time already is the inner
eval. Specifically, each token can be one of a number, a predefined function or a custom function. There should be a switch or a polymorphic call, but I have a combination of a
catch and an
if. My first attempt with an enum:
I didn’t even use the
TokenType yet, but I already don’t like where this is going.
detect required me already to:
customFunctionsmap into it,
- duplicate logic from
All three together smell of feature envy, they refer back to
Calculator class itself too often. Besides, my code is now WET. I could try to DRY it out a bit, but looking at it I’m getting an idea of a better approach.
eval() looks absolutely hideous, but you probably already know where I’m going with that. I also didn’t get rid of this
catch combination, but at least it doesn’t pollute
eval function. What
TokenType needs now is a method, that will take everything that is necessary for all three implementations to do their jobs. Then all the statements from
ifs will go to their respective classes. But before I could do that I need one more thing. There seem to be three objects constantly being passed around together:
maxFractionDigits. They seem to form some
Now I can change all implementations of
TokenType and add an
apply function. I’m following Java naming convention from
Function interface here.
Now it looks far better,
if series trying to figure out the class is gone along with class casting. Now it looks like
function belongs to the
PredefinedFunction class and
parse to the
Number class, let’s move them.
One last tiny thing. Custom functions are still kept as full strings and are tokenized on every execution. I don’t like that.
Now I’m satisfied and all my tests are still green, good. A small comment, all those refactorings I did are to my liking. They seem to be going slightly into the direction of functional programming. It’s honestly a matter of taste. The problems with the design I was trying to fix were also not critical, I could live with them in the code. In real life, I’d probably do some of them, but later, once they really start to bug me. But as this series is about learning and showing how to do things, taking care of the design, etc. I decided to do all those refactorings. The tests helped me a lot, at no point in time I was worried that I would break something without noticing.
That’s it this time. In the next iteration, I’ll try to look into Java libraries. That was also one of my goal for this series. I might not have that much code yet, but it should be enough to try and build the project using libraries. That also means that I’ll not be really coding, only messing around with building the project. See you next time!