ESLintを活用した漸進的リファクタリングのすすめ

この記事はドワンゴアドベントカレンダー2019の3日目です。

qiita.com

TL;DR

  • リファクタリング前の実装をESLintで指摘しよう
  • disabledコメントで黙らせて、リファクタリング待ちのマークにしよう
  • 指摘するためのルールにちょうどいいものがなければその場で作ろう

ベルリシア(@berlysia)という名前で活動しています。Webフロントエンドを少々やっています。ドワンゴでは N予備校 のWebフロントエンド開発と、 N Air の開発を兼任しています。

何の話

今回は、N予備校のWebフロントエンド開発でも実際に活用している、ESLintを活用した漸進的(=少しずつ進めていく)リファクタリング術を紹介します。

リファクタリングしたいことって、たくさんありますよね。一瞬で終わるものなら良いですが、手のかかりそうなことも少なくないでしょう。日々の開発も進めなければいけないなかで、リファクタリングにまとまった時間をとるのはなかなか難しいのが実情です。

隙を見ながら少しずつリファクタリングしていくのなら、いつでも速やかに手をつけられて、いつでも手を止められて、進めたぶんは忘れていいようになっていてほしいです。

古い流儀を参考にした新しい実装がうっかり発生することも防がなければいけません。レビューで見つけられればよいですが、うっかりにはうっかりが重なりえて、あんまり信用できませんね。人間が気をつけることは少なくするべきです。コードの問題なのだからコードの中に記述して、勝手にチェックされて、CIで落ちてほしいわけです。そうですね、自動化です。

つまり、漸進的リファクタリングのために目指したい形としては:

  1. これ以上リファクタリング前の形式が増えない
  2. リファクタリングしたい場所がマーキングされて検索可能
  3. マークされた場所の何が問題で、どんな風にリファクタリングすればいいかわかる

が満たされればいいことになります。

がんばる前に手を抜く

一瞬で全部置き換えられるなら、それが一番いいですね。

簡単な書き方の変更であれば自動修正が使えるかもしれません。たとえば最近話題の Optional chaining operator などは、TypeScriptで開発していて @typescript-eslint/eslint-plugin を利用している場合なら @typescript-eslint/prefer-optional-chain が利用可能です。

ルールになっていなくとも、正規表現でがんばったり、 jscodeshift を使ったりすることを検討すると、できることの幅が格段に広がることでしょう。*1

でも、長く付き合うことになるリファクタリングって、こういうことが簡単にできる性質のものではないですよね。それぞれの場所で手がかかって、一気には倒せそうになく、それでも倒したいから長期戦を覚悟したはずなのです。

では長期戦に備えて、自動で怒られる仕組みを作っていきましょう。

ESLintでがんばる

アプローチ

これ以上リファクタリング前の形式が増えない、に対して

ESLintを使って、リファクタリング対象の記述が報告されるようにします。

例えば、webpack等を前提にCSSをimportする記述を報告したいときは、 no-restricted-syntax を活用して、次のように設定できます。

// ありがちなCSS Modulesの記述
import styles from "./style.css";
module.exports = {
  rules: {
    "no-restricted-syntax": [
      "error",
      {
        // 末尾が .css で終わるパスからのimport文にマッチ
        selector: "ImportDeclaration[source.value=/\\.css$/]",
        // どう置き換えたらいいかを書いておく
        message: "styled-componentsを使用してください"
      }
    ]
  }
};

リファクタリング前の箇所は disableコメント で無効にします。無効にするだけなら ESLintのoverrides で無効にしてもよいのですが、次の項目のマーカーとして役立ってもらうためにも、コメントを使いましょう。

// eslint-disable-next-line no-restricted-syntax
import styles from "./style.css"; // エラーにならない

上記の用途から、ファイル全体ではなく行単位で無効にする、 eslint-disable-line eslint-disable-next-line を使うのが望ましいです。加えて無効にするルールを明記するようにしましょう。 eslint-plugin-eslint-comments を採用してこのあたりもESLintに指摘させるのがおすすめです。

でも、そんなに都合よく、いい感じのルールを見つけられるでしょうか

リファクタリングしたい場所がマーキングされて検索可能、に対して

disableコメントを検索したらいいじゃない……で済みそうならよいのですが、 no-restricted-syntaxAST Selector でマッチ対象を表現できることから極めて便利です。同じルール名で様々なリファクタリング対象を検出することになりやすいです。ルール名から何を指摘されている箇所なのかを想像することも、難しい名前です。

すると、disableする段階でも、明らかに特別な用途だとわかるといいかもしれません。ユニークなルール名になっていると、検索して0件だったらリファクタリングが終わっているとわかります。disableコメント自体が単体では残らないようになっていたり、ルールの設定自体も用が済んだら捨てていいとはっきりしていると、将来的に幸せになれそうです。

マークされた場所の何が問題で、どんな風にリファクタリングすればいいかわかる、に対して

no-restricted-syntax を使うとマッチしたセレクターごとにメッセージもカスタムできるので、エラーの中に十分な説明を与えられます。あまり長くなっても嫌なので、gistやwikiなりで経緯を書いてURLを埋め込んでおくと便利でしょう。

見つかった課題と対処

リファクタリングが完了したのにコメントだけ残る

reportUnusedDisableDirectives を使って検出しましょう。warnが出るので、見つかったコメントを消して、おしまいです。

残りの問題

  • いい感じのルールが見つからないときどうするか
  • ルールが特殊な用途をもつとわかること(いつか捨てるルールだとわかること)

残りの2件には、「自前でいい感じのルールを書き」「特殊な用途とわかる名前をつける」で対応できそうです。隙間時間で進めたいリファクタリングのお供ですから、ルールを作るのは一瞬であってほしいです。

ルールがないなら作る

ESLintプラグインやルールの作り方自体は本筋でないので説明をしませんが、いくらか参考資料を示しておきます。

ルールを作る上で便利なサイトとして、 AST explorer も紹介しておきます。これでコードから構文木が透けて見えない人でも安心です。細かいプロパティを覚えておかなくてもよくなるので、手放せないサイトです。

ルールの作り方を知ったあとは、自動生成できるようにしておくといいでしょう。Yeomanのジェネレーターが generator-eslint として提供されているので、使うと便利です。(私は出力が手に馴染まなかったので自前で 作りました が、できることは本質的に同じです)

作ったルールを使う

Runtime Rules

ESLintには Runtime Rules の仕組みがあります。VSCode上での動作とnpm-scripts用の記述で二重管理になりがちなので、この形をとるなら eslint-plugin-rulesdir を使うとよさそうです。

このパターンでは、ルール名の段階でリファクタリング用だとわかるようにしておくといいでしょう。 refactoring のような語を入れてみたり、 no-more-hoge-module のようにストレートな名前にしてもいいと思います。

// eslint-disable-next-line no-more-hoge-module
import { hoge } from "../features/hoge-module";

すごくいけないことをしている場所に見えますね。

ESLintプラグインの体裁を整えてプロジェクトの依存に入れる

例は省略しますが、このパターンでも同様に、いい名前を与えてやると扱いやすくなりますね。

実際にN予備校で使用しているプラグインは、 @berlysia/eslint-plugin-refactoring としてscopedモジュールで公開しています。*2 特に他の方が使う用には考えていないので、参考が必要な方は参考にしてみてください。

まとめ

この記事では、N予備校のWebフロントエンドで実用している、漸進的リファクタリングを支えるESLintの活用法を紹介しました。

コードを指摘する用途で、ESLintは極めて強力です。日々のレビューの中で人間が気にすることを減らすためにも、うまく使い倒しましょう。

*1:jscodeshiftについては、 JavaScript Advent Calendar 2019 - Qiita に素敵な記事が上がったところです JavaScriptのリファクタリングツール「jscodeshift」の使い方 - Qiita

*2:本当は no-restricted-syntax で足りる内容でしたが、この記事を書きあげるまで完全に忘れていました。いい名前がついてインターフェースが整っていることに価値があるので、よいことにします。