A periodic republishing of habits that I would like to have. New items at the top, but each issue includes all prior items.
- Duplication Always-ε Hides a Design Problem
- OK, I might grant, just to avoid an endless argument, that somewhere there might exist an example of duplication that isn’t hiding a design problem, but it’s the way to bet. If we find ourselves duplicating code, it should raise our suspicions. There are two main possibilities that I look for.
One is Feature Envy, discussed below. The duplication may be repeatedly making some complicated manipulation of a naked object (Wrap Native Objects), often a collection (Wrap Native Collections), or it’s just making a series of calls to get something done, and that series could have a home somewhere and be used where we presently see the duplication.
Another thing that duplication may suggest is that there is an object missing. We have some common collection of activity, and because it has nowhere to live, we just repeat it everywhere we need it. Figure out the missing object and build it.
A final possibility that comes to mind is that may on-line examples show a series of actions all done one after another, such as to create a window or something like that. So we copy that … and copy that … and copy that. It’s quite likely that the series of actions is a good way to explain how to do the thing, but that once we see it, we should package it up in a nice object of some kind.
Duplication is a sign, a signal, an indication. Notice it, think about it. Removing the duplication seems always-ε to make the code better1.
- Watch for Feature Envy
- Often we find that we’ve written a method that doesn’t refer much to the members of the class it’s in, and does refer to members or functions in one or more other classes. The code word for that is “feature envy”, and it’s meant to suggest that this code is trying to be a feature of one of the other classes.
It almost always pays off to create the suitable capability on the classes referred to. It’s usually as simple there, and often simpler. And the formerly envious code now does as it’s supposed to, dealing more with its owner class’s concerns.
- Data Over Procedure
- I like to lean just a bit in the direction of preferring data over procedure. There’s often a benefit to encoding information as data rather than as code. A table mapping product to product type, or synonym to base word, is often far more clear, more compact, and easier to modify than an
switch. Combined with “Wrap Collections” below, this approach can create very powerful transformations that are all nicely encapsulated. It doesn’t always apply, but when it does, the results seem to me to be quite nice.
- Wrap Collections
- I’m not really one for “always” and “never”, but it seems to me that wrapping native collections comes close to “always” helping and “never” hurting. It gives us a clear picture of what can be done with the collection, and what cannot. It allows us to change the underlying native implementation with a different, better one, at will. And it lets us provide a clear and useful interface to what is generally an over-powerful native structure.
- Wrap Native Objects
- I’m thinking primarily of numbers and strings, having addressed collections above. Wrapping numbers gives us a better chance of keeping the right number in the right place. In the Gilded Rose exercise, keeping
sellInis important. The units of
sellInare days. The units of
qualityare not. And
qualityhas very special rules applied to it. String manipulation is often very ad-hoc and can benefit from centralizing. And, like numbers, it’ all too easy to put the string representing the date into a slot intended for the last name. Isn’t that so, Chet Sep26?
I can’t think of any good reasons not to wrap native objects, especially collections, except that we think it will take us more time and we can “save time” if we put it off. In some cases, that may be true, but I think in general it saves time in the medium and long term, while not wrapping causes defects and longer debugging cycles.
On this one, and all these habits, they are my observations, things where I would prefer to develop habits that seem better to me. Your mileage may vary, and I’m not here to tell anyone what they should do. I offer these thoughts for consideration, and invite you to do your own experimentation with the ideas, to see what happens to you.
- Make Testing Easy
- I took the time the other day to make it possible to write some very similar tests with one line, calling a generic testing function. I found that I wrote more tests, because it was so easy, and the individual tests were very easy to read and understand.
- Fewer Assertions (per Test)
- I spent [DELETED] time a day or so ago, trying to figure out how a test result could possibly be what it was. I expected something like Expected 28, Got 30, but it said Expected 19, Got 20. Impossible … until I realized there were two assertions in the test and it was the first one, which I was sure would work, that was failing. Single-assertion tests are easier to understand, and point to the error when they fail.
In math — don’t run away, this won’t hurt — we write something like X-ε, read as “X minus epsilon”, to indicate that the value might not be X but it is only off by a very small amount, which traditionally in math, has been represented by the epsilon. So always-ε just means “really really close to always”. ↩