Hachirog

作業メモ

JavaScriptの書式と整形についてあれこれ

JavaScriptの書式はどうすればいいのか?

しばらくJavaScriptの案件から遠ざかっており、すっかり世の中の動向から取り残されてしまった、これはまずいと巷のJavaScript情報を収集していました。

すると、なにやら書式(インデントや単語間のスペースなど記述スタイル)も昔と雰囲気が変わっているぞ、と気になって動向を調べることに。 しかし、調査してみたものの結局のところどうすればいいんだ、よく分からんとさじを投げるに至りました。

書式のトレンドは?

いまやインデントは2スペースがメジャーな模様。昔の書籍は4スペースを推奨されていたような気がします。 最近の人気OSSや有名企業のGithubをのぞいてみると2スペースが多く見受けられました。 JavaScript MVCフレームワークのAngular、React、Vueも2スペース。NodeJSも2スペース。

きりがありませんが、最近は2スペース派が勢力を伸ばしているということが分かったところで JavaScript Standard Styleというスタイルガイドが話題になっているということも知りました。
行末にセミコロンをあえてつけないという、これまでの私の見聞とは真逆のルールで衝撃的でした。 しかし、これも世の流れと思い、取り入れてあれこれ書いてみたのですが、なるほど確かにコードがすっきりすると思うようになりました。 住めば都というものですね。また何よりもStandardという名前が強烈です。 本当にスタンダードになるかもしれないと期待感を抱きこのスタイルでしばらくやってみようと決めました。

構文チェックツール

構文のチェックツールを調べてみると、ESLintがメジャーであると知りました。 書籍 JavaScript: The Good Parts の中でJSLintを知りましたが、現在はESLintがデファクトスタンダードのようですね。

書式のチェックに加え、マジックナンバーの禁止やら複雑度の制限やらルールの豊富さに圧倒されます。 細かく設定するのは骨が折れそうだと思いつつも、構文チェックツールはESLintを使えば間違いなさそうだと思いました。

整形ツール(フォーマッタ)

ソースコードの整形はエディタの標準機能でも行える場合も多々ありますが、コマンドライン上でも整形をしたいところです。 (往々にしてチーム内のエディタの好みがバラバラだったりするので、整形がエディタ依存だと厄介)

その用途にあう整形の手段としては調べたところ次の3つが有力そうでした。

ESLintの --fix オプションで整形

ESLintはルールに違反した箇所の検出だけでなく、--fix オプションで自動修正する機能もある模様。
構文チェックのルールがそのままフォーマッタのルールともなるので無駄がない。

しかしながら、この機能を利用するのは若干不安に感じました。
どのルールがどう修正されるのか(あるいは修正されないのか)、めっぽうたくさんのルールがあるので把握しきるのが大変そうです。

例えば、インデント程度なら修正してくれて全く問題ないのですが、マジックナンバーを禁止するルールで --fix オプションはどういった挙動になるのか。
たぶん何も起きないんでしょうが、そういったことをいちいち確認していくのが辛いのではないかなと思いました。

というわけで、ESLintはあくまで構文チェックだけの用途にとどめることにしました。 --fix オプションは今後もう少し調べてみたいと思います。

JS Beautifyで整形

整形に限定したツールが欲しい、という需要にマッチするのがJS Beautifyでした。 ダウンロード数も多く、アップデートが続いているようなので安心感があります。何より以前、私も使っていました。 JSの整形に加えてHTMLやCSSに対応しているのもありがたいです。 VS CodeにもJS Beautifyに対応したプラグインがあったので、利用させていただくことに。

ところが、しばらく便利に使っているとちょっとした不都合に遭遇しました。 Reactに挑戦してみようとチュートリアルを試していたときです。 JSXの整形がうまくいきません。

// チュートリアルのサンプルコードが
class Square extends React.Component {
    render() {
      return (
        <button className="square" onClick={() => alert('click')}>
          {this.props.value}
        </button>
      );
    }
  }

// 整形すると崩れる
class Square extends React.Component {
  render() {
    return ( <
      button className = "square"
      onClick = {
        () => alert('click')
      } > {
        this.props.value
      } <
      /button>
    );
  }
}

調べてみるとオプションの指定でXML部分の整形を適用外にすることができるようです。 確かにこれで難をしのげるのですが、XML部分のスタイルを整えようと思ったら自分で編集する必要があります。

// e4xオプションを有効にするとXML部分を崩すこともしなければ揃えることもしない
  render() {
    return (
            <div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
    </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
               </div>
    <div className="board-row">
       {this.renderSquare(6)}
      {this.renderSquare(7)}
                {this.renderSquare(8)}
     </div>
      </div>
    );
  }

Prettierで整形

Reactを使っている人はどうやって整形しているんだろう、と思って調べているとPrettier という整形ツールを知りました。 JavaScriptはもちろん、JSX、TypeScript、JSONCSS、SCSS・・・と幅広く対応しており今後もサポート対象を広げていく模様。

// さきほどのガタガタなXML部分もPrettierなら整えてくれる
render() {
  return (
    <div>
      <div className="board-row">
        {this.renderSquare(0)}
        {this.renderSquare(1)}
        {this.renderSquare(2)}
      </div>
      <div className="board-row">
        {this.renderSquare(3)}
        {this.renderSquare(4)}
        {this.renderSquare(5)}
      </div>
      <div className="board-row">
        {this.renderSquare(6)}
        {this.renderSquare(7)}
        {this.renderSquare(8)}
      </div>
    </div>
  );
}

素晴らしいですね。

実際に私がJSXをガリガリ書いていくか、そもそもReactを利用するかどうかは分かりませんが、 Prettierを使っておけばどう転んでも問題なさそうです。

これで決まり

ようやく着地点が見えました。

  • 書式: JavaScript Standard Style
  • 構文チェック: ESLint
  • 整形: Prettier

具体的には以下のようにしました。

  • JavaScript Standard Styleの書式にあわせてPrettierの設定ファイルを作成
  • ESLintのルールはJavaScript Standard Style(eslint-config-standard)
    • ESLintは構文チェックだけで --fix オプションは使わない
    • その他、有用そうな構文チェックルールは適宜追加していく方針にする
  • エディタはVS Codeを使用

これで安心してコーディングができます。

と思ったら、まだ終わっていなかった

ようやく納得のいく構成になったところで心機一転JavaScriptを書き進めていました。

しかし、しばらくしてまたもや不都合が生じました。なにやら関数宣言のところでESLintがエラーを吐いていました。

f:id:hachir0:20180224020809p:plain

JavaScript Standard Styleのfunctionの後にスペースが必要というルールの違反でした。

// functionキーワードの後ろにスペースが必要
const hello1 = function () {
  console.log('hello')
}

// 関数名の後ろにもスペースが必要
function hello2 () {
  console.log('hello3')
}

// オブジェクトやクラス内での関数宣言(ES6)でも同様
const obj  = {
  hello3 () {
    console.log('hello')
  }
}

一番上のケースについては アロー関数 で宣言するようにすれば、functionキーワードが登場しないのでほとんど問題がないかもしれません。 (実際、私は極力functionキーワードを使わずにアロー関数にする方針でいたのでルール違反に該当していませんでした。) しかし、三番目のオブジェクトやクラス内での関数宣言の記法は一般的に用いられていくと思います。

そして厄介なのが、Prettierではこのスペースに対処できるオプションが設けられていない点でした(2018年2月時点)。 現在、議論中という状態です。

関数宣言のスペースに関する議論

Prettierではこの議論が次のissueから始まり、

github.com

その後、2つのissueに別れ継続しています

github.com

github.com

そもそもJavaScript Standard Styleではどういった論拠でスペースありというルールにしているのか。 JavaScript Standard Style以前にはあまり見かけたことがなく、スペースなしが一般に普及しているように感じられます。

Githubリポジトリにも異口同音の質問が投稿されていました。

スペースありとする理由を読み解くと以下のようです。

  1. JavaScript Standard Style はそう決めているから
    • どちらが優れているかという議論は不毛
      • 両者の言い分それぞれに正しさがある
    • 決定することが大事
      • JavaScript Standard Styleではスペースありというルールにしただけのこと
  2. 関数宣言と関数呼び出しが見分けられる
    • スペースがあれば関数宣言
    • スペースがなければ関数呼び出し
    • 単純なルールで区別できるのでgrep検索でも見分けやすい

確かに関数宣言と関数呼び出しを見分けられるという理由はもっとものように感じます。

// 関数宣言
function hello () {
  console.log('hello');
}

const obj = {
  // 関数宣言
  hello () {
    console.log('hello');
  }
}

// 関数呼び出し
hello();

また別の観点として、functionキーワードの後のスペースは他のキーワードと同様にして一貫すべきという意見もあります。

//  ifキーワードの後にスペース
if ( count > 10 ) {
   console.log(count)
}

// forキーワードの後もスペース 
for (let i = 0; i < 10; i++) {
   console.log(i)
}

//  "function" もキーワードなのに後にスペースがないのは不揃い
const hello = function() {
  console.log('hello')
}

そういった観点も踏まえてPrettierのissue #1139は、

  • 関数名の後のスペース(#3845)
  • functionキーワード後のスペース(#3847)

に分けて議論すべきということで、二つのissueに別れました。

よく分からなくなった・・・

一連の議論を読んでみて、functionキーワード後のスペースについては確かに他のキーワードと同様に指針を揃えるべきではと思うようになりました。 ただ、関数名の後のスペースについて、Prettierのissue #1139で次のような指摘があります。

https://github.com/prettier/prettier/issues/1139#issuecomment-361718553

f:id:hachir0:20180225103324p:plain

JavaScript自体にはない機能ではあるものの、Genericsのことを考えると何がよいのか分からなくなってきました。 いずれはTypeScriptにも挑戦してみたいと思っていた私にとっては気になる点です。

// 関数名の後にスペース
function identity <T>(value: T): T {
  return value;
}

// 関数名、Genericsの後にスペース
function identity<T> (value: T): T {
  return value;
}

どちらのパターンにしても私には奇妙に見えてしまうのですが、Javaではこのようにスペースを入れる書き方をした事がないからかもしれません。 TypeScriptの公式ドキュメントでも関数名、Generics、括弧の間にスペースがありません。 MicrosoftC#のドキュメントを見てみると同様のようです。 今更ですが、そもそもその他のプログラミング言語を見回しても関数名の後のスペースはマイナーなようです。

エディタ標準のJavaScript整形機能ではどうなっているのでしょうか。

VS Codeではスペースが除去されることを確認できました。

Atomでは・・・

というところで気力が尽きました。 ここまで来て、なんだかJavaScript Standard Styleにこだわらなくてもいいような気がしてきました・・・。

結局どうする?

JavaScript Standard Styleを採用したい場合は、 eslint-config-standardを導入してESlintの--fixオプションで整形するのが良さそうです。

Prettierを使用しつつJavaScript Standard Styleを実現するnpmパッケージやエディタのプラグインもあるようです(内部でPrettierとESlintを併用している模様)。

私はひとまずJavaScript Standard Styleへの熱がクールダウンしてしまったので、いったんはPrettierをデフォルト設定で利用するでよいかなと思い始めました。 ESLIntもPrettierのルールにそったもの(eslint-config-prettier)を利用し、その後、良さそうなルールを個別に追加していこうと思います。

JavaScriptの書式は今後どうなるのか。動向を見守りたいと思います。