Fighting complexity

Having worked on a significant codebase for a couple months for the first time has been an interesting experience. I've realized that it's easy to complain about a particular section of a codebase and say, "this part is so confusing! who would ever write it this way?" only to later make a quick patch or hacky workaround that causes you to scratch your head just days later.

I have listed below several common trends or themes that I've noticed that drives complexity and also some thoughts on how to manage complexity and simplify things when possible.

Generalizing for future use. It's natural as a programmer to want to make things reusable, but I think the greatest problem with this is that while we may instinctively know that we will want to reuse a certain module or component in the future - we don't know exactly what that use case is. What ends up happening is that we build a framework without actually validating its utility with real world usage. The XP methodology can be best summarized as advocating for emergent design and following the mantra of YAGNI (you ain't gonna need it). Rather than doing up-front design or creating things that are reusable from the get-go, XP prefers doing something once, and then later when you end up needing to do something similar to refactor your code so you can actually reuse it. The key is that you make a component reusable at the last possible minute. I think what makes creating a framework upfront so appealing is that it seems more straightforward than having to create it once and then refactor it to adapt to another use case.

Creating a complex dependency graph. This can actually stem from the previous trend. In the desire to generalize for future use case, it may seem appealing to create a dependency (e.g. an NPM module) that does a specific functionality. And then to further make a part of that dependency reusable for other dependencies you split that out into a micro-dependency. Soon, in order to understand one functionality of a website, it requires hopping through multiple files in your project repo and then going through several layers of dependencies to understand how something truly works. I do want to balance this by saying that of course using libraries can provide significant benefits, and it's unfeasible to do "everything by hand" without external dependencies. But I think the key is that when you are using dependencies, you try to pursue these principles:

  • Use the least number of dependencies possible. All things being equal, if there are dependencies that aren't pulling their weight or aren't being currently used, I think it's better to pull them out. First, it reduces bloat (important for client-side dependencies that have to be sent down the wire) and, second, it reduces the time to do builds, and third, most importantly, it makes it simpler to look for what dependencies are doing what. When you have a long dependency list with many small (little heard-of) dependencies, it can be quite tricky to figure out which dependency is responsible for doing what. Of course at least in the npm world, each dependencies probably has another set of 8 dependencies, and each of them have more. It's easy to imagine one top-level dependencies that ends up requiring 20 total dependencies when all is said and done. But in the end, I would more on the number of top-level dependencies rather than the overall number of dependencies.
  • Use dependencies with good documentation and stable APIs. The above statement should be qualified with a quality aspect. Dependencies that act like good citizens such as thorough and easy-to-understand documentation and stable API that follows semantic versioning will cause much less issues. When you're picking a dependency, as I mentioned in an earlier post, it's worth making good docs and test coverage a key evaluation criteria. It's harder to evaluate how stable an API is, unless you know someone that's a long-time user of that library who can either vouch or lament the stability of the API.
  • Dependencies should follow the Unix philosophy - do one thing well. I think this is an easy to thing and a really hard thing to follow. In a way, this basically rules out any strongly opinionated framework (e.g. any large MVC framework). I think using a framework provides a lot of benefits in terms of scaffolding a project and getting it up to speed quickly, but in the long run frameworks require a lot of energy to use effectively (e.g. upgrading a site from one version to another).

Getting lazy and doing hacky workarounds (aka the "I'll refactor tomorrow [insert date]"). I've said this to myself so many times and it's bitten me many times. In the end, making quick fixes may be necessity from time to time, but most of the time I'm making a quick fix in a dirty way because I'm being lazy or I can't think of a better way at the moment. I'd like to become more disciplined and focus on refactoring sooner than later and when I really get stuck on how to refactor something to either make a to-do item for myself or ask for help from someone else.

 

views