Imagine I'm your CTO, as your CTO I expect you to follow this principles:
- The following advices are very opinionated. Before saying "bullshit", please read the why below in the Details section. One of the main ability of developer is to understand the why.
- The goal of this document is more to start a reflection than make you change your code.
|1||Simple code, easy to read|
|2||Explicit "Needs" first, do not over-engineer|
|3||Fail Fast: Crash as soon as possible with explicit message|
|4||Be careful with "silent return" or "if" without "else"|
|5||Immutable POJO / Struct objects|
|6||One source of truth|
|7||Composition over inheritance|
|8||Feature over layer|
|9||Write unit-testable code|
|11||Avoid god objects|
|13||Minimize cross feature dependencies|
|16||Copy & Paste over generic & factorisation|
|17||Do not use tools you do not masterize|
|18||Always produce your best "code"|
|19||Design affordance in code, no need comments|
|20||Design API and SDK like if shared publicly|
|21||Do not make variable name, class name or package name impact the output (when possible)|
|22||Consistency. Keep project coherent|
|23||3 comments max per MR|
|24||Always improve your "best practices": WHY / HOW|
1. Simple code, easy to read
The most important rule. A program must be "maintainable" so "readable". Software is meant to change in constant time, not in exponential time. "Readability" is the first step to achieve that goal.
2. Explicit "Needs" first, do not over-engineer
Be pragmatic. Even if it's great to anticipate the futur when possible, do not produce dead code. Make the code evolutive to be able to modify it later. Keep it simple, easy to maintain, easy to adapt. Explicit what's your main "needs" first to avoid to produce code you will not need. Keep in mind that the sentence "if one day we need that" is a common sentence and often you will never need that.
3. Fail Fast: Crash as soon as possible with explicit message
Avoid side effects & Avoid silent wrong behaviours. When you detect something wrong, crash! When a variable, an argument or a field contains something wrong, crash! At first your code will crash, sure. But you will spot issue very fast. Then your code will be more robust and easier to read because in the range of all possibilities, side effects have been excluded. With "fail fast", you will the fix the reasons of crashes, not consequences.
- Try to exclude edge cases with
throwas soon as possible in top of methods / constructors.
- Try to avoid
More details here.
4. Be careful with "silent return" or "if" without "else"
We try to avoid as much as possible return that does nothing in method. Why? Often that the cause of issue difficult to spot. That's why it's better to crash when something is wrong than simply return. Here Linus Torvals point in video.
POJO / Struct objects5. Immutable
Like in real life. Most of object are immutable. Most of "chair" properties will not change during its lifetime, same number of feet, same color... Immutability makes object comparison easier. Creating a new object to change a property is totally OK. Having method giving you mutable data from an immutable object or the
id of the object is also possible.
6. One source of truth
Multiple sources of truth lead to:
- one will not be maintained for sure
- more changes to do at maintenance
- less obvious for clients which one to use
So be careful to:
- API exposed to your Dependency Injection graph
- Extra fields you have in your classes (presenters, controllers, managers...).
- Maintain documentation as the code is the only source of truth
7. Composition over inheritance
"Be something" notion brings by inheritance is a strong notion. Factorization / "Avoid code duplication" should never be the only reason leading to inheritance. To avoid "code duplication", other possible solution
15. Copy & Paste **over** generic & factorisation
8. Feature over layer
A project splitted by feature instead of layer is more scallable. Feature packages/namespaces are "more standalone". In other words a feature can be easily deleted. Moreover that give to developer a better look of the feature scope and context.
9. Write unit-testable code
Unit testable code will force your code to be designed carefully, less dependent of your platform and less inter-dependant. For pure "logic" code like Algorithm to convert decimal number to Roman Numeral, TDD is a great way to go.
- Do not let managers/PO decide if you should skip tests.
- Never expose code coverage out of the developer team. This metric is for developers only because only developers are fully aware of the meaning of this percentage.
What code must be tested:
- Most critical part like billing, main feature...
- Storage to assure non-regression
- "Pure" logic code to help the code reviewier and futur developers
What code must not be tested:
- Never test implementation details (private method, code that change). Avoid tests that bring rigidity.
Unit-testable code doesn't mean all the code must be unit-tested. Indeed, the more your code is tested, the more that will be difficult to change your code. So please, do test only what's need to be tested.
More details here.
10. Split concern
Single responsibility principle. One "module", "class" or "method" should only have one role.
11. Avoid god object
Same reason as the Split concern rule.
In the same way, for arguments, try to ask only what a function/constructor need. For example, on Android, do not ask a
Context (that is a god object) when your only need is to convert a ColorRes to a ColorInt. Try to abstract the Color convert notion from the god object
By asking only what you need and not god object, your class will be easier to understand and easier to test (mocks will be easier to do).
12. Minimize dependencies
On a project, the less dependency on other SDK you have, the more robust your code will be. Why? Remember the cost of a library:
- Because with less external libraries, you control every part of the code
- Because with less dependencies, you are aware of the code in your project (remember log4j security issue CVE-2021-44228)
- Because some library may not be maintain in the futur
- Because there are a cost to learn how to use new tool
Having sayed that, of course you cannot re-code every library. But peek your library carefully =)
13. Minimize cross feature dependencies
Same reason as the Split concern rule. Will make your feature easier to change, easier to delete.
14. Minimize API
API: Contract your software as with the world. I'm taling of public API out of your software as far as you inner feature that you will share to the rest of your app.
Great design try to avoid redoncancy. Why?
- Easier to maintain
- Avoid client questions: which one should I use
15. Expose API with interfaces
- More readable contract
- Easier to change implementation
- Makes clients of this API easier to test
16. Copy & Paste over generic & factorisation
To be more robust to change. A lot of juniors are using the "duplicate code is evil" principle. This principle is dangerous. If the code serves separate feature that means that the code will change for different reasons.
So copy & paste is more that OK. If you really want to factorise, do not use inheritance without strong notion of "is one". Otherwise use "composition".
"Generic" could be done via "generics". "Generics" is one of the most difficult concept of programmation (with covariance...). Please do not use generics uless you do not have other alternative. Why? Difficult to maintain, difficult to onboard developers. Remember the
1. rule: simple, easy to read.
17. Do not use tools you do not masterize
PROs and CONs of third-party libraries, new languages or external softwares must be known before taking the descision of using them. Adding new tool has hidden costs to be aware:
- Teach people how to use the tool
- Be sure the tool will be maintained
- What will be the cost of changing the tool to another one
18. Always produce your best "code"
No excuse to not respect that. Wrong, you will not "gain time"! For a non developer, this point is obvious, for some developers, not so obvious.
Any git-repository must be better over time. Refactorization should be done continously. Architecture should be improved continously.
"Rush" periods are not an excuse.
19. Design affordance in code, no need comments
No need to comment great code. Here the reason. Affordance in code is having your code self-explained by design.
Of course, sometimes, impossible to explicit what the code is doing only via method names, fields and variables. In these case it's OK to comment your code, but keep in mind it's an admission of weakness.
You may say, "OK, but for public API exposed to external clients, provide a documentation is better ?". Maybe, but again, most developper will read your documentation only if the API is not design with affordance in mind.
20. Design API and SDK like if shared publicly
Like the famous Jeff Bezos email
21. Do not make variable name, class name or package name impact the output (when possible)
I will take kotlin jvm example to hightlight this rule.
In Kotlin, it's possible to have an enum, for example
Color, and then to do
name is a string that is
name is breaking one of the main concept of the programation: split the "behind" and the "front". It's like
breaking the fifth wall. That could be great sometimes, but thatt dangerous.
Because be able to refacto without any fear is what's make the codebase robust. You should be able to change variable and classe names without breaking the product. As a developer, it is fair to assume we can rename a classes whithout breaking the product.
Concept: inexpensive adaptivity
22. Consistency. Keep project coherent
- No one should be able to detect, the dev that writte the code
- No one should be able to detect, the age of the code written (without using git blame of course)
Why? Consistency accross the project allow better readability, rule number 1.
23. 3 comments max per MR
MR comments are not here to change every line of the MR. Please anticipate the work before with tech daily for example. The reviewer should not discover the code / archi.
24. Always improve your "best practices" WHY / HOW
In debate, the argument "because that was already done like that" is not an argument to use. In practice, of course, we need to make compromises and we cannot refactor every code base. Here my point is not to not follow project conventions. My point is to always understand the why behind to improve your future code. Think Kaizen. Avoid authority argument.
WHY / HOW Ask you "WHY" it's like that, "HOW" that could be done better
Principles are defining a goal, a vision. In day to day code, sure, as principles are not strict rules, it's OK to not follow strictly principles.
To go further, here are Android developer: Principles to complete this list.
Other articles and projects on Mercandj.