In Pursuit of Codebase Consistency
There’s one mistake I see more often than anything else, and it’s absolutely deadly: ignoring the rest of the codebase and just implementing your feature in the most sensible way. In other words, limiting your touch points with the existing codebase in order to keep your nice clean code uncontaminated by legacy junk. For engineers that have mainly worked on small codebases, this is very hard to resist. But you must resist it! In fact, you must sink as deeply into the legacy codebase as possible, in order to maintain consistency. —Software Engineering After the Vibe Shift
To Sean Goedecke, consistency is the “most important thing” for software engineers to think about when adding to a large codebase.
I’m very lucky to work with engineers at Middesk who are serious about code quality and in particular respecting what came before. And because we are a Rails monolith codebase, the convention over configuration doctrine also plays a big role in the way they approach building our products. Were it not for their influence, I might find myself falling for the trap of optimizing for speed at the expense of code quality and maintainability.
Following this dicta is not without its challenges. In particular, I’ve found that:
- Consistency requires careful judgment. As it turns out, consistency doesn’t mean mindlessly copying existing patterns. While implementing a feature at Middesk, I found what on the surface looked like a similar existing feature and figured I’d just follow its patterns, basically to a tee. The surface similarity was so tempting, and it seemed like the fastest path forward! But I didn’t dig deep enough into why those patterns existed in the first place, and as a result I ended up with code that mimicked patterns of the existing feature but didn’t fit the data model or CRUD patterns of the problem I was solving. During code review, it became clear I’d need to rework significant portions because I was not careful in exercising my judgment about which patterns were appropriate. In trying to be consistent, I actually ended up writing a worse feature than had I just disregarded the codebase altogether!
- Consistency comes into play at multiple levels. A change I make should aim to be consistent not only with the immediate codebase patterns but also with broader language and framework conventions. In a Rails codebase especially, this means honoring Ruby/Rails-prescribed conventions and broader community-adopted best practices in addition to the specific patterns your team has established. But judging by the fact that I still hear parts of the codebase described as “not idiomatic Ruby” or “not very Railsy,” it’s clear that codebase-level and framework-level consistency can pull in different directions.
- Consistency applies to naming conventions just as much as code conventions. Naming is famously one of the two hard problems in computer science, and consistency in naming is just as important and difficult as consistency in code patterns. At Middesk, I was introducing a new feature that required a constant definition, and corresponding constants used a suffix that wasn’t really accurate to the feature I was building. A more verbose choice of constant would have been more technically accurate, but would have resulted in different parts of the feature having different names, likely leading to even more confusion than the technically-inaccurate constant suffix. It seems that the tradeoff between consistency and accuracy is not always cut-and-dry.
Whether these tradeoffs get easier with time remains to be seen, but so far experience has made them more visible and, hopefully, more intentional.