Skip to content

Instantly share code, notes, and snippets.

@Mercandj
Last active August 22, 2023 14:47
Show Gist options
  • Save Mercandj/de3cc178556dfd3a341c2646b109d221 to your computer and use it in GitHub Desktop.
Save Mercandj/de3cc178556dfd3a341c2646b109d221 to your computer and use it in GitHub Desktop.
I'm your CTO! As a developer, here the rules I expect you to follow:

Developer: Principles

Description

Like Robert C. Martin (clean architecture) would say on this video,

Imagine I'm your CTO, as your CTO I expect you to follow this principles:

Untitled-collage-10-min-9 (1)

  • 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.

Principles

# 24 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 / Struct objects
6 One source of truth
7 Composition over inheritance
8 Feature over layer
9 Write unit-testable code
10 Split concern
11 Avoid god objects
12 Minimize dependencies
13 Minimize cross feature dependencies
14 Minimize API
15 Expose interfaces
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

Details

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 return or throw as soon as possible in top of methods / constructors.
  • Try to avoid if statement without else

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.

5. Immutable POJO / Struct objects

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

  • Composition
  • Rule 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 Context.

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
  • Readable

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 Color.WHITE.name. name is a string that is "WHITE".

Using this 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.

Why?

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

Conclusion

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment