Sunday 17 June 2007

When good programming practices are just too good

Recently I saw an article on CodeProject (the article itself is very good, so I'd rather not post the link here) that had a great explanation of things like MVC, flexible design patterns, dependency injection, programming with interfaces etc.

What struck me in this article is the ridiculous amount of code required to implement simple things. The author explained everything using a WinForm application as an example. So, the dependency injection was implemented not just for the form, but for a button as well! The author designed an interface for the Click event, and an extra class that implemented this interface and forwarded the call to the underlying button.

Now, I see it as an excellent example of good programming practices -- everything is very clear. But at the same time, it is an example of what happens when a good practice it taken to an extreme. Take a look at this snippet:

Private Sub OkButton_Click(...) Handles OkButton.Click
DoIt()
End Sub

Now, a "good programmer" would design an interface that provides the Click event, then design a class (say, GoodButton) that implements this interface and use it as a button on your form. Every single method or property that you need with a button you should put into the interface and then implement in your control, propagating them to your underlying button. In a way, you have "triplication" of your code here, and you write a lot of code that actually doesn't do anything, except for making your program "well designed" and use "proven practices". Why? Because this way "you have no hardcoded dependency on your Button class". Doesn't it sound ridiculous? And think of it, you now has a class GoodButton that just got a hardcoded dependency, and you have your form that has a hardcoded dependency on your GoodButton. Unless, of course, you put the class name in your config and load it dynamically. This way you just have to change "Button" in your config without recompiling the application.

Sounds familiar?

Next, you, of course, is going to test this code. You want to test that each time the button is clicked, the method DoIt is called. In order to do it you, perhaps, will make it public (which is clearly against the best practices), have some mock tests etc. But think again -- do you really want to test this 1-liner? How many lines of test code do you want to write, how much time to dedicate to fix bugs in your test code? Do you really think you are going to refactor it at some time? If yes, just put a comment: "NOT TESTED: REFACTOR WITH CARE" next to it, and you'll feel fine. Use NUnitForms to test your GUI, use TypeMock to test your hardcoded dependencies, and use interface-first design where you feel you need flexibility. In other words, use "thought driven design".

We all love rules -- with them, you don't have to think much, you just apply what the smart guys think is the best. However, lately we younger generation begin rebel and declare these rules as "elitist crap". This happens in response to posts like this, when convenient tools are slammed down just because they don't force "good practices" on us. You can guess that the least thing I want is that something enforces me to do that somebody else thinks is "good". As a result, after I spend too much time on implementing some practice that I don't need, next time I prefer to forget about it. Same thing with testing -- after spending too much time on 100% testable projects, I discovered that I tend to skip testing totally, which proved to be very wrong decision.

So, my idea is -- follow the big guys' ideas, but don't take them as rules, rather as advices. Implement them in say 90% of your application, and carefully weight all benefits each time. This is especially true in places where the code is autogenerated (it's already flexible enough: it takes little effort to replace a dependency, and you can trust it and use without testing). Your business layer is a good place for these practices. Hell, you might even want to change your ORM tool at some point!

To summarise the whole Thought-Driven-Development-versus-Big-Guys issue:
- Always think about the benefits of the proposed pattern. Don't get content with words like "flexible" and "eliminates dependency", even more with "proven". Think about what "flexible" means in your case, and what is "inflexible" if you do it straight, and why it is bad to be "inflexible" here. Estimate how much do you have to change if you need that flexibility later.
- Always think about the costs. How much to you need to code just to make it flexible or testable. Note that you don't have to make it testable with TypeMock, for example, but that's considered a "bad" tool among purists, because it doesn't "enforce" us (meaning it liberates us a lot, actually).
- If you discover that thу proposed practice makes your design unnecessarily complicated, unclear, adds too much nonfunctional code, or in any other way distracts you from your main purpose, write a blog entry about it, or comment somebody's article on good patterns, in order to provoke thinking and stop people from blindly following the rules.

2 comments:

Anonymous said...

hmm. sounds like you know everything.
You will get burnt by sloppy habbits.
All readers beware of this VERY bad advice!

ulu said...

Since when thinking before applying a rule is considered a bad advice?