LIVESENSE ENGINEER BLOG

リブセンスエンジニアの活動や注目していることを発信しています

CSSは簡単だなんて誰が言った

はじめまして!正社員転職サービス転職ナビ(旧ジョブセンスリンク)のフロントエンドを担当しているsueshinです。
CSSはプログラミング経験の浅い方でも扱いやすく、比較的習得が早い言語だと言われることが多いと感じている次第です。
もちろん、それはCSSの素晴らしい側面の一つです。
一方で、設計のアンチパターンと言われることが、CSSでは積極的に採用され、長期の保守になると難しいという側面もあります。
今回は企業様用採用管理ツールのCSSの保守で苦戦し、リファクタリングした時のお話をいたします。
お手柔らかにどうぞ。
尚、ソースコードは一部記事に合わせて表現を変えています。

なぜCSSをリファクタリングしたの?

企業様用採用管理ツールのCSSをリファクタリングする前は以下のような大きな問題を抱えていました。

管理されていないCSS

転職ナビはリブセンスの創業初期からある歴史あるプロダクトです。
デザインリニューアルだけでなく、ブランド名変更によるリブランディング等、度重なる大規模変更が過去にあり、数々の負債を抱えながら成長してきました。
そのため、非常にプロダクトが複雑で、社内用の管理ツール向けのCSSを修正すると企業様用採用管理ツールのビジュアルが崩れる等、非常に保守コストがかかっていました。
また、CSSの格納ディレクトリは全てのアプリケーションのCSSを含んでいるため、40000行を超え尚肥大化し続けています。
以下はCSSディレクトリの抜粋です。

css
├── 2009/
├── file/
├── user/
├── htmlMail/
├── lp/
├── manage/
├── pc/
├── pr/
├── sp/
├── admin/
└── blog/

意味のないファイル名

下記は企業管理画面のディレクトリツリー抜粋です。
一見、トップページ用にしか見えない命名のファイルがあらゆるページで使用されていました。
他にもページ単体で使用されているように見える命名のCSSが、多数のアプリケーション全体で使用されたまま放置されており、開発は長らく混乱していました。

manage
├── recruit/
│   ├── top.css
│   ├── works.css
│   ├── works_hint_detail.css
│   └── works_title.css
├── scout/
└── vendor/

どこにいったDRY原則

開発メンバが既存コードに合わせて実装しようとして、フロントエンドエンジニアの私に質問してくれても、そもそもの保守方針はなく、残念ながら歯切れの悪い回答しかできませんでした。
結果、開発スピードを重視して繰り返されるハードコピー。
DRY原則を意識した再利用性のあるコードを書けず、私はずっと歯がゆく思っていました。

いまどき生CSSなんか触りたくないと入社2年目の後輩にいわれた

大人でも辛くて泣きたくなる時はあります。

要約すると

問題点を並べましたが、成し遂げたいことを箇条書きにするとこのようになります。

  • 他のアプリケーションを修正したらバグがでるという状況を阻止する
  • 保守方針を立てる
  • CSSからSassへ移行することで、より保守を容易にする

具体的にしたこと

以下の手順で進めました。

  1. コードリーディング
  2. Sassの導入
  3. アプリケーションをまたいでいるCSSをひっぺがし
  4. 各ページ単体で使用しているCSSをそれぞれディレクトリごとに分類
  5. リンター導入
  6. README.mdを作成

1.コードリーディング

まず手始めにひたすらコードを読みました。
そうすると、読み進めていくうちに既存コードの以下のような特徴に気づきました。

  • 要素タグに対するスタイル定義がCSSファイルの上部に固まっている
  • 多くのページで呼ばれているCSSファイルに組み合わせが存在する
  • (一部のCSSファイルでは)id属性とclass属性で汎用性別に使い分けられていそう

これらを踏まえると下記のようにCSSを分類できそうだと判断しました。

  • ブラウザ定義スタイルのリセット
  • 各要素タグ毎の基本的な装飾
  • グローバルで使用できるコンポーネント定義
  • 各ページ固有のコンポーネント定義
  • ヘッダーやフッターのレイアウト定義

2.Sass導入

コードリーディングの結果、大多数のURLで同じ組み合わせのCSSが呼ばれていることがわかったので、これらを一つのファイル(application.scss)として再定義しました。
これらはsymfonyやRoRではレイアウトファイルに記述がまとめられていることが多いと思うので、パスの置換作業などは比較的容易かと思います。
その後、作成したapplication.scssを複数のファイルに分割しました。
分割することで数千行あるCSSの可読性を高めることが狙いです。

3.アプリケーションをまたいでいるCSSをひっぺがし

別アプリケーション名のディレクトリに存在しているCSSがあったので、ファイルをコピーして作業対象の企業管理画面では、専用のディレクトリからしかCSSをよばないようにしました。

4.CSSを使用されているディレクトリごとに分類

この作業に一番時間がかかりました。
URL別にCSSを読んでいたのですが、top.cssなのにトップページ以外からも呼ばれており、判断に迷いました。
それらも踏まえて大別すると以下の3パターンに分類できました。
※尚、各ページのパスを/module/action/と考えています。(例えばトップページなら/home/indexとなります。)

  1. 複数のmoduleで使用しているがグローバルなCSSというほどではない
    • →commonディレクトリを作成
  2. 固有のmodule内でしか使用されていない
    • →module名のファイルを作成
  3. ひとつのactionでしか使用されていない
    • →moduleディレクトリにaction名のファイルを作成

実際のディレクトリ構成です。

├── common
│   ├── company.scss
│   ├── scout.scss
│   ├── top.scss
│   ├── view.scss
│   └── works-skill.scss
├── home.scss
├── info
│   ├── help.scss
│   ├── home.scss
│   └── kiyaku.scss
├── recruit
│   ├── plan.scss
│   ├── works.scss
│   └── works-title.scss
└── detail.scss

5.リンター導入

今回成し遂げたいことの一つは保守性の向上。
これを逃すと機会がないと思いやや強引ですが、ユーザー側で導入しているstylelintを導入しました。 各ファイル1行1行の修正は大変なので、ファイルごとに例外処理を追加して導入しました。

コード例

/* stylelint-disable selector-no-id, declaration-no-important  */

.test {
    color: #fff;
}

なぜ、例外処理を追加してでもリンターを導入したかというと、実装方針さえ決まっていたら、守ることが容易なルールがファイル内でさえ統一できていなかったからです。
目立ったところでいうと以下のようなルールです。

  • インデントはソフトタブかハードタブか
  • インデントの深さ
  • カラーコードは大文字か小文字か
  • 空行の有無
  • セレクタの後の半角スペース

6.README.md作成

本来はプロダクションコードからコーディングルールやクラス・ディレクトリ命名方針が類推できることが良いと思います。
ただし、今回のリファクタリングでは実装方針が定まっただけであり、コードの品質が高まっただけではないので、補足説明を手厚く書きました。

躓いたポイント

上記の手順を立て、リファクタリングを進めましたがスコープがあまりにも大きく、何度もつまずきました。
以下は躓いたポイントです。

1.CSSハック

設定を変更したら、変更できたのかもしれませんが、どうやらSCSSにも許せないCSSハックがあるようでした。
私はWEBを覚えて浅いこともあり、ブラウザ対応はIE8からしか対応したことがありません。
既存コードがどういった目的でCSSハックをしているのか、わからない記述も多かったです。
調べても中々良い情報に出会えず途方に暮れている中、信じられそうな情報に出会えた時は感動しました。

具体例としては、下記のようなスラッシュハックを

#footer {
  /zoom: 0;
}

こちらのアスタリスクハックに変更することで

#footer {
    *zoom: 0;
}

無事CSSへのプリコンパイルができました。

2.CSSファイルパスの変更

ファイルのパスを変えてCUIで一括置換していたのですが、以下のコードが存在する可能性が有るためにリファクタリング中の不安は拭えませんでした。

<link rel="stylesheet" type="text/css" href="<?php echo(''/css/manage/' . $val . '.css''); ?>">

このようにgrepしても検索にヒットしないファイルの存在による置換もれをどのように対応するかは最後まで悩みました。
他にいい方法が合ったのかもしれませんが、最終的に私はPHPディレクトリのCSSという文字列を全部grepして目視して確認しました。

3.CSS最大の難関、詳細度戦争

世界各地で問題になっているCSSの詳細度がやはり頭痛の種でした。
今回迷ったのは以下のような場合です。 例えばこのようなコードがあったとします。

//1
.modal {
    color: #aaa;
}
//2
#top-modal {
    color: #bbb;
}
//3
#container .modal {
    color: #ccc;
}
//4
.detail-modal {
    color: #ddd;
}

1のmodalと2のtop-modalの順番を変更した場合デザインに影響がある可能性はあるのでしょうか?

//2
#top-modal {
    color: #bbb;
}
//1
.modal {
    color: #aaa;
}
//3
#container .modal {
    color: #ccc;
}
//4
.detail-modal {
    color: #ddd;
}

答えは「可能性はない」です。
では、1のmodalを4のdetail-modalの下に変更した場合デザインに影響がある可能性はあるのでしょうか?

//2
#top-modal {
    color: #bbb;
}
//3
#container .modal {
    color: #ccc;
}
//4
.detail-modal {
    color: #ddd;
}
//1
.modal {
    color: #aaa;
}

答えは「可能性はある」です。
幸いID属性とクラス名は飛び飛びで固まっていたため、セレクタがID属性同士、class属性同士で順番を変更しないように整理しました。 可読性はあまりよくないですが、デグレーションを避けるため順番はできるだけ変えないようにしました。 application.scssの例

@import "layout/header";
@import "layout/container";
@import "layout/aside";
@import "layout/footer";
@import "object/project/modal";
.clearfix {
    *zoom: 1;
}
.clearfix:after {
    content: '';
    display: block;
    clear: both;
}
@import "object/project/tooltip";
@import "object/project/link";
@import "object/utility/margin";
ul.modBtn,
.modInfo dl {
    *zoom: 1;
}
ul.modBtn:after,
.modInfo dl:after {
    content: '';
    display: block;
    clear: both;
}

4.ユーティリティクラスがユーティリティじゃない

ユーティリティクラスとして定義されているけど、実際に使われていないユーティリティクラスをどのように分類していいか非常に悩みました。

.txtRed {
    color: #f44336;
}
.txtOrange {
    color: #f60;
}
.txtGreen {
    color: #53acac;
}
.txtGray {
    color: #999;
}

これらは開発メンバの誰も知らないクラスです。
ユーティリティクラスとして分類しても、開発メンバが余計に混乱すると考え、ユーティリティクラス専用のディレクトリに分類しませんでした。

5.リンター導入の作業は地道でした

一部例外処理として設定しましたが、可能な限りリンターのルールに違反する箇所は修正しました。
膨大なソースコードのカラーコードを小文字にしたり、ソフトタブとハードタブを統一したりと地味に膨大なコストがかかってしまいました。
守ってくれたら儲けものと思って、アプリケーションを作った際は、コーディング規約を真っ先に導入すべきだと思いました。

そしてリリース!!

やりました。
もう後輩にCSSなんか触りたくないなんて言われません。
数々の困難と組織的な諸事情もあり最初のコミットからリリースまで1年以上経ってしまい、隣の島にいた後輩はもう近くにはいませんが。。。
でも、問題ございません。
我々が欲しいのはハッピーエンドではなく、鍛え抜かれたハッピーマインドなのだから。(*1)

まとめ

  • 学び
    • 古くなった情報でも、レガシーコードを改善する上で役に立つことがある
  • 課題
    • フロントエンドエンジニア以外には改善が実感しにくいリファクタリングになってしまった
    • 大規模改修になるため画面チェックにコストがかかってしまった
  • 効果
    • 今ならあまり心を痛めること無くレビューができる気がします。
    • ソースコード量の減少に向けて舵を切ることができた。
    • 企業管理画面を修正するとリブセンス用のツールでエラーがでるということはありません
    • 現状を整理できたことで、開発メンバの学習コストがへった。
    • 新しい開発メンバが入ってくる度に怯えなくていい

さいごに

このリファクタリングを進める上で、チームメンバは勿論のこと、沢山のサイトや書籍に助けられました。
特にCSSハックに対する知見を共有しているサイトを夜中に見つけたときは、本当に感動しました。
この記事が少しでも負債と戦うエンジニアの助けになれば幸いです。

*1: 元ネタは矢沢あい著「ご近所物語」です