Saturday, 27 October 2018

C# Exception Handling: Catching, Throwing, and Performance

TL;DR;

  • Use `throw;` not `throw ex` 
  • Use `catch(...)` as few times as possible 
  • Reserve Exceptions for Exceptional conditions: 
    • 1 in 100 is not exceptional 
    • 1 in 1,000 probably isn't exceptional either

The Stack Trace

The Stack Trace is extremely useful for debugging errors, and can significantly shorten the cycle time of your work. There is however a common mistake that is made across many codebases that makes it less useful than it could be.

Let's start with an example...

A New Support Request

Your support team has identified an issue in production, they've diligently compiled:
  • The user affected
  • The time it occurred
  • A bunch of other useful information 
  • The actual error message 
  • The Stack Trace
Your stack trace looks like this:
System.Exception: I wasn't able to complete that request!
  at <Library>.<Service>.DoWork() in /Library/Service.cs:line21
  at <Library>.<Controller>.UserRequestedAction() in /Library/Controller.cs:line 21
You feel confident you can fix this quickly, after all, you have the actual error message and stack trace! Then you open up the code at the top of the stack trace - Service.cs:
    public void DoWork()
    {
        try        
        {
            var service = new ExceptionThrowingService();
            service.ErroneousMethod();
        }
        catch (Exception e)
        {
            // Perform special handling, logging, etc.            
            throw e; // Line 21!        
        }
    }

Uh oh, that isn't where the actual error message occurred!

In the sample code we can probably deduce that the error occurred in either the constructor of the service, or the call to ErroneousMethod(). You may have been more fortunate than I have, but rarely does the code I inherit look as simple as this. A Try/Catch block like this might have as little as 5 lines, but is often somewhere between 50-200 lines, and unfortunately, sometimes 1,000 lines or more.

In addition, the dependencies that are utilised (such as ExceptionThrowingService in this instance) mean that the actual error could have occurred somewhere within one of those dependencies, and therefore the number of lines of code to search for the error is significantly larger than just the lines within this Try/Catch block.

Maintaining the Stack Trace

Fortunately, maintaining the stack trace is easy. Replace throw e; with throw;. Simply omitting the Exception object will result in .Net not touching the stack trace.

Stack Trace Recommendation

Ensure that your default coding style is to use  throw; without including the Exception object. You can then selectively choose when you want to rewrite the Stack Trace.

It should be extremely rare that you want to overwrite the Stack Trace on the Exception object that you caught. If you are going to overwrite the Stack Trace, then create a new Exception. You may or may not want to include the caught exception as the Inner Exception depending upon your context.

Performance

While Exceptions are a powerful tool, they come at a cost, one of these costs is execution time. If you throw one Exception, you are unlikely to notice the cost, but if you throw thousands, or millions, then you are most likely paying a performance penalty for your choices.

Personal Anecdote: I have improved performance from hours to a couple of minutes by changing control flow from Exception throwing to alternative control flow such as return values. I have experienced this saving on multiple occasions.

The Stats

Using a skeleton implementation we can compare the execution time of a loop with exceptions vs a loop that doesn't throw exceptions. On a reasonably powerful modern laptop the timings come out at:
IterationsWith Exceptions (ms)Without Exceptions (ms)
1003< 1
1,00025< 1
10,000282< 1
100,0003,3531
1,000,00040,3059

This aligns with my personal experience, loops that perform calculations and/or validations across a large number of objects/records can often improve performance significantly be decreasing reliance on Exceptions.

These results are only going to be indicative when then execution time of the "work" being performed for each item in the loop is limited. This is often true when there is no reliance on a data store or network dependent services inside of the loop.

Performance Recommendation

Exceptions inflict a significant performance penalty whenever they are thrown. Exceptions should be reserved for exceptional circumstances. If 50% of the calls to a method result in an Exception being thrown, then this is not an exceptional situation, this is business as usual.

I appreciate that if the data is invalid, then from a theoretical point of view, it may be correct that an exception be thrown. However, if you want the services that you write to scale and deliver responsive user experiences, then consider the performance impact of all Exceptions that you throw and use them sparingly.

Example Code

Please see the example on BitBucket for a minimalist demonstration of these concepts.

References:

Saturday, 21 July 2018

Why I Stopped Seeking Acknowledgment

One of the biggest improvements I made as a leader (and probably a work colleague!) was to stop seeking acknowledgment from others. I used to be one of those annoying people who would call out a shortcoming (or opportunity to improve) and talk about it with someone until they acknowledged that the issue existed.

We don't like admitting when we are wrong, I'm sure you can think of your own examples, be it with children abdicating any involvement, be it car accidents and traffic infringements, or at work when mistakes have been made.

As a result, these interactions were very frustrating for me, and I'm sure that they led to resentment towards me by these individuals.

I'd love to tell you an awesome story about how I reflected on these interactions and intentionally experimented with different ways to make these conversations more effective, but that wouldn't be true.

The real story is that I was having a conversation with a member of my team and something came up that wasn't working. I forget the topic now, but when it came up the reaction from the other person was very defensive. We were interrupted before I could delve into the excuses and blame that the person expressed (of which I thought there was plenty!).

To my surprise, the next week I observed the person changing the way that they worked, and I had the change that I had been seeking!

This was the moment that caused me to reflect and question the way I had been behaving and what my real drivers were.

It is quite uncomfortable (but probably not that surprising) to acknowledge that even though I saw the change that I wanted occurring, I still felt that something was missing because there had been no acknowledgment. My instinct was to bring it up with the person in our next catch up, and it was very hard to fight that strong desire.

This was a turning point for me, and I intentionally changed the way I offered feedback so that I fought against the urge to seek acknowledgment that an issue exists. This has led to a significant increase in the adoption of behavioural and technical suggestions by those I manage, mentor, and/or coach.

Even though it has been many years, and the desire to push for acknowledgement is long gone, it still feels nice when acknowledgement occurs. Maybe I am not fully cured after all!

I have since applied the same approach to organisational and product improvement suggestions, but I'll write about that in a future post.

Agile and the Spotify Yardstick

I was lucky enough to attend LAST Conference 2018 last week and it reminded me to finish my reflections on a session I attended 3 years ago at LAST Conference 2015!

At the time, one session in particular made me really stop and think about 'agile' and how organisations and teams just starting on their journey can so easily get lost in information overload. I believe this is just as true today as it was 3 years ago.

The session was @muir_maria's session "It's okay to be Hybrid" that started it all. @muir_maria very rightly pointed out that there is a spectrum (a very wide one) where at one end exists 'Waterfall' and the other 'Agile', and at the agile end, she has put Spotify as the yardstick. This represents what many in the industry believe (or seem to), that they aren't really agile until they have copied all the practices that Spotify are using.

Don't get me wrong, Spotify must be a great place to work, and they are extremely advanced in their agile practices, however, I'm not convinced that the Spotify model is the right model for every other organisation on the planet, nor do I believe that the Spotify model is perfect (their marketing machine has to be commended for the impact that this has had, I'm sure that their subscriber base is significantly higher because of it).

I believe that the key to their success is that they are continually identifying possible ways to improve, being brave enough to have a go at these new ways, and keeping the things that work while discarding the things that don't. If you rinse and repeat that enough times, you are going to be in a great place. They are doing great things, but if another company in another industry was that aggressive with continuous improvement, would they end up with the same model? Possibly. But I'm tipping they would end up with a different model, one that was appropriate for the problem domain that they are solving.

There are a number of problems with continually measuring ourselves against the Spotify, or Netflix, or Atlassian, or any other specific organisation's Model. The first is that it stifles innovation, we have all these organisations and intelligent people who are so focused on whether they are as good as 'Org X' that they try to apply aspects of the 'Org X' model to their business instead of identifying an improvement that is based around their problem domain (the team, the business model, the regulatory environment, etc.).

The bigger problem is that for organisations new to agile (and even a number of organisations who have been practicing agile for a while), they think that the only way they can be agile is to do all the things that 'Org X' does. That isn't going to be achievable, as it is a huge mountain to climb, and I'm not surprised that a lot of organisations aren't signing up to drop everything they know to do what 'Org X' does.

I'm not advocating that we stop observing and copying practices from successful teams, that definitely needs to continue, I'm proposing that the culture and practices of regular reflection, resulting in experimentation and further reflection using the shortest possible cycle time (weeks and days, not months) is the more important goal to be chasing.