Do you think about how you should write your code to make it perform well? Do you think about good performance as in making compiler hacks in your code and optimized algorithms? In his awesome talk named ”Designing for Performance” at JFokus 2016, Martin Thompson argues that if you follow clean code principles, chances are you already are designing for performance. While getting the fastest code possible still might require careful min-maxing, bit shuffling and hacks, you get a long way towards decent performance just by having clean code, and a good model of your problem.
A lot of it comes down to understanding the big picture of the problem you want to solve. A very common trap is making local optimizations when the best solution might have been to make another design. You have probably heard the term ”Premature optimization is the root of all evil” . This is actually very accurate in describing the most common pitfalls that cause developers to make bad performing and smelly code.
Abstractions: Abstraction is a form of optimization. You identify and extract common denominators of your code, and put them somewhere else. This sounds all well and good, but the problem starts when you make false assumptions your model that causes you to make abstractions that do not fit. There is a cost for making abstractions, both in code cleanliness (another layer makes it harder to follow the code) and in performance. The abstractions must pay for themselves. The greatest risk of making false assumptions that cause these ill-fitting abstractions is when you begin to code and don’t see the big picture yet. Then you are stuck with a bad design that gets harder and harder to get rid of as time passes.
Frameworks: Frameworks are abstractions, so the same rules apply. You choose a framework before you start to work, and a few months down the line you realize that you have had to make hacks and workarounds to make your domain fit in the framework, and suddenly the framework is a bulky liability instead of an asset. Remember that every framework, every API that you use will put a limit on your choices. This has to be worth it.
Don’t Repeat Yourself: While DRY is of course a good thing, it is not something that should be achieved at any cost. Duplicating simliar code at two places is probably not worth making an abstraction for. One of the costs of abstractions is coupling, and we can agree on that coupling is something we want to avoid as well.
So the pro tip is: Do not make abstractions until you are sure that you need them – which perferably is when you see that you need them. Do you really need that DTO?
Cohesion in your models
Most programmers don’t have to think very much about how the hardware underneath the code works. But knowing a few general things can be good. For example, accessing your memory in a linear fashion on a common server takes less than 1 nanosecond. If you go about the heap randomly instead, it takes 100 nanoseconds per access. For this reason, it is a good idea to model your code in a cohesive way because it is likely to be laid out in the in your memory in a similar way.
For example, consider Class A and Class B. Class B has properties that causes it to constantly ask Class A for values. This is not cohesive, and it becomes harder both for the developer and the machine to put the data together. Give it some thought. A class that models your domain well will contain data that is likely to be accessed at the same time. This will make the class very understandable to the person reading the code, and the bonus feature is that your hardware will benefit for the same reason! This is even more important when using micro services. If you create micro services that aren’t cohesive, you’re going to have calls back and forth across them to collect the data you need which will have a huge impact on performance.
All of this boils down to one thing: Know your domain, know the problem that you’re about to solve before you start fitting structures and abstractions on it. If you know that an algorithm will never run over lists greater than 10, it doesn’t matter if it’s exponential. If a list is never null, the beginning null check that will run every time is an extra completely useless branch. Document things, design tests, talk it over with your colleagues to get the big picture clear to everyone. This will massively increase your chances of designing for performance.
If you find this interesting, check out Martin Thompsons full talk from JFokus 2016. In the talk, Martin goes further into details and also talks about performance testing. It’s available at http://www.jfokus.se/jfokus/video.jsp.