Saturday, November 4, 2017

On Default Choices

Nowadays, I tend to say less of "I always do X" and more of "X is my default choice".

Default choice is a term (and a conceptual tool) I use a lot in design discussions and coding as of late. I find it very unfortunate that, although I see default choices implicitly mentioned in almost every design book, blog post, and discussion, a concept of default choice has never received much attention. Thus, I decided to write something about it myself.

My homegrown definitions


Over the years of thinking and talking about default choices, I have developed three homegrown definitions of the term, each of them approaching the idea from a different angle. To me, a default choice is:
  1. Something I currently consider the best trade-off in most cases.
  2. Something I need a reason not to apply. 
  3. The first solution I consider, when presented with a problem.
Definition 1 suggests several important things:
  1. The "what I consider" part suggests that making a choice default is subjective. My default choices are drawn from experience I acquired while solving problems myself or what I learned from other people. Of course, when I work with somebody in a team, we need to collectively agree on some team-wide default choices, and this agreement becomes a part of our culture as a team.
  2. The word "currently" suggests that default choices are not set in stone but rather refined over time. As mentioned before, their primary source is experience, so as this experience develops, so do the default choices.
  3. The word "trade-off" suggests that a default choice is not the One True Way - like all other choices, it has its share of real and potential issues. 
  4. The "in most cases" part suggests that "context is king". The reason I have default choices is that many times, a context I encounter is similar to ones I already encountered in the past.
Desinition 2 introduces an interesting twist. When something becomes my default choice for dealing with certain problem, I need justification to NOT apply it. The point for me is that having a default choice saves me some thinking and analysis where instead I can draw from my past experiences. For example, I don't need a reason to apply the Single Responsibility Principle. I don't need to justify it everytime I use it. I do need a reason to withhold myself from doing so.

Definition 3 suggests that even when there are several equally good solution, I start with evaluating my default one. For example, when trying to avoid using null, my default choice is to use a Null Object pattern, but I often end up using an Optional/Maybe instead when the situation at hand is a better fit. Still, I consider Null Object first.

Examples of default choices


In my approach to coding and design, I apply lots of default choices. Some of them are:
  1. Don't pass or return null.
  2. Design objects in tell don't ask manner.
  3. Do not have more than one constructor in any class.
  4. Separate objects usage from creation.
  5. Develop in outside-in style.
  6. Use test-driven development.
  7. Design classes to maximize cohesion and minimize coupling.
  8. Prefer object composition over class inheritance.
  9. Use hexagonal architecture for my components.
  10. ...
Does it mean I always do all of that? No, of course not. For example, I pass null when a third-party library demands it (although even in such case I have some default choices on how to do it). I use class inheritance when implementing exception classes (likewise, my exception classes often have more than one constructor). I don't use hexagonal architecture when writing build scripts. 

This is the whole notion of defaultness. Having default choices allows saving time thinking about the same things over and over again, but at the same time, it's not an excuse for turning off thinking.

Gradation of defaultness


If you look at some of my open source code on github, you will notice that not every class is pretty and not everything even has a test. This is because I vary what choices I consider default depending on the context I work in.

Some of the things that influence how much of my default choices I apply:

  1. Do I work alone or in a team?
    • this influences how much mess I can tolerate in the code. When I work alone, I can leave something in a messy state and I know no one else will have issues understanding the code or that the code will not change in my absence.
  2. How big is the team?
    • the bigger the team, the more disciplined I tend to be as unattended mess can grow much faster when more people work on the code.
  3. What is the level of expertise in my team?
    • with more expert team, I can leave a little underdesign in the code and trust that when other team members encounter it, they will know how to clean it up.
  4. What language do I write in?
    • Assuming the same size of app, I tend to be more disciplined when writing in dynamic languages as they don't have static cheking. I extract methods, classes and DSLs faster to maximize code reuse and avoid silly mistakes.
  5. What is the required level of quality?
    • If this is my small home pet project that only I use, I may not even bother with automated tests or write less of them. After all, when I encounter an error, I will know how to fix it (still, almost all of my home pet projects have automated tests). Maybe I will shock you by saying that some of my code doesn't even have to work correctly (this applies to some of my training examples). On the other hand, when writing code that human life depends on, I'd like to have a huge test coverage on several levels.
  6. Do I have a schedule?
    • this may occur as a paradox to some people, but I tend to apply more design principles, techniques and patterns when I have a schedule. This is because in such case, no one will probably agree to stop for several weeks and fix a piece of design. On the other hand, when working on home pet projects where no one is waiting for a specific new feature, I can spend two months refactoring and have lots of fun doing so!
  7. ...

Default choices as a discussion tool

Personally, I am not a fan of "we must do X because it's the right thing to do" as much as I once was. This is because my technical discussions based on "rightness" have never really brought any satisfying conclusions and led to conflicts and each side of the discussion losing respect for the other.

Now, if we agree that many of our disagreements arise from having different default choices, we can then achieve at least three things:
  1. Remain in disagreement without losing trust in each other as professional software developers.
  2. Explore the differences in our default choices and uncover how we can reconcile them or make better choices. In other words, we can learn from each other.
  3. When we (e.g. as a team) agree on default choices and what a default choice means, we know better what we can expect from each other, which enforces trust and can speed up code reviews.