In every composition made in day to day lives there are always the building blocks that the composition is ‘composed’ of. A house is composed from bricks. A book is composed of words. A compound is made of molecules and humans of cells. Then there are complex compositions that are composed of other simple compositions. Regardless of what you look at though, the quality of the composition is always dependent on the smallest unit that builds it. So in software what is the smallest building block, is it the variables, the classes etc.? Well I vote for methods. To me it’s the quality of the methods composing the application is what makes all the difference. When we unit test, we do so most of the time on a method. While usually forgotten with all the technology centric and high-level views we take on software, your method is still your most important asset. So what’s in a method?
Beginner Developers
A method will usually be written to achieve a single function. At least simple methods will. Methods can do that by either running some simple statements or by leveraging other methods. But for this post let’s concentrate on how you would look at a method with simple statements.
The following static method divides two numbers.
static double Divide(double x, double y)
{
return x/y;
}
By making this method static we already decided that the method can be called without the instantiation of an object, The second step that a beginner developer will decide is usually the scope of the method. Is it private or public? Let’s say for now that the method is public.
public static double Divide(double x, double y)
{
return x/y;
}
So far we have established the following characteristics of the method:
- The method divides two numbers.
- The method can be called without instantiating an object.
- The method can be called from outside the containing class.
Most of the time this is usually where a beginner developer would stop.
Junior Developer
A junior developer with several months experience will know that this is not the end of it. The developer will usually add more characteristics to the method and consider where the method can fail by analysing possible failure points. For example, knowing that if y is 0 the method will throw an exception the developer will arm his method with a validation mechanism. Perhaps throw an exception of his own. The junior developer can also dislike the naming strategy so he renames variables to make things clearer.
public static double Divide(double dividend, double divisor)
{
if(divisor == 0)
{
throw new ArgumentException("divisor cannot be zero", "divisor");
}
return dividend/divisor;
}
The characteristics of the method, after being touched by the junior developer became:
- The method divides two numbers.
- The method can be called without instantiating an object.
- The method can be called from outside the containing class.
- The method is more readable.
- The method validates for a divisor being 0.
Intermediate Developer
When the developer gets to an intermediate stage he\she starts thinking about reusability and neat code. The developer wants to be able to use an extension method to facilitate calling the code from any double variable in the application with no class calls. So a static class is created and a helper extension method follows.
public static class ArithmeticHelpers
{
public static double DivideBy(this double dividend, double divisor)
{
if (divisor == 0)
{
throw new ArgumentException("divisor cannot be zero", "divisor");
}
return dividend / divisor;
}
}
The characteristics of the method, after being touched by the junior developer became:
The method divides two numbers. The method can be called without instantiating an object. The method can be called from outside the containing class. The method is more readable. The method validates for a divisor being 0. The method is more re-usable and can be applied through any double type variable.
Senior Developer
Our developer now gets to a senior level. The developer glances over his old code and finds that the method is next to perfect. One thing he wants to make sure of, is that if any other developer changes the method with breaking changes (ones that do not meet expectations) a notification will arise. So the senior developer decides to write some unit tests trying to cover all his possibilities.
[TestMethod]
[ExpectedException(typeof (ArgumentException),
"The Divisor cannot be zero.")]
public void DivisionTestCase1()
{
const double num1 = 10;
const double num2 = 0;
num1.DivideBy(num2);
}
[TestMethod]
public void DivisionTestCase2()
{
const double num1 = 10;
const double num2 = 2;
Debug.Assert(num1.DivideBy(num2) == 5);
const double num3 = double.MaxValue;
const double num4 = double.MinValue;
Debug.Assert(num3.DivideBy(num4) == -1);
Debug.Assert(double.IsInfinity(num3.DivideBy(0.1)));
Debug.Assert(double.IsNegativeInfinity(num3.DivideBy(-0.1)));
}
Wow, our method is getting richer by the minute and the characteristics are now:
The method divides two numbers. The method can be called without instantiating an object. The method can be called from outside the containing class. The method is more readable. The method validates for a divisor being 0. The method is more re-usable and can be applied through any double type variable. The method is tested and unit tests will indicate any breaking changes.
Architects \ Team Leads & People you can’t Please
What an annoying bunch of people those are (just kidding for the sensitive kind :)). Now an architect looks at the method which is almost perfect and think to himself “How can I make something this good better?” The architect then identifies the “junction points of a method”. If you were at our (Paul Stovell and I) talk at CodeCampOz you would know what junction points are and those can be defined by the following diagram.
The architect now decides that the method needs to be auditable and exceptions needs to be handled by a policy. In other words, that nosy architect wants to use Aspect Oriented Programming to handle the cross cutting concerns of the method. For this he leverages PostSharp and creates a couple of aspects.
[Serializable]
public class OnDivideByInvocationAspect : OnMethodInvocationAspect
{
public override void OnInvocation(MethodInvocationEventArgs context)
{
Trace.TraceInformation("Entering {0}.", context.Method);
context.Proceed();
Trace.TraceInformation("Leaving {0}.", context.Method);
}
}
[Serializable]
public sealed class OnDivideByExceptionAspect : OnExceptionAspect
{
public override void OnException(MethodExecutionEventArgs eventArgs)
{
if (eventArgs.Exception.GetType() == typeof(ArgumentException) &&
eventArgs.Exception.Message.Equals("The Divisor cannot be zero."))
{
Trace.TraceInformation(eventArgs.Exception.Message);
}
eventArgs.FlowBehavior = FlowBehavior.RethrowException;
}
}
Now our method looks something like this.
[OnDivideByExceptionAspect]
[OnDivideByInvocationAspect]
public static double DivideBy(this double dividend, double divisor)
{
if (divisor == 0)
{
throw new ArgumentException("The Divisor cannot be zero.", "divisor");
}
return dividend / divisor;
}
Only thing is when you thought it’s almost over, another architect does a further refinement to the exception aspect and wants less code in the method so now he goes and….does this.
[Serializable]
public sealed class OnDivideByExceptionAspect : OnExceptionAspect
{
public override void OnException(MethodExecutionEventArgs eventArgs)
{
if (eventArgs.Exception.GetType() == typeof(DivideByZeroException))
{
Trace.TraceInformation(eventArgs.Exception.Message);
}
eventArgs.FlowBehavior = FlowBehavior.RethrowException;
}
}
and now the method looks like:
[OnDivideByExceptionAspect]
[OnDivideByInvocationAspect]
public static double DivideBy(this double dividend, double divisor)
{
return dividend / divisor;
}
Wow almost a full circle and now one more characteristic is:
- The method divides two numbers.
- The method can be called without instantiating an object.
- The method can be called from outside the containing class.
- The method is more readable.
- The method validates for a divisor being 0. (but with a twist)
- The method is more re-usable and can be applied through any double type variable.
- The method is tested and unit tests will indicate any breaking changes.
- The code is concise to the problem at hand – division.
Conclusion
I wrote this post inspired by various discussion with developers over @ Datacom. The boundaries I drew between the different levels of developers here are only an example and do not necessarily resemble real life. This post was also never meant to say the applying AOP is always the perfect end solution. I wrote this post to point your attention to the importance of investing time in the quality of your simplest and smallest building block, the method. The method shown in this post is a simple division one yet we managed to think about it and implement it in various ways. Everyday in your coding life, pay attention to your methods. The journey didn’t finish when the fictitious architect was done with applying AOP nor do the steps in this post conclude your ‘method perfection journey’. Of the top of my head I think this method is yet to be performance tested, memory profiled and dependency analysed :)
Your method is your core building block. If it is of high quality then you got more chance of success with whatever software you’re building. I believe if you just spend 5 minutes gazing at a method after you wrote it, you will have more chance of making it better.
Enjoy!
An anti-pattern ???
I can understand the value of doing this to the entry points (e.g. from the UI) to a complex business object to handle business rules violation - but to a simple helper method? Overkill!
Wouldn’t it fall into the following categories … ???
Gold plating: Continuing to work on a task or project well past the point at which extra effort is adding value
Accidental complexity: Introducing unnecessary complexity into a solution
Cargo cult programming: Using patterns and methods without understanding why
Expection handling: (a portmanteau of expect and exception) Using a language's error handling system to implement normal program logic
Source: http://en.wikipedia.org/wiki/Anti-pattern
I’d be fascinated to know what others think – and more fascinated to know what they’d do ;)
Posted by: Chris | Friday, 18 September 2009 at 08:31 AM
:) Hey Chris. This was merely an example dude. If I end up doing this daily on every method I will end up with nothing being delivered. I though the simple method would help me get my point through and I understand you're every concern if it was a regular practice. :)
Posted by: The Archidev | Friday, 18 September 2009 at 09:37 AM