Code smells in CSS

Credit

This article is translated with permission of Harry Roberts.
You can find original article at Code smells in CSS

本記事はHarry Roberts氏の了承を得て翻訳された記事です。
原文はCode smells in CSSにて掲載されています。

Code smells in CSS

Chris Coyierが回答したとある問いがある:

CSSコードが「臭い」かどうかをどう知ることができるのか?コードがいまいち、あるいはデベロッパがいまいちかどうかの兆候はどんなものか?コードの中でどんな部分を見て、善し悪しを決定づけるのか?

Chrisの素晴らしい回答にいくつか追加して私なりに回答してみようと思う。

私はBSkyBでインハウスデベロッパとして働く毎日を送っている。私は最近のサイトではフロントエンドに1年以上もかけ(まだ作業中)て制作が必要なほどの大きなウェブサイトに携わっている。その私にとって、悪いCSSとはとても具体的で厄介な代物だ。1つのサイトの制作に数ヶ月にも渡ってかかるような場合、CSSであろうとそうでなかろうと、質の悪いコードの存在が許される余裕はなく、悪いコードは改善する必要性がある。

私がCSSのクオリティ、メンテナンス性、そして品位のようなものに対して注意していることを少しだけ(もちろん疑いの余地もなく言及していないこともある)共有しよう。

スタイルの取り消し

スタイルを取り消すスタイル(リセット以外で)はすぐに注意が必要であるとわかる。CSSの本質からしてスタイルは、もちろん、カスケードするし、以前に定義したものから継承される。ルールセットは常に継承のみを行うべきで、以前のものに追加するべきであり、取り消しをするべきではない。

以下のようなCSSの宣言は:

    border-bottom:none;
    padding:0;
    float:none;
    margin-left:0;

…は 往々にして 悪い知らせだ。もしボーダーを削除しなければならないとしたら、それを適用するのが少し早すぎたのだ。非常に説明するのが難しいので簡単な例を見ていこう:

    h2{
        font-size:2em;
        margin-bottom:0.5em;
        padding-bottom:0.5em;
        border-bottom:1px solid #ccc;
    }

ここですべてのh2にいつものようにfont-sizeやスペースのためのmargin、ほんの少しのpadding、そしてページ上で次の要素と見た目上の区別を付けやすくするため、ラインを下部に追加した。しかし、場合によってはこのラインが 必要なくなる ことも考えられるかも知れない。あるいはh2borderpaddingを追加しない場面もあるかも知れない。そうなると以下のようなことになりがちだ:

    h2{
        font-size:2em;
        margin-bottom:0.5em;
        padding-bottom:0.5em;
        border-bottom:1px solid #ccc;
    }
    .no-border{
        padding-bottom:0;
        border-bottom:none;
    }

10行のCSSに1つの醜いクラス名がある。より良い方法としては:

    h2{
        font-size:2em;
        margin-bottom:0.5em;
    }
    .headline{
        padding-bottom:0.5em;
        border-bottom:1px solid #ccc;
    }

8行のCSS、取り消しはなく、すばらしく合理的なクラス名となった。

スタイルシートを書き進めるに従って、スタイルを追加するのみにし、削除しないこと。 もしもスタイルの取り消しをしている自分に出会ってしまったら、多くの場合、多くのスタイルをすこし早めに追加しすぎているということになる。

この例は非常に遠慮がちな例ではあるが、私が伝えたいことを完璧に表現したよい例だ。こんなCSSが数万行に及ぶことを想像してみて欲しい。とてつもない無駄だし、とてつもなく必要のない取り消しだ。スタイルするものの前にあるシンプルなものを見破ること。複雑すぎる部分から初めて、あとで取り消し作業をするリスクを冒すべきではない。最終的に 多くのCSSを書くはめになるのに、少ないスタイルになってしまうことになる

以前のスタイルを取り消しをしているCSSを見ればすぐに何かしらが質の悪い設計がなされたことと、その何かは修正されるべきであることもわかる。

マジックナンバー

これらは私にとっての特に悩みの種である。私はマジックナンバーが大嫌いだ。

マジックナンバーとは(理由もなく)「動作するから」というだけで使われている値を指す。以下の例を見てほしい:

    .site-nav{
        [styles]
    }
        .site-nav > li:hover .dropdown{
            position:absolute;
            top:37px;
            left:0;
        }

top:37px; これがマジックナンバーだ。この値が正しい振る舞い(らしい)をするたった1つの理由はli内の.site-nav運良く 37pxの高さを持っているからで、.dropdownのフライアウトメニューはその下に出現する必要があるからだ。

ここで問題にしているのは37pxが 完璧に 状況に依存している点であり、だからこそ、その数字を信用することができない。もし誰かが.site-navfont-sizeを変更し、29pxの高さになったとしたらどうなるか?この数字からは妥当性は失われ、他のデベロッパもこの数字をアップデートすることを知っておく必要がある。

もしChromeは 確かに liを37pxとして描画したとして、IEが36pxだったらどうなるのか?この数字はある1つの条件でしか動作しないのだ。

絶対に、 絶対にだ 、なんとなく動作するからという理由で数字を使ってはいけない。このシチュエーションでは、top:37px;top:100%;、これは基本的にはトップからすべてという意味になる、に差し替えたほうがはるかにいい。

マジックナンバーにはそれらにまつわるいくつかの問題がある。上に挙げた通り、依存できるものではないということに加えて、それらの「なんとなく動いたから」という性質から、他のデベロッパにその数字がどこからやってきたのかという情報を伝達しづらい。もしも、より複雑なマジックナンバーを使った例があったとして、そのマジックナンバーが無効になった場合、以下の1つ以上の問題に出くわすことだろう:

マジックナンバーは悪い知らせだ。すぐに期限切れになるし、他のデベロッパを混乱させる上、説明もすることもできず、信用もできない。

他人のコードで説明なしの番号に出会うことほど酷いものはない。一体その番号が何をするのか、何のために必要なのか、それに触れるべきなのか否か悩むことになる。

CSSにマジックナンバーを見かけたら私はすぐに質問をしはじめる。どうしてここにあるのか、何をするものなのか、どうやってこの番号は動作するのか、マジックナンバーを使わずに同じ結果を得ることは可能なのか?

厄災から逃れるつもりでマジックナンバーは避けるべき。

制限されたセレクタ

制限されたセレクタとは以下のようなセレクタだ:

    ul.nav{}
    a.button{}
    div.header{}

基本は例のように不必要に要素が追加されたセレクタだ。
これらが悪い知らせである理由は:

これらはすべて悪い特徴である。このセレクタは以下の様にできるし、そうするべきだ:

    .nav{}
    .button{}
    .header{}

こうすることで、.navolに適用することができるし、.buttoninputに適用することもできる。そしてサイトがHTML5に対応できるようになったら、ヘッダのdivheader要素に変更してもスタイルが無効になる心配をしなくても済む。

パフォーマンスに関しては小さな問題にしかならないが、それでもやはり問題ではある。どうしてブラウザに対して、クラス.buttonaにあるかどうかを探して貰う必要があるのか、.buttonを探して貰えば済む場合は特にだ。セレクタを制限することでブラウザの作業負荷を上げてしまうことになるわけだ。

より過剰な例としては:

    ul.nav li.active a{}
    div.header a.logo img{}
    .content ul.features a.button{}

これらセレクタは以下のように大規模に削減するか、完全に書き直すことができる:

    .nav .active a{}
    .logo > img {}
    .features-button{}

こうすることで:

ができる。

スタイルシートを見ている際、制限しすぎたセレクタを見かけ次第、どうしてそんな冗長に書く必要があったのかを知りたくなるし、どうやったら最も短くなるように削減できるかを考える。

ハードコード/絶対値

マジックナンバーと同じように、ハードコード値も悪い知らせだ。ハードコード値とは以下の様なものを指す:

    h1{
        font-size:24px;
        line-height:32px;
    }

line-height:32px; ここがよろしくない。line-height:1.333とするべきだろう。

行間はより寛大で、柔軟になるように常に相対値で設定するべきだ。h1font-sizeを変更する際に、line-heightにもその変更を追従させたいはずだ。相対値のline-heightがないと、h1を変更する場合に以下のようになってしまう:

    h1{
        font-size:24px;
        line-height:32px;
    }
    
    /**
     * Main site `h1`
     */
    .site-title{
        font-size:36px;
        line-height:48px;
    }

この例のように初期値が柔軟でないため、固定のline-heightを無制限に追加しつづけることになってしまう。単位なし、そして相対のline-heightがあれば、単純に以下のようにするだけで済む:

    h1{
        font-size:24px;
        line-height:1.333;
    }
    
    /**
     * Main site `h1`
     */
    .site-title{
        font-size:36px;
    }

大きな違いのように思えないかもしれないが、大規模プロジェクトのテキスト要素を考えてみれば非常に大きなインパクトになる。

注意: line-height以外にも 多くの 場面に当てはめられる。基本的には、どんな スタイルシート内のハードコード値には注意と嫌疑が必要である。

ハードコード値には将来性がなく、柔軟でもなく、寛大でもないため避けるべきである。ハードコード値が 本当に 必要になる場面はスプライトくらいで、スプライトはどんな場合でも 常に 特定のサイズである必要がある。

ハードコード値をスタイルシートで見かけたら、なぜそれが必要なのか、どう回避できるかを知りたいと私なら思う。

力ずくでやる

ハードコード値と似た流れではあるが、もう少しだけ詳細だ。CSSを力ずくで書くということはハードコードされたマジックナンバーや様々な手段を用いて、力ずくでレイアウトをすることを意味する。例を見てほしい:

    .foo{
        margin-left:-3px;
        position:relative;
        z-index:99999;
        height:59px;
        float:left;
    }

この例は 酷い CSSだ。すべての宣言はハードコード値で、力ずくで、レイアウトに影響する宣言となっていて、 明確に 強制的に何かを描画させたいところに描画させるためだけに利用されている。

この手のCSSが示しているのは、このような操作が必要な貧弱にコーディングがされているか、ボックスモデルへの理解が足りないか、またはその両方だ。

上手にコーディングされたレイアウトは力ずくで行う必要は一切なく、 きちんと ボックスモデル、レイアウトを理解し、算出済みのスタイルをより頻繁に見るということでこのような状況になることは稀なはずだ。

力ずくのCSSを発見しだい、どうしてそれが発生したのか、どれくらい前の段階に戻ればレイアウトをより合理的に行うことができるかを知りたくなる。

危険なセレクタ

「危険なセレクタ」とは影響が広範囲にわたるセレクタを意味する。非常にわかりやすく単純な危険なセレクタの例は:

    div{
       background-color:#ffc;
       padding:1em;
    }

この例はどんなデベロッパに対してでもすぐにその叫びが聞こえるだろう。一体どうしてサイト上のすべてのdivに対して絨毯攻撃をしかけたいのか?これはいい質問だ。だとしたら、なぜaside{}header{}またはul{}というようなセレクタを使うのだろうか?このようなセレクタは、それはもう、 とてつもなく 影響範囲が広く、最終的には前段で書いたようにCSSの取り消しを行うはめになる。

header{}の例を詳しく見ていこう…

多くの人がheader要素を使ってサイトのメインとなるヘッダをマークアップする、それ自体はなんら問題はないが、もしサイト全体のヘッダを以下のようにスタイルしたとしたら:

    header{
        padding:1em;
        background-color:#BADA55;
        color:#fff;
        margin-bottom:20px;
    }

…この場合は問題がないとは言えない。header要素は「あなたのサイトのメインヘッダ」という意味には ならない。仕様によればheader要素は複数のコンテキスト内で複数回利用することができる。この場合は.site-header{}のようなセレクタでターゲットするべきだ。

このように具体的なスタイルを一般的なセレクタにあてるのは危険だ。そのスタイルはその要素を再度利用する場合に、適用するべきではないエリアにスタイルが漏れてしまい、結果スタイルを取り除く作業(スタイルを取り除くのに コードを追加する)で対抗するしかなくなってしまう。

セレクタはよいセレクタの目的をもたせるようにすること。

以下を見てほしい:

    ul{
        font-weight:bold;
    }
    header .media{
        float:left;
    }

セレクタがタイプセレクタか、上記のような非常にベーシックな抽象化をされたセレクタを見かけると、私はパニックになる。これらのセレクタは影響力が大きすぎるし、すぐに問題に直面することになるからだ。これらの要素を再利用したらすぐに必要のないスタイルを継承しているのを見ることになる。なぜなら、どこかで大きな影響力を持つセレクタがその要素にも影響を与えてしまっているからだ。

受動的な!important

!importantには問題はない。なんの問題もないし、当然、重要な ツールである。しかし、!importantは特定の状況下で利用するべきである。

!importantは能動的にのみ利用するべきで、受動的に使ってはならない。

どういう意味かというと、絶対に 常に スタイルを優先させたいということがあり、かつそのことが初めからわかっているというタイミングがあるということだ。

例えば、 常に エラー状態は赤くなる場合に以下のように記述することにはまったく問題がない:

    .error-text{
        color:#c00!important;
    }

もしエラーが常にテキストが青くなるdivで発生したとして、エラー時にそのルールを変更することに対して自信を持てる。エラーは 常に 赤で表現される、なぜならそれはエラーだからで、ユーザに対するメッセージは常に一定に表現するべきだからだ。常にエラーが赤であるべきだから、ここでは能動的に!importantを追加している。

!importantが悪さをし始めるのは、それらを受動的に使う場合だ。誰かを詳細度の問題から逃がしたい時に使われた場合とも言える。またはちょっとした苦境にいて、!importantを使って強制的に動作させるような場合だ。これらのケースは!importantを受動的に利用している場合で、これは悪い知らせとなる。

!importantを受動的に利用するということは、よろしくないCSSにより発生した問題を回避するためだけに利用したということになる。根源的な問題は一切解決できず、症状を抑えたにすぎない。問題そのものは存在しつづける上に、強力な詳細度を携えたレイヤを追加し、また1つ余計な詳細度問題を追加したにすぎない。

私は能動的に使っている限りでは、!importantに気がとがめることはない。受動的に!importantが使われていれば、すぐに質の悪い構造のCSSのせいだとわかるし、問題の解決にはリファクタが必要であり、性急な詳細度の追加などではないこともわかる。

ID

これは私の場合と、大きなチームの場合に特化している。以前にどうしてIDが悪いアイデアであることについては書いたとおり、IDは詳細度を引き上げ、誰も利用しないし、CSSで利用するべきではない。IDはHTML上のフラグメント識別子とJSのフックとして利用し、CSSでは利用しない。

利用は単純だ:

もし最後のポイントを読んでもIDを使うことを辞めないとしたら、私にはどうしていいかもはやわからない…

IDがスタイルシートにあれば、クラスと差し替えたくなる。詳細度こそが、プロジェクトが悪循環を始めるきっかけだ。だからこそ、小さく止めることが大切だ。

楽しいエクササイズ: この問題華麗に 解決してほしい。ヒント: これは華麗とはいえない、またはこちらも

※訳注: IDがクラスの255倍詳細度が高い〜という記述、およびクラスを256個連結してIDを上書きできる〜という記述について、現時点では特定のブラウザ上のバグであり、仕様としてそのように定義されているわけではありません。
それくらい詳細度が高いのがIDである、という風に思ってください。

ルーズなクラス名

「ルーズな」クラス名とはその目的を表現するのに十分な説明ができていないクラス名のことを指す。例えば.cardというクラスを想像してほしい。一体このクラスは何をするものだろうか?

このクラス名は非常にルーズだ、ルーズなクラス名は以下の主要な2つの理由で非常に悪いものだと言える:

初めのポイントはすごく単純だ。.cardは何を意味するのか?なにをスタイルしているのか、Trelloっぽいコンセプトでカードがコンポーネントになっているのか?ポーカーのウェブサイトでトランプに追加したクラスなのか?クレジットカードの画像を指しているのか?ルーズすぎるため、知るよしもない。クレジットカードを意味するものだとしよう、そうするとクラスは.credit-card-image{}であればよりわかりやすい。長くなった? そうだ。がより良くもなった? もちろん!

ルーズなクラス名における2つ目の問題が(過失により)簡単に再配置/再定義されてしまうことだ。ショッピングサイトで.cardを使ったとして、ユーザアカウントに紐づくユーザのクレジットカードのことを示しているとしよう。そして、他のデベロッパがやってきて、他人にメッセージカードを贈るオプションが付いたプレゼントが買える機能を追加するのを想像してほしい。きっとどこかで.cardを使いたくなるはずだ、(それは 間違い だが) そうなったらまず間違いなく(そんな状況がそもそも考えにくいが)、.cardクラスは再定義され、上書きされてしまう。

これらすべてはより厳密なクラス名を用いることで回避できる。.card.userなどのようなクラス名はルーズすぎて、理解するのが難しく、過失による再利用/上書きが発生しやすい。

ルーズなクラスを見かけたら、私ならいったい何を意味するのかを探し出し、名前を変更できるか聞くことだろう。
クラス名は十分に明細なものにするべきだ。

最後に

そういうわけで、これらが私がCSSにおけるコードが「臭い」と感じる、多くの 条件のうちの少しだ。これらは私が毎日のようにどんな犠牲を払ってでもさける努力をしている事柄だ。何ヶ月におよぶ(最終的には何年も)大きなプロジェクトの場合、統制をとることは 必要不可欠 であり、上にあげたような事柄、もちろん他の事柄も、に注意しておくことは最優先されるべきだ。(これらがどれほど小さなサブセットであるかについていくら強調しても足りない。もっと もっと多くの事柄について注意している。)

もちろんどんなルールにでもかならず例外は ある 。しかしそれらはケースバイケースで評価する必要がある。多くの場合、これらは私が苦労して避けている事柄であり、遠くからでもCSSに見つけることができるものばかりだ。