In Defense of Sass
February 11, 2009I've been playing with Sass and Haml in my rails projects the last few months. While I'm a bit ambivalent about Haml, I've wholeheartedly adopted Sass. A friend just forwarded me this post at fecklessmind, which excoriates Sass as a maintainability nightmare.
While I understand the guy's complaints, I have to say I disagree. I think he's complaining about a code convention that he shouldn't be following in the first place, rather than the underlying language, and he's ignoring some of the other most useful things Sass brings to the table.
Nesting in Sass
One of the most common problems I've faced over the last eight years of writing stylesheets is interfering selectors. When you have a complex cascading selector, it's often not obvious exactly where it will apply, because of the way priorities work. So a hundred times I've set some styling on UL's and LI's (thinking of ones in my #content block), only to have them accidentally interfere with the layout of my suckerfish dropdowns back in #nav.
That's an easy case, but sometimes with complex selectors it can be hard to figure out who's interfering with whom. However, once you've found the culprit, the solution is generally to go back and wrap all of the rules in an outer selector, changing all of my li {rule} selectors to #nav li selectors, or whatever. When you have twenty different rules in that section, doing this is a royal pain in the butt. Especially when you have multiple tag selectors on one line: it's seriously annoying to change the nice clean h1, h2, h3 to #content h1, #content h2, #content h3!
When you do need these wraps, Sass makes it super easy via auto-nesting:
#nav
li
:color #whatever
:float left
a
:whatever etc
will compile to:
#nav li {
color: #whatever
float: left
}
#nav a {
whatever: etc
}
Now, the author of fecklessmind is complaining about how this makes rules harder to find, and how it slows down parsing. Both of these can be true, if you overdo it. But I don't - Sass doesn't force you to wrap your rules this way, and I frequently don't when it doesn't provide any benefit or when it would cause me to write redundant rules. I can and frequently do write single-line cascading selectors, and rules without wraps at all - the very things fecklessmind is complaining that Sass takes away from him.
Nothing about Sass prevents me from writing things like this:
body #nav ul li a
:float left
or even
#content h1, #nav h2, .article h3, p h4
:font-weight bold
If that's really what I want to do. I learned how and when to wrap selectors with a near-decade of writing CSS, and I apply those same guidelines when I write Sass - Sass just makes it easier when I do want to do it.
The benefits of nesting early
While I don't use Sass nesting everywhere I possibly could, I do often use it slightly more than would be absolutely required.
The reason is that it heads off a lot of annoying bugs with interfering selectors. For example, say a rule I wrote for .article .body p, and it's not getting applied. After some sleuthwork (long, painful, frustrating sleuthwork if I'm on a platform without firebug, like IE), it turns out this is because there's a #content p rule 2000 lines earlier in the CSS file that's obscuring it. When I nest things in Sass to create a clean cascade hierarchy, this kind of interference is far less likely to occur in the first place.
Meanwhile, the other benefits of Sass
CSS is riddled with problems, and Sass solves two of the most egregious: magic numbers/constants, and compiled server-side imports.
Eliminating magic numbers in CSS
For constants, Sass lets me define commonly used tokens (like colors, for example), and reuse them throughout my stylesheets. This means if I want to adjust a color, I can change it in only one place and the result is reflected throughout my code. Very handy:
!main_link_color = #48950a
a
:color= !main_link_color
#content h1
:border-width 0 0 1px 0
:border-color= !main_link_color
Now, if the client says "make the links blue, not green", I can change that constant and it gets automatically reflected everywhere else. Brilliant.
Organizing my code
fecklessmind says this:
... imagine that the stylesheet is 5000-lines long and you’re looking for p selector, rather than #article. In classic CSS you could just search for #main p, but in Sass they are miles apart. Swell, isn’t it?
A five-thousand-line line file? You're doing it wrong. No code should ever look like that. CSS is the only major language that compels you to work that way and Sass fixes it.
Every good programming language lets me put my code across multiple files, in a nice, organized heirarchy. One class per file and all that: essential for readability and maintainability. But if I use CSS, I can't very well organize my stylesheets into multiple files. If I do, I have to import them client-side, which generates extra hits for the user's browser and extra load for my server. As a result, CSS files tend to be monolithic multiple-kiloline monstrosities.
Sass fixes this. If I use @import to import a Sass file into another Sass file, Sass automatically and transparently compiles that server-side and ships out a single file to the user.
The result is that, writing Sass, I often have 20-30 files containing only a page or so of code, each for a specific feature or layout section. The client still only sees screen.css (and maybe print.css, mobile.css, and ie6.css), but screen.css contains the compiled contents of layout.sass, nav.sass, links.sass, content.sass, footer.sass, etc. In case I need to scan through the compiled screen.css and figure out where a rule came from, I start each file with a single comment containing the name of the file; /*------ nav.sass and such.
If the rule is for paragraphs that could appear anywhere in #main, in my Sass code it would be a file called main.sass, which is usually a relatively short file; 50-60 lines. (All the things destined for other elements would appear in articles.sass, or calendar.sass, or data_tables.sass, keeping main.sass short for only the universal elements).
That logical grouping — the way every other programming language does it — helps me find my CSS rules much more quickly, I think, than fecklessmind's "search for one-line selectors" would. Because with his approach, I might think I'm searching for #main p, when in fact what I really want is #main .section p, and thus my search won't find it.
In reality, there's no way to make a single 5000-line file easily maintainable, period. fecklessmind's little tricks are just that: tricks built from years of experience working in a broken system. Better to use logical organization to solve the problem, and Sass lets me do this.
The bottom line is, badly-written Sass could be horrible to maintain, and maybe worse in some ways than badly-written CSS (but better in others, particularly in weird cross-reactions between unwrapped css selectors). But the same is true of badly-written code of any type. And in my experience Sass gives me much better tools to write maintainable stylesheets than CSS alone does.


