Cutting Down on Code Telepathy
Let’s say that you have some public facing method as part of an API:
public void Process(CustomerOrder order)
{
//Whatever
}
CustomerOrder is something that you don’t control but that you do have to use. Life is good, but then let’s say that a requirement comes in saying that orders can now be post-dated, so you need to modify your API somewhat, to something like this:
public void Process(CustomerOrder order, DateTime orderDate)
{
if(orderDate < DateTime.Now)
throw new ArgumentException("orderDate");
//Whatever
}
Great, but that was really painful because you learn that publishing changes to your public API is a real hassle for both yourself and for your users. After a lot of elbow grease and grumbling at the breaking change, though, things are stable once again. At least until a stakeholder with a lot of clout comes along and demands that it be possible to process orders through that method while noting that the order is actually a gift. You kick and scream, but to no avail. It has to go out and it has to hurt, and you're powerless to stop it. Grumbling, you write the following code, trying at least to sneak it in as a non-breaking change:
public void Process(CustomerOrder order, DateTime orderDate, bool isGift = false)
{
if (orderDate < DateTime.Now)
throw new ArgumentException("orderDate");
}
But then you start reading and realize that life isn't that simple and that you're probably going to break your clients anyway. Fed up, you decide that you're going to prevent yourself ever from being bothered by this again. You'll write the API that stands the test of time:
public void Process(CustomerOrder order, Dictionary options)
{
if(((DateTime)options["orderDate"]) < DateTime.Now)
throw new ArgumentException("options");
}
Now, this can never be wrong. CustomerOrder can't be touched, and the options dictionary can support any extensions that are requested of you from here forward. If changes need to be made, you can make them internally without publishing painful changes to the API. You have, fortunately, separated your concerns enough that you can simply deploy a new DLL that handles order processing, and any new values supplied by your clients can be handled. No more API changes -- just a quick update, some testing, and an explanatory Word document sent to your client explaining how to use the thing. Here's the first one:
public class ProcessingClient
{
private OrderProcessor _orderProcessor = new OrderProcessor();
public void SubmitAnOrder(CustomerOrder order)
{
var options = new Dictionary();
options["orderDate"] = DateTime.Now;
options["isGift"] = true;
_orderProcessor.Process(order, options);
}
}
There. A flexible API and the whole "is gift" thing neatly handled. If they specify that it's a gift, you handle that. If they specify that it isn't or just don't add that option at all, then you treat those equally as the default case. Important stakeholder satisfied, and you won't be bothered with nasty publications. So, all good, right?
Flexibility, but at what cost?
I'm guessing that, at a visceral level, your reaction to this sequence of events is probably to cringe a little, even if you're not sure why. Maybe it's the clunky use of a collection type instead of something slicker. Maybe it's the (original) passing of a Boolean to the method. Perhaps it's simply to demand to know why CustomerOrder is inviolate or why we couldn't work to an order interface or at least define an inheritor. Maybe "options" reminds you of ViewState.
But, whatever it is, doesn't defining a system boundary that doesn't need to change seem like a worthwhile goal? Doesn't it make sense to etch painful boundaries in stone so that all parties can rely on them without painful integration? And if you're going to go that route, doesn't it make sense to build in as much flexibility as possible so that all parties can continue to innovate?
Well, that brings me to the thing that makes me wince about this approach. I'm not a fan of shying away from the pain of "icky publish/integration" instead of going with "if it hurts, do it more and get better at it." That shying away doesn't make me wince in and of itself, but it does seem like the wrong turn at a fork in the road to what does make me wince, which is the irony of this 'flexible' approach. The idea in doing it this way is essentially to say, "okay, publishing sucks, so let's lock down the integration point so that all parties can work independently, but let's also make sure that we're future proof so we can add functionality later." Or, tl;dr, "minimize multi-party integration coordination with hyper-flexible API."
So where's the irony? Well, how about the fact that any new runtime-bound additions to "options" require an insane amount of coordination between the parties? You're now more coupled than ever! For instance, let's say that we want to add a "gift wrap" option. How does that go? Well, first I would have to implement the functionality in the code. Then, I'd have to test and deploy my changes to the server, but that's only the beginning. From there, I need to inform you what magic string to use, and probably to publish a Word document with an example, since it's easy to get this wrong. Then, once you have that document, I have to go through my logs and troubleshoot to discover that, "oh yeah, see that -- you're passing us 'shouldGiftwrap' when it should really be 'shouldGiftWrap' with a capital W." And if I ever change it, by accident or on purpose? You'll keep compiling and running, and everything will be normal except that, from your perspective, gift wrapping will just quietly stop working. How much pain have we saved in the end with this non-discoverable, counter-intuitive, but 'flexible' setup? Wouldn't it be better not to get cute and just make publishing a more routine, friction-free experience?
The take-away that I'd offer here is to consider something about your code and your software that you may not previously have considered. It's relatively easy to check your code for simple defects and even to write it in such a way to minimize things like duplication and code churn. We're good at figuring out how not to have to keep doing the same thing over and over as well and to simplify. Those are all good practices. But the new thing I'd ask you to consider is "how much out of band knowledge does this require between parties?"
It could be a simple scenario like this, with a public facing API. Or, maybe it's an internal integration point between your team and another team. But maybe it's even just the interaction surface between two modules, or even classes, within your code base. Do both parties need to understand something that's not part of the method signatures and general interaction between these entities? Are you passing around magic numbers? Are you relying on the same implicit assumptions in both places? Are there things you're communicating through a means other than the actual interactions or else just not communicating at all? If so, I suggest you do a mental exercise to ask yourself what would be required to eliminate that out of band communication. Otherwise, today's clever ideas become tomorrow's maintenance nightmares.
Yet again, a thought provoking post!
Yes, if something is hard and painful get good at it so that it stops being those things.
I am currently involved in a project where I am trying to introduce the concept of short development cycles coupled with publish frequently.
We have the short development cycles working great but the publish frequently idea is being treated with the utmost scepticism by, yet again, management.
Apart from the challenge of describing the benefits of this in terms they can understand I am now adding Continuous Delivery to my portfolio of skills.
I’m curious as to what they’re skeptical of. If I’m a manager, even a pointy-haired type, it would seem like faster cycle times or responses to defects would be a win. Is it that they’re worried about breaking changes or users being impacted?
It is part of the culture here (I work for a German company in Germany) and there are many industries which are stuck in the 1950’s when it comes to management style. Resistance to change is part of the genetic make-up anyway and here things can be taken to extremes. In this case it is a knee jerk reaction, “No, I don’t need this” without justification for the decision. Fortunately we can get around this by just making changes as part of a Proof of Concept and showing the benefits.
I hate office politics!
I can empathize with the general attitude (I actually once worked for a company with German ownership, but I didn’t have much interaction with the folks over there). And, while I find the subject of office politics interesting, I’m with you in that I hate participating in it.
I’m surprised you didn’t move the CustomerOrder order parameter into the Dictionary. That would rock! Your method could even support multiple different versions of CustomerOrder! You know those darn customers that won’t update from V1 of the product and you are on V5. No problem just keep adding more code to your method to support all the possibilities!
Yes, I am joking. I had the horror of working on a project that did exactly this. Of course we had inArgs and outArgs (a feature you completely ignored).
The only thing worse than doing versioning is to not doing versioning.
outArgs… *shudders*
That last line is a great quote, by the way. Hope you don’t mind if I borrow it, with proper attribution, of course.
I stole it myself. Don’t bother with “proper attribution”. I originally heard it when we were fighting over coding standards. A guy on our team said the only thing worse than a bad coding standards is no coding standard.
I guess both kind of seem like a variant of the Oscar Wilde quote, “There is only one thing in the world worse than being talked about, and that is not being talked about.”
Hi Erik, Great post. I often write code that will only ever get used for 3 months while we convince a client that we are worth their time. Sometimes I use a class with a single instance variable of type dynamic as the single argument in an MVC or WebApi controller in order to rapidly prototype with a front-end guy on my team. I even pass the Request itself to backend service to dynamically pull out URL params by name. It feels dirty when we don’t go back and tie in actual backing classes to stabilize the API or build… Read more »
I imagine there’s a good amount of situational nuance to what you’re describing, but I’m curious as to why a late binding scheme (e.g. “string typing”) would be faster up front. Going back to my example, are you saying it’s faster to get the dictionary implementation out the door than it would be to figure out how to sub-class the order or create a strongly typed overload or something?
You are right in that I should not have said ‘Doing it right takes longer in the immediate’. I should have started that statement with ‘unfortunately, where I work,’ That being said, it’s not a matter of figuring it out, and I am sure in lots of shops, they have the existing structure to make the ‘proper’ way faster out the door, but in our shop, it is faster in the sense that we have some existing structure for the exact purpose of using dynamic types and especially Expando objects to do this sort of stuff. We even have some… Read more »
One thing that I’ve picked up, particularly in consulting capacities, is that criticisms without walking a mile in someone’s shoes are cheap. Everything your group is doing might make a lot of sense, and, were I sitting in your shoes, I might agree with the approach. (Though this dynamic does make every blog post come with the an implied consultant caveat of “depending on your situation…”) And I don’t know that I’d inherently dislike a scheme because of runtime binding or dynamic typing or anything else. I just view it as a matter of tradeoffs, and those tradeoffs may or… Read more »
I’ve noticed this quite a lot too in web frameworks. Most of them use a generic Request/Response objects that have post data and URI parameters as generic maps.
Integer userId = (Integer) request.queryParams.get(“userId”);
vs.
Integer userId = userRequest.getUserId();
When jumping into some code I’m not familiar with, I like the second approach since it’s clear what properties are available.
Definitely agreed. Rightly or wrongly, I’m definitely someone who discovers APIs by Intellisense. Strongly typed implementations are great for me because I can discover the inputs and outputs via experimentation.
I know this post is old but you reposted it on Twitter and I couldn’t help myself… https://gist.github.com/dgw2jr/d5d233eb28d214d12a96776335408ebe It seems like a lot of problems like this can always be broken down and solved with some combination of design patterns from the Gang Of Four, Fowler, or Martin etc. For example, in my gist I used Introduce Parameter Object to wrap the “inviolate” CustomerOrder along with its options. Each option is handled by its own Command object. The collection of commands are handled by a command handler, the Command Handler Pattern. Future change requests are facilitated by adding a new… Read more »
Makes sense. Requires no out of band knowledge — enforced by the compiler — but doesn’t suffer the rigidity problem.