When building software, we are often faced with a choice: Should we build just what we need now, or should we do extra work now to make things easier in the future? Consider this simple example:
// 1. Simple GasGrill grill = new GasGrill(); Hamburger h = new Hamburger(); grill.cook(h, 375, 8);This is great, but what if I want a charcoal grill? One alternative would be use a factory:
// 2. Factory Grill grill = GrillFactory.newInstance(); Hamburger h = new Hamburger(); grill.cook(h, 375, 8);Now, the details of grilling are hidden behind the
GrillFactory, and we can choose another grill without modifying code. Or, I could avoid the dependency on factories with POJOs and dependency injection:
// 3. Dependency Injection
public class InjectionGrillAction {
private Hamburger h;
private Grill g;
public Hamburger invoke() {
g.cook(h, 375, 10);
return h;
}
}
Agile practice argues "You Ain't Gonna Need It (YAGNI)." So we could build the simple, concrete implementation, and switch to factories or DI only when and if necessary.
Modifying code to be more flexible or general is one form of refactoring. Refactoring is a Good Idea, but in this case there is a Better Idea. What if we could recontext? Instead of changing the code, we change the context in which the code runs. Consider this example:
# 4. Hygienic: grill_main.rb h = Hamburger.new g = Grill.new g.cook(h, 375, 8)
Is this an example of the Simple, Factory, or Dependency Injection approach? You can't tell, without context. Depending on what other code runs first, the Hygienic code might exemplify any of these approaches, or none. I can run the code in a Simple context:
ruby -rsimple_context grill_main.rb => Hamburger is medium_rare and has a totally generic flavorOr, I can run the code in an Injection context:
ruby -rinjection_context grill_main.rb Hamburger is medium_rare and has a generic outdoor flavorThe injection framework I am using here supports injection through system properties, so we can inject a charcoal grill:
export Grill=CharcoalGrill ruby -rinjection_context grill_main.rb => Hamburger is medium_rare and has a delicious charcoal flavor
The Hygienic approach is as simple as the Simple approach, and more powerful than the Dependency Injection approach. How is this possible? All of the other approaches commit to too much. Rather than simply solving the task at hand, the Simple, Factory, and DI approaches all embed presumptions about future needs. They either do not plan for the future at all (Simple), or they complicate the code with concerns peripheral to the task at hand (Factory, DI).
Hygienic code cleanly commits to solving the task at hand, and avoids committing to anything else. This is a simple idea, but look what it does to some sacred cows:
- Factories (and most other design patterns) are code smells.
- Refactoring IDEs may have done more harm than good. How often are you refactoring code that should be recontexted?
- POJOs are a necessary evil. In more hygienic languages, the notion of a POJO is almost meaningless.
- Domain-Specific Languages (DSLs) are a Good Thing. They naturally tend to be hygienic.
If you would like to see the grilling code example in full, you can get it here. If you are interested in learning more about hygienic code, we are integrating this approach into our curriculum. Or, come to North Carolina and work with us.
Comments
Great post, Stu. I wrote up some similar stuff regarding Java properties and some of the Java 7 proposals around them at http://tech.puredanger.com/2007/01/26/java7-property/. Of course, I first start thinking about this as a result of seeing you give a talk at the St. Louis No Fluff conference, so you get all the credit!
Frameworks like Guice make it just as easy to take the DI-approach up front, i.e. they don’t entail much “extra work.”
With Guice, you can make Grill a concrete class to begin with. Later on, you can come back and make Grill an interface with multiple implementations instead, and you won’t have to change Grill’s clients.
I’m not sure how I feel about swapping one concrete class in for another like you’ve suggested here. It looks neat in a small example like this, but how do I ensure that the replacement implements the exact API the clients depend on? If I’m maintaining your code, it’s hard for me to tell that you’ve replaced Grill with a different implementation.
When we have dependencies, what is the harm in committing to a DI based approach ? It scales very well from simple to complex usecases. And, as Bob has mentioned, frameworks like Guice (and of course Spring) provide a nice layer of abstraction to handle dependency lifecycles. Basically, the DI framework sets up the context of operation – what u call recontexting is simply having a new dependency being injected (or an old dependency being replaced).
I don’t quite understand what you mean by “Hygienic”—do you just mean that a code’s use doesn’t reveal its underlying implementation?
Debashish – I think that dependency injection is often a necessary evil. “Committing to a DI approach” certainly can be an appropriate decision on some platforms. I prefer to develop on platforms where the phrase is meaningless (and there are plenty of such platforms available).
Robert – Implementation abstraction is part of it. Hygienic code does exactly the task at hand, without introducing anything extraneous. As an example, rigorous application of AOP can be pretty hygienic, if all cross-cutting concerns can be implemented as aspects.
Stu, I prefer to develop on platforms where I can understand other people’s code—I read a lot more code than I write. ;D
Stu, I agree with Bob. Codes are first meant to be read by human beings and then, incidentally, meant to be executed by the computer. Every developer reads more code than he writes. In case of DI, the dependencies are clearly spelt out either externally (in XML) or as metadata (annotations in Java) or through some other means of configuration. In all of these, the code reader can understand clearly. I am not for what you call hygienic code that replaces implementations through the runtime black magic. Statically typed interfaces along with pluggable implementations injected through a DI container that takes care of all lifecycle issues of the collaborators – this metaphor has proved to deliver time and again (at least for me).
Bob, Debashish – Good OO code is readable. Good DI code is readable. Good hygienic code is readable. And, BTW, all of these approaches have been criticized for their reliance on runtime black magic.
I’ve always been a fan of operator overloading in C++, and so I naturally would like to see that feature in Java (which is currently my main language).
If that RFE ever passes then I could see overloading the new operator to do exactly that is being done here with Ruby.
Sure there are pitfalls if used incorrectly, but my initial reaction to recontexting and hygenic code is that it can serve as a useful design principle to balance against our other principles.