Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)

Keep it simple

Some tips for reducing effort and improving results by avoiding needless complexity

Architecture

  • Architectural complexity comes with significant development and maintenance overhead, that overhead might not be worth it!
  • Start simple, evolve as needed based on actual problems that manifest themselves
    • "Big design up front" doesn't work, because at the point you are making that design you don't know the system and domain well enough yet
    • Changes in requirements can come at any time and may affect the desired architecture
    • Evolving a simple architecture is easier than evolving a complex one!
      • Changing boundaries inside a monolith is orders of magnitude easier than changing boundaries in a system of distributed services
    • Typical example regarding microservices: projects that split into microservices from the start typically face a lot more problems than projects that start with a well-structured monolith and only split off into services where it provides clear benefits
  • What worked for someone else will not necessarily work for you!
  • Premature complexity is also a form of technical debt!

Technology

  • By default, pick boring, proven technologies that you know over shiny new ones
    • Maintaining and learning new technologies comes at a cost. Is that cost worth it in your case?
    • Spending less time and energy trying out new technologies means you have more time and energy to solve real business problems
    • When solving a problem with boring, mature technologies, there are likely tried and tested solutions available
    • Community size and active maintenance are important!
    • Note: this doesn't mean you should stay stuck with ancient tech forever! As always, It depends
  • Use existing solutions for technological problems that you do not understand deeply or are not at the core of what you do

Code

  • Go for "dumb code"
    • It takes a good developer to write code that looks so simple that any idiot could have written it
    • Benefits:
      • You can immediately see what it does and how it does it
      • Makes review, troubleshooting and changes easy
    • Writing simple, obvious code is way more challenging than writing complex, "clever" code that takes a while to wrap your head around
      • "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
    • An idiot might make a better code reviewer than a genius, because if the code isn't "dumb" enough the idiot won't understand it
    • The biggest compliment you can get about your code is "is that all there is to it?"
    • You know you have a great developer if you give them a complex problem and they solve it using simple building blocks that fit together in obvious ways

Features and scope

  • Is a new feature really worth the development/maintenance effort and added complexity in codebase, operations, application user interface, ...?
  • Customers asking for a complex feature might be missing an alternative, simpler feature which provides the same or even more benefits
    • Ask yourself: "What is the user really trying to accomplish"
  • Features that seem important to you might not be important at all to your customers
  • Try to look for information and processes that you can keep out of scope of the system
    • The fact that something exists in the real world doesn't mean that our system needs to know about it
    • The system doesn't need to know what happens as part of a certain process (either in the real world or in another system) if it only cares about the result and has a reliable way of getting that result

Approach

  • "Do the simplest thing that could possibly work"
    • Start with something ridiculously simple and only add complexity when needed
    • Avoid non-essential complexity that doesn't add value
    • In case of doubt between different approaches, try the simplest one first (see also It depends)
    • Simplicity means it's also easy to adjust if needed
  • Don't solve problems you don't have
    • Only spend time and energy solving a problem if you are facing it right now or are absolutely sure you will be facing it in the near future
    • Solving a problem you end up never having is a waste of time and effort
    • It's very hard to predict exactly what your future problems will look like, so any premature solutions are likely to be solving the wrong problem
    • Solving a problem you don't currently have makes it very hard to validate your solution
    • See also YAGNI
  • Small, incremental steps
    • You don't have to solve everything at once, some things can be "good enough for now"
    • Split problems and look for decisions you can postpone or things you can improve later
    • Limit scope of discussions
      • Often makes sense to explicitly separate high-level and low-level discussions or discussions about different problems (even if somewhat related)
  • Ask yourself: "What problem are we actually solving here?"
    • When you find yourself solving a sub-problem three levels deep, it might make sense to consider an alternative high-level approach

Processes

  • Processes are there to streamline the way the team works, not to cause unnecessary delays and frustration
  • When something doesn't work for you, adjust it or try something different
  • When something works for you and solves your problems, there's no need to change it, even if it's not the current flavor of "how things should be done"

Effort

  • ‘Doing it right’ doesn’t make sense if it costs you more effort without real practical benefits
  • Pareto principle: you can likely get 80% of the benefits by only spending 20% of the effort
    • 20% of the functionality can provide 80% of the value to your end users
    • 20% of the testing can give you 80% of the confidence that your application works
    • It's not the end of the world if some very rare situations lead to a transaction failing because of deadlock, suboptimal error message, ...
    • It's not the end of the world if some exceptional cases require an additional action taken by some batch process or even a human
  • Know when to stop optimizing!
    • The more you optimize, the lower the ROI of additional optimizations and the higher the benefit would be of spending the effort on other things instead
    • Applies to architecture, code quality, solution approaches, ...
    • At some point, rather than to keep discussing and polishing, it makes sense to ask if you can all agree that it’s the current option is good enough
      • Also goes for code reviews
  • Not everything is a priority
    • Some things are not worth spending more time and energy on, even if you know they could be improved
    • If you're feeling overwhelmed, it's time to take a look at what is really a priority, possibly together with your team, manager, ...
    • Pick your battles: not every discussion is a hill you should be willing to die on.
  • Ask for help when it makes sense
    • If you're struggling with something, you're likely to find a colleague that can help you out
    • Asking for help is actually a good way to bond with your colleagues, as it shows you trust them and value their expertise
  • Learning
    • You don't have to know everything!
    • Keep learning, but accept that you will never come close to knowing everything, and neither will anyone else
      • Someone who knows a lot more than you about a certain topic is likely to know less than you about another topic
    • Learning high-level concepts already goes a long way
    • It might help to keep a list of "topics to research later"

Don't take it too far

  • Determining how simple something should be is a tradeoff
  • You should probably not try to "simplify away" the unavoidable complexities in your domain (those complexities might even be the reason your system exists)
  • The definition of "good enough" will likely be different based on how crucial the problem you are solving is to the system/domain
    • It is probably not a good idea to take a lot of shortcuts in the foundations of your codebase/architecture
    • A situation that occurs all the time in the domain probably deserves some extra effort
  • If you want to build a reliable system, you might have to spend more time on proper error handling than on development of the actual happy path!
  • Relaxing quality standards can help you move fast in the beginning, but too much technical debt can slow further development to a crawl and require an enormous amount of time and energy to fix later on
  • As always, It depends

Resources