Before we talk about this problem, I have a bit of a theory about how it arises. Your typical developer reads a bunch of books in school about this cool thing called Object Oriented Programming, and how it has these awesome features called inheritance and polymorphism and stuff like that. Then they go on to higher education and they do the mandatory first year programming course. The lecturer starts at the beginning and slowly explains about methods and how they enable the scaling up of design. And the developer-to-be is thinking “Yeah, I know this already… can we just skip to the good stuff about classes and vtables and overrides? Methods are easy and boring.”
Nonsense, you say? Well how else to explain all those C# developers trying to compose symphonies with their object models when they haven’t mastered the use of methods, which is far more fundamental?
If you ask a C# developer what methods are for, they will probably tell you that they are building blocks, which is true. But if you ask them how methods achieve this building-block quality, they might mutter something vague about ‘grouping related lines of code’, which is also true but misses the point.
The great thing about methods is that you can describe their purpose in one simple and compelling statement. A good method is one that you can figure out how to use without seeing its implementation. Ideally, the only thing you should need to see is the signature – the name, the arguments (if any) and the return type. If the method is especially complicated then you might need to see a bit of documentation. Now ask yourself honestly: how many of your methods do you feel comfortable stepping over in the debugger? And how many of your methods have a name like ProcessData() and a 200 line implementation? If you can’t understand and trust your methods without having to look at every line, you will never be able to break out and manage code on a large scale.
Like anything, designing good methods takes practice. But the good news is that there are a relatively small number of very reliable principles that can guide us:
1. Use good names
Naming in general is such a big deal that I’ve given it its own article. TL;DR: for methods specifically, let me just say that method names are like newspaper headlines. Somehow you want to tell an entire story in a handful of words (the fewer the better). Suppose you saw the newspaper headline “Hurricane to hit tonight.” This is enough to give you the gist of the article. Sure, reading the article would give you extra details like the hurricane’s name, size, expected trajectory, etc. But the headline alone gives you enough information to act on.
2. “Method: You Have One Job (do it well)”
That’s right – it’s the Single Responsibility Principle. Just enforcing this principle alone will cure so many deficiencies it isn’t funny. Your method will be shorter because it won’t be trying to solve all the world’s problems. It will be easier to name because its essence will be easier to summarise in just a few words. It will be more likely to be reusable because it will be more generally applicable (just as a hammer is more generally applicable than a crane with a wrecking ball).
Yes, I added the bit about doing the job well because it was a great excuse to include a You Had One Job image, but it’s still a serious point. Once you start to make good methods with good signatures, you start to realise that a good signature (or interface, if you prefer) is a valuable thing in its own right. You can quickly knock out an implementation that satisfies the method’s signature and start using it. And you won’t worry (much) about whether you get the implementation perfect right away. Why? Because you will know that any problem with the implementation won’t spread beyond the method itself. Callers of the method are written to rely on the agreed behaviour. If the method doesn’t “do what it says on the tin” (as they say in Britain) then you just it change it so that it does.