Showing posts with label Software Development. Show all posts
Showing posts with label Software Development. Show all posts

Tuesday, 10 January 2012

10 Commandments of Inversion of Control Containers

Inversion of Control containers can be a very powerful tool for decoupling spaghetti code in large software systems. However, with any power tool, you can hurt yourself badly if you don’t use it correctly. In this post, I present “10 commandments” to help you avoid causing problems with IoC, with a particular focus on very large software systems with many developers and hundreds of interfaces. I am currently using Unity, but these tips apply to pretty much any IoC container.

1. Configure everything before your first resolve

It is possible to ask the IoC container to resolve an interface before you have finished configuring the container. So long as it knows how to make the interface you asked for and its dependencies, it will have no problem fulfilling your request. But if your application hasn’t yet finished configuring the container yet, you run the risk of getting the wrong thing. Consider the following simple example:

IUnityContainer container = new UnityContainer();
container.RegisterType<IFoo, Foo>(new ContainerControlledLifetimeManager());
var f1 = container.Resolve<IFoo>();

Fairly straightforward, we ask for IFoo and get an instance of Foo. But what if we hadn’t quite finished configuring the container, and some other part in your app attempts to override the registration for IFoo:

container.RegisterType<IFoo, Foo2>(new ContainerControlledLifetimeManager());

Now, whenever we attempt to resolve IFoo, we get Foo2. But the initial component that did an early resolve is using the wrong implementation. This can make for horrible debugging sessions. Configure your container completely, before you start to resolve things from it. Which leads us to our second commandment…

2. Don’t pass the container around

What I mean here, is don’t pass around the top-level interface that allows further configuration of the container. In Unity, this is the IUnityContainer interface. It might feel very powerful to send it around, since it allows other parts of your application register new rules, but it opens the door for the kind of bugs we just discussed.

But what about just a Service Locator interface. Can I pass one of those around? Onto commandment 3…

3. Avoid passing an IServiceLocator around

Passing a service locator in as a dependency gives your class great power. It can ask for anything it wants, which is super convenient. But it also introduces some problems.

First, it means your class no longer advertises what it needs in the constructor. Without examining the code you can’t be sure what needs to be in the container for the class to work correctly. It means that unit tests, or callers of your class that aren’t using a container, will have to mock a container just to instantiate your class.

This has been called the “Hollywood principle” – Don’t call the DI Container, it’ll call you. Just put the interfaces you really need in your constructor.

4. Avoid making the container a singleton

Making your container a singleton is the quickest route to providing access to all your services everywhere in your application. It can seem like a great idea because wherever you are, you can just do this to get whatever interface you like:

var foo = MyContainer.Instance.Resolve<IFoo>();

But there are two big problems. First, this introduces hidden dependencies into your class. Instead of your constructor advertising its dependencies, we again must examine the code to know what needs to be set up in the container.

Second, it assumes that all parts of your application will work with the same container, and the same implementations of each interface. This may be true for small applications, but in large enterprise systems, there may well be the need for multiple IoC containers. In our systems, we make use of Unity child containers, allowing different sections of the application get access to their own implementations of interfaces. With a singleton container, this is impossible.

5. Avoid constructor bloat

One of the great things about IoC containers is that you don’t have to call constructors yourself – the container does the hard work for you. This means you can have a dozen constructor parameters, each representing a different dependency, and yet without ever having to write code that calls it.

public MyClass(IFoo foo, IFoo2 foo2, ILog log, IExporter exporter, IEmailer emailer, ISettings settings, IAudit audit)
{
}

That is, until you want to test it. Then you have to mock up all of those interfaces. And probably you will find that your class only needs to call one or two methods on each. This is the time to apply the Interface Segregation Principle and replace them with one or two more focussed interfaces that represent the real dependencies of your class under test.

6. Avoid property injection

Most IoC containers offer a way to let you put attributes on properties in order to tell the container that it needs to set that property after constructing the object. In Unity you use the Dependency attribute:

class Bar
{
    [Dependency]
    public IFoo Foo { get; set; }

    public Bar()
    {
    }
}

Although this seems like a great feature, it has the effect of hiding this dependency from anyone who is constructing your object without an IoC container. At the very least, your class should report a good error message if someone forgets to set up a property dependency.

7. Document your interfaces

If you are using an IoC container, chances are you are working on a large system, and other developers are resolving interfaces that you put in the container.

Often developers will add good comments to the concrete implementation of a class, but spend very little time commenting the interface (who likes to write the same comments twice?). So in the concrete class we might have a comment like this:

/// <summary>
/// Call this to process all the files in the InputFiles collection, using the rules from the Rules collection
/// </summary>
/// <param name="mode">Processing mode, 0 = replace, 1 = update, 2 = overwrite, 3 = test only</param>
public void Process(int mode)

but in the interface, we couldn’t be bothered to type that all again (and we hate cutting and pasting anyway) so we just have this:

/// <summary>
/// Process
/// </summary>
void Process(int mode);

But it is the interface that is the public API for your service. The comments on the interface will be used to display Intellisense to the user. The caller may not even have access to the code for the concrete implementation. If you are going to spend time writing good comments, write them on the interface. Here’s the difference in intellisense experience:

bad-intellisense

versus:

good-intellisense

8. Don’t depend on Dispose in a specific order

When you Dispose your IoC container, it will go through all of the IDisposable instances it knows about and call Dispose on them. But this can introduce a problem, because we cannot guarantee the order the services are disposed in. If you make a call into another service in your Dispose method, how do you know that service hasn’t already been Disposed?

Instead, design your services in such a way that they can be disposed in any order, and use events or some other form of messaging to report to your system that a shutdown is about to happen, allowing any last minute logging, saving etc to be done beforehand, while all the services are still up and running.

9. Make sure Lifetime Management is communicated

If you call Resolve<IFoo> twice on your IoC container, you might get two new instances of the Foo class, or you might get the same one twice. Without looking at how your container is configured, you have no way of knowing. But this can be a real headache if IFoo implements IDisposable. How do you know whether you ought to call Dispose on it or not?

I don’t know of any slick solution to this, but I would typically avoid instances where a Resolve gives you something you need to Dispose yourself. Instead I would return a factory object that makes it very clear you are building a new instance that you are in control of its lifetime yourself. Whatever approach you use, make sure your whole development team understands it. You don’t want someone Disposing a service too early, resulting in the next person to use it getting a nasty exception.

10. Document your public API

In a large system, a container can easily fill up with a lot of interfaces. The trouble is, not all of these are at the same level. Some are high-level interfaces, allowed to be called from anywhere, whilst other things are only in the container so they can fulfil the dependencies of those high-level interfaces. It means that you run the risk of developers guessing incorrectly about which interface they are supposed to call to achieve a particular task. They will assume that if they can get at it from the container, then they must be allowed to call it.

Now there are ways of having interfaces defined in your container that people can’t get at from the wrong place by making good use of assemblies and the internal keyword, but really, you need to make it easy for developers to know what is in the container and how they are intended to use it. This may well involve maintaining an API document, and also means writing good comments on the interface. If you don’t do this, don’t be surprised to see code that inadvertently circumvents key functionality by calling into a component at too low a level, or the wheel being reinvented, simply because a developer didn’t know the container included a service that had the desired behaviour.

Do you have any tips for getting the best out of IoC containers? Please let me know in the comments.

Sunday, 29 May 2011

Development process signal to noise ratio (a rant)

In the world of audio recording, maintaining a high signal to noise ratio (SNR) is vital. Signal is the singer’s voice; noise is the traffic on the road outside. Signal is what we want to record; noise is what we don’t.

Just as noise can get introduced at any stage in the audio recording process, degrading the signal quality; the software development lifecycle has many points at which noise can be introduced. The only difference is, that the signal is information rather than sound. In software development, signal is information that is of use to me; noise is information that wastes my time.

In the audio world, if the signal to noise ratio is too low, it becomes simply too difficult or distracting to keep listening. The same is true with information. A low SNR to ratio bores people into switching off.

Here’s five areas in the software development / business process that you need to watch your SNR in…

Meetings

As a general rule, I dread meetings. They get me away from what I would rather be doing (coding). A meeting is maximally useful if the information that flows within it is relevant to all those present. Meetings to tell people things they already know, or that they don’t need to know, or that only one or two people need to know, are invitations to daydream, resulting in attendees missing the little that could have benefitted them.

The worst offenders are the “weekly” meetings, or meetings that are required by some kind of process (“kick off” meeting, “release” meeting, “checklist checker checking meeting”). If you attend a 90 minute meeting with only 5-10 minutes having relevance to you, something is wrong.

Talking in detail through everyone’s weekly progress, or everyone’s holiday plans, or everyone’s tasks for next week, or every open bug, or every support case, is likely to be relevant only to the person who organized the meeting. For everyone else, the meeting SNR is minimal.

Documents

Software documents, particularly those created from templates, also often suffer from poor SNR. Instead of getting right down to business telling us the crucial facts, we have to wade through pages of boilerplate sections marked “Not applicable”.

In one company I worked for, there were several pages in every software design document we ever produced devoted to the utterly irrelevant topic of PCB layout. If the first section of your document that contains useful information is numbered 4.6.12.5, something is wrong.

Documents with low SNR actively discourage people from reading. And the less likely anyone is to consult your document, the less motivated you are to write good documentation, resulting in a vicious cycle.

Presentations

Presentations can have terribly low SNR. A favourite example is presenters who try to follow the rule that says: “Tell people what you are going to tell them, then tell them, then tell them what you told them”.

Fine advice, but not when they interpret this as “tell people that you are telling them what it is that you are going to tell them, then tell them that you are telling them the things you told them you were going to tell them, and then tell them that you are telling them that you have finished telling them the things you told them you told them you were going to tell them.” Instead, use your introduction to make us want to hear what is coming next.

As with meetings, any attempt to exhaustively cover a topic in a presentation is a mistake. Don’t show me every possible graph and chart from last quarter’s financial results. Don’t show me every feature and configuration option. Show me something interesting. Leave me wanting more at the end of your presentation, not praying for it to end.

Comments

Comments in source code are also prime offenders for low SNR. Comments that state the obvious, or are sources of misinformation soon train the developer to completely ignore comments. It’s like the boy who cries wolf. Soon no one is listening anymore. The one really helpful comment you write might as well be invisible. No one will read it.

Comments are like footnotes in a book. Most readers will pause to see what a footnote says the first time they encounter one. But if what they find there is of no relevance or interest to them, they will soon train themselves to stop looking.

Code

What about your code? Surely that is all signal right? After all, we can’t delete 50% of the lines and leave the behaviour of the code intact. But the truth is that code can be written in a very noisy way. This happens when so much code for logging, or caching, or error handling, (or whatever) is mixed in that the structure and intent of the method in question is obscured from view.

The solution is simple: write clean code. Extract each task out into small, well-named helper methods or dependencies. This allows people to see the “signal” of what is going on, and only delve into the details that are of interest to them. Implementation details are usually noise.

Summary

Stop writing comments that are just noise. Delete comments that are wrong. Refactor “noisy” code out into helper methods. Learn what clean code is. Hold fewer and shorter meetings. Write shorter documents. Tell me what I need to know. Stop wasting my time.

Thursday, 2 July 2009

Where are you going to put that code?

Often we want to modify existing software by inserting an additional step. Before we do operation X, we want to do operation Y. Consider a simple example of a LabelPrinter class, with a Print method. Suppose a new requirement has come in that before it prints a label for a customer in Sweden, it needs to do some kind of postal code transformation.

Approach 1 – Last Possible Moment

Most developers would immediately gravitate towards going to the Print method, and putting their new code in there. This seems sensible – we run the new code at the last possible moment before performing the original task.

public void Print(LabelDetails labelDetails)
{
    if (labelDetails.Country == "Sweden")
    {
        FixPostalCodeForSweden(labelDetails);
    }
    // do the actual printing....
}

Despite the fact that it works, this approach has several problems. We have broken the Single Responsibility Principle. Our LabelPrinter class now has an extra responsibility. If we allow ourselves to keep coding this way, before long, the Print method will become a magnet for special case features:

public void Print(LabelDetails labelDetails)
{
    if (labelDetails.Country == "Sweden")
    {
        FixPostalCodeForSweden(labelDetails);
    }
    if (printerType == "Serial Port Printer")
    {
        if (!CanConnectToSerialPrinter())
        {
            MessageBox.Show("Please attach the printer to COM1");
            return;
        }
    }
    // do the actual printing....
}

And before we know it, we have a class where the original functionality is swamped with miscellaneous concerns. Worse still, it tends to become untestable, as bits of GUI code or hard dependencies on the file system etc creep in.

Approach 2 – Remember to Call This First

Having seen that the LabelPrinter class was not really the best place for our new code to be added, the fallback approach is typically to put the new code in the calling class before it calls into the original method:

private void DoPrint()
{
    LabelDetails details = GetLabelDetails();
    // remember to call this first before calling Print
    DoImportantStuffBeforePrinting(details);
    // now we are ready to print
    labelPrinter.Print(details);
}

This approach keeps our LabelPrinter class free from picking up any extra responsibilities, but it comes at a cost. Now we have to remember to always call our DoImportantStuffBeforePrinting method before anyone calls the LabelPrinter.Print method. We have lost the guarantee we had with approach 1 that no one call call Print without the pre-requisite tasks getting called.

Approach 3 – Open – Closed Principle

So where should our new code go? The answer is found in what is known as the “Open Closed Principle”, which states that classes should be closed for modification, but open for extension. In other words, we want to make LabelPrinter extensible, but not for it to change every time we come up with some new task that needs to be done before printing.

There are several ways this can be done including inheritance or the use of the facade pattern. I will just describe one of the simplest – using an event. In the LabelPrinter class, we create a new BeforePrint event. This will fire as soon as the Print function is called. As part of its event arguments, it will have a CancelPrint boolean flag to allow event handlers to request that the print is cancelled:

public void Print(LabelDetails labelDetails)
{
    if (BeforePrint != null)
    {
        var args = new BeforePrintEventArgs();
        BeforePrint(this, args);
        if (args.CancelPrint)
        {
            return;
        }
    }
    // do the actual printing....
}

This approach means that our LabelPrinter class keeps to its single responsibility of printing labels (and thus remains maintainable and testable). It is now open for any future enhancements that require an action to take place before printing.

There are a couple of things to watch out for with this approach. First, you would want to make sure that whenever a LabelPrinter is created, all the appropriate events were hooked up (otherwise you run into the same problems as with approach 2). One solution would be to put a LabelPrinter into your IoC container ready set up.

Another potential problem is the ordering of the event handlers. For example, checking if you have permission to print would make sense as the first operation. The simplest approach is to add the handlers in the right order.

Conclusion

Always think about where you are putting the new code you write. Does it really belong there? Can you make a small modification to the class you want to change so that it is extensible, and then implement your new feature as an extension to that class? If you do, you will not only keep the original code maintainable, but your new code is protected from accidental breakage, as it is isolated off in a class of its own.

Thursday, 1 May 2008

The Great Refactoring Debacle

The Vision

I have been giving some technical seminars to the rest of the development team at my workplace every few months, trying to keep them up to date with good coding practices and the latest technologies. While a few of them are already quite interested in these topics, on the whole most are simply content to plough on with the knowledge they have and only learn new stuff when an obstacle confronts them. But on the whole, my presentations have been well received.

It was a few months ago that I brought up the subject of refactoring. Twenty developers all coding away merrily on the same project for two years things had resulted in some less than ideal code. It was not as if the product was low quality. Far from it. Our bug count was low and the customers who were using the product hadn't escalated any major issues to the development team.

But there was a growing awareness that the codebase was getting a little out of hand, and management agreed. We were going to spend a few months simply working on the quality of the code, ready for a big "general release". No new features, just bug fixing and "refactoring".

The Strategy

This was a "once in a blue moon" opportunity to right some of the wrongs from earlier phases of the project. I presented my opinions on where I thought our efforts would be best spent during our two months of refactoring. We would focus on code smells, breaking huge classes and methods down into smaller pieces that did one thing and did it well.

At the same time, the more daring developers would try to eradicate some of the very tight coupling that had crept in, and to separate the GUI from business logic. Without these two advances, any hopes of running automated tests would be merely a pipe dream.

Finally, there were some classes that were being used in multiple places resulting in it being far too easy to inadvertently break someone else's code. I proposed to separate this code off to form some new extensible components that could be customised and used in different ways by different parts of the application.

The Refactoring

So we got to work. The build-breaking changes went first. Thousands of lines of code were changed. Developers were delighted as they successfully turned previously intimidating classes into groups of much more manageable code. Some of the worst-designed features of the application gradually morphed into a more streamlined and extensible framework.

There wasn't time for everything. In fact, some of my top recommendations had to be left to one side. And other ideas just proved too difficult. For example, I tried to extract some groups of Windows Forms controls from a Panel containing thousands of controls, but the process was just too fiddly (if only one of the refactoring tools could do this).

The bug count dropped dramatically. In fact, it dropped so fast that most of the development were seconded to test to help them catch up. All was looking good.

The Aftermath

Then came the system test. The first "full" system test in over a year. And there were lots of new bugs found. On the whole we managed to stay on top of them, but as the release date grew closer and closer, some "showstopper" bugs came out of the woodwork. The release date slipped three or four times and ended up coming a few weeks late. The overrun wasn't huge by comparison with many other projects, but an explanation was needed.

I wasn't in the meetings where blame was apportioned. In fact I was on holiday that week, but it was clear when I returned that a scapegoat had been identified. Refactoring had caused us grief, and perhaps we shouldn't have done it. On the surface of things, it was the refactoring that caused some of the very defects that delayed the release.

Now I could get on my high horse and demand a chance to stand up for "refactoring". I could point out that...

  • It's not really refactoring if you don't run unit tests (because you don't have any unit tests). It's simply re-architecting which is very risky.
  • Refactoring is always best done on code that you are actively working on. You understand what it is doing and why, and you have already allocated some time to testing it. Diving into a class and refactoring it simply because it is "big" is also risky (especially if you don't have any unit tests).
  • Refactoring is primarily about making small, incremental changes. Over a long period of time the structure and design of the code should improve. Trying to do it all in one hit is risky.
  • Refactoring does introduce bugs from time to time, because all modification to code risks introducing bugs. That is equally true whenever you add features or fix bugs. Indeed, it could be argued that the new features that "sneaked in" to the refactoring phase were the cause of a lot of our problems.
  • The main rewards of refactoring are felt in the future. For example, new features can be added much more quickly. Bugs can be found and fixed much more easily. In fact, my team is already reaping the fruits of the refactoring in the new feature we are adding, but this kind of benefit is not highly "visible" to management.

... well I could point these things out, but I suspect it would just come across as whining because my idea of "refactoring" apparently didn't "work". And if I say too much I risk someone deciding that refactoring should be "banned" because it is clearly too dangerous.

So I'll keep my mouth shut, and continue to improve the codebase bit by bit, sticking to the code I am already working on, and adding unit tests as I go. Management won't credit any resulting quality enhancements or development speed increases to the discredited "refactoring" idea, but at the end of the day, much of my job satisfaction comes from the knowledge that I have contributed to a quality project.

I suppose the bottom line is that the benefits of refactoring are not easily measurable. Bug counts are measurable. Completing features is measurable. But what is not measurable is how much refactoring has contributed to the speed at which bugs are fixed and new features are added. And as the saying goes, "if you can't measure it, it doesn't exist".

Technorati Tags: ,

Thursday, 24 April 2008

Branching and Merging Anti-Patterns

I came across a helpful article on MSDN today, on the subject of branching and merging in source control. What grabbed my attention was a list of "branching and merging anti-patterns". Sadly, my current company is guilty of a few of these. Here's the list from the article:

You know you are on the wrong track if you experience one or more of the following symptoms in your development environment:

  • Merge Paranoia—avoiding merging at all cost, usually because of a fear of the consequences.
  • Merge Mania—spending too much time merging software assets instead of developing them.
  • Big Bang Merge—deferring branch merging to the end of the development effort and attempting to merge all branches simultaneously.
  • Never-Ending Merge—continuous merging activity because there is always more to merge.
  • Wrong-Way Merge—merging a software asset version with an earlier version.
  • Branch Mania—creating many branches for no apparent reason.
  • Cascading Branches—branching but never merging back to the main line.
  • Mysterious Branches—branching for no apparent reason.
  • Temporary Branches—branching for changing reasons, so the branch becomes a permanent temporary workspace.
  • Volatile Branches—branching with unstable software assets shared by other branches or merged into another branch.
    • Note   Branches are volatile most of the time while they exist as independent branches. That is the point of having them. The difference is that you should not share or merge branches while they are in an unstable state.
  • Development Freeze—stopping all development activities while branching, merging, and building new base lines.
  • Berlin Wall—using branches to divide the development team members, instead of dividing the work they are performing.

The most costly I would say is a "development freeze" which can leave whole teams of developers unproductive for weeks at a time.

The most common one I have run into is probably "merge paranoia". Some developers are adamant that the source control software is riddled with bugs that will ruin your work given the slightest opportunity. They couldn't possibly have used the tool wrong. Thus multiple checkouts are often disabled, and branches are never merged back into the tip - bug-fixes are done twice.

As for "branch mania", what can I say? Some managers just seem to sleep easier if they know there was a branch made, "just in case" we need it.

And if there is perhaps one missing, I would add "wrong way branch", where a branch is made and then the tip is renamed so that the branch can take over the project name of the original tip. Guaranteed to cause pain if anything was checked out before this hare-brained scheme is put into effect.

Thursday, 29 November 2007

Productivity and Enthusiasm

During my holidays in my first year at university I did some warehouse work to earn some extra cash. My job involved offloading pallets of food from lorries using hydraulic pallet jacks and depositing them down a ramp from which the fork-lift truck drivers would collect them to stack in the warehouse.

Now when a lorry came in to be offloaded, there was nowhere to hide. Whether I was feeling full of enthusiasm for doing my job or bored witless, I still had to get to work. The net result was that whether my "enthusiasm" (E on the graph below) was low or very high, my "productivity" (P on the graph below) didn't vary that much.

Productivity versus Enthusiasm for manual labour

What's this got to do with software? Well, the following years I started doing programming as a holiday job, and went on to become a full-time computer programmer. But I soon noticed that in the world of software development, my Productivity versus Enthusiasm curve was radically different.

Suppose I come into work feeling tired and the task I have been given to do is not an enjoyable one (e.g. fixing elusive bugs on a legacy product, or adding a pointless feature to please marketing, writing documentation). In that case I find that a day can easily go past where productivity is close to zero.

On the other hand there are days when I am feeling very enthusiastic about my work. Perhaps it is because I am learning and using some new and interesting technology, or perhaps I am refactoring some badly designed class into a really elegant and simple structure. In this case I find that my productivity increases exponentially. As long as I am not interrupted by distractions, in this state I can become super-productive, fixing bugs and churning out new features at an incredible rate.

Productivity versus enthusiasm for software development

Why the difference between the two graphs? Why could I get work done at the warehouse on low enthusiasm days, but not in the world of software development? The answer is simply that to write software requires concentration and concentration requires interest. At the warehouse I could offload lorries while thinking about all kinds of unrelated subjects. But to write code, my mental focus must be entirely on the task at hand.

If this is true, then the way to maximise productivity is to minimise boredom. If we recognise the things that capture our interest and enthusiasm, we can design development strategies that promote productivity.

This is I think part of the genius of the "agile" approach to software development. Running tests manually is boring, but writing automated tests is much more intellectually stimulating. Bug-fixing is boring, but refactoring code to improve its design while you fix a bug gives a real sense of achievement for a developer who takes pride in their work.

I don't expect boredom can ever be completely eliminated, and so there will always be the need to grit your teeth and show some professionalism. But the route to increased productivity in software development is more closely tied to motivation and enthusiasm than many of our managers seem to appreciate.

Monday, 26 November 2007

Unit Testing is Not About Testing

When I started drafting this post, I was under the illusion that I had just stumbled across an amazing discovery, never before articulated by any programmers before me. But over the past few weeks, I have found out that what I am about to say isn't even slightly original. Everyone is saying it. But it took me quite some time to realize, so here it is anyway, for the two people out there who are as slow as me...

The concept of automated unit testing is appealing to most developers, even if the idea of writing all tests before writing code seems a bit over the top. It is certainly attractive to think that I might never have to perform another boring manual test of my software, because I can check it is working correctly simply by launching NUnit (or similar) and running through a suite of tests.

Unautomatable Tests

The trouble is, automated testing only works for a certain subset of classes - those whose outputs are clearly defined and easily measurable. And of course, those are exactly the type of classes I keep finding myself working on.

For example, I spent many months writing a library of custom Windows Forms controls. They had to be tested with keyboard and mouse entry, and at a variety of screen resolutions and font sizes. Unit testing was sadly out of the question...

Assert.LooksReallyCool(button); // not possible

Then there was the audio related code I worked on. Passing audio to the Windows MME APIs and checking that the sound played back correctly without stuttering. Or passing audio through a DSP algorithm to speed it up to double speed and automatically increase the volume in quiet bits.

Assert.DoesntStutter(audio); // not possible    
Assert.SoundsGood(audio); // not possible

I could go on. How does an automated test check that a webpage renders correctly on IE and FireFox (or harder still, on Mac and Linux)?

And here lies the trouble with the documentation for Unit Testing frameworks. It conveniently ignores these awkward cases and gives us an example of a BankAccount class that has a Withdraw method. Classes that add numbers or sort lists are ideal candidates for automated testing.

Why Bother?

The observation that a large proportion of your classes cannot be meaningfully tested with an automated test therefore puts off many developers from writing unit tests at all.

But what if the main benefit of unit testing was not actually testing? Here's my grand (but unoriginal) idea: the main benefit of unit testing is not testing, but design principles.

Design Benefits

Having a unit test for each and every class in your project means that each and every class is used at least more than once. It forces you to design classes that are loosely coupled. It will highlight hidden static dependencies. It will also enforce separation of GUI and business logic.

The trouble with thinking that Unit Testing is about testing is that "untestable" classes won't have unit tests written for them, which in turn means more badly designed classes. In short, if all your unit tests ever do is create instances of classes and call their methods without doing any checking of what they return at all, you will still benefit from greatly improved design.

Friday, 23 November 2007

Pair Programming for Loners

I first heard about pair programming years ago while reading some articles on Extreme Programming. My initial reaction was "That sounds like an interesting idea. It might just work" followed very quickly by "I can think of a few people I would not like to work in a pair with".

The most obvious benefits are:

  • Having someone to bounce ideas off
  • Having someone watch you while you code and make constructive suggestions
  • Watching someone else code and learning from their approach

Obviously if you develop alone, the first two benefits are largely unavailable. The best you can hope for is to get some feedback from people on user forums.

But the third option is perhaps not as out of reach for the lone developer as it may seem. Where can you go to watch other developers at work on real projects and learn from them?

The answer is open source websites like CodePlex or SourceForge. The value of looking at real code (as opposed to sample applications) should not be underestimated.

As I have been slowly learning ASP.NET over the last year I have been frustrated with the numerous options for doing things - different project types, different data access approaches, different theming mechanisms etc etc. Before I could even start a project I was left with a thousand questions about whether I should use Web Site or Web Application, or whether to use DataGrid or GridView, or whether to use custom business objects or SqlDataSource.

So the great thing about downloading some open source projects is that it gives a great chance to see how other people have chosen to do things. The main three I have been looking at to help me with ASP.NET are BlogEngine.NET, SubText and DotNetKicks. Just spending a few hours browsing the code can teach you a lot about how to structure a project, as well as demonstrating various real-world examples of usage of the different approaches to data access.

The developers of each of these projects are all people who are not only experienced ASP.NET projects but clearly have a passion for best-practices in the way they design and structure their code. These are people who I would want to be pair programming with if I could, asking them questions about why they did things certain ways. But for now, I'll have to content myself with silently looking over their shoulders and working out what's going on for myself.

Friday, 26 October 2007

Not worth the paper...

I've been reading a bit on "Agile" development recently, and I like what I am hearing. I didn't pay much attention to it at first, as it all seemed to be about "SCRUM", a project management strategy which is realistically never going to be introduced at the company I am working at (or indeed any of the companies I have worked for).

But one thing that I really like is the willingness to question the usefulness of documentation. Whenever I have been in discussion meetings about software quality, someone always suggests that the answer is for us to write more documentation. If only there were documents describing exactly how things worked, then all would be great. Any piece of code you were about to work on, you could simply look up the relevant document and immediately understand it. After you finish your work, you update the document with your changes. Job done!

There are a number of problems with viewing documentation as the answer to improving software quality.

First of all, at best these documents capture intention - what we planned for the software to do. As we all know, features get cut, added and modified. And the documentation ... well let's just say it never quite gets to the top of the priority pile to update it. Gradually it drifts further and further from reality until it is nothing more than an "elaborate lie".

Second, these documents rarely contain the type of detail a developer wants. Often I find myself writing down the convoluted path of events, function calls and thread signals that lead from an action being initiated to the part of the code that I actually want to change. If only there was a nice diagram I could consult that shows me. But there isn't. Or if there is, it is too simple or simply wrong.

Third, even if there is a great document, that has exactly what you need to know, it is not at all obvious how you know it exists and where to find it. They end up in obscure locations on network drives or internal websites. The person who wrote it acts shocked that you didn't look at it before you asked them, yet quite how you were supposed to become aware of its existence is never considered. The end result is that even when documentation is done well, it is seldom read by those who would benefit most from reading it.

So if documentation is not the answer, what is?

Ultimately, there are two main resources that a developer uses when trying to understand code they are about to modify. First is the person who wrote the code in the first place. Hopefully they are still around, and hopefully they are helpful. They can often save you large amounts of time. Amazingly, many developers will dive right into modifying code they haven't written without thinking of asking the original author whether there is an easy way to accomplish what they want. The end result is often overcomplicated add-ons that introduce subtle bugs, decrease cohesion and tighten coupling.

The second resource is of course the source code itself. The strongest reason for needing to write smaller classes and functions is the need for other developers to understand the code. Code that works but is impenetrably hard to comprehend is bad code. Bug reports should be filed against it. The "if it ain't broke don't fix it" maxim does have a certain wisdom to it, but I would argue that if code is so complex that no one can modify it without breaking it, then it is already broken.

Given a choice between a week of refactoring a complicated class and a week of writing about how it works, I know which I would choose.