Imagine I'm your CTO, as your CTO I expect you to follow this principles:
|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 objects|
|6||One source of truth|
|7||Composition over inheritance|
|8||Feature over layer|
|9||Write unit-testable code|
|11||Avoid god objects|
|12||Minimize cross feature dependencies|
|15||Copy & Paste over generic & factorisation|
|16||Do not use tools you do not masterize|
|17||Always produce your best "code"|
|18||Design affordance in code|
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. Explicit what's your main "needs" first to avoid to produce code you will not need.
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.
- Try to exclude edge cases with
throwas soon as possible in top of methods / constructors.
- Try to avoid
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.
5. Immutable POJO objects
Like in real life. Most of a "chair" will not change during its lifetime. Immutability makes object comparison easier. Creating a new object to change a property is totally OK.
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 should never be the only reason leading to inheritance.
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 developer
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.
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 when youre only need is to convert a ColorRes to a ColorInt. Try to abstract the Color convert notion from the god object Context.
12. Minimize cross feature dependencies
Same reason as the Split concern rule. Will make your feature easier to change, easier to delete.
13. 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
14. Expose API with interfaces
- More readable contract
- Easier to change implementation
- Makes clients of this API easier to test
15. Copy & Paste over generic & factorisation
To be more robust to change. A lot of junior are using the "duplicate code is evil" principle: that's dangerous. If the code serves separate feature that means that the code can change for different reasons.
16. 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
17. 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.
18. Design affordance in code
No need to comment great code. Here the reason. Affordance in code is having your code self-explained by design.
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.