Strict Mocks and Characterization Tests 21
This week I worked with a great group in Canada. This group of people had me using Moq for the first time and I found it to be a fine mocking tool. In fact, it reminded me of why I think the Java language is now far outclassed by C# and only getting more behind (luckily the JVM has many languages to offer).
One issue this group is struggling with is a legacy base with several services written with static API’s. These classes are somewhat large, unwieldy and would be much improved with some of the following refactorings:- Replace switch with polymorphism
- Replace type code with strategy/state
- Introduce Instance Delegator
- Use a combination of template method pattern + strategy and also strategy + composite
This is actually pretty standard stuff and this group understands the way forward. But what of their existing technical debt?
Today we picked one method in particular and attempted to work our way through it. This method was a classic legacy method (no unit tests). It also had a switch on type and then it also did one or more things based on a set of options. All of this was in one method.
If you read Fowler’s Refactoring Book, it mentions a combination of encapsulating the type code followed by replacing switch with polymorphism for the first problem in this method (the switch). We were able to skip encapsulating the type code since we wanted to keep the external API unchanged (legacy code).
So we first created a base strategy for the switch and then several empty derived classes, one for each of the enums. This is a safe legacy refactoring because it only involved adding new code.
Next, we created a factory to create the correct strategy based on the type code and added that to the existing method (we also added a few virtual methods). Again, a safe refactoring since it only involved adding effectively unused code (we did create the factory using nearly strict TDD). Finally, we delegated from the original method to the strategy returned from the factory. Safe again, since we had tested the factory.
So far, so good. But next, we wanted to push the method down to each of the subclasses and remove the parts of the logic that did not apply to each given type. We did a spike refactoring to see what that’d be like and it was at least a little dicey. We finally decided to get the original method under test so that as we refactored, we had the safety net necessary to refactor with confidence.
We started by simply calling the method with null’s and 0 values. We worked our way through the method, adding hand-rolled test doubles until we came across our first static class.
Their current system has DAO’s with fully static interfaces. This is something that is tough to fake (well we were not using and AOP framework, so …). Anyway, this is where we introduced the instance delegator. We:- Added an instance of the class as a static member (essentially creating a singleton).
- Added a property setter and getter (making it an overridable singleton).
- We then copied the body of the static method into an instance method, which we made virtual.
- We then delegated the static method to the virtual method.
- Then, in the unit test, we set the singleton to a hand-coded test double in the setup and reset the singleton in the tear down method.
We had to do this several times and on the third time (I think it was the third time), the hand-rolled test double would have had to implement several (17ish) methods and it became clear that we were ready to use a mocking framework. They are using Moq so we started using Moq to accomplish the remainder of the mocking.
After some time, we managed to get a test that essentially sent a tracer bullet through one path of the method we wanted to get under test. When the test turned green there was much rejoicing.
However, we had to ask the question: “So what are we testing?” After some discussion, we came up with a few things:- This method currently makes calls to the service layers and those calls depend on both an enumeration (replaced with a shallow and wide hierarchy of strategies) and options (to be replaced with a composition of strategies).
- It also changes some values in an underling domain object.
So that’s what we needed to characterize.
We had a discussion on this and as a group. We wanted a way to report on the actual method calls so we could then assert (or in Moq parlance Verify). We looked at using Moq’s callbacks, but it appears that those are registered on a per-method basis. We briefly toyed with the idea of using an AOP tool to introduce tracing, but that’s for another time (I’m thinking of looking into it out of curiosity) but we decided that we could instead do the following:- Begin as we already had, get through the method with a tracer.
- Determine the paths we want to get under test.
- For each path:
- Create a test using strict mocks (which fail as soon as an unexpected method is called)
- Use a Setup to document this call as expected – this is essentially one of the assertions for the characterization test.
- Continue until we have all the Setups required to get through the test.
- Add any final assertions based on state-based checks and call VerifyAll on the Moq-based mock object.
This would be a way we could work through the method and characterize it before we start refactoring it in earnest.
This might sound like a lot of work and it certainly is no cake walk, but all of this work was done by one of the attendees and as a group they certainly have the expertise to do this work. And in reality, it did not take too long. As they practice and get some of the preliminary work finished, this will be much easier.
Overall, it was a fun week. We:- Spent time on one project practicing TDD and refactoring to patterns (they implemented 5 of the GoF patterns).
- Spent time practicing some of Fowler’s refactorings and Feather’s legacy refactorings.
- Spent a day practicing TDD using mocks for everything but the unit under test. At the end they had a test class, one production class and several interfaces.
In retrospect, the work they did in the first three days was nearly exactly what they needed to practice for the final day of effort. When we started tackling their legacy code, they had already practiced everything they used in getting the method under test.
So overall great week with a fun group of guys in Canada.
Your code is not flat 11
There are three possibilities for the universe. On the one hand it is Closed. There’s enough matter that the universe, currently expanding, will eventually stop expanding and collapse in on itself.
The universe might be open. There’s not enough matter for gravitational forces to bring the universe back in on itself.
The final option is that the universe is flat. There’s just enough mass that it will stop expanding but it will not collapse in on itself.
Assuming an infinite number of universes, the final option, the flat option, must surly exist, somewhere. Probably not here, but somewhere. So for all intents and purposes, our universe is likely open or closed but not flat.
While you were reading that, your code rotted just a little. You’ve heard of it, bit rot. The bits in a program sitting on a disk rot. It’s a well known fact. Sure the magnetic surface could give out or if it’s in memory, a stray cosmic ray could flip a bit.
But that’s not what I’m talking about. You know the experience. It was just working and now it is not working. You did not change anything so therefore it is the same as it was and so something else must have changed. This is bit rot.
Typically, bit rot is really just our expectations being crushed by reality. Never let a fact get in the way of a good theory, that’s my motto. This works especially well if you tend to be a better talker than listener (guilty).
At any rate, your code continues to rot as you continue to read this. Why? Someone using it has managed to find another thing programmers did not think about. So what was “working” before is not “working” now. Wait, was it really working? If someone was getting value out of using it then yes it was working. Is it working now? If nothing about the system has changed but it no longer serves a purpose, then it is not working.
Here’s a question. If a tree falls in the forest and nobody hears it, does it make a sound? Answer from a human perspective, no. If there’s nobody to hear it, then it didn’t make a sound. Sure it moved some air about and you could take an infinite sum of sine waves to characterize that air movement but then if you characterize it then something was there to observe it and that seems to violate the spirit of “nobody hears it.”
What about if there’s a problem in the code and nobody hits it, does your code have a defect? Using the tree as an example, then the answer is no. However, unlike the tree’s sound, which quickly dissipates (if for no other reason that then laws of thermodynamics), your code tends to stay around a bit longer so the chance for observation of a sleeping defect is higher (though to be fair, if you wait long enough your code, like the sound in the forest, will go away).
OK, but it is even worse than that. Things change. If your body is not changing then you are dead. That’s a fact. In one study by Dr. Frisen, he demonstrated the age of cells in a rib bone of a 30 year old to be just over 15 years, while the cells that line the stomach to be closer to 5 days. Your body is constantly fixing itself. Cells replace themselves. That’s why you can donate platelets every 3 days and blood every 56 days.
What about your code? Is it fixing itself? Is it repairing itself? Does it need to? If your code is not changing, then the project is dead. Sure, it takes some time for all usefulness to finally wear out of the system but at some point the system will essentially bit rot so badly that is serves no useful purpose. (While there are multiple clinical definitions of “dead” for a body, some life remains long after you are clinically dead – your fingernails will keep growing for some time after you expire.)
There are several things that cause your system to decay. Here are just a few:- What the business needs changed so until the system catches up it has less value
- The act of introducing the system has changed the business so that the system needs to change
- Someone misunderstood something (it’s actually no small miracle that people can communicate at all – my wife would argue she cannot get through to me)
- Someone actually changed some code badly (most 1-line bug fixes introduce new defects – see Weinberg)
- Someone is compelled to hack it in to meet the deadline/demo/beat the lunch crowd/...
Your code is rotting while you read this because the environment around it is changing. Add to that an observation that most changes to code introduce rather than reduce chaos and it’s no wonder your system is either closed (code collapses in on itself, weighed down by its own incidental complexity) or open (nobody can decide what to do and the project eventually sort of fizzles into nothingness).
So what are you doing to actively maintain your system’s integrity? Are you just using antibiotics (patching things quickly hoping that there’s no super bug on the horizon – there’s always a super bug on the horizon no matter how strong an alligator’s immune system might be), or are you doing what the surgeon general has been saying for years: getting enough sleep, working out regularly, eat healthily (there’s a moving target).
Your code is rotting every day in every way unless you are actively working against that tide. Talking to your users – being friendly even, writing tests (acceptance, integration, smoke, unit, load, exploratory, ... mostly automated), integrating often, refactoring, learning to see beginning of rot rather than the end of it, etc.
There are a lot of things we can do and need to do to make a system. Part of that is keeping the system alive and breathing. Unlike biological systems where nature (and natural selection) has resulted in a self-monitoring, self-healing systems, code does not really monitor itself and fix itself (well most code does not do that).
Maybe there’s been a hidden force driving the creation of these things we called code maintainers whose existence is predicated on the need to repair living code to keep it around just a bit longer.
If this is the case, then just like the waist lines of americans are increasing as our biological systems have not had enough time to adjust to the changing environment, our code bases are bloating and getting to the point where code maintainers cannot keep the systems living long enough.
It seems a shift is in order to make it possible to keep these living, but often very sick, applications alive longer. In a sense, if you are practicing keeping your code clean, you are like a doctor because you diagnosing sickness, prescribing a path to heath and, if necessary, making incisions, using slugs or just waiting for a given sickness to simply follow its natural course before going away (like the requirement that simply must be done right away, just because).
Unfortunately, if we are calling ourselves doctors, then we’re still learning the value of keeping our hands clean between procedures. Unlike doctors of the mid-19th century who thought more blood on clothes = more experience, Ignaz Semmelweis figured out that hand washing saved lives.
As a community we’re not there yet. There’s still pride is hacking something together.
Your code is getting worse, what are you going to do about it?
Shoveling Code 19
I was ill when the first snow fell last weekend, and didn’t get out and scrape the drive and sidewalks. Sadly, neither did I bundle up my kids and send them out with shovels. As a result, the snow melted and refroze. When I left home on sunday morning, clearing the snow was frustrated by the presence of hard, packed ice under the snow.
Now I have to be very careful when moving in the drive or sidewalk because of the black ice in some places and thick lumpy stuff in others. I could choose to leave it that way and just walk carefully and try to avoid the area, but I really don’t want it to be like this all winter.
Instead, I bundled up and spent several painful hours trying to clear not only the six to eight inches of new snow, but the ice under it. Clearing the fresh stuff is tough because the rough and lumpy ice is under it. The ice snags the shovel and hurts my elbows and shoulders. I use the metal edge of the shovel to plane off the ice so that it’s not quite so difficult, and the work gets a little easier.
Smooth, planed ice is not a solution. It is actually going to be more slippery and dangerous when the sun hits it and it melts a little. It will then refreeze to an even smoother and slicker sheen. I can’t ignore the problem, and I can’t smooth it over and leave it at that.
I invested in some complicated salt product to lower the melting point of the ice so I can scrape it away. It wasn’t enough, but now there are some safe areas in my driveway. I continue to scrape it and hope that I can get it all squared away so that my wife and kids have safe passage, though it is tedious and unpleasant work.
Yes, it’s my fault. I should have bundled up my sick self or my children and taken care of this when the first snow fell, back when it was easy.
Then it dawned on me that my driveway is a lot like source code. If I always take care of it when it’s relatively easy and not too polluted, it will remain easier to deal with in the longer term. The more unsafe and ugly it is, the more important it is to clean it up. It won’t do to smooth it out a little or to leave it be. It needs to be safe for me and my team.
And I guess that’s my parable for today.
Naming: A Word To The Wise 12
Please pronounce your variable names as words, even if they are merely abbreviations. The phonemes indicated by your spelling may correspond to an existing word with an entrenched meaning. It may be one that you do not intend to communicate.
On Being Stupid 40
This was posted originally to a mailing list, but is reproduced here essentially unchanged by request of a friend.
I frequently see code (written by others) that is completely double-spaced, heavily commented, loaded with many abbreviated or meaningless variable names, and hundreds of lines long. In order to read the function and understand what it’s doing, poor Tim must wear out a mouse, skip comments, and track the variables on paper. A “smarter” programmer could just load it into his head, I suppose, but not the simpleton who writes this email.
I’m not smart enough to just read it from top to bottom and understand it. Sadly, when I read through and understand what in the heck the thxIniFvt variable is doing, I will forget it by the time I figure out the purpose(es) of pszVbt. I can spend all day, or even a few days to figure out a method, and that’s an admission of feeble-mindedness to be sure. I guess I’m not up to the level of some of the rapid hackers. That’s a limitation I face most days.
I find that I can sometimes understand a method like that only if I just delete all the blank lines and comments first, then reformat to break lines, then inline all methods with seven or more parameters, and then start renaming variables, extracting explanatory variables, and extracting explanatory methods. I may have to break the method into classes even. I guess I’m not one of the smart kids.
I used to be one of the smart kids. I once built a module so complex and fragile that nobody but me could figure out what to do with it. It was all tables and indicators, and stunningly clever. I am so ashamed that I wrote it. It was such a mistake that they eventually disabled it rather than field it in such a state. That was years ago, but so memorable to me. Other programmers said it was like the inside of a swiss watch, all delicate and perfectly balanced, and scary to mess with unless you first knew exactly what each part was doing, and why.
I would like to be faster than I am both mentally and in the sense of quickly producing code. I’d like to be a little less intimidated at the start of a project. .But I would not want those things if it meant building crap that people who are not appreciably more talented than myself would trip over every day. Instead, I sometimes wish I could teach the really fast, smart kids how to dumb down the code for the rest of us morons to read.
The funny thing is that dumbing code to my level doesn’t make it harder for the smart kids to use it, and sometimes allows a compiler to do a better job with it. I guess stupid isn’t so stupid after all.
Why we write code and don't just draw diagrams 25
Advocates of graphical notations have long hoped we would reach the point were we only draw diagrams and don’t write textual code. There have even been a few visual programming environments that have come and gone over the years.
If a picture is worth a thousand words, then why hasn’t this happened?
What that phrase really means is that we get the “gist” or the “gestalt” of a situation when we look at a picture, but nothing expresses the intricate details like text, the 1000 words. Since computers are literal-minded and don’t “do gist”, they require those details spelled out explicitly.
Well, couldn’t we still do that with a sufficiently expressive graphical notation? Certainly, but then we run into the pragmatic issue that typing textual details will always be faster than drawing them.
I came to this realization a few years ago when I worked for a Well Known Company developing UML-based tools for Java developers. The tool’s UI could have been more efficient, but there was no way to beat the speed of typing text.
It’s also true that some languages are rather verbose. This is one of the ways in which Domain-Specific Languages (DSL’s) are going to be increasingly important. A well-designed DSL will let you express those high-level concepts succinctly.
I’m not claiming that there is no place for graphical representations. UML is great for those quick design sessions, when you’re strategizing at a high level. Also, the easiest way to find component dependency cycles is to see them graphically.
I’m also not discounting those scenarios where a diagram-driven approach actually works. I’ve heard of some success developing control systems that are predominantly well-defined, complex state machines.
Still, for the general case, code written in succinct languages with well-designed API’s and DSL’s will trump a diagram-driven approach.
100% Code Coverage? 36
Should you strive for 100% code coverage from your unit tests? It’s probably not mandatory and if your goal is 100% coverage, you’ll focus on that goal and not focus on writing the best tests for the behavior of your code.
That said, here are some thoughts on why I still like to get close to 100%.
- I’m anal retentive. I don’t like those little red bits in my coverage report. (Okay, that’s not a good reason…)
- Every time I run the coverage report, I have to inspect all the uninteresting cases to find the interesting cases I should cover.
- The tests are the specification and documentation of the code, so if something nontrivial but unexpected happens, there should still be a test to “document” the behavior, even if the test is hard to write.
- Maybe those places without coverage are telling me to fix the design.
I was thinking about this last point the other day when considering a bit of Java code that does a downcast (assume that’s a good idea, for the sake of argument…), wrapped in a try/catch block for the potential ClassCastException
:
public void handleEvent (Event event) throws ApplicationException { try { SpecialEvent specialEvent = (SpecialEvent) event; doSomethingSpecial (specialEvent); } catch (ClassCastException cce) { throw new ApplicationException(cce); } }
To get 100% coverage, you would have to write a test that inputs an object of a different subtype of Event
to trigger coverage of the catch block. As we all know, these sorts of error-handling code blocks are typically the hardest to cover and ones we’re most likely to ignore. (When was the last time you saw a ClassCastException
anyway?)
So my thought was this, we want 100% of the production code to be developed with TDD, so what if we made 100% coverage a similar goal? How would that change our designs? We might decide that since we have to write a test to cover this error-handling scenario, maybe we should rethink the scenario itself. Is it necessary? Could we eliminate the catch block with a better overall design, in this case, making sure that we test all callers and ensure that they obey the method’s ‘contract’? Should we just let the ClassCastException
fly out of the function and let a higher-level catch block handle it? After all, catching and rethrowing a different exception is slightly smelly and the code would be cleaner without the try/catch block. (For completeness, a good use of exception wrapping is to avoid namespace pollution. We might not want application layer A to know anything about layer C’s exception types, so we wrap a C exception in an A exception, which gets passed through layer B…)
100% coverage is often impossible or impractical, because of language or tool oddities. Still, if you give in early, you’re overlooking some potential benefits.