In Defence of Static

Picture of my app working after I fixed a bug... using extensive unit tests.
Picture of my app working after I fixed a bug… using extensive unit tests.

Maybe it’s my functional programming background, but I love static methods. Not everyone gets this, some people parrot the phrase “static methods are bad for testing” but don’t seem to understand what that means.

Static methods are great for testing. Inputs in, outputs out, side effects – what side effects? What’s not to love?

What they are bad for, is mocking. This is an entirely different problem.

Note: here I’m using static to mean methods without side effects, which technically includes Objective-C methods that are denoted with a + (class methods). Turns out, these methods are not technically static, they take place on another object [detailed explanation]. Static methods in Objective-C should properly be C methods.

Also, you can mock class methods, in OCMock by using: 

id classMock = OCMClassMock([SomeClass class]);

When and Where

I’ve been doing a bunch of image processing work lately, and it looks like this:

  • Break image down into pieces.
  • Do stuff to it.
  • Put it back together.

Each of those things individually are great for static methods. Take an image, break it down – do I have the requisite pieces? Put some pieces through this process, is the result right? Given some pieces, do we get an image with the right properties? Wonderful.

I’ve spent hours (OK, days) writing tests for these things, because I was not an expert on image manipulation and in my opinion there is no better way to deeply understand code. I found and fixed a horrible bug. Next, I need to optimise, but my thorough test suite gives me confidence that I can do this refactoring without breaking things, and that I have a good API.

But – I don’t want to deal with this elsewhere in my app. So I absolutely want this stuff to be mockable. So I use a very simple pattern:

  • Expose a class method that updates the object.
  • Have a static method that takes whatever information is necessary, and returns whatever needs to be updated.
  • Class method calls the static method with the requisite information, and updates itself accordingly.

This becomes easy to test. My static method has tests that cover a full range of inputs and outputs. My class method just needs a single test to ascertain that stuff gets updated.

When I’m mocking this object, I just mock the class method. The static one never gets called.

Some people’s irrational loathing for static methods can cause problems in code review. If you don’t want to argue, two options:

  • Split the static methods out into a “MyClassHelper” object. For larger things I do this to be tidy.
  • Just don’t declare the methods as static, but use them as such following this pattern – I really don’t like having to do this. But I have done it.

Other People’s Static Methods

The main issue I run into with static methods, is that other people have used them – I find it is common in API design when classes shouldn’t be subclassed.

In this case, I group things together logically in a wrapper. For example, in my current project I have a class that wraps various static things I need for handling images.

Coverage vs Confidence

I am a proponent of code coverage, and regularly use it to guide me to the places in my code base that need a bit more love and understanding. But it’s important to note that coverage doesn’t necessarily mean confidence.

If you have a (say, static!) method that takes a test image and returns an array of length 9, and you verify at the end of it that the array is indeed of length 9, voila you have coverage. What you don’t have, however, is confidence. What are the 9 things?

In part for this reason, I start small and work up incrementally. If I start writing a large test and get confused, I break it down and write a small one first. So in this case I might write 3 tests:

  1. The result has 9 things in it.
  2. The 9 things are correct.
  3. The completion block gets called.

So if I break the test and 9 things are no longer produced, tests 1 and 2 will both fail. If I break it and 9 wrong things are produced, just test 2 will fail. If I accidentally delete the line that calls the completion block, just test 3 will fail.

So when I break something, multiple tests will fail, and the simplest one that is failing will be where I go first to determine what I’ve done.

The other thing about confidence, is that tests should break when you change stuff. The other thing I use to improve my code confidence is when I make a change that doesn’t result in failing tests and I think it should, I go and write some.

TDD Wut?

I don’t often do pure TDD, when testing UI code I expect it would be difficult to the point of not being worthwhile, or in some cases not even possible. Occasionally I TDD, if I’m trying to figure out how something should behave I will start with test cases. More commonly what I do is:

  1. Write the simplest thing.
  2. Write tests for it.
  3. Refine my code, optimise it etc.

I used to employ this strategy a lot when I was writing lots of Guava. I adore functional programming, but a nested for loop is easier to read, understand and debug with print statements if necessary! Because at least initially, it’s nearly as likely that I have made a mistake in my test as in my code.

TL;DR

Static methods are great. Hide them away and they make testing easier, not harder.

 

Thanks go to Martin, for reviewing a draft of this post.

11 thoughts on “In Defence of Static

  1. Great post! You articulate something I’ve been doing more haphazardly than I should have. Thank you for writing about it.

    [WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.

  2. I strongly recommend the term “pure function” to describe a function that has no persistent state: it has no side-effects. It has inputs and outputs; the same inputs will always generate the same outputs. (And it’s best if the inputs are not modified.)

    [WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.

    1. Oh good call, I should have remembered that from functional programming.

      [WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.