Keep your classes and methods as small as possible

I have a friend who recently started a new programming job and he kept complaining that his code was being rejected. Most of the time it was because his methods and classes were too long; it didn’t make sense to him why he needed more classes or methods to accomplish something simple. He felt that it would make his code more complex and harder to debug instead of just keeping it only one class and a couple of methods at most.

This is an advice that we programmers tend to give a lot, but most of the time we don’t really explain why is a good advice. Some people take it to far and I see why you would think a class with too many methods will be more complicated than one with very few. In the interest of keeping things simple, I won’t go into the details of SRP ( Single Responsibility Principle ) and encapsulation, but a basic definition would be that a class or method should be responsible for one thing and you can change it, without affecting the other parts of the system.

Let’s say that we need to send an email with some data when a user is older than 18. The data will be read from a config file. To keep it short we’ll assume that the email sender and config are in different classes.

Seems simple and easy to read, but if you take a closer look, its not very testable since we can only test on user age and not if the config is empty.

So we need to refactor the class by taking out the read config logic into its own method.

By doing this, we now can test both conditions by stubbing the getValuesFromConfig method and defining our own value. This allows us to check that the config is returned as an array as well.

But even though this improves things, the class is still not very maintainable.  What if the action now depends on the user being a certain age, gender, or country? We could create a lot of methods to validate each thing, but our class would do too much.  Don’t forget SRP, so lets create another class to handle this.

Now we can write separate tests as well and know that if we change our validations we will not affect the other class and viceversa.

Quick note, when writing these kind of validators, I like to use exceptions in the private methods instead of returning true or false.  That way I don’t have to write one big if and I can take advantage of the @expectException notation. This leads to cleaner tests.

Now the code is looking a lot better.

But wait! There’s more. We still have not completely solved our testable problem. We moved one dependency into its own method but we still have two left that will cause problems.  We can create new methods for them, but remember when I told you that some people take it too far with creating a lot of methods?

Dependency Injection (DI) to the rescue!

Instead of the class getting the object it needs, you provide the class with the other objects they’ll use by “injecting” it, just like the name says. One of the advantages of using DI is that it makes our tests a lot easier, instead of mocking our config object like we did above, we can create an empty config object and use that for out tests. For this, always prefer interfaces over direct classes, it will make your code cleaner and easier to maintain since no matter what object is passed along you’ll know that it will have the methods it needs.

Dependencies can be injected in the constructor or with setter methods, lets use the constructor

Now we can write our tests and be sure we won’t be using production data

So in the end we took 20 lines of code and turned them into 67 lines ( 2 classes, not counting interfaces ), but I hope that you see that by taking “simple” classes and breaking them into smaller ones, you end up with code that’s flexible to change, easier to debug, and cleaner.

2 thoughts on “Keep your classes and methods as small as possible”

  1. Surprisingly, not everyone is doing test-driven design. If TDD isn’t a requirement, abstracting the imperative functionality down to SRP or SOLID principal is more work. I’m not saying doing TDD is necessarily ever right or wrong, but if a team doesn’t utilize the value of TDD, this abstraction is costly and impacts ROI. Since your friends team obviously rejects non-TDD code, he should follow this approach, but his observation is on point: changes that increase your codebase by 300% and genericize and complicate the code are tough to swallow if you don’t see the value in doing it.

Leave a Reply

Your email address will not be published. Required fields are marked *