Javascript Checkout -- Triangulation 3
In which I consider various conflicts between multiple domains
This article is part 6 in a series in which I’m doing a well-known code kata in the “TDD as if you meant it” style. I’m also strictly following the 4 rules of simple design and looking for opportunities to use implicit coupling to drive refactoring under the Once And Only Once rule. (If you missed the start of this series you can catch up with part 1 here.)
Last time, I decoupled the code from the prices of A and B.
Currently on the branch we have this code:
The tests pass, so I move straight on to readability — does this code tell the domain’s story clearly? I’ve not looked at this code for a couple of days, and so I’m reviewing it again today with somewhat fresh eyes. What jumps out at me right away is the two length checks on lines 3 and 5: based on what we know of the domain, they surely can’t stay like that for long (in the “real world”, not every shopping cart that has only one item will contain an A, for example). But I’m shooting for TDD, and right now I don’t have a test that requires those checks to change. They currently do the job I need them to do, and I know I can rely on future tests to drive out a more realistic algorithm.
This is an interesting point of tension, particularly for folks who are new to test-driven development. We “know” that the current code is an incomplete representation of the “real world” domain. But it is also the simplest way to make the current set of tests pass. So the code is in fact a highly faithful model of the domain as defined by the tests. That’s an important distinction: there are two domains in play at any moment. The current set of tests defines one domain, and we’re gradually incrementing that to be closer and closer to the “real world” domain.
On another day I could be easily tempted to say that those length checks are so obviously “wrong” that I would immediately look for another test or two to allow me to drive them away. That would definitely be a legitimate course of action, and there’s a lot to discuss in regard to the idea of deferring a few REFACTOR steps. Remind me to come back to that concept in a few weeks? But the purpose of this series is to push hard on refactoring away the coupling as soon as it appears, so let’s move on to that next.
(Another interesting point — at least for me — is that I wasn’t expecting to talk about this topic today. I don’t have a plan for this series of articles, and so I’m being completely carried along by the current. Whatever I see today is what I put down here. It’s not quite stream of consciousness, but I’m finding this level of deep introspection quite fascinating. I hope you’re enjoying that aspect too…)
And so on to rule 3. Does anything in this code violate Once And Only Once? I don’t think so. And yet there is Connascence of Position, because nothing prevents the caller from passing those three arguments — items, priceOfA, priceOfB
— in the wrong order. This is exactly the situation I unpicked in Functions with Multiple Parameters a couple of weeks back: there is no way to write the caller’s code such that it is obvious which is the correct parameter order.
So, following my own advice from that earlier article, I’m going to replace the pair priceOfA, priceOfB
with a containing structure:
The tests pass, so I commit: Replace two parameters with a structure
.
I want to note a couple of things about this step. Firstly, aficionados of code smells might call this Long Parameter List. And in my book1, our recommended remedy is to use Martin Fowler’s Introduce Parameter Object refactoring. Here, I have no evidence (yet) that an object is necessary, so a structure will suffice.
And secondly, this still leaves the function having two parameters. And because Javascript offers no static types in the source code, I still can’t know in the caller which order is correct. So I’ve improved the situation a little, but not yet completely eradicated the problem.
But first, I made a change and so by the rules of these articles I have to go back around the 4 rules of Simple Design. The tests pass (rule 1), but does this code express everything in the best way possible (rule 2)? Well, it seems to me that prices.priceOfA
is not only clumsy but also contains duplication. Instead I can use the product name as the key into the lookup table:
The tests pass, and I’m happy that this expresses my intention. Commit: Improve relationship between prices and products.
I note that Javascript would allow me to say prices.A
at line 4. But prices[‘A’]
expresses my intention better, I feel, because of the symmetry with line 13. If the product name contained spaces, or weren’t a string, the approach I’ve used is the only workable one; but even in the simple current case I prefer the approach of expressing the lookup key in the same way everywhere it occurs. I’ve noted before that self-symmetry is a powerful tool for me: symmetries make the code easier to read (maybe something to do with having less to remember?). Symmetries also offer clues to various kinds of coupling, so I seek them out and highlight them whenever I can. So even though my programming language offers me a more succinct, idiomatic, option I prefer to expose the symmetry and express the domain a little more faithfully.
And so let’s look again at how well this code tells the story my Customer told me. I just introduced the name prices
, so I re-read all of those changed lines. There’s something not quite right in the story-telling, and I finally spot what it is. The little structure defined on line 12 doesn’t just contain prices, so the name is wrong. And looking at line 22 I can now see that priceList
would be much better:
That feels much better. Commit: Express the domain more clearly
.
Two thoughts strike me now, both triggered by making that change. Firstly, that structure obviously isn’t a “list” in the data structure sense. But that is what we tend to call them in the real world. Could that cause confusion? Only if the reader brings along with them expectations of programming style. Because shoot me if I ever include a language type in the name of a local variable or constant. Hungarian notation creates coupling between implementation and intention, so it’s generally a bad idea. And as with prices.A
above,
whenever there’s a conflict between expressing the domain and writing idiomatic code, the domain should win every time.
Secondly, I chose to change the name in both the code and the test, even though they are strictly independent of each other.
But I can’t think of different names that might work better, so I’m happy to leave it for now. Something to be mindful of as we proceed, maybe.
Next time: is it time for the next test, or do those references to product names (lines 4, 6, 12, 13) represent coupling that we want to fix?
Things to try
Find a function in your own code that has two parameters of the same type. Try following Fowler’s mechanical steps for Introduce Parameter Object. How did that feel, compared to using an IDE to accomplish the same thing?
Look for places in your own code where you could increase the symmetry. What happens to understandability when you do?
Do the names used inside your functions reflect the function’s internal context, or are they coupled to the names used by the callers?
As always, if you try anything here please let your fellow readers know how it went by leaving a comment.
Wake, Rutherford: Refactoring in Ruby. Addison-Wesley, 2009. ISBN 978-0321545046. On amazon.co.uk: https://amzn.to/3RKPpHT (affiliate link).