Driving Tom back from a children’s party this afternoon, I was thinking about Noda Time.
I’ve been planning to rework the parsing/formatting API, so that each chronological type (ZonedDateTime, LocalDateTime, LocalDate, LocalTime) has its own formatter and parser pair. I suspect this will involve quite a bit of similar code between the various classes… but code which is easy to understand and easy to test in its simple form.
The question which is hard to answer before the first implementation is whether it will be worth trying to abstract out that similar code to avoid repetition. In my experience, quite often something that sounds like a good idea in terms of abstraction ends up becoming significantly more complex than the "just implement each class separately" approach… but we’ll see.
Around this time, I ended up stuck behind a car which was going at 20mph between speed bumps, decreasing to 10-15mph near the speed bumps, of which there were quite a few. I could have tried overtaking, but visibility wasn’t great, and I wasn’t far from home anyway. I regard overtaking as a somewhat extreme measure when I’m not on a dual carriageway. It was frustrating, but I knew I wouldn’t be losing very much. The risks involved in overtaking (combined with the possibility that I’d end up stuck behind someone else) weren’t worth the small benefit of getting past the car in front.
It struck me that the two situations were somewhat similar. I know from experience that trying to extract a general purpose abstraction to avoid repetition can be risky: corner cases where the abstraction just doesn’t work can hide themselves until quite late in the proceedings, or you may find that you end up with nearly as much repetition anyway due to something else. The repetitive code is a burden, but one which is more easily reckoned with.
I suspect I won’t try too hard to abstract out lots of the code for formatting and parsing in Noda Time. I’m sure there’ll be quite a lot which can be easily factored out, but anything which is non-obvious to start with can wait for another time. For the moment, I’ll drive slowly but steadily, without any flashy moves.
4 thoughts on “Mini-post: abstractions vs repetition; a driving analogy”
At least in the coding case, given a suitable version control system, you can wind back any mistakes having lost a little time but gained some experience.
Unfortunately in the driving case once you make that decision to go there is often no turning back and, if the risk turns out to be higher than anticipated, some nasty experiences in store.
It seems to me that one fairly significant flaw in the analogy is that not only is the improved travel time negligible even if you successfully pass the other vehicle, you enjoy that improved travel time just once, on that particular trip.
On the other hand, well-abstracted, generalized code allows a time savings over the entire lifetime of the code, which can be hugely significant.
Granted, another big difference of course is that in the driving scenario the time-savings is immediate. But it may actually cost significant time up-front to do a good abstraction and generalization of the code. In your analogy, I think this balance is represented by the potential risk in passing.
Fortunately, the up-front time is often offset by savings early in the development process. All it takes is one bug in the basic implementation that was copied and pasted to the other classes to not only gain in the time savings of not having to revisit all that code to fix the bug that was also copied and pasted, but also to reap the reward of improved reliability, as one can confidently fix the bug once in one place and know that every piece of code that used that implementation will get the bug-fix.
There are definitely situations in which it just doesn’t pay to generalize. But I’ve found that the threshold for payback on the work put into generalization is _very_ low. Surprisingly low.
It’s sometimes best to do the abstraction phase AFTER the two similar implementations are up-and-running. Then, the unanticipated corner cases that would have blocked you don’t; they may be easier to spot; and the abstraction process resembles refactoring to DRY-form.
“But I’ve found that the threshold for payback on the work put into generalization is _very_ low. Surprisingly low.” – Peter
Have to agree with this. Abstractions and generalizations are highly undervalued in my experience. The ability to pick up a piece of code months or years after it was written and use it in a new scenario with little or no modification, because that code was designed with re-use in mind to begin with, is hugely advantageous in reducing the friction in the development process and keeping a project running smoothly.