CSS refactoring
Join the DZone community and get the full member experience.
Join For FreeIt's quite easy, when jumping on the CSS bandwagon, to start using complicate selectors in every angle of the stylesheets, and to disseminate our (probably generated) markup with classes and ids with the goal of a finer selection.
But here's the news: CSS are composed of code. Unless you use CSS preprocessors or PHP-based stylesheets, it is mostly declarative code, and it is usually present in a fairly small quantity with regard to the Java, Python, PHP or HTML code of the same project.
However, without proper maintenance every kind of code is prone to the same corruption: duplication, lack of simmetry and encapsulation, leaky abstractions.
In this article we'll see an example of refactoring of a small set of CSS rules to address their smells. Clean code saves you time and money, and this is true also in the realm of CSS files, which can grow to thousands of lines long.
Smells and testing
For starters, note that there is a inherent coupling between the markup (HTML and PHTML or JSP files) and the CSS selectors you're using. As so, refactoring one of them means changing the other.
We'll initially focus on finding smells in our CSS code and on trying to eliminate them. We haven't got many tools at our disposal - such as classes and methods - but many situations can be improved.
In our example, the refactoring targets are:
- duplication of code, in every form; selectors and rules.
- overspecification, when the selectors are too precise.
- fake abstraction, when the selectors names include physical characteristics.
Unfortunately there are no automated testing frameworks for web design (are they?), so we should rely on a manual test. These frameworks may be possible to buidl by simply taking snapshot of pages viewed during Selenium tests and checking the disposition of elements and color. However, this approach is very brittle and a test would break every time a design trait is changed, even for a 1 pixel difference.
Snapshot 1
Let's start with the code. The example consists in a small menu built with divs.
<style> div.menu { float: left; width: 80px; padding: 1px; border: 1px solid gray; } div.menu_item { float: left; width: 80px; background-color: gray; margin-top: 2px; } a.menu_blue_link { color: #003366; text-decoration: none; } br.clear { clear: both; } </style> <div class="menu"> <div class="menu_item"><a class="menu_blue_link" href="/">Home</a></div> <div class="menu_item"><a class="menu_blue_link" href="/contacts">Contacts</a></div> <div class="menu_item"><a class="menu_blue_link" href="/portfolio">Portfolio</a></div> <div class="menu_item"><a class="menu_blue_link" href="/prices">Prices</a></div> </div> <br class="clear" />
We are going to perform the following operations:
- change the markup to a more semantic one. Divs are used too much nowadays, given their generic feeling.
- omit declaration of elements from selectors since the are already pretty unique (removing overspecification from the picture, so that we can change the divs to other elements many times without touching the CSS anymore.)
- change the names menu_blue_link to menu_link; this is a form of leaky and unuseful abstraction: what if we change the color? We would end up being forced to change also the CSS class to avoid a green menu_blue_link. The same would be done for the infamous <div class="right">.
Snapshot 2
<style> .menu { float: left; width: 80px; padding: 1px; border: 1px solid gray; } .menu_item { float: left; width: 80px; background-color: gray; margin-top: 2px; list-style-type: none; } .menu_link { color: #003366; text-decoration: none; } br.clear { clear: both; } </style> <ul class="menu"> <li class="menu_item"><a class="menu_link" href="/">Home</a></li> <li class="menu_item"><a class="menu_link" href="/contacts">Contacts</a></li> <li class="menu_item"><a class="menu_link" href="/portfolio">Portfolio</a></li> <li class="menu_item"><a class="menu_link" href="/prices">Prices</a></li> </ul> <br class="clear" />
Now the situation has been improved a bit, but we have more work to do:
- we can inline a class that reveals itself with its name: clear. It occurs only on a <br> and while it is a de facto standard to use classes named clear, I argue that they are a leaky layer of abstraction too.
- We can also use containment selectors to unify the various .menu* classes.
- We introduce the trade-off of using the tag <li> in the relative CSS selector instead of the class. However now we'll have one class less to refer to in the markup.
- menu_link would be ever applied only to an a element, so it would be wrong to abstract it away (underspecification): we'll just refer to it in the selector.
Snapshot 3
<style> .menu { float: left; width: 80px; padding: 1px; border: 1px solid gray; } .menu li { float: left; width: 80px; background-color: gray; margin-top: 2px; list-style-type: none; } .menu a { color: #003366; text-decoration: none; } </style> <ul class="menu"> <li><a href="/">Home</a></li> <li><a href="/contacts">Contacts</a></li> <li><a href="/portfolio">Portfolio</a></li> <li><a href="/prices">Prices</a></li> </ul> <br stye="clear: both;" />
Now we have a much cleaner markup, which shows its semantic properties with the list elements, and a CSS ruleset with a reduced amount of duplication (for what we can accomplish without imperative code).
Conclusions
This isn't rocket science, and I trust you knew many of these tricks already; but applying the principles of clean code in any situation, even graphic design, is the difference between the novice and the craftsman.
Opinions expressed by DZone contributors are their own.
Comments