Thank God I don’t have my old 3 hours a day of commuting into Manhattan, but I needed to spit out some little blog postlets in more than 140 characters at a time, so I present to you the latest Train of Thought. Opinionated blathering ahead, comments are always open at the bottom.
Don’t Overreach in Your Designs
One of my older posts on The Last Responsible Moment and another post on evolutionary design were recently linked on some of the DZone/DotNetKicks sites and got some traffic. I got some comments and emails to the effect of “we did evolutionary design and it bit us in the ass with all that refactoring and rewriting.” Maybe, but let’s talk about how to do evolutionary design in a way that minimizes outright rework. From my experience, the worst rework results from choosing elaborate abstractions upfront that turn out to be harmful. The analogy that I like is trying to walk on slippery ground. Anybody who’s walked across an icy patch or a muddy field this knows that the way to do it is to keep your feet as close to your center of gravity as possible by taking short steps. If you take a big step you’re much more likely to slip and fall. Design is the same way. Bad things happen when you allow your design thinking and abstractions get ahead of your development and requirements.
We’re doing evolutionary design, and yes we have had to rewrite some functionality when we’ve found shortcomings in the design or simply found a better way to do it. I would attribute the worst example of avoidable rework on our project to overreaching with some infrastructure outside of user stories. We’re using the new ASP.Net MVC framework, and we didn’t like the way that it handles (or really doesn’t handle) the “M” part of the MVC triad. We had one of those conversations that starts with “wouldn’t it be cool if…” and ended with one or both of us spending days of architectural spiking on an approach for screen synchronization – before we created our first web page. We created the idea of a “ViewModel” that would represent screen state and help us to move data between the web page form elements and our Domain Model objects. We wrote a very elaborate code generation scheme to automate a lot of the grunt coding. As soon as we started to work on our first couple web pages we quickly realized that much of our ViewModel infrastructure was unnecessary or just plain wrong. We effectively rewrote the ViewModel code generation in a simpler way and got on with the project. Since then, we’ve extended the ViewModel code generation to add new behaviors on an as needed basis, but we haven’t had to rewrite any of it.
Just to head off the comments, I didn’t know about the BindingHelperExtensions in the MVC at the time (shame on me). I don’t regret rolling our own infrastructure at all because I don’t think that BindingHelperExtensions is adequate, but I wish we’d played it a little smarter and put off the ViewModel code generation until we had a couple working pages to point out the real patterns.
What I’m trying to say here is to avoid speculative abstractions and fancy patterns outside of feedback from the real features and needs of the system. It’s relatively painless to extend simple code for more elaborate usages than its original intentions, but it hurts to throw out or change elaborate code. You can hedge your design bets by (almost) always starting simple.
Enabling Evolutionary Design
So, how do you do evolutionary design without incurring a lot of rework? Here’s my recipe:
- Worry a lot about cohesion and coupling as you work.
- Follow the Open/Closed Principle
- Follow the Single Responsibility Principle
- Use TDD or BDD for low level design. First because it does more to ensure good cohesion and coupling on a class by class basis than any other technique, but also because the automated unit testing coverage left behind enables changes in the code to be cheaper in many cases. Regression testing is a cost and risk associated with changing code is a considerable road block to making design improvements midstream. If you reduce that cost and risk, evolutionary approaches are a lot more attractive. That test coverage is one of the ways that TDD/BDD is more valuable a practice than merely applying some unit tests after the fact to strategic areas of the code.
One way to think about TDD is an analogy to Lego blocks. The Lego sets I had as a child were the very basic block shapes. Using a lot of little Lego pieces, you can build almost anything your imagination can create. If you buy a fancy Lego set that has a single large piece shaped like a pirate’s ship, all you can make is a variation of a pirate ship.
In that context I was talking about TDD, but I feel like the analogy holds very true for doing evolutionary design effectively. Composing your system of small Lego pieces that can be rearranged is much better than using big monolithic pieces of code that are more likely to be modified later.
In the end, it really amounts to just design things well, all the time. Unsurprisingly, I think that teams with strong software design skills are best equipped to do evolutionary design.
On Software Factories
Last week I was at an Open Spaces event in Colorado with a very diverse group of folks in a session that rambled around until “Software Factories” came up. I stated, and not for the first time, that Software Factories are often Big Design Upfront dressed up in sexier new clothes. I definitely think the software factory idea can work (with Ruby on Rails as exhibit A), but I think the activity of defining elaborate project and class templates upfront is risky or at least unoptimal. A project team’s is going to have much less willingness to reconsider designs if design changes require changing the software factory templates. To me, software factory techniques will succeed if and only if it’s easy to modify the factory automation guidance as the team works and learns more about their system.
My other point with software factories was that I think micro code generation (live templates, file templates, ReSharper tricks, scaffolding, etc.) where the developer is in complete control has a much better chance of succeeding than the elaborate factories that try to generate most of the application for you.
Ruby on Rails introduced “Opinionated Software” into the common lexicon, but it’s been around for a while. I think that my team is gaining some advantages from our design’s “opinions,” but what if you don’t like the opinions of your chosen framework? Take CSLA.Net as an example. I want absolutely nothing to do with CSLA.Net because I think its basic model is severely flawed, but I bet that it’s providing a lot of value for many teams. That value is largely because CSLA.Net has firmly engrained “opinions” about how almost every common development task should be done. I can’t use CSLA.Net, and a lot of the Microsoft tooling for that matter, because I don’t agree with the “opinions” baked into that tooling. I’ll happily build my own infrastructure to support the way that *I* feel software should be created, or go around a piece of infrastructure I don’t agree with. Heck, the MVC framework isn’t even released and we’ve already considerably diverged from its opinions. Other developers will simply go with the flow of whatever tooling that their using and invest time into learning the idioms of that particular tool and not waste time questioning that tool.
I think this comes down to a question of “go with the flow or change the course of the river.” I’m a “change the course of the river” to the optimal path kind of guy, but I frequently wonder if it would be better to just give up and go with the flow.
TypeMock is only a Bullet of Ordinary Composition
I was out of pocket last week at a little open spaces event, so I missed most of the latest Twitter and blogging flareup of the TypeMock question. I’ll repeat my opinion that there’s nothing inherently wrong with TypeMock itself, but I think that the rhetoric from TypeMock proponents is often harmful to the greater discussion of software design and practices.
TypeMock might be a better mocking framework than Rhino Mocks or Moq, but it does NOT change the fundamental rules of mock object usage. Just because you can use TypeMock to mock a dependency doesn’t mean that it’s the right thing to do. Let’s remember some of my rules of mock object usage:
- Don’t ever try to mock chatty interfaces like ADO.Net or anything related to HttpContext because the effort to reward ratio is all wrong and you can never read those tests anyway.
- Be extremely cautious of mocking interfaces that you do not understand.
The only thing that TypeMock changes is *how* the mock object is introduced into the code being tested. If you really think that having separate interface definitions plus Dependency Injection is hard, then yeah, use TypeMock (an assertion that I would obviously dispute in this age of auto wiring, auto mocking containers, ReSharper, and convention driven configuration of IoC containers). Just remember a couple things please:
- Mocking in general isn’t going to be an effective technique with classes that aren’t cohesive or have a lot of semantic coupling with their dependencies. In other words, interaction testing with any mock object is going to be painful with badly written code. TypeMock simply doesn’t change that equation. I’ve heard TypeMock put forward as a solution for unit testing legacy code. In theory yes, but the reality that I’ve found is that interaction testing inside Legacy Code is an exercise in pain. Most legacy code (and I’m using the Feathers definition of legacy code here) has very poor internal structure and poor separation of concerns. Exactly the kind of code that you shouldn’t bother using interaction testing on. I’d instead recommend surrounding Legacy Code with more coarse grained integration tests to preserve behavior first, then trying to modify the internal code to a better structure before writing fine grained unit tests. Yes, it is possible to use TypeMock to “unit test” typical legacy code, but those tests would almost automatically be the type of overspecified unit tests that cause more harm than good. The problem with legacy code is often the structure of the code more than the fact that it doesn’t have any unit tests.
- Yes, you can unit test the class in question that news up its own dependencies and calls static methods, but you still have a very tight runtime coupling to those dependencies and the static method calls. Regardless of your ability to unit test the class in question, that tight coupling can often be a problem. Your ability to reuse those classes is compromised by the tight dependencies. Your ability to practice evolutionary design is compromised because of the tight coupling. Remember that Dependency Inversion and Inversion of Control have other benefits than just unit testing.
I think the TypeMock proponents are too focused on unit testing in a way. I firmly believe that code that can’t be efficiently unit tested is almost automatically bad code (to me, testability == productivity). However, code that can be unit tested isn’t necessarily good.
To recap, I don’t think there’s anything wrong with TypeMock per se, but I think that much of the TypeMock proponent’s rhetoric is irresponsible. Just because TypeMock *can* do something, doesn’t mean that doing that something is a good idea.
In Tribute to George Carlin
I couldn’t think of 7, and it’s a couple months late for a Carlin tribute, but here’s my list of the words or phrases that are henceforth banned from appearing in my blog or presence (starting right now). Almost no conversation is going to be useful if it includes one of these words:
- Mort – Apparently Microsoft is now referring to the developer formerly known as “Mort” as “Pragmatic Developers.” Puh-leeze. Everybody in the world thinks that they’re pragmatic, but yet we disagree on many significant directions in the best way to build software. I was dead set against ALT.NET getting renamed “Pragmatic.Net” for the same reasons. I gotta say though, “Pragmatic Developer” is much less a pejorative than “Mort” became and the typical “Joe Schmoe Developer who builds LOB apps at General Motors” line you hear from Microsoft employees.
- Entity Framework – At least until there’s something new to say. I’m liking that my attention lately has been on the advance of Fluent NHibernate instead of worrying about a tool that I’m very unlikely to use in the next 2-3 years.
- Stored Procedures – I’ve seen nothing to change my opinion about sprocs for several years (good for edge cases and utility database scripts, bad everywhere else, i.e. 95%+ of the time I think sprocs are unnecessary)
- “Vietnam of Software Development” – Most overblown and misused analogy this side of Software as Construction.
- “Software as Construction” – I worked on the engineering side of construction projects measured in the 100’s of million dollars and even billion dollar+ projects (and this was in the pre-W days when the USD was more than paper money), plus I worked for my father building houses as well. I feel perfectly qualified to say that the “Software as Construction” analogy is an extremely poor fit. Software as Manufacturing is better, but I bet that somebody will write a rant about that comparison in the next couple years.
- Foo Considered Evil – It’s a cliche now
- “Cargo Cult” – used as a magic talisman to win any argument, regardless of whether the use of the phrase is applicable or not.
- “Your Emperor has no Clothes” – see above
- “Jumped the Shark” – see above
- “You should just use whatever is best for your project” – The intellectual equivalent of empty calories
- “You’re just being dogmatic!” – Lamest way to try to win an argument. Basically, this is code for “I’m pissed that you don’t agree with me so I’m just going to call you names and declare victory, so there!”
- “You can just Refactor it later” – You can write simplistic code upfront and say you’ll refactor it later to eliminate duplication or handle more complicated cases as those cases arise, but you don’t write bad code on purpose. You certainly don’t use Refactoring as an excuse to just not think about design.
- “We’re refactoring” when the team really means “we’re rewriting that code altogether.” There’s no such thing as a big refactoring.
Okay, I’m done. Your turn: