Less is More Redux
In my short happy life as a developer, I’ve run into all kinds of development efforts that include frameworks, libraries, web sites, and much more.
The one thing that stands out as an irritant to me is complexity. Specifically, unnecessary complexity. I’ve seen developers author entire library assemblies that provide wrapper utility methods consisting of calls to .NET BCL methods that take one line of code – methods that could have been called inline without even the need for the “helper” classes.
I’ve seen frameworks that duplicate code that is already present in the .NET Framework, usually because the developer didn’t know they already existed (e.g., writing your own “connection pool” when the providers already expose a perfectly fine one).
I’ve seen frameworks with layer upon layer of interfaces, base classes, derived classes and convoluted, multiple code paths that are inefficient and slow down execution because the developers kept building and building on top of something that wasn’t designed with simplicity in mind in the first place.
And of course, the Granddaddy, using exceptions to handle business logic. Don't do it! The vast majority of the time, as long as you can find out why an exception would be generated, you can code defensively to prevent it and avoid burning up a whole bunch of useless CPU cycles. And believe me, that is precisely what exceptions do.
The subcorollary of the above, of course, is the empty Catch Block, which, if you are not the original developer who put it in, can drive you mad since it completely SWALLOWS exceptions. Bad, bad, bad!
Einstein said, “Everything should be made as simple as possible, but not simpler.”
Bill Wagner, in his excellent book, “Effective C#” Second Edition (which I reviewed here), gives a good example in his “Item 11 – Understand the Attraction of Small Functions”:
Bill says that one of the most common examples of premature optimization is when you create longer, more complicated methods in the hope of avoiding method calls.
The .NET Runtime performs JIT compilation on a method – by – method basis at runtime, as the methods are used. Methods that do not ever get called don’t get JITed. Bill gives a short example:
public string BuildMsg( bool takeFirstPath )
{
StringBuilder msg = new StringBuilder( );
if ( takeFirstPath )
{
msg.Append( "A problem occurred." );
msg.Append( "\nThis is a problem." );
msg.Append( "imagine much more text" );
} else
{
msg.Append( "This path is not so bad." );
msg.Append( "\nIt is only a minor inconvenience." );
msg.Append( "Add more detailed diagnostics here." );
}
return msg.ToString( );
}
The first time BuildMsg gets called, both paths are JITed, but only one is needed. But if you rewrote the method this way:
public string BuildMsg( bool takeFirstPath )
{
if ( takeFirstPath )
{
return FirstPath( );
} else
{
return SecondPath( );
}
}
-- the body of each clause has been factored into its own method, and that method can be JITed on demand rather than the first time BuildMsg is called. The example is deliberately short and contrived, but think about how you code: Do you write code with 20 or more statements in each branch? How about switch statements where the body of each case block is defined inline instead of in separate methods?
Smaller, simpler methods make it easier for the JIT compiler to support enregistration, which means you get more effiicient compiled code that runs faster.
There is no "free lunch" in .NET programming. You can write code that is convoluted and complex, and poorly engineered. It can even have misspellings and grammatical errors - and if they are consistent throughout, the compiler will not complain. Yes, your code may compile and run. But did you do right? NOT!
I’ve seen developers approach a concept in either of two general ways:
1) Build a simple, basic prototype, allowing for extensibility but start out with a small, manageable concept. Then test it, work with it, and continue to build it out – if needed or desirable.
2) Engineer the “Big Vision” – working on everything at once, and don’t deploy it until it’s complete.
Of the two, I much prefer the smaller “Less is More” iterative prototype approach. I think it is safer, and less prone to errors that may be difficult to undo later due to over-engineering or poor design.
Developers can learn about programming from reading Ernest Hemingway. He was a master at the short sentence, and would often throw away dozens and dozens of typed pages until he finally settled on one that he was happy with. His distinctive writing style, characterized by economy and understatement, can serve as a literary model for what programmers "should' be doing when they design and write code! Less is more! For an example try this for size.
I once worked on a large healthcare .NET application as a contractor in Atlanta. The manager there was a funny guy. He would walk around saying “Simplify, Simplify, Simplify!”. He was right.
Comments
Post a Comment