Introducing Code Conventions in Liquid Development Teams
I joined Silverfin over six years ago as the third member of what was then called the Professional Services Team, which has since evolved into the Business Solutions (BSO) team. Our team operates as a product team and specialises in building and managing solutions (i.e. templates) on the Silverfin platform, using the Silverfin Templating Language (STL). STL incorporates Liquid, along with a bit of HTML and CSS. Liquid, an open-source template programming language created by Shopify, is designed to be highly accessible and user-friendly.
Liquid's primary advantage is that it doesn't require extensive programming expertise to grasp. At Silverfin, we've built a team of 22 individuals (at the time of writing) who are dedicated to developing and maintaining our Liquid codebase. Since most of our team members had no prior programming experience before joining Silverfin, and given that we work with a custom language, we needed to establish clear processes and standards.
This article delves into how we tackled the challenges and navigated the pitfalls of introducing coding standards and conventions to our Liquid development teams.
Introduction
As developers, we strive towards writing readable, maintainable and understandable code. These principles are often defined in the form of formal coding standards and code conventions. It is generally agreed that these serve as a crucial tool in software development. They enhance code comprehension and maintainability, facilitate collaboration, and ultimately reduce bugs. Adhering to code conventions not only fosters better understanding among team members, but also ensures code remains reusable and maintainable even as projects evolve beyond the original author's involvement.
According to the 2022 CPSQ report, software developers spend about 70% of their coding-related time in understanding the code (while only 5% of their time on writing code). Moreover, it is estimated that approximately 80% of lifetime software costs are allocated to maintenance. The same 2022 report estimates the cost of poor software quality at an accumulated amount of $2.41 trillion in the U.S. (1, 2, 3, 4, 5).
So in theory there should be a general consensus in the developer community on the benefits of having code standards in place, and there should be enough value to have business wide support to adopt code conventions. However, how does it work in practice? Based on my personal experience, agreeing, writing and applying conventions with the goal to produce high-quality code is quite often “easier said than done” (6).
In this article I will elaborate in detail on the pitfalls and challenges the Silverfin Business Solutions team has been facing, how the team handled them, and the best practices we’ve formulated along the way.
Agreeing on code conventions
Since the templating layer on our platform is mostly written in a Silverfin derivative of Liquid (with a bit of HTML and CSS), there are not many general rules or existing guidelines on the web we could use as a starting point. We did align some of the more general principles like naming conventions of variables with those of more common coding languages like Javascript, but because of the nature of our codebase and language we settled on coding standards that are industry-specific. This is often regarded as a good practice, and based on the challenges we have been facing I can definitely support that (7).
Actually achieving consensus on code conventions however, is far from a straightforward endeavour.
Teams that have been working together for quite some time have most likely crafted their own set of (unwritten) rules and practices. You could argue that some of these proven yet unwritten rules don’t need to be challenged. However, when working in a department where developers are often changing teams it should be possible for newcomers to immediately understand the code and be able to contribute to the codebase. That is often where we learn that what works for one team might not necessarily be the silver bullet for all.
Sure, there's a case to be made for respecting existing (unwritten) conventions. After all, they've likely evolved to address specific challenges and streamline workflows. However, when these practices start breeding performance bottlenecks or turning maintenance into a nightmare, it's time to hit the pause button.
In the past, I’ve repeatedly urged more senior members of different teams to agree on our coding standards and code conventions. Those meetings were usually quite intense. People are so used to working in the same way that it’s become very hard for them to agree on something else. Coming to an agreed set of rules is almost like politics: you give, you take, and you end up with a set of general rules that avoid controversial topics.
Early in my career this led to a lot of frustration. How could it be that as a group of professionals working on the same product, with the same tools and in the same coding language, we could not come to a detailed set of code guidelines and standards? In time I started to understand that the middle ground where we eventually settled (i.e. some general guidelines with room for more team specific rules) is perhaps not so bad. After all, should we really agree on everything? And how far should code conventions and standards in general even go?
Writing code conventions
Once you’ve agreed on a set of best practices, the next step is to formalise these principles into comprehensive documentation. Sounds straightforward, right? Well, just like any other project there needs to be sufficient business justification, and experience has taught that justifying these types of projects to the business is not as straightforward as it seems.
One could make a case that product-driven work needs to be prioritised over internal improvement projects, as the latter do not always result in immediate business value. In a way you could get stuck in a vicious circle where the development team spends more time on features and bugs because there is no standards on clean and maintainable code, so there is less time to write code guidelines, leading to lesser code quality, so the team keeps spending a lot of time on maintaining and expanding the code, and so on.
I guess it all comes down to finding a nice balance between product-centric tasks and code refinement.
Finally, code guidelines should be considered a living organism; they must evolve alongside the product and the development team itself. Because of the above reasons it can be hard to focus on maintenance on a structural basis, but in my opinion it should be equally important as drafting the initial code conventions. (8).
Applying code conventions
This might be the hardest part of all. When you finally come to a point where you have formalised the agreed guidelines, how do you systematically apply them?
Every codebase contains a lot of legacy code. One could argue that outdated code does not necessarily mean buggy code. Why would you want to spend time on code that is maybe not structured in the best way, and is hard to maintain, but still works as intended?
The truth is that maintaining a healthy codebase goes beyond mere functionality. It's about future-proofing your software and ensuring its long-term sustainability. However, in my personal experience, it’s not always that straightforward to convince the business stakeholders and even the development team of this.
As explained, our codebase is primarily written in a custom programming language, which lacks both general principles and supporting tools commonly available for more mainstream languages. As a result, enforcing code conventions is challenging without the necessary tools. Until recently, we relied on a basic Ace editor on the Silverfin platform. Although we started using IDEs last year, the support from these tools remains limited and not tailored to our specific needs (9).
Therefore we needed to approach it more as a change in mindset. Developers need to be aware that the code they write and leave behind will be read and maintained by other developers, so they should do the best job they can to make sure their peers can easily understand it. In this respect we installed a culture where it’s okay to (publicly) question why code is written in some way (without finger-pointing), and we encourage people to ask questions on code structure or architecture before the code is written (this often leads to highly amusing ‘to array or not to array’ discussions). Overall, there is common understanding that we as a team should take ownership of the general condition of our codebase.
Now, when the team decides to take said ownership, how would you practically approach it? I am aware there is a general consensus in the developer community on the boyscout approach to clean the codebase (10,11). However, for our specific use-case we found that this rule sounds fine in theory but could actually prove to be difficult to implement:
- Leaving the code cleaner than you found it when working on features or bugs with the tools at hand can take up a significant portion of additional time, and definitely when working as a product team, that additional time spend should still be justified towards the business;
- Due to the nuances of the Liquid codebase another layer of complexity is added (e.g. no split between functionality and view, direct impact on customer data, etc.). The slightest misstep in refactoring could potentially unleash a cascade of bugs, jeopardising the trust in the platform;
- Cleaning up the code goes beyond the codebase itself. It extends to cleaning up and refactoring the tests and documentation. That in its own turn, to add to point 1 and given the context in which we develop, consumes a significant chunk of precious development time.
Because of these reasons we found ourselves in general more in favour of dedicated refactoring projects:
- Business justification: Refactoring projects present a compelling case to business stakeholders. By framing them as targeted investments in long-term code maintainability and performance optimization, allowing for easier/faster introduction of new features in the future, you're more likely to garner support.
- Clear boundaries: With a defined scope, objectives and timeline refactoring projects set clear boundaries of what is expected from the developer. Compared to the boyscout rule there is less room for interpretation, and expectations are aligned.
- Focused efforts: Refactoring projects allocate dedicated time and resources to improve the codebase, ensuring a higher quality outcome.
Conclusion
To summarise the key takeaways and best practices of this writing ‘Introducing Code Conventions in Liquid Development Teams’:
- When agreeing to best practices, do not underestimate the challenge to get people (and opinions) aligned. Try to be flexible and open when agreeing on code guidelines, there might not be one best way of doing things.
- When formalising the guidelines in documentation, make sure there is sufficient time foreseen to write, but also maintain it.
- When sharing agreed coding standards and guidelines within a team, work towards a culture where everyone understands it’s in their best interest to have such standards applied.
- When applying coding standards and guidelines in the codebase, choose refactoring projects over enhancing your code as part of other tasks.