By Akshay Hegde
If you’re a mobile developer, do a mental replay of the discussion your team usually has before building a new feature or an app. What is discussed the most?
What architecture to use? MVC? MVVM? VIPER?
What design patterns to follow for this feature?
What external libraries or frameworks to use?
In the iOS world: What dependency management solution do we choose?
Do we build UI on XIB? Code?
Endless questions — all of them important to build something worthwhile. After intense meetings and deliberations, you find an answer to all these questions. Finally, you’re ready to code. 👩💻👨💻
Before everyone goes heads down in work, it’s crucial to understand one thing:
Every design pattern and architecture can be ruined with badly designed classes and APIs.
In the rush of completing the app and the fact that we spent a ton of time picking all the right patterns, we tend to ignore a subtle but important aspect of writing beautiful code.
API design is not just designing restful APIs. Every class, framework, or module you code in the app has APIs.
Clear, consistent, and predictable
Design the APIs not just for yourself or your team members, but for other developers and users. Consider that someone else will call this method or use this class.
Keep the naming convention across classes, variables, parameters, and methods consistent. For instance, you name certain kinds of classes as coordinator and then at another position name another class with a similar responsibility as a manager. Better yet, avoid generic naming such as ‘manager’ or ‘common’.
It’s not enough to say a class follows a single responsibility principle, if the name of the class doesn’t clearly define it. When you have a large team, some other developers in the future may interpret this responsibility differently than you initially did.
Functions should always be predictable. By reading the signature, one should know what it does.
Don’t handle every obscure edge case inside your function. It’s better to throw the right kind of exception from the function, instead of handling it in some very specific way, which may return undesired output. Provide different APIs that can cater to some special cases of inputs instead of handling everything in one.
A word on unit testing
A unit test is when we “test a unit of code for specific input and assert on it to match a known output”.
When you have an API written to do one thing, i.e., handle a specific input and return a predefined output, your tests will be more robust, have better coverage and you can trust them more.
We’ve all heard someone say:
“Your code itself should be the documentation.”
While one should certainly aspire to write clear, easy to read, and understandable code, there needs to be legible documentation.
It’s vital to document APIs and classes. Use the documentation to write about the code complexity if there are exceptions that may be thrown, link to other related APIs. Even a simple usage code snippet will go a long way.
Access control and extension points
Open, public, internal, private: Know that these access controls exist so we can design cleaner APIs to implement encapsulation principles.
Think for a moment about how you want your class to be extended. Do you expect users of your APIs to subclass your class and extend it? Or just expose it such that one can only extend without subclassing it?
If you don’t expose internal details or working of your classes, you can improve or change them without affecting the external usages of your APIs.
Using the right access controls will improve the performance of your code.
Compilers can make better optimisations when we declare a class as final or a method as internal. This will help reduce runtime complexities to find out which method/class is being invoked.
The more hints you provide to the compilers on the usage of your code, the better optimised the generated machine code will be.
Make use of attributes and mark old class or methods as deprecated/obsolete.
When you’re rewriting an existing class with a completely new logic, instead of changing multiple methods inside the class with if/else statements, just create a new class and name it as ‘MyOldClassNameV2’. Once ready, you can experiment using the new class and put the entire implementation behind a single feature flag.
If the scope of this refactoring is only limited to a single function of a class, introduce a new function instead of an if/else statement or a new parameter inside the function. Once you’re ready, you can experiment and put the call for the new function behind the feature flag.
Remember that experimentations must always be done at the call site or where this class of functions is being used. Once you are happy with your function/change, deprecate the old method/class.
Learning by reading and being curious is an endless process, especially when we have access to an abundance of high-quality professional code.
When you use this networking or database library in your code, pay attention to the method signatures, documentation of their classes, and conventions that are being followed.
They teach you a lot and help you build a natural ability to write clean code.
In your team, do code reviews. It’s really useful and important even if it’s just a small team of three people. Read each other’s code and keep the API design guidelines in mind as you do.
Here are some of these guidelines mentioned above and many more explained with better examples. So I encourage you to just keep these at the back of your mind when you write your code next.