Audience: Whoo! [Laughter and applause]
Harry Roberts: Thank you. Yeah, actually, so about the Beyond Tellerrand Dusseldorf. I’d done two public speaking events by the time Marc invited me in 2013, and I was really nervous during them, didn’t particularly enjoy them. And I was saying to Marc, like, “Look, dude. Public speaking, it’s not for me. I’m too nervous. Don’t really enjoy it.”
For about two months I said no, and he obviously couldn’t find a replacement because he kept pestering. I ended up saying yes, and Beyond Tellerrand Dusseldorf 2013 completely redefined things for me. It left me feeling very motivated, invigorated. It completely changed my perception of public speaking, and btconf became a truly important thing for me. Now, 60 speaking events later, I am back at btconf, this time in Berlin.
Can I just get – can we get a huge round of applause for Marc and the team? Beyond Tellerrand is –
Audience: [Cheers and applause]
Harry Roberts: Beyond Tellerrand is, in my opinion, the second or third best conference there is.
Harry Roberts: No. Yeah, I love btconf. It’s really good to be here. Right. Yeah, so my talk is about refactoring CSS. It’s going to be quite a philosophical start to the talk discussing perhaps when we should refactor, when we shouldn’t refactor, providing the business case for refactoring. Then we’ll step into some more practical tips, some more immediately applicable techniques just to try and make refactoring that little bit easier.
It is going to be looking specifically at CSS, but a lot of the techniques discussed could be applied to other programming languages, front-end languages, software, that kind of stuff. Hopefully it will – by the end of it, hopefully we will have a little clearer idea on how to approach a refactoring project, how to measure its success, and what we should refactor and when.
Yeah, I'm Harry. I’m a consultant, front-end architect from the U.K. What this means is I spend a bunch of time traveling to different clients and kind of helping them troubleshoot problems they are having with their kind of user interfaces and the code they write.
I tend to deal with CSS architecture and performance, and I tend to deal with this at scale, so I tend to do it with larger clients, larger companies who have got bigger code bases, more developers working on those code bases, longer running projects, so code bases that may have been around for a few years. As you can imagine, this kind of work means that I have to do quite a lot of refactoring. Unfortunately, I cannot stand refactoring CSS. It’s a horrible, horrible job.
Has anybody refactored some CSS before? Yeah, a show of hands. Okay. Keep your hands up if you enjoyed that process.
Harry Roberts: Yeah. Right. Exactly, right. Refactoring is awful, especially in CSS. I think refactoring in CSS is even harder because we can’t get proper test coverage. We can’t really write proper tests for CSS. It all operates completely globally, so you might refactor the nav and all of a sudden the contact form and the footer falls over. It’s easy to introduce regressions. It’s not a particularly easy language to refactor at all, so we’re going to look at kind of taming that beast a little bit.
Before we get into the actual tips and the meat of the talk, we’ll start with just laying out a definition. What do we talk about when we talk about refactoring? There’s a really nice definition by a guy called Martin Fowler, and he describes refactoring as the process of changing a software system in such a way that it does not alter the external behavior of the code, yet does improve its internal structure.
I think this is a really nice definition because it’s relatively succinct whilst capturing kind of every aspect. What it tells me, what I’m getting from this is that refactoring isn’t really for our users because it doesn’t alter the external behavior. There isn’t any benefit for customers or users in having a refactored code base.
It’s a decision that would be made by teams, development teams, management teams. It’s a decision and an exercise for the product, right? However, I think, after a while the user does indirectly begin to reap the benefits of a nicely refactored code base.
A nicely refactored code base is going to be much easier to maintain. It means we could probably spend a lot more time adding new features. We’re not going to spend a lot of time fighting regressions and bugs. We’ll be adding new features much more quickly. A code base that has fewer bugs, fewer problems, is probably going to be a lot more reliable for the user.
I think eventually refactoring the business case falls outside of just how can we save money, but it also does ultimately service our users. I think there’s a strong business case to be made in that regard.
To my mind, and certainly for the purposes of this talk, we’re going to look at three different kinds of refactoring. The first kind is the as you go refactoring. This is just part of everyday life as a developer. This is refactoring that we should be doing just all the time.
For example, we build a new feature. We hack it together in CodePen or something. We hard code a lot of things. We move it back into the project. But then when we do that, we also tidy up naming conventions. We will start using variables from the wider project.
This is the kind of refactoring that we do day in and day out, which is a matter of course. However, if we’re not very diligent with that, if we end up letting things slip, we step through into technical debt. We’ll talk about technical debt in a little more detail shortly, but technical debt is basically when we took a shortcut. We took a shortcut right now in order to get some immediate value, but we want to go back and tidy things up. We’d either learn a better way of doing something or we’ve learned that we should have done it a different way in the first place. And we go back and tidy things up.
If we don’t keep on top of our technical debt, we step into the third kind of refactoring, by far the most expensive kind of refactoring, which is the rewrite or the overhaul. The rewrite is when basically we didn’t look after things enough. We sort of let things sprawl and spiral for enough time, perhaps it’s a number of years, and we just have to tear things down and start again. This is really, really expensive. This is the stuff we want to avoid.
Usually what you find is that the sort of time, the sort of size and scale of these kinds of refactoring, they alter. They change as we progress through a project. Over time, we encounter different styles of refactoring.
As you go, refactoring, like I said, happens all the time, so this will be encountered right from the very first day. This might cost us kind of minutes to hours’ worth of work, spending 20 minutes just tidying up the CSS for the nav. If we don’t keep on top of this, we need to be looking at sort of days to weeks, right? Technical debt could cost us somewhere in the realm of a few days tidying something up to a few weeks sort of rebuilding a feature. This is quite expensive.
Then the absolute most drastic sort of nuclear kind of refactoring is this rewrite. Rewrites are incredibly expensive. They take weeks to months. It could take 12 months to completely rewrite your application. We’ve got to fund that 12-month period. The money for that has to come from somewhere.
But there’s another hidden cost. There’s an opportunity cost. The 12 months you’re spending rewriting your application to get kind of back to feature parity is 12 months time you could have spent adding new features. Rewrites and overhauls have a double cost. They’ve got the cost of actually doing the work, and there’s the opportunity cost of having to spend your time away from feature development.
This talk is going to look mainly at the latter two and the overlap between them. There is an overlap between these two kinds of work where we can start to try and hedge things off of there, right? This overlap is where we need to kind of nip things in the bud and stop it getting to the next level.
I just want really briefly touch upon technical debt. Could you just put your hand up if you’ve heard of technical debt? Yeah, most of us. That’s good. Whenever I do conferences in Germany, the Netherlands, or places like that, everybody already knows everything, so we have to notch things up a bit.
Yeah, technical debt, we’ve probably all heard of technical debt. We’ve probably all got some technical debt or had some or are paying off somebody else’s technical debt. But I think it’s a very, very misunderstood term. A lot of people think that either all bad code is technical debt or that all technical debt is bad code. It’s not actually the case.
Technical debt can be a good thing. Technical debt is a strategic decision. Technical debt is when we decided to take a shortcut right now to get immediate value in exchange for a long-term payoff, this idea that we’ll have to pay for that immediate benefit a little more over time.
A really nice definition from a very good article called Technical Debt 101: A debt means you acquired something right now for a long-term financial burden. This burden is not just about repaying what you’ve got. There’s also interest involved. It means that even if you pay your debt timely, you’ll pay more than you took. And if you don’t, your debt will keep increasing. If you ignore your debt long enough, it’ll become unpayable, and you’ll become bankrupt.
Technical debt isn’t about good code or bad code. It’s about getting an immediate return in exchange for long-term financial burden. When clients discuss with me the fact that they’ve got technical debt, sometimes I’m thinking, if you don’t have technical debt you just did a bad job. Or they might say, this code is really bad. It’s like, well, it’s not bad. It’s actually fulfilling something pretty useful for you. It just could be better. It’s very common to conflate bad code and technical debt with each other.
Technical debt we have to understand. We have to admit and accept that we’re going to incur some of it. It’s going to happen. Every project will incur some technical debt. It’s just important that we keep up the repayments.
There are good debts and there are bad debts. A business loan is a good debt, right? You take a business loan so that you can make even more money and you can pay the loan back and make cash. A mortgage is a good debt, right? It’s a sensible bet. Borrowing $1,000 from a loan shark so you can go to Vegas for the weekend is not a good debt, right?
Your technical debt follows the same kind of pattern. Technical debt isn’t necessarily a bad thing. We just have to make sure it’s a considered decision, so we need to schedule in this kind of fixing and this tech-debt cleanup for every sprint we do. That involves making the business case for refactoring.
Has any of you ever struggled to convince a manager that they need to refactor something? Anybody struggled to get the time to tidy up? Yeah, of course. It’s really difficult because, on the outside, it doesn’t seem to be adding business value. But when we discuss it as a debt and the fact that we could go bankrupt, and bankruptcy in this case is a rebuild, it becomes really important to try and make the business case for doing so.
A lot of my work ends up discussing what we should refactor and when. A lot of clients say to me, we need to refactor some things. We know we need to tidy things up. Our CSS is a mess. Where do we start?
My usual cheating answer is, well, you should have been doing it anyway. You should be refactoring all the time.
But to go into some more detail, actually, you can’t just give this to a client and then send them an invoice. If the projected cost of maintaining a feature is higher than the cost of rewriting it, then perhaps you should refactor it. If you can get some numbers that suggest that, look, it’s going to take us – over the next year it will cost us two whole weeks to maintain this feature as it is without adding any more functionality to it, it might take us a week to rewrite it. Over the course of that 12 months, it’s cheaper to do the rewrite.
If the current version or implementation of a thing is actually slowing you down and causing you problems, refactor it. If you can measure the frustration with something or you can measure the slowdown with a feature, that’s another chance to prove a business case for refactoring something.
To approach it from a slightly different angle, you might not be getting slowed down by something right now. You might not actually being caused any problems, but there’s every chance that a new version of something could actually make you faster. It’s not like you’re doing bad now. It’s just you know you could do much better.
Again, it’s all that measuring of these things to make the business case. A very simplified exercise that I go through with clients, mainly kind of development teams themselves, is to just have a handle on how it would look to kind of project and estimate these costs. Unfortunately, developers, we really don’t like making estimates. We don’t like making projections. But it’s just something we do all have to get better at.
The example I use is a simple one. It’s, we want to add a feature to a project, and the project has an existing version–that’s how it is right now–or it has this theoretical refactored version–how it could look if we spent some time refactoring it. What we’re doing here is let’s imagine this is a CSS project. Let’s imagine the feature we want to add is a theme.
We built a website. It’s a software as a service application. The customer has started asking to be able to theme this. One customer in particular said, we want to theme this website to look like our brand.
Now the CSS architecture, the CSS framework, the CSS project that we built was never intended to have theming in there, so we tell our project managers that we could hack a theme on top of this quite quickly. In two days we could have this kind of skinned up to look like the client’s website. But then if the next customer asks for a theme as well, that’s probably going to cost us two days to hack on top of a hack. Because we’re incurring interest, it might be two and a half days subsequently because it’s a hack on top of a hack on top of a hack.
Even though we can cheaply provide that value in the short-term, in the long-term it might take us nine days to provide these features for these clients. We’re not getting any economies of scale here. The cost doesn’t get cheaper the more we do it. It actually increases, so is the problem with the debt. The debt is increasing its interest.
Conversely, if we were to say to our project managers that, okay, they’ve asked for a theme. Theming was never meant to be in this project. We will need seven days to re-architect it in a way that we can slide theming in quite easily, but then every subsequent request we get drops down to a quarter of a day. We can see that our projections are much more in favor of refactoring.
The problem here is that, in the first instance, your project manager focuses on the two or the seven. They see that, well, hack it in. If it’s going to take two days, that’s way better. What they fail to understand is that the projected maintenance cost of this is actually much higher if we carry on with an unrefactored code base. This is a very, very simplified example, and it does require good estimations. But looking at projected cost and framing it in a way that represents the true problem is a good way of getting business buy-in for refactoring.
The second column represents investment. We talk about technical debt, but we can also talk about technical investment. The investment is just the opposite of debt. Making technical investment allows us to make things much more cheaper in the future. It paves the way for easier development.
However, I also think there is value in working out when not to refactor things. You can make the business case for not refactoring something. If you’re not actually being slowed down by something, it would be very costly and wasteful to spend time refactoring it.
Out of context, this slide looks like terrible advice, doesn’t it? If it’s something that could be ignored or avoided: that sounds like terrible advice. But what I mean by this is if you’ve got a part of the project that you know is bad code, you know it’s ugly code, but it doesn’t actually affect you, just leave it alone. It might be ugly. It might not be very nice. But if it isn’t actually causing problems for you, the team, or your users, it shouldn’t be a candidate for refactoring.
In traditional software development practices there’s a thing. You can use a thing called a façade, which is basically an API onto the bad code, right? Rather than refactoring the bad code, what you can do is just write an API onto it. It means you can carry on ignoring it for a little bit longer whilst having a nice, neat API onto it. All people in the banking industry do this because all the Cobol and Fortran developers are dead now. They write APIs onto old code. Because they don’t have the time to go in there and refactor it, they’ll write like a Java interface onto it. So if you can ignore something, if you genuinely can ignore it, it isn’t causing you problems, it could be a candidate for not refactoring.
If it’s something that can be captured by a rewrite later on, if you’ve got plans to rewrite your application, don’t spend the interim time tidying it up. Just trying and tweak every last drop out of it until you can tear it down and start again.
Looking at specific examples, this was in an email I got from a client. They said, we want to refactor our CSS into the BEM naming convention. Anybody who knows me knows that I really love BEM. I think it’s fantastic. I think it’s a very powerful tool for the front-end developer. But taking two weeks out to rewrite your CSS onto the BEM naming convention is probably not going to pay itself back very quickly. It’s a very expensive thing to implement.
I think we have to be very well considered or very considered when you start thinking about what you would refactor first. Is it actually worth refactoring your entire product into a different naming convention? Probably not. However, if your numbers suggest that, yes, it is worth it, then you go ahead. But if you can’t prove it, if you can’t make the business case for it, don’t start refactoring.
Another example is this one: We think the code for this nav is pretty ugly. I working with a client and, a day when we just had like an audit of what they would like to achieve over our kind of time together, one developer said this: “We think the code for this nav is pretty ugly.”
I had a look, and he was right. It was disgusting. But I had a few questions. Rather than just diving in and start encoding, I had a few questions for them.
Is it causing any actual problems for you right now? It turned out that the nav was completely accessible, completely accessible to any user who needed it. It’s fully responsive, worked in every single browser that it needed to. The nav wasn’t in any way faulty. It was completely functional, completely operational.
My next question was, well, how often do you actually work with this nav? How often do developers dive into these files? It turned out never. You just add a new page in the CMS and a new item pops onto the nav. It had very little manual intervention.
My third question was, can we just leave it alone then? Don’t refactor based on pride. Don’t refactor because you think you could do a better job or it could be neater. If it works, if it’s working correctly and not slowing you down, try and ignore it for as long as possible.
Okay, right. Let’s move away from this theoretical stuff. Let’s talk about some actual tips and techniques.
A sort of concept I came up with earlier this year when I was working with a client was this concept of what I call a refactoring tunnel. A refactoring tunnel represents the length of a refactoring task. The metaphor works like this: One day one you decide you’re going to refactor something, so you step into a refactoring tunnel. Behind you, you can see the light at the entrance. You can see the light where you came in, but you can’t see the light at the exit. It’s around a corner somewhere.
Day two, you continue refactoring. You’re further into the tunnel. And behind you, you can still see the light at the entrance, but the light at the exit is somewhere still around a corner.
Five days into the refactoring project, all of a sudden you can’t see the entrance any more, but neither can you see the exit. You don’t actually know if you are a quarter of the way in, three quarters of the way in. You don’t know if you’re nearly at the end. You’ve got no idea where you are in this refactoring task.
Then we just do this.
Harry Roberts: Show me your hands. Hands up who has done something like this before halfway through a refactoring task. Exactly. All of us.
This is a problem caused by long refactoring tunnels. A long refactoring tunnel represents a piece of work that is just too much. It goes right the way through your project.
Another way of thinking of a refactoring tunnel, if its length kind of dictates or, sorry, illustrates just how much work is involved, another way of thinking of it is anything with a large surface area. Refactoring anything that runs all the way through your project or touches a lot of your project is usually always a bad idea. Instead of picking off these long refactoring tunnels, instead of refactoring your entire naming convention, instead pick off much smaller things.
Long refactoring tunnels come with a bunch of problems. The first one is it actually leaves things messy and it takes too long. If you decide to refactor all of your naming convention from one thing to another, that means you’re going to be out of sort of feature or product work for perhaps two weeks. It’s taking a long time. You’ve removed yourself from being like a productive developer to being someone who is just renaming things. We don’t spend too long on actual refactoring tasks when we can be providing value.
We then get really specific problems like merge conflicts. If you were to go and refactor something all the way through your project, it can be very hard merging that back into a master. We’ll get huge, huge conflicts out of these deltas that we’re trying to merge in.
Other problems: If you try and refactor something that touches your entire website, the chances of you introducing regressions or new bugs is much higher. Statistically it’s more likely you will introduce a bug if you’re refactoring a large part of the project. Instead, just pick off small, granular things.
Find a shorter tunnel. For example, refactoring just the nav. When you refactor the nav, this is when you would move to your new naming convention. You would swap PNGs for SVGs. You’d do everything you want to do across the entire site, but instead you’d just do it for the nav.
Then you’ve got a decision to make. You could jump back onto product work and start building features with the rest of the team or, if your schedule allows it, you could go on and then refactor the sidebar or the contact form or something like that. We pick all these short tunnels, rinse and repeat, and eventually the site refactors itself.
Once we’ve identified these nice, short tunnels, it’s really important that we refactor them in isolation. A really common, very specific problem I see with refactoring projects specifically in CSS is that people try and rewrite new CSS back into the existing code base. The problem with that is you’re relying on a stale environment. You’re relying on CSS that you already know is faulty because of the cascade, because of inheritance. The fact that you’re refactoring CSS tells you that there’s something wrong with it.
If you try and rebuild a feature back into the exact same code base, you’re going to get problems with you’re relying on an out of date reset or, when somebody refactored the reset and uses normalize instead, your new version of the refactored component is going to break. Fire up in CodePen, JS Fiddle, whatever, and build the new version of a feature there. Build the new perfect version outside of your project and port it back in. Once you’ve ported it back in, you do all your tidy up work at that point.
For example: Identify a sensible, short refactoring tunnel, perhaps just the header. Fire it up somewhere else. Rebuild the header exactly as you would want it. Get it perfect or as close to perfect as you can. Then just copy and paste that back into the CSS project you’re trying to refactor. By building something in isolation like this, it means we won’t accidentally make use of other out of date CSS.
Of course, copying and paste it back in won’t be as easy as that. It’s safe to assume we’ll get some breakages. We will get some collisions. Those breakages and collisions, we fix those at the project level. There’s a really nice little tool that can help us with what.
The next step I want to discuss is something called “ALL: INITIAL;”. Has anyone hear of this? Show of hands. Yeah, like 20 of us. That’s cool. “ALL: INITIAL;” is amazing. It effectively stops inheritance. It sets all of an element’s properties back to their initial value. It effectively stops inheritance and starts a brand new scope within your page or within the application.
Yeah, it effectively stops inheritance, which is really good at preventing legacy styles from leaking into fresh work. It’s a very progressive way of achieving this. I mention progressive because, unfortunately, we do have to look at browser support in a couple of slides’ time. But it’s a very progressive way of stopping inheritance and preventing things leaking in to our new work.
A quick example here. I hope that’s large enough to see. But we can see there’s a bit of text down here. I write “hello,” and it’s bold and it’s red because that’s how inheritance works. It’s inherited it from previous DOM loads.
As soon as we drop “ALL: INITIAL;” in there, that paragraph drops back to being black and font-weight normal. What we’ve done here is, by using “ALL: INITIAL;” we’ve stopped anything cascading into this rule set.
It’s really, really handy, very good for refactoring work, but it does come with a couple of weird oddities. If we were to refactor our nav, and we call it nav-primary and nav-primary_link, the font size and font family declarations we have here will not actually be picked up by the link any more. Because the link itself has “ALL: INITIAL;” on there, it will go back to being all its initial values.
To get around this, we’ve just applied these kind of cascading declarations onto the leaf node, so we just move those down onto the link itself. We do have to structure certain CSS rules in a little, slightly different order, but it is protecting us. It is safeguarding us from inheritance from the legacy stale project.
Browser support for this isn’t terrible. There’s a lot of green on there, but there are two very concerning bits of red on the left. What this means is we can’t just use “ALL: INITIAL;” en masse. We can’t use it to fix all of our problems. The way I see it is like a last line of defense. It just buys us a little bit more time.
If we’re refactoring a larger application, we are bound to get regressions. But if we use “ALL: INITIAL;”, at least only a small subset of our users will ever see those regressions on live. It just buys us a little bit more time to fix things while the majority of our users in the green area, they will just see the site exactly as it’s meant to be. “ALL: INITIAL;” can’t be relied on as like a silver bullet. What it should be seen as is kind of buying us a little bit more time. It’s this last line of defense.
On the subject of defense, a thing I did for a client earlier this year was to create something called defence.css. Defence.css answers a very specific question, and that question is, what happens when we need to run refactored code and legacy code side-by-side? This was a question asked of me by a company in the U.K. called Sky.
I used to work for Sky full time about three to five years ago. I’m now back consulting with them on their new, kind of UI toolkit. They’ve got a new design language. They want to modernize their sort of application architecture. They want to modernize their design style. So I’m consulting with them on this new toolkit we’re developing.
Now because Sky is a very big company, employs about 30,000 people in England, it’s a huge, huge site. It’s a huge project, and there are lots of different teams working on different properties all around the country. It’s not like we’ve got one centralized dev team who works on this. We’ve got lots of fragmented, scattered dev teams. So it had to be rolled out very gradually.
The homepage was the first candidate. This always happens. Marketing teams think, “Well, we’ll make the homepage responsive first. Then if that works, we’ll do the rest of the site,” as if that’s how CSS works. But the homepage was the first candidate. What we did is we took the homepage, and we tried to implement the new toolkit there.
The site is now using old and new toolkits side-by-side. We had to run old and new code in tandem. This is kind of like trying to run Foundation and Bootstrap on the exact same project. As you can imagine, it didn’t go very smoothly. As you put old CSS, an entire project’s worth of CSS and an entire new project’s worth of CSS together, things get a little messy.
We weren’t getting the output that we wanted, and we didn’t expect it to work. We knew it wouldn’t, but we had to actually solve this problem. We’ve got one project using two completely different CSS code bases.
We needed to start fixing this stuff, and we couldn’t really initially decide where the fixes should live. Do we put all the fixes in the existing toolkit? That was certainly a nicer solution than putting all the fixes inside the new toolkit. It’d be awful to write a brand new, greenfield project and just fill it with legacy hacks.
What we ended up settling on is the idea of this defence.css. It’s run as a third project, a brand new file. This just is crammed full of all the nasty bits of CSS that smoothens that transition from old to new.
There are several benefits of running this project this way. We run it kind of internal open source. Defence.css was a brand new dependency. It was a brand new file that got included. It was hosted on GitHub.
We run it open source for a few reasons. Firstly, we could crowd source our bug fixing. It meant that as other teams started implementing the toolkit on their properties, they could make use of the fixes we’d already given to them. But as they found new hacks or new problems, they could contribute those fixes back upstream, and we can just seed out the fixes to every consumer of the new and old toolkits.
It also means that it’s very delete key friendly. Defence.css is a temporary solution. I know, famous last words, but it is temporary solution because, as soon as we decommission the old toolkit, we want to get rid of the hacks as well, so it’s very delete key friendly. It’s just one package that lives on its own and we can remove as easily as we can include it.
This is the worst CSS anyone has ever written, by the way. This file is just full. It’s type until it works, right? That’s the kind of CSS we’re dealing with. I’ve worked with developers who seem to apply that to all of their career, but this was actually intentional. We were meant to actually type until it just looks correct.
I wanted to show you what was in this file, like some examples of the kind of stuff we put in here, but I wasn’t entirely sure if the code of conduct would allow it because it is that offensive. What I have done is I’ve got a screenshot, which I’ve just pixilated. I think that’s safe enough.
Harry Roberts: What we can see, though, is a lot of red down here. Those are important. This is just hacky, nasty CSS. We’re brute forcing this thing, just making it work. What it allowed us to do is sort of ease the transition from old to new. It allowed us to run old and new completely side-by-side whilst crowd sourcing all the fixes for any interference.
The next little tip I want to talk about is a very, very simple, very small tip. I just call them RF classes. The RF class is basically any new refactored class that we write. Just prefix it with the string RF- (refactored). What this does is it tells the next developer that something has been fixed. If you see an RF-, you know that, okay, this class is brand new. It’s got this name space. It’s a refactored class. It means that this work has already been done.
It also offers a soft encapsulation, which means that we’re not going to get any collisions with existing CSS, so every brand new class has this name space, which tells developers what it’s used for. It also gives us a soft encapsulation, which is guaranteed to prevent regressions. I say guaranteed. You’d have to pick. I use the string RF-, but pick anything that is unlikely to collide.
Because we’ve got this RF in the class now, we can do things automagically. We could write a selector like this. If a class contains the string RF-, sling a green border around it. We could just have this behind like a feature switch, a debug mode in our SaaS project. That means we can just click through the website, and we can see what work needs to be done.
The nav and content blocks here are covered in green. That means this has been fixed. What this means is you could set up an instance of a dev site, which has got this feature turned on, and you could give it to a product owner to give them a visual indication of what work has been done. A non-technical person doesn’t have to read the classes. They can click through the site and get a rough idea of the health of this refactoring project.
This represents the work that we have done. As we get further into the project, it might be more useful to know what we haven’t done yet and what work is left to do. We can slightly invert that selector and say, find me a class that is also a class that doesn’t contain RF- and sling a red boarder around that. What we do now is we go to the site, and we can see that there actually is a lot of work left to do here.
Only the weather in London masthead has actually been refactored on this website. This tells me that we’re not very far into our refactoring project at all. It gives me a very visual health check to show me the current status of the refactoring task. This represents the work left to do.
This is a very specific tip that I’ve got for you next. Anybody in my workshop on Monday will be familiar with these concepts by now, but refactoring specificity. If you’re refactoring a legacy CSS project, you can almost guarantee you’re going to run into specificity problems. Other people have used IDs or we nested selectors really heavily. We’re going to spend a lot of time refactoring just trying to get out of these specificity messes.
Dealing with specificity on a legacy project is pretty difficult. Newly refactored work will likely be overridden by existing stuff. But we can hack specificity around with minimal side effects. If we take a small snippet of HTML, a link with a class and an ID, there are three ways of binding onto this bit of HTML. We could select it via the A selector, we could select it via the foo class, or we could select it via the bar ID.
Just to pull out into a slide here, because of how CSS works this link is going to be blue. Even though we’ve defined red the very last, because of how specificity works, we know this link is going to end up rendering blue on the screen. This is one of the biggest problems with specificity. CSS is a source order dependent language. As soon as you throw specificity in there, you can completely reverse the order. This is why people struggle to scale CSS and why people struggle to understand how and why CSS works.
You have three very differently weighted selectors all working against their source order. We could use important to address the prominence of one of these. But of course we shouldn’t do that. What we can do is lean on certain hacks to nudge specificity around.
Rewriting those three selectors as this, they all have the exact same specificity as each other. Now these are hacks. Please remember that these are not good things to do. But if we can’t refactor our HTML very easily, of if we don’t want to move things around DOM or rely on a nested structure, we can write our selectors like these to guarantee minimal or zero side effects. We can move this link anywhere in the DOM and it will still render correctly by doing stuff like this. Writing IDs as attribute selectors lowers their specificity down to that other class.
The second one, we’ve got an element and a class worth of specificity. On the final one, we’re using the root pseudo selector to just bump it up by one class worth of specificity. Again, to pull this out into a more digestible slide, this is how we do it.
Writing IDs as attributes like this gives them the same specificity as a class. This means if you get handed legacy HTML or you’re using a third party plugin that is littered with IDs and you can’t get to those IDs to remove them, select them via an attribute selector instead.
You can chain classes with each other to bump their specificity up by that amount. Here we’re doubling the specificity of foo. We can triple it. We can a billion times it just by chaining foo with itself that many times.
This is a really interesting kind of very useful technique to use if you do need to bump specificity around in certain scenarios. If you’ve got a class, just chain it with itself. You don’t need to write that class twice in the HTML. It just needs to exist once in the HTML, but chaining a class with itself is completely valid and will increase its specificity.
Finally, using the root element, sorry, the root pseudo selector, we can add an artificial class worth of specificity to any element level selector. The root selector is generated – well, it points to whatever the root node is, so in an HTML file with HTML tags. HTML tags are always present at runtime, so this will always work.
Here’s where we can hack specificity around. I really recommend these. Instead of going like full nuclear and using an important, try using one of these first.
It is really important, though, for me to note that these are hacks, right? I’m not advocating the use of these. They’re just like, on the list of things we should do these aren’t the worst one. Ideally, we’d refactor our CSS until we don’t need any of these hacks, but I don’t know a single developer who worked in an ideal environment.
Realistically, we’re going to have to use one of these. But never use important. Please don’t tweet that last line out of context because I don’t want to spend my next week dealing with that.
There are certain use cases for important in CSS, but this is not one of them. Don’t use important to nudge your specificity problems around because it will only make the problem worse. In fact, I think important is one of the best examples of technical debt in CSS. Three seconds to type it, but three weeks to refactor.
Important: The reason we use important is because we’re making a strategic decision. Every time you have used important, your thought process has probably gone like this: Hmm, right, that’s causing a problem. I might set aside the afternoon to refactor it so that I don’t have to use this important.
That’s what you would do. But because you know you can use important, you know you can get a shortcut that’s going to take three seconds and will make the client happy, you type “important.” That is technical debt. That’s a strategic decision that you made to take a shortcut to deliver value sooner. But it’s going to incur debt, and it’s going to get more and more expensive to get rid of later.
I think the final tip I want to share with you is something called shame.css. Shame.css was a little kind of joke that started off between me and a friend of mine. We were discussing where do you put your nasty CSS in projects.
I just realized it’s in very bad taste to quote oneself on a slide, but I seem to have done it anyway.
Harry Roberts: This is from the blog post that kind of introduced shame.css a couple of years ago. The idea of shame.css is you have a completely new style sheet reserved just for your hacky code: code you had to write to get a release out on time, but code that makes you ashamed.
This is your dumping ground for all the nasty, hacky bits of CSS that we’re a little bit ashamed of. It’s because hacks are inevitable. It’s going to happen. We’d be very naïve to assume we could write an entire project without having to hack anything around at all.
What is important, though, is to isolate and signpost them because it makes everybody else aware of them. It makes them very clear and very prominent. It makes these nasty bits of code, your technical debt, very accessible, easy to find, and fix things.
An example entry into a shame.css file might look like this. What we need to see here is, of course, at the bottom we’re got the hack itself. What we need to put in here is why did we do it. We can see here that the promo A selector was overriding the button style, so I’ve had to increase the specificity artificially in order to override that behavior.
What this does, even though the hack isn’t great, what this explanation does is it gets you an excuse. It gets people to forgive you. What I’ve realized in sort of ten years of front-end development is that if I just write a comment saying why I did something, I can get away with a lot of bad work.
If I’d have just seen this .btn.btn selector in a project, I wouldn’t know what it actually fixes. It might fix something that doesn’t even exist in the project any more. It’s just dead code. As soon as I just type what it is, why it exists, and how I would fix it given enough time, the next person who encounters it understand the effect or the reach of this bit of work, and they also understand how they might fix it if they need to.
Yeah, so it’s a self-writing to-do list. Everything you need to fix exists in this file. It’s very nice to have that isolated somewhere. When you do get your refactoring time, this is the first file you should look at. It keeps good code nice and clean, so all the nice code that exists in the majority of your project is going to be untouched. But this one little bit for like the 3% of nasty code is kept out of view.
There’s a thing called the Broken Windows Theory, which states that in – the metaphor goes like this. In New York if there’s a council building that’s vacant and someone smashes a window, they will send someone out immediately to repair that window. Even if they know the building will be vacant for the next 15 years, they send someone out to repair it immediately because as soon as somebody else sees a smashed window they’ll think, oh, I can smash this window. Then when all the windows are smashed, people realize that, oh, this is obviously an abandoned building. Let’s have, you know, all night raves here. Then everything just goes downhill from there.
What happens is by having all your code in the shame.css, the rest of the project, the place you spend 95%, 97% of your time, has a really, really good standard of quality. That means that the 3% you see far less frequently, and you’re all kind of incentivized to keep the rest of the stuff nice and clean.
By having all your hacks in one file, you can actually start to get numbers behind the cost of that feature. The shame.css file did have 17 lines of CSS in there. After building your feature, it’s now got 117. We can see that we’ve added 100 more lines of code just to hack this feature around. Now I’ve got the data. Can we go back and revisit that design decision?
Finally, it’s much easier to run git blame over a single file than it is over an entire project. This sounds very Machiavellian, like I’m trying to keep an eye on people and sort of embarrass less skilled developers. But what it actually does, it tells you who is struggling with which specific feature.
I had an incident a few years ago where I was working with a developer who they’d contributed a lot of hacks to shame.css, and all the hacks were to do with the site’s grid system. All that told me was that I hadn’t explained the grid system very well. I ended up going for a coffee with this developer and saying, “Look. I noticed you put this, this, and this in here. There’s actually a class already in the CSS that does that for you. Let me show you it,” and we had like a 15-minute tutorial. We could rip a lot of this code out. It allowed me to keep an eye on who needs teaching what or which bits of knowledge might be missing.
I’ve just got a couple of minutes to wrap up. I want to leave you with an interesting thing that happened to me about two years ago. I had an interesting point of view kind of bestowed upon me. This is this idea of a second chance.
I was doing a workshop in London, a public workshop, and I went around the room and asked people, what do you do? Everyone was like, I’m a freelance developer, I’m a designer, I’m this and this. One guy said, “Oh, I’ve actually been a Web developer for like two weeks. I used to be a civil engineer. I used to build roads and bridges for a living, but wanted a complete change of path, and I’m now a Web developer.”
I was like, “Oh, that’s really cool. Congratulations on taking such a big leap. How are you finding Web development?”
He said something really interesting. He said, “I’m a civil engineer by trade: roads, bridges, that kind of thing. I never get to refactor my work. I don’t get to refactor my architecture.” It made me think that he’s right.
Gees. Yeah. I always used to view refactoring as a really horrible, boring task, this thing that I had to do, and it was a drag. It’s actually an amazing privilege. Refactoring is a second chance that a lot of industries just don’t get. We get the opportunity to study our code and learn about it for six weeks, six months, go back, and fine-tune it. The more we learn about how that code is used or how it’s implemented, how it needs to be reused is a chance to go back and refactor it.
You can’t really refactor a botched heart surgery. You can’t really refactor a failed flight, right? These are not things a lot of industries can refactor. With code, it’s cheap. It’s nearly free, right? We can bend it. We can break it. We can refactor it. And we can study it.
It made me realize that refactoring is actually a pretty cool thing to get to do. Instead of viewing it as this horrible drag or this boring chore, I started to just reframe it as like a second chance, a chance to do a better job next time.
Yeah, just to recap then. Prevention is far cheaper than the cure. Try and keep on top of refactoring as it happens. Don’t bend to the temptation of using that important.
Technical debt is fine. Do not let anyone tell you technical debt is always bad. It’s a strategic decision.
Only refactor when you see tangible benefit in doing so. Avoid long refactoring tunnels. Don’t refactor something that touches all of your project.
Isolate and highlight both hacks and refactored work. Make sure people know what has been fixed. Make sure people know what needs fixing next.
The very last thing I’d like to leave you with is there’s a saying from Sir Robert Baden-Powell, founder of the Boy Scouts movement. He famously told his boy scouts, “Always leave the campground cleaner than you found it.” I think we’ve got a similar duty as Web developers to try and leave any project cleaner than we found it.
Thank you very much for your time.