Code generation

One of the technologies that helps us in our project to follow the Inversion of Control (IoC) principle is Dagger framework (https://google.github.io/dagger/). It allows to implement Dependency Injection – one of the IoC forms. Most Dependency Injection frameworks relay on reflection that is used to scan a code for annotations, and this was the way how the early Android DI frameworks like Guice were implemented. Contrary to back-end solutions were the extra time and memory required for DI frameworks usually is not a problem, because dependencies are resolved and created at application’s start time and not during a request time, and user experience is not degraded, on mobile devices this extra time and memory might be crucial and can significantly impact user experience. Therefor Dagger which is recommended DI framework (https://developer.android.com/topic/performance/memory), takes a different approach – it resolves dependencies at compile time by generating extra classes. The code generation is something that most of our team members have skeptical attitude, since very rarely generated code is of the same quality as the one written by seasoned developer. And we decided to take a little bit closer look at the area of code generation and whether it should be our concern.

The whole history of programming languages is about raising the abstraction level and generating the lower level code. It started with assembly languages when developers didn’t have to write the program in binary code (zeros and ones) anymore but were able to use human readable keywords. And assembler was used to convert the assembly code in to the machine language. Since the conversion was quite straight forward developers didn’t worried much about the resulting machine code but mostly about the assembly code. The different story was with the next generation programming languages like Fortran, Cobol, Pascal, C known as high-level programming languages. The main advantage of high-level languages over low-level languages is that they are easier to read, write, and maintain. But the process of translation (done by compiler) of such language in to machine code is much more complex and therefore the generated machine code was not that optimal as in the case of assembly language. But after awhile the compilers improved and most of developers stopped to bother with the compiled code and focused solely on high-level programming language code.

The main conclusion we made from the history of the programming languages is that as long as the generated code is not the part of the code base that developers have to read, extend and maintain and it does not impose performance penalty, there is no risk of using generated code. And from the other hand the benefit it brings is significant – it saves time a lot of time for boilerplate code and allows to focus on the application’s main logic.

Abstraction layer can always help

In my last article about the good software architecture I wrote that in order to have a good architecture we should apply Object Oriented programming principles (some of them are known as SOLID principles) and design patterns. Let’s discuss today some of these principles, and let’s start with Inversion of control and see how it shapes the design and contributes to good architecture. According to Martin, Robert C principle states:

A. High level modules should not depend upon low level modules. Both should depend upon abstractions.

B. Abstractions should not depend upon details. Details should depend upon abstractions.

With other words a class dependencies should be expressed as interfaces and not implementations. This simple principle leads to significant benefit in design – loose coupling. And loose coupling in turn leads us to

  • extensible code. Do you remember the traits of poor design from my previous article? When you come to the point when you don’t know where to put your new code, because there is no place where it would fit well, that could be the sign that code is not extensible. Think about this principle next time, when you will be puzzled. Maybe Inversion of control is the solution how to make your architecture extendable.
  • testability – dependencies can be easily swaped with mocks thus unit tested
  • parallel development – the only thing you have to take care now is the interface definitions between modules and work on modules can be done in parallel.

As you can see even by applying this one OO principle, we can improve the design in several areas. But don’t rush to provide an interface for every object. So when is it better to not abstract an implementation behind an interface? Most of the time we are writing a code by extending existing framework (in our case it is Android) using the classes and the methods of the framework and calling the third party libraries. In order to abstract away these kind of classes we would need to take another extra step of wrapping them in our own classes and only then abstract our class behind the interface. In this case, the only benefit we would gain is testability, but in case of Android with the help of Mockito we already can easily mock framework classes. Another case are models: ViewModels, DTO, Database Models and static methods (once you made them static, you must be counted on the fact that they won’t change the signature). The one thing in common for all those cases is that you are not planning or you can’t swap the existing method implementation. And so there is no need for abstraction.

Once we make necessary dependencies abstract, we still need to provide an implementation for them but in the way that the module is not aware of the implementation. Remember module sees only abstractions! And here at hand comes another design principle Inversion of Control (IoC) sometimes known as Hollywood Principle. The general philosophy is that control is handed over. For example in a plugin framework, you’re expected to override some call back methods. Let’s take the Android’s Activity class and it’s lifecycle methods. The class doesn’t have the control over when the methods get called, Android framework decides when to call them. The control is inverted. Dependency Injection (DI) is another example of IoC – the class doesn’t create its dependencies but instead gets them from someone else. By using DI we can follow one more principle – Single responsibility principle (SRP):

1) object creation and its lifetime management is delegated to DI container and thus

these responsibilities are taken away from the module and we are one step closer to SRP

2) if too many arguments are in the constructor it could be signal that we are violating the SRP. In this case, we should consider grouping dependencies and hiding them behind a Facade.