ここではコーディングスタイルを考える上で、ES6(ECMAScript2015)の以下の特徴に注目して考察するものです。
- 変数宣言は、
const
を基本とする - クラス宣言文や関数宣言文はブロックスコープ
- 文字列では、テンプレートリテラルを活用する
- ジェネレータのために、複数の構文が拡張された
また、以下の基準を採用していることを特に触れておきます。
- ES5コードの混在、ES5時代のスタイルとの互換性は取らない
- ES6 modulesが存在しない状況で適用可能なスタイルを取る
- 視認性を犠牲にコーディング時のミスの影響を隠蔽するスタイル(ヨーダ記法など)は取らない
- 局所視認性より、全体視認性での高さを重視する
- 微妙に違うものを区別しやすいスタイルにする
- 同じものは同じスタイルを取る
- 一覧しやすくコンパクトに書ける方を採用する
変数宣言は、const
を基本とする
ES6では再代入を認めないconst
変数が導入されました。そしてビルトインの機能も合わせて、コード中の変数の大半はconst
変数で済むようにできる状況になりました。
変数は、その利用範囲がなるべく小さくなるように用いる
- 変数は、使う場所のなるべく近くで宣言する
- 可能な限り
const
変数を基本とし、そうできない場合に限り再代入可能なlet
変数にする-
var
変数はグローバルコンテキストに影響させる目的でのみ用いる(関数内では用いない) -
for-of
ループでは、const
変数を用いる
-
- 変数1宣言文での複数変数宣言は、論理関係で同等になるもの(
x
とy
、width
とheight
など)だけでまとめるとよい
ES5以前のvar
変数には、関数スコープおよび宣言部のhoisting(巻き上げ)があって、その存在が有名なコーディングスタイルに大きく影響を与えていました。とりわけ、変数宣言を関数の頭にまとめるスタイルは、モダンなプログラミング言語で行う**「変数の影響範囲をなるべく小さくする」**コードデザインを阻害し、しかも複数箇所で同名変数を再利用する可読性の低いコードが量産しました。const
変数では、このような頭での一括宣言自体が不可能になります。
const
及びlet
変数は、その範囲をなるべく小さくなるよう宣言します。なるべく後ろ側に、変数を使う式に近いところに配置します。このため変数宣言は、その論理的な意味の違いによって、ブロック内の別々の位置に来ることが基本です。
(for of
ループでのconst
変数の利用は、firefoxで長らく使用できない状態でした。firefox-51にてやっと利用可能になったようです。)
インデントは2スペース
- 2スペースのインデントを用いる
-
const
宣言文の折り返しは6スペース、let
宣言文の折り返しは4スペースを用いる
const
が5文字であるため、宣言文で複数の変数で折り返して記述する場合、6スペースで折り返すと変数の頭が揃います。4スペースだとconst
宣言文の折り返しだけが行頭が違う状況になります。
クラス宣言文や関数宣言文はブロックスコープ
- 関数オブジェクト式を与える変数宣言文ではなく、関数宣言文を用いることを考える
クラス定義や関数定義分も変数同様ブロックスコープになりました。
ちなみに、引数のデフォルト値の式は、関数内(の頭)で順に評価されるコードです(Pythonのような宣言時で1回の評価ではない)。値でキャプチャしてるわけではないので、副作用のある変数を使う場合には注意すること。
// tail call recursion
function uniq(A, i = 0, r = [], h = A[i], l = r.length) {
if (i === A.length) return r;
return uniq(A, i + 1, l === 0 ? [h] : h === r[l - 1] ? r : [...r, h]);
}
console.log(uniq([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])); //=> [1, 2, 3, 4]
文字列では、テンプレートリテラルを活用する
- 文字列リテラルは、ダブルクォート
"
を基本で用いる - 文字連結は、
+
を使わず、すべてテンプレートリテラルで行う - シンボル用途は、なるべく文字列を使わず、
Symbol
値を用いる
ES6では、バッククォートでくくった文字列を作るテンプレートリテラル構文が、追加されました。テンプレートリテラル内には${式}
を記述でき、その式の評価値から文字列が展開されます。
文字列の結合では、+
やArray
に入れてjoin('\n')
を用いたスタイルが使われていたが、テンプレートリテラルはその用途を広くカバーすることになります。
const line = `${name}: ${value}`;
またES5のコーディングスタイルでは、HTML属性との兼ね合いから、シングルクォート'
を基本にする文字列を採択するものが多いが、シングルクォートと(シンタックスハイライトを行っていても)バッククォートとの識別性が、とくにターミナル上で、悪いという欠点があります。
また、シングルクオート文字列をシンボルの代わりに用いるスタイルも存在します。この場合は、クォート文字で区別するのではなく、ES6で追加されたSymbol
を用いるのが望ましいです。
const localProps = Symbol`props`;
のように、テンプレートリテラルでシンボルを書くことも可能でしょう(式展開は使えないが、シンボルなら文字列埋め込みのほうがよい)。
ジェネレータのために、複数の構文が拡張された
-
function *()
やyield *iter
ではなく、function* ()
、yield* iter
を用いる - オブジェクトやクラスのメソッド構文でのジェネレータは、
* method()
や* [method]()
ではなく、*method()
、*[method]()
を用いる
function
のあとの*
のあるなしではなく、function*
の時点で、ジェネレータであると識別できることを重視した。
ちなみにyield*
式の評価値は、(pythonのyield from
同様)ジェネレータのreturn
の値が得られる。Array
など普通のイテレータではundefined
になる。
その他
- 一括代入文は、カンマ式ではなく、
Array
のdestructuringを使う
// euclidean algorithm
while (b > 0) [a, b] = [b, a % b];
- 1引数アロー関数式の括弧の省略
const len = (str) => str.length; //代入式の右辺の場合、括弧を入れておく
["a", "ab", "abc"].map(s => len(s)); //引数の場合、括弧は省く
-
method.bind(this)
は使わず、アロー関数() => this.method()
を使う -
使わない引数名には
_
や_1
(ブロック内でlodash等は使わない前提) -
オブジェクトの情報隠蔽は、
class
ではなく、Proxy
を使う
追記: 実行速度に関して
シンプルなコードは、最速なコードではないでしょう。極端な話をすると、JavaScriptの実行環境では、Array
にアクセスするコードは、ループを変数で展開すると実行時間が大幅に短くなるが、コードの量とその複雑度は上がります。
しかし普通は、小さくシンプルであるほうが読みやすく、なによりバグが入る隙が小さいです。そして実行速度第一で書いたつもりの未成熟なコードとくらべても、小さなコードは十分速いでしょう。たとえば上述のuniq
はたった4行だけど末尾再帰です。速いつもりではじめから書かれたコードで実際どれだけ速いでしょうか。
プログラムは、「任意の入力データと結果の間の全体関係」の記述という側面があります。それは処理中の状態でも常に成立する関係(不変条件invariant)であり、それこそが処理の正しさの源です。シンプルなコードというのは正しさがその記述から自明であることです。このuniq
でいえば、「要素が一つ増えた時(i + 1
でh = A[i]
)に、最後の値と同じ値だった場合(h === r[l - 1]
)はそのまま(r
)が、違う場合では最後にその値を追加したもの([...r, h]
)が結果である」が任意の入力全体に対して成り立つのがuniq
の意味の本質であって、(そこに「最後の値」がない最初の状況での関係を加えたうえで)再帰呼び出し部分にそのまま記述しています。
これを表現するのに何行もあるとどうでしょう。その構成要素である変数が長い範囲に散らばってたら、正しいこと(もしくはバグが入った時どこが間違ってるか)を判別するのにどれだけかかるでしょうか。