At Human Made, we have two distinct types of projects: short-lived projects, and long-running products. Both of these projects are core to what we do and share a lot of best practices. However, there are natural differences between the two that need to be considered when developing for either, and especially when switching between them.
Documentation Is Not Optional
Across all our projects, we scatter liberal documentation amongst the code, as well as writing extra documentation in documents. This is something that benefits both short-lived projects and long-running projects.
Long-running projects need to be maintainable for a long time, and may cycle through different developers on the same pieces of code over time. The nature of this means that documentation ends up saving both you and everyone else time, as future developers don’t need to follow up with the original author of the code.
Short-lived projects need documentation too for the same reason. While they may not be around for the same amount of time (especially one-off microsites), typically they will have a higher concentration of developers on the same code due to the time restraints. These time restraints make it even more important to avoid unnecessary back-and-forth over code, especially with colleagues working across timezones. For many of these projects, they’re often part of long-term client relationships, and may need revisiting in the future as well. Documenting now will save you time later, but these likely don’t need quite as much attention to the documentation.
We’re not yet perfect at this. We’re getting better.
Avoid Overengineering: Good Enough Is Good Enough
As engineers, we look at a lot of things and immediately see the problems with them. We also have a tendency to see problems that don’t exist to anyone else. Overengineering is a common time sink on all types of projects, and it’s important to always be mindful of this.
Short-running projects are under heavy time constraints. While we don’t believe in pumping out sites as fast as possible, we don’t want to take forever to deliver on projects. It’s up to us to make the call as to whether we need to spend a day rewriting a system, or do the simplest thing that works. Time constraints help here, as they can force you into doing minimal work, but should never mean that you skip spending time on those parts that are worth it.
Long-running projects often suffer from overengineering, as a result of spending so much time on a single project. Often, we’ll look at code and want to rewrite it from scratch because we know the hacks that got it running in the first place. There’s also an element of not-invented-here syndrome. Focusing on shipping is important to not lose sight of the goal, especially when you can simply bump the issue to the future and reconsider it then.
It’s up to engineers to decide how much time a problem is worth. Good design now might save you countless hours down the road, but the code might also be ripped out tomorrow as requirements change. Use your time judiciously.
Short-lived projects cover projects lasting anywhere from several months to a one week rush job. These projects typically have an end-goal with deliverables, and a schedule that must be met eventually.
Architecture/Time trade-off: Feel free to break rules to Get Shit Done
It’s often the case that timelines can be tight, if not strained. In these conditions, it’s likely you need something that works, and you need it yesterday. While we love creating the best solution, it’s not always feasible to build something the exact best way.
Don’t be afraid to pull out the string and glue to hold the project together. It’s possible that your solution won’t scale up to tens of thousands of users (or posts or what-have-you), but if you need to launch the site tomorrow just for internal usage with 3 users, your time is better spent fixing the bugs than scaling for users that don’t yet exist. Don’t over-engineer it.
With that said, it’s important to use best practices and engineering where you can. If you can do it the right way now, it may be worth the time. Be pragmatic, and always consider the architecture/time tradeoff.
Long-running products are much different to short-lived projects. Typically, these products don’t have a fixed deliverable or a schedule, apart from a self-imposed one.
Our products are designed modularly. This gives us the flexibility to add, remove and change parts of our products without needing to rewrite the core or other modules. It also gives us the ability to spin parts of our products off as open-source plugins or as internally reusable code (for example, WP Remote and Happytables share billing code).
Where possible, modules should interact with each other through the use of actions and filters. Actions and filters are actually an implementation of the Mediator pattern, and mediating between different parts of code is where hooks truly shine. It’s important to also note that this is mediation between two modules; that is, your module should have functionality independent of the hooks. Although building functionality directly into your callbacks may get it working, it’s prone to breaking.
As an example of how useful this is, take the Happytables HipChat integration. On Happytables, we had various events send messages to our HipChat room, such as when new accounts are created. By loosely hooking this across the rest of the system, we can disable the messages on development easily, and were able to migrate this to Slack easily by swapping out the module.
Note however that we do not enforce barriers between modules. Sometimes, it makes more sense to have modules depend on one another than to force everything to communicate via a mediator. Forcing a system often leads to making it overly generic, which can reduce both readability and performance. In a lot of cases, code will need to be rewritten when changing other modules for other reasons anyway (sending extra data, for example), so be pragmatic.
Corollary: modules should be designed to be reusable.
Long-running projects have the advantage that because they need to be maintainable, they’re typically high quality by default. (The architecture/time trade-off here needs to look at time across the entire project’s lifecycle, allowing extra engineering time to be amortized.) Because of this quality, as well as the modularity of the system, modules are often prime candidates to be released as open-source projects.
By ensuring modules are high-quality and reusable, these can benefit the community as a whole. Opening them to outside development means extra eyes on your code, which can aid in improving the code quality further and reducing bugs. Giving back to the community means not just contributing to existing projects, but ensuring the future of the community by releasing new ones.