In my last post on WordPress services and pricing I’ve mentioned that I prefer to work on different assignments and avoid the “cloning” pattern – such as building standard 5-page business websites all the time, 20 times a month. Working and experimenting with different APIs, trying to bend a platform in a non-standard way, providing features that others were unable to implement before are the challenges that bring joy in my work.
However, to be realistic in most projects, I need to rely on code snippets and patterns. I have a private GitHub repository with some of my previous projects, keeping a track of the main challenges I’ve had with each one of them, so that I could save some time figuring out what was my solution last time.
Architecture Doesn’t Fit Everywhere
My latest courses on Design Patterns had two main goals. The first one is to teach some developers a bunch of design patterns. The second goal is to prevent them anyway possible to start implementing design patterns in every single possible scenario.
It’s quite common for people who discover a framework, tool, pattern or anything to get so excited about the knowledge being the “eye-opener” that they try to apply that everywhere. While some practices such as using version control are a must, other approaches need to be evaluated against the given requirements. Being able to bend a system and make it work in a given way doesn’t necessarily mean that this is the best possible approach. For example, if you’ve heard about the Singleton pattern before, some architects consider it an anti-pattern for some projects.
Working on features or updates for existing products means that you have to comply with the current system. Following the code standards and patterns being used, etc. Even if you follow all of the best practices from the Codex and the Handbooks there are plenty of ways to build a plugin or a platform. I’ve explained some of the things I’ve seen in my Over-abstractionism post a while ago, and even if I prefer higher layers of abstraction, hierarchies and ways to implement dependency injection here and there, occasionally this might be an overkill.
I guess that most developers have met clients who asked for maintenance and small features, and while browsing the codebase they end up with: “The last developer was an idiot, their code does not comply with the guidelines, they are reusing the same snippet a few times instead of extracting it into a class/method…”. It’s common in the WordPress world due to the nature of the community – welcoming freelancers with no technical education or experience in the layers below WordPress.
Technically one of the preferred ways to build software is following the “Strong Cohesion, Loose Coupling” principle. In essence, it’s building single-purpose methods and avoid dependencies, which allows you to move smoothly and easily decouple pieces, change implementations, etc.
Another perk of the object-oriented programming is the inheritance – creating code units (classes) that share a common set of features. The common part lives in the parent class, and all child classes extend the parent and add the features that are specific to them.
It’s a good practice for DRY (don’t repeat yourself) and maintaining the snippets altogether.
At least on paper, when we have the initial requirements for a project and we know what to expect.
The maintenance factor isn’t lean all the time. Especially if you work on a large platform that’s supposed to be maintained for years. New features are added, refactoring is being done for better performance and scalability. Sometimes a significant percentage of the code is being extracted to another module, or the project is split in two. Check the WordPress Core Trac for some examples that could be quite large given the number of sites running WordPress.
Over the past two years I’ve had several examples where my decent architecture (in my opinion) got me locked with features and led to more refactoring than if I have abandoned the DRY principles and best practices. The inheritance was one of the keys for that. One of the projects (SaaS) got split in two different projects and while all components were expected to behave similarly in the first system, we had to move half of them to another project with different requirements. Same features, different logic and flow.
It turned out that all of the common logic in the parent class had to be revised. Dependencies were lost, and the entire project got a serious amount of restructuring to move the new classes somewhere else and drop some of the integrated APIs from them.
Another similar project had the same issue, a bunch of widgets with a common feature set were extending a parent widget, and it was all playing along nicely. With time, some widgets got so different that they weren’t using parent features anymore, and some checks for authentication and validation that were embedded for all of them had to be removed. This led to another serious restructuring that left most widgets with no parent class and completely different flow.
This might seem as a small change, but in practice it’s quite large. Relying on different APIs means that you need to inject their logic into those components, implement other checks here and there against the list of components that share that logic, and rely on those components in a way that each component reacts in a given way. So detaching some of them from their parent means that few other layers get affected too.
Languages such as C++ could handle this with their multiple inheritance methodology – extending several parent classes. Most languages however don’t have a multiple inheritance support – you can implement interfaces which define a plain skeleton, but you can’t add code snippets that are being reused, so it’s not a solution here.
The issues mentioned above could be solved with other patterns – adding hooks in the different components and implementing everything externally, or using aggregation and composition of components, or redoing the logic of the parent class to maintain the rest of the code. They have their weak spots as well and other use cases are affecting them too.
Is there a solution?
Not really. The majority of the projects work pretty well with the architecture defined at first, and the changes don’t affect the overall flow later. However, larger projects are often extended to a level where a major refactoring has to be done. It’s not a bad thing per se, and people should not be afraid of refactoring their code (with the proper amount of testing, of course), but relying on proven methodologies is not always the best fit for every project.
Using WordPress is fine with some projects (a lot, really), but the database structure, the admin panel, the flow could be useless (overhead actually) for others.
Building a website in C++ is possible, but it doesn’t mean that it’s the best solution out there.
The waterfall model works great for some projects, others use some agile methodologies.
Our best bet is to keep our code as clean as possible, as flexible as our environment allows us, as testable as our features could be decoupled. Major restructuring happens here and there and we need to be ready. Don’t rely blindly on the latest libraries, frameworks or patterns – evaluate them and assess them against your project and plans.