Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
85
Help us understand the problem. What is going on with this article?
@bellbind

ES6時代のコーディングスタイルを考える

More than 3 years have passed since last update.

ここではコーディングスタイルを考える上で、ES6(ECMAScript2015)の以下の特徴に注目して考察するものです。

  • 変数宣言は、constを基本とする
  • クラス宣言文や関数宣言文はブロックスコープ
  • 文字列では、テンプレートリテラルを活用する
  • ジェネレータのために、複数の構文が拡張された

また、以下の基準を採用していることを特に触れておきます。

  • ES5コードの混在、ES5時代のスタイルとの互換性は取らない
  • ES6 modulesが存在しない状況で適用可能なスタイルを取る
  • 視認性を犠牲にコーディング時のミスの影響を隠蔽するスタイル(ヨーダ記法など)は取らない
  • 局所視認性より、全体視認性での高さを重視する
    • 微妙に違うものを区別しやすいスタイルにする
    • 同じものは同じスタイルを取る
    • 一覧しやすくコンパクトに書ける方を採用する

変数宣言は、constを基本とする

ES6では再代入を認めないconst変数が導入されました。そしてビルトインの機能も合わせて、コード中の変数の大半はconst変数で済むようにできる状況になりました。

変数は、その利用範囲がなるべく小さくなるように用いる

  • 変数は、使う場所のなるべく近くで宣言する
  • 可能な限りconst変数を基本とし、そうできない場合に限り再代入可能なlet変数にする
    • var変数はグローバルコンテキストに影響させる目的でのみ用いる(関数内では用いない)
    • for-of ループでは、const変数を用いる
  • 変数1宣言文での複数変数宣言は、論理関係で同等になるもの(xywidthheightなど)だけでまとめるとよい

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 + 1h = A[i])に、最後の値と同じ値だった場合(h === r[l - 1])はそのまま(r)が、違う場合では最後にその値を追加したもの([...r, h])が結果である」が任意の入力全体に対して成り立つのがuniqの意味の本質であって、(そこに「最後の値」がない最初の状況での関係を加えたうえで)再帰呼び出し部分にそのまま記述しています。

これを表現するのに何行もあるとどうでしょう。その構成要素である変数が長い範囲に散らばってたら、正しいこと(もしくはバグが入った時どこが間違ってるか)を判別するのにどれだけかかるでしょうか。

85
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
bellbind

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
85
Help us understand the problem. What is going on with this article?