On Style Maintenance

Share this:

I was talking to a brilliant engineer friend the other day who mentioned they never get to build anything from the ground up. Their entire career has consisted of maintaining other people's (often quite poor) code.

In a perfect world, we'd all get to write code from scratch, it would work perfectly, and we would put it into a bin in the sky, never to be looked at by anyone again.

We all know that's not how it works. Code need to be maintained.

Kyle Simpson often begins his talks explaining that we spend only 30% of our time writing code, and 70% of our time maintaining it. Being a good coworker and programmer is not just about being a skillful problem solver, but being legible. Great developers produce code with maintenance in mind.

I often joke that I don’t want to hire a code ninja. Ninjas come in the middle of the night and leave a bloody mess.

I want a code janitor. Someone who walks the hallways of code, cleaning up pieces, dusting up neglected parts, shinning up others, tossing unnecessary bits. I prefer this gentler, more accurate analogy. This is the person you want on your team. This is a person you want in your code reviews.

Shoutout to JD Cantrell, who is an awesome code janitor and code reviewer.

Let's think about code maintenance through the lens of two different programming paradigms

In programming there are many tools and procedures available to meet the same end. There is no right answer. Here is a succinct definition by Michael Feather of two very popular programming paradigms:

Let's consider both in terms of not just understandability but also maintenance.

Functional Programming

I have seen for myself the value of using a functional approach in front-end JavaScript. When we write code, sometimes we want to assume that it will be used forever the way we intended, but most of us have been doing this long enough now to acknowledge that that isn’t always the case.

Functional programming includes, but is not limited to:

It is declarative - we’re writing in a way that can be reused and not telling the computer exactly what you need it to do at every step. This shares some similarities with abstraction.

It is pure - we’re not modifying or changing things outside of the function’s scope, and for this reason...

It is immutable - you won’t get into a situation where you feed it the same value and retrieve different results on multiple executions.

I believe functional programming can be extremely useful in terms of maintenance because of the lack of side effects. This is huge. This keeps our code from becoming brittle. People sometimes think the biggest problem is errors in our code. I’d argue that the worst code isn’t code that errors out and fails. We can track down with methodical isolation. The worst code is code that behaves a way that you can’t predict, quietly, all over the place. You run around the code base playing whack-a-mole and sometimes it’s difficult to find the culprit. A functional programming style takes on this problem preemptively, because it’s built from the ground up to keep this from happening (as much).

Here are some resources if you’d like to dig deeper into functional programming:

As much as I'm in love with functional programming, please be aware that there can still be issues with maintenance. If a few things use the same pure function, and over time you adjust that function for one of its applications, you can get into a problem where you're also altering other things that are hidden dependencies. Good documentation is really helpful here.

Object-Oriented Programming

In contrast, Object-Oriented Programming is a little more like following the steps to a recipe. It uses objects, which may or may not contain data, and methods, which tends to be procedural. Typically objects have an idea of self (e.g. "this" in JavaScript). Object-Orientation doesn't focus on purity, but rather, tends to use encapsulation to make sure nothing leaks into outer scope.

At it's best, Object-Oriented approaches tend to think of the highest order of something, then pares down to each type of instance you can have, breaking out what to do in each case. If you're thinking about it like the Linnean System of Classification for animals and how we'd make Morphology Trees (who wouldn't? :)) You'd start by asking is it warm-blooded? Then, does it have fur? Then does it have a snout? etc. I'm really butchering biology here, but it's an example.

At it's worst, Object-Oriented programming gets a lot of pretty necessary criticism because sometimes you're not clearly describing what you think you're describing. You can think of this like: you think something’s a banana but really it’s a peach because the only thing that’s described to you is that you have a fruit. We talked a little bit before about how that kind of code can be a nightmare to maintain, so while that's not always the case, it can certainly come up.

An applicable example in CSS

With both functional and object-oriented approaches in our minds, let's consider how that applies to CSS and the concept of authoring versus maintenance. CSS is written declaratively and purely. You can't mutate one CSS block using another CSS block.

I think a lot of people would anticipate that a purely functional approach would be best for CSS. They might associate that with keeping CSS as pure as possible, which is where ideas like CSS Modules come from. The concept is that if you encapsulate the styles for the thing you're modifying them right where you need them. You're only ever dealing with that instance. No side effects. I don’t disagree. You avoid a few things this way.

You avoid collisions. This is where object-oriented programming gets a bad rap for sometimes.

You avoid naming. Naming is hard. You can avoid naming with this approach.

You avoid dealing with the cascade. I’ll address this in a minute.

For other kinds of (non-CSS) programming, we keep things pure and avoid global scope and we’re golden! So it must apply everywhere, right?

Here's where we need to get into the right tool for the job. If we’re considering maintenance over writing, here is where other approaches shine:

Companies big and small tend to have redesigns at least every 1-2 years. If your code base is large, with many people, and you need to change the line-height everywhere from 1.1rem to 1.2rem, are you having to go back into every module and change that value? A global or an object that’s extended becomes extraordinarily useful here.

The cascade can help, if you understand and organize it. This is the same as any sophisticated software design. You can look at what you’re building and make responsible decisions on your build and design. You decide what can be at a top-level and needs to be inherited by other, smaller, pieces. This avoids spaghetti code in CSS and keeps your code DRY.

CSS is about design. Good design, is by it's nature, successful when it's cohesive. A CSS codebase that aligns itself with the design infrastructure it's built from allows for cleaner code, with the added benefit of better collaboration. CSS can be self-checking for designers as well: "Wait we have another tertiary button? Why?" Leaks in cohesive UI/UX announce themselves well in this model.

Naming can help with documentation. This point is a little more prickly, and I'm not sure totally necessary, but worth mentioning. Whether you like BEM, or SMACSS/OOCSS, or Atomic, naming when done well can actually give you good information on where a class is used and why.

The paradigms share traits

As you might suspect, though I see value in other paradigms, my preference is a functional style. With that in mind, you might wonder why I would find value in an Object-Oriented approach to CSS.

Despite it's name, OOCSS shares traits with Functional Programming. To correctly work with OOCSS the way it was intended, you're mostly creating mixins that are expressed similarly to functions with parameters (often with defaults) across your system. They are pure, and can be applied to multiple uses.

It still uses Object-Oriented approaches as well. With a very intelligent architecture built with anticipation of how things might be affected by each other, and carefully considering what should be inherited. Considering that CSS is fundamentally a group of objects, this tends to make more sense in a CSS context than in other languages.

Last year I lead a team to completely refactor a giant Front-End component system. It used an OOCSS architecture, I and saw for myself the benefit of this type of model. Designers could ask me to modify something, and we could quickly see it update across the site. They could even change their minds without too much of a hassle. A last minute request from above did not stall our release. I'm definitely not saying it wasn't without it's pain points- but it was surprisingly smooth for it's scale. This has made me look at the way I write CSS for other applications in a whole new light.

Future-forward

People tend to pit these things against each other:

CSS

Styling via JavaScript

I would argue that you can be maintenance friendly or create a maintenance nightmare either way.

You can absolutely deal with things like line-heights, fonts, and behaviors responsibly in Aphrodite, or React-CXS, or CSS Modules. I quite like some of these approaches. They can handle inheritance, and be written to extend for a DRY-er and more maintainable approach.

But even though you can, I personally haven’t seen enough examples of projects that do. (I'd love to see some examples!)

Mostly I see people pull out a variable or two for the brand color and nothing else. A few variables does not a responsible system architecture make. I think we can do better here, if we think about our code not in terms of what is easier to write, but what's easier to change. Or better yet, in terms of what's easier for others to change.

Please also take care to create good documentation! With either programming paradigm, you have to know about all of your dependencies before you adjust it. Great documentation also means you are more purposeful in your decision-making as well. There are less "oh well"s when you have to explain what a thing is and where it's used to other people.

The issues I’m tackling in this article are issues of collaboration and scale. Not every company or site or even web app is tackling these things. This is an opinion article that comes from experience. Different things work for different projects.

My hope for this article is to encourage developers to think ahead. We’re all in this together, and the best we can do is learn from one another. If you completely disagree with me, that’s totally fine. My hope is that even in taking a stance, you might solidify a direction you head towards with maintenance as the core concept.

Share this:

Comments

Thanks for the interesting read. Maintenance and especially documentation are issues that apparently always show up. To share something from my workplace: Even on a team of 3 developers (and one trainee) proper decision-making and documentation can be crucial.

One of our team members is currently on vacation and when it comes to fixing things the lack of documentation really makes our efforts inefficient.

Thanks for the comment, I’ll try to clarify what I mean. Let’s say you have a few inline form elements. A label, an input, and a button. In order to maintain visual consistency, you might write a mixin or extend a class that allows you to apply padding, font-family, and line-height all at once. That way you’re not writing those (and worst of all) maintaining those styles separately.

It’s a small example, I know, but hopefully expresses why it can be great to DRY out styles no matter how you write them.

I actually think CSS encourages DRY simply because of how the cascade works. It just works better through composition (classic OOP principal).

You can have base styles that establish necessary look-and-feel as broad strokes, and then as your modules get more specific, you can add more specific styles that aid in both functionality, and appearance.

This way you’re not having to reestablish base styles across modules, and you can avoid overwriting other modules through specific layers of specificity, which is more or less how OOCSS works in the grand scheme of things.

OOP in practice is only as good as its design, and that usually comes down to how experienced that developer(s) is in understanding how things should be structured. I personally try to DRY out my styles as best I can, but to your point, certain edge cases necessitate some ugly solves in the interest of deadlines.

The tone of this article resonates with me. Although I have been employed as web designer and developer over the years, 70% or more of my role was comprised of the re-use (and eventually we-writing) of the code of others I was taking over for. I never fully had the chance to play out my role as an actual designer or developer in that sense. Rather, I was sort of a janitor – a maintainer. This of course has held my career aspirations back unfortunately as no one seems to ever be wanting to hire a “mid-level” designer or developer (aka “janitor”, maintainer”); unless someone is reading this who wants such a thing, I am looking!

I love your work, Sarah, I keep aiming high such that one day I can produce awesome things for actual pay.

How we write CSS is changing a lot these days (for good). With more component based design in JavaScript, we are working to make CSS adapt the same culture. But I have always found cascading as a powerful feature of CSS and not a side effect (Now days I am kinda debating… not sure :) ).
What are you thoughts on cascade feature with component based CSS design these days?

Is Functional Programming really becoming the cool kid again? Seems like a “Be careful who you call ugly in middle-school” meme might be appropriate.

My issue with most of the arguments for Functional Programming is that they assume your objects are dumb objects and that your interactions with them are always blind to the contents.
Take your analogy that I assume a fruit is a banana because I’m told it’s a fruit. That IS a problem, but the problem isn’t that I was given a fruit, the problem is that I made assumptions of what that meant.
The same applies to OOP and Functional Programming.

If I’m handed a (piece of) fruit, I examine it. Even though I do so very briefly, it prevents me from biting into tomato hoping it’s an apricot. The same goes with OOP. If you object can be in different states, and an interaction would be different depending on that state, then you make confirming that state part of the interaction.

Hehe, just noticed I switched examples between text and code.
Interacting with objects assuming they are in a particular state is the issue. Not having the object in the first place.
The fruit analogy would probably be a good example of when we should consider extending an object.

I think the OO paradigm is a failure. It sounds good in theory, but in practice it requires you to keep track of too many moving parts, and when spread across a team, those moving parts amplify exponentially. At this point I’m convinced the solution is to do what Dave Rupert suggested in an episode of CodePen: have a junk.css file every designer/developer dumps their styles into, and there should be an in-house CSS harbormaster that converts that junk to a manageable/documented system with a styleguide.

I also find it funny that there wasn’t a shortage of snarky Tweets about Atomic CSS, but Atomic CSS seems like the epitome of compositional CSS whereas OOCSS seems like the epitome of inheritance in a language built with globals.

For now, I think the best solution is to:

Create a global reset that includes generic typography.
Break things apart into smaller component files. Jake Archibald’s post on stylesheet <link>‘s, and HTTP2’s love for a lot of small files will make this progressively and quickly load.
Kill globals and specificity while maintaining composability with something like CSS Modules.
Hire a few people to be CSS harbormasters if your company can afford it. If they can’t, then your CSS probably isn’t impossible to manage or costing you very much.

The “choose the right tool for the job” thing is starting to feel like a cop-out. I’m guilty of it too, but we don’t evolve until we can definitively say something sucks. In the real world OOCSS isn’t ideal. It got us thinking critically about CSS, and now we have the ideas/tools to fix it. Let’s use them and let OOCSS go.

It’s totally cool that you disagree, and the point of the article wasn’t to get everyone writing OOCSS. The point was to clarify thinking about maintenance. Your plan seems like a good one- granted that you’re on HTTP2 already (there are other ways to compile even if you’re not). That said, I’m not totally sold that I heard purposeful thinking about long-term maintenance in your argument. There’s a bit of “let them eat cake” in your prescription to hire away the styles for your site, but simultaneously a strong opinion about how it should be written.

Regarding “choose the right tool for the job” being a cop out- I think there’s a danger in not understanding shades of grey in development. If you’ve heard it before, it might be because people tend to repeat things that ring true.

Well, a schism or scheme for newbies and old dogs to get away from feature creep and enforce maintanability.
I’ve been playing by the same rules, without setting up a big manifest, which I would try to force whenever I get the chance, since 2001. My scheme is called Semantic Web though.

One thing that became apparent early on is despite most giving lip-service to the user needs being most important, if you concentrate on developer ergonomics in CSS, and put tooling in place to enforce consistency (particularly important with 20+ developers on a project), the net result is code that is easier for developers to remove and change as needed. De-coupling is VERY important for code-maintenance. That’s why ECSS makes a lot of provision around it. I like CSS Modules a lot as it solves many of the same problems but it wasn’t around when I was developing ECSS.

Atomic is the ‘daddy’ of Single Responsibility Principle approaches. These typically result in less CSS but require the understanding of an abstraction, and generally speaking, more work in templates. This may or may not suit your needs (it didn’t suit mine but there is plenty of merit in the system).

For me, on a large code base, the principle choice is whether you go with abstraction or isolation. These are the two principle choices to make and beyond that there are lesser choices.

Abstraction (e.g. SRP approaches) provides a smaller resultant file size at the cost of (typically) more template work and devs needing to understand a foreign abstraction.

Isolation provides simple removal of entire modules and more straightforward developer ergonomics (usually you are just writing CSS as normal) but by its nature results in more CSS – although never enough for me to consider it a problem.

This comment thread is closed. If you have important information to share, please contact us.

Related

How do you stay up to date in this fast⁠-⁠moving industry?

A good start is to sign up for our weekly hand-written newsletter. We bring you the best articles and ideas from around the web, and what we think about them.

👋

CSS-Tricks* is created, written by, and maintained by Chris Coyier and a team of swell people. It is built on WordPress and powered up by Jetpack. It is made possible through sponsorships from products and services we like.