JavaScriptスタイルガイド

書籍「メンテナブルJavaScript」と Google JavaScript Style Guide 和訳 を参考に自分なりのJavaScriptスタイルガイドを用意してみた。適時アップデートする。

本家Google JavaScript Style Guide(英語)ではES6に対応したスタイルガイドに更新されているが、まだぼくのスキルでは難解のため、和訳も更新されるのを待ちたい(汗)。

基本フォーマット

1.1 インデントのレベル

インデントの各レベルは、2個の空白とする。タブは使わない。

// Good
if (true) {
  doSomrthing();
}

// Bad: 空白が4個
if (true) {
    doSomrthing();
}

1.2 文の終端

常にセミコロンを使う。

// Good
let name = 'Taketake';

// Bad: セミコロンがない
let name = 'Taketake'

1.3 1行の長さ

1行100文字とする。

1.4 改行

100文字を超える場合は、読みやすい形で複数行にする。

// Good: 2レベル分(空白4つ)をインデントする
callAFunction(document, element,
    navigator);

// Good: 長い関数名の場合
foo.bar.doThingThatIsVeryDifficultToExplain(
  document, element);

// Good: 引数を強調したい場合
foo.bar.doThingThatIsVeryDifficultToExplain(
    veryDescriptiveArgumentNumberOne,
    veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy,
    artichokeDescriptorAdapterIterator);

// Good: if文の場合、本体は1インデントレベルであることがポイント
if (isYear && isMonth && isDay &&
    isYourBirthday) {
  //...
}

// Bad: 演算子の前で改行されている
if (isYear && isMonth && isDay
    && isYourBirthday) {
  //...
}

// Good: 例外 変数を代入するとき
let result = somthing + anything +
                    somthingElse;

// Bad: 変数を代入するときに通常ルール
let result = somthing + anything +
    somthingElse;

1.5 空行

以下の箇所で空行を追加する。

  • if や for などのフロー制御文の前
  • メソッドの前
  • メソッドの中のローカル変数と最初の文の前
  • 複数行コメントや1行コメントの前
  • メソッド内の論理的セクションの前

1.6 命名

キャメルケース(小文字で始まり、その後に頭文字が大文字で単語が続く)を使う。

1.6.1 変数と関数

変数は名詞、関数は動詞を使う。

// Good
let count = 10;
let myName = 'Taketake';
let found = true;

// Bad: 関数と間違いやすい
let getCount = 10;
let isFound = true;

// Good
function getName() {
  return myName;
}

// Bad: 変数と間違いやすい
function theName() {
  return myName;
}

can, has, is はブーリアンを返す関数、
get は非ブーリアンを返す関数、
set は値を保存するために使われる関数、
として使う。

MEMO: 変数名である程度、型がわかるようにした方がいいかも。isFoundは名詞として使ってもいいし、numCountとすることでnumber型と分かるようにするのもいい。命名はもう一度熟考する必要あり。

1.6.2 定数

すべて大文字、単語の間はアンダースコアとする。

// Good
const MAX_COUNT = 10;
const URL = 'https://simplesimples.com/';

1.6.3 コンストラクタ

Pascalケース(先頭文字が大文字である以外はキャメルケースと同じ)を使う。

1.7 リテラル値

1.7.1 文字列

文字列にはダブルクオートよりもシングルクオートを使う。
常に1行内に収める。スラッシュを使って文字列内で改行してはいけない。

// Good
let name = 'Hi, I am Taketake.';
let name = "Hi, I'm Taketake.";

// Bad: シングルクオートのなかにシングルクオート
let name = 'Hi, I\'m Taketake.';

// Bad: 次行に折り返している
let longString = 'アイウエオ\
かきくけこ';

1.7.2 数値

数値は10進数の整数、e表記の整数、16進数の整数、小数点の前後に少なくとも1桁ずつある小数にする。8進数リテラルリテラルは使わない。

// Good
let count = 10;  // Integer
let price = 10.0;  // Decimal
let price = 10.00;  // Decimal
let num = 0xA2;  // 16進数

// Bad: 小数点で終わっている
let price = 10.;

// Bad: 小数点で始まっている
let price = .1;

// Bad: 8進数は非推奨
let num = 010;

1.7.3 null

undifinedと混同されがち。nullは、下記のケースで使う。

  • オブジェクトの値が代入されないかもしれない変数を初期化するとき
  • 初期化された変数と比較するとき
  • 関数にオブジェクトを渡すとき
  • 関数からオブジェクトを返却するとき

null を使うべきではないケース

  • 引数が渡されたどうかをテストするときには、null は使わない
  • 初期化されていない変数と値 null の比較テストをしない
// Good
let person = null;
// Good
function getPerson() {
  return (condition)? new Person('Taketake'): null;
}

// Good
let person = getPerson();
if (person !== null) {
  doSomething();
}

// Bad: 初期化されていない変数とテストしている
let person;
if (person != null) {
  doSomething();
}

// Bad: 引数が渡されたかをテストしている
function doSomething(arg1, arg2) {
  if (arg2 != null) {
    doSomethingElse();
  }
}

1.7.4 undefined

nullと混同されがち。
undefined は使わない。変数が宣言されているかどうかを利用するときにのみ、typeof演算子を使って undefined を使う。
それ以外、つまり後でオブジェクトの値が代入されるかもしれないし、されないかもしれない変数を使うときは、null で初期化しておく。

// Good
if (typeof variable === 'undefined') {
  doSomething();
}

// Bad: undefinedリテラルを使用
if (variable === undefined) {
  doSomething();
}

1.7.5 オブジェクトリテラル

  • 開き括弧は、それを含む文と同じ行にあること
  • プロパティと値の組みは、開き括弧の次の行に現れる最初のプロパティと同じレベルでインデントする
  • プロパティと値の組みは、クオーテーションなしのプロパティ名を持ち、その後にコロン(前に空は置かない)と値を続ける
  • 値が関数のとき、プロパティ名の下で折り返し、関数の前後に1行ずつ空白を置く
  • 関連するプロパティをまとめると可読性が向上するときには、空白行を挿入しても良い
  • 閉じ括弧は単独で1行にする
// Good
let object = {
  key1: value1,
  key2: value2,
  func: function () {
    doSomething();
  }
  key3: value3
};

// Bad: インデントが正しくない
let object = {
                        key1: value1,
                        key2: value2
                      };

// Bad: 関数の前後に空行がない
let object = {
  key1: value1,
  key2: value2,
  func: function () {
    doSomething();
  }
  key3: value3
};
// Good
doSomething({
  key1: value1,
  key2: value2
});

// Bad
doSomething({ key1: value1, key2: value2 });
// Good
let book = {
  title: 'ノルウェーの森',
  author: '村上春樹'
};

// Bad: 滅多に使われない
let book = new Object();
book.title = 'ノルウェーの森';
book.author = '村上春樹';

1.7.6 配列リテラル

// Good
let colors = ['red', 'green', 'blue'];
let numbers = [1, 2, 3, 4];

// Bad: 問題視される
let colors = new Array('red', 'green', 'blue');
let numbers = new Array(1, 2, 3, 4);

コメント

コードの内容がはっきりしないときにはコメントすべきであり、内容がはっきりしているときはコメントすべきではない。

  • 難解なコードにコメントを付ける
    他の人がそのコードを読むとき、コードの目的の理解を助ける際の鍵となるようにコメントする。
  • エラーになりそうな箇所にコメントする
    他の人がそのコードを読むとき、間違いに見えるコードを書いているときには、コメントを書いて誤解を避ける。
  • ブラウザ特有のハック
    止むを得ず、非効率で美しくないコード、あるいは古いブラウザで正しく動かすための汚いコードを使う場合、コメントする。
    予期しないかたちで変更されることを防ぐためだけでなく、時間が経ってから、新しいブラウザで同じ問題が発生するかどうか確認するきっかけになる。

2.1 1行コメント

2つのスラッシュの後に空白を入れてコメントする

// 1行コメント
  • コメントの後に続く行を説明するため。この場合、コメント行の前に空白行を置く。コメントはそれに続く行と同じレベルでインデントする
  • コードの行末に付けるコメント。コードとコメントの間には、1つインデントする。コメントを付けても行の最大長の制限を超えないようにし、もし超える場合、コードの上にコメントを移動する
  • コードの行をいくつか連続してコメントアウトする
// Good
if (condition) {
  // ここに通ればセキュリティチェックは合格
  allowed();
}

// Bad: コメントの前に空行がない
if (condition) {
  // ここに通ればセキュリティチェックは合格
  allowed();
}

// Bad: インデントが正しくない
if (condition) {
// ここに通ればセキュリティチェックは合格
  allowed();
}
// Good
let result = something + somethingElse;  // コメントコメントコメント

// Bad: コードとコメントの間に余白がない
let result = something + somethingElse;// コメントコメントコメント
// Good
// if (condition) {
//   doSomething();
// }

// Bad: これは複数行コメントにすべき
// 次のコードの説明を補足
// 条件がtrueかどうかで実行内容が決定される
// こんな感じで1行コメントで
// 説明書きを足していくのはBadケース

2.2 複数行コメント

  • 1行目はコメント開始を示す /* だけで構成される。この行には説明文を書かない
  • 左端の*を揃える。この行に説明文を書く
  • 最後の行にはコメント終了を示す */ を前行と位置を揃えて書く。この行には説明文を書かない
  • 複数行コメントは、必ずそのコメントで説明するコードの直前に書く
  • 複数行コメントの直前に空行を置く
  • 説明するコードのインデントレベルに合わせる
// Good
if (condition) {
  /*
   * 複数行コメントを書くときのサンプル
   * テキストテキストテキスト
   */
  doSomething();
}

// Bad: コメントの前に空行がない
if (condition) {
  /*
   * 複数行コメントを書くときのサンプル
   * テキストテキストテキスト
   */
  doSomething();
}

// Bad: *(アスタリスク)が揃っていない
if (condition) {
  /*
  * 複数行コメントを書くときのサンプル
  * テキストテキストテキスト
  */
  doSomething();
}

// Bad: *(アスタリスク)の後に空白がない
if (condition) {
  /*
   *複数行コメントを書くときのサンプル
   *テキストテキストテキスト
   */
  doSomething();
}
// Bad: インデントが正しくない
if (condition) {
/*
 *複数行コメントを書くときのサンプル
 *テキストテキストテキスト
 */
  doSomething();
}

// Bad: 行末のコメントとして複数行コメントを使わない
let result = something + somethingElse;  /* テキストテキスト */

2.3 コメントのアノテーション

1語の後にコロンが続く形式を取る。1行コメントでも複数コメントでも、どちらでも使える。一般的なコメントと同じフォーマットのルールに従う。可能なアノテーションは以下の通り。

TODO
このコードの実装が未完であることを示す。次のステップに関する情報を盛り込むべき。

HACK
このコードが応急措置的ハックで書かれていることを示す。なぜハックが使われているのか、その理由についても書くべき。これは問題を解決するより良い方法で置き換えた方が良いことも示す。

XXX
コードが問題含みであり、できるだけ早く解決すべきであることを示す。

FIXME
コードが問題含みであり、早期に解決すべきであることを示す。XXXよりも重要度は低い。

REVIEW
コード改変のためにレビューする必要があることを示す。

2.4 ドキュメントコメント

@namespace
オブジェクトを包含するグローバル参照。

@class
オブジェクトあるいはコンストラクタ関数の意味で使う。

@constructor
この「クラス」が実際はコンストラクタ関数であることを示す。

@property と @type
オブジェクトのプロパティを説明する。

@param
関数が取る引数のリスト。パラメータの型は波括弧の中に書き、その後にパラメータの名前と説明を続ける。

@return
メソッドの戻り値を説明する。名前の指定はなし。

/**
 * 説明テキストテキストテキスト
 * テキストテキストテキスト
 *
 * @method merge
 * @param {object} objects* マージする1個以上のオブジェクト
 * @return {object} マージされたオブジェクト
 */
/**
 * 数学ユーティリティ
 * @namespace MYAPP
 * @class math_stuff
 */
MYAPP.math_stuff = {
  /**
   * 和を計算
   *
   * @method sum
   * @param {number} a 数値1
   * @param {number} b 数値2
   * @return {number} 2つの数の和
   */
  sum: function (a, b) {
    return a + b;
  }
}
/**
 * Person オブジェクトを作成
 * @class Person
 * @constructor
 * @namespace MYAPP
 * @param {string} first フィーストネーム
 * @param {string} last ラストネーム
 */
MYAPP.Person = function (first, last) {
  /**
   * ファーストネーム
   * @property firstName
   * @type string
   */
  this.firstName = first;
  /**
   * ラストネーム
   * @property lastName
   * @type string
   */
  this.lastName = last;
}
/**
 * その人の名前を返却する
 *
 * @method getName
 * @return {string} 名前
 */
MYAPP.Person.prototype.getName = function () {
  return this.firstName + ' ' + this.lastName;
}

文と式

// Good
if (condition) {
  doSomething();
}

// Bad: {} がない
if (condition)
  doSomething();

// Bad: 1行に省略している
if (condition) doSomething();

// Bad: 1行に省略している
if (condition) { doSomething(); }

3.1 波括弧を揃える

// Good
if (condition) {
  doSomething();
} else {
  doSomethingElse();
}

// Bad: C#, Visual Studioスタイル
if (condition)
{
  doSomething();
}
else
{
  doSomethingElse();
}

3.2 空白

空行は、論理的に関連するコードのセクションを区切ることでコードの可読性を向上させる。

次の状況では2行の空行を必ず使う。

  • ソースファイルのセクションの間
  • クラスとインターフェース定義の間

次の状況では1行の空行を必ず使う。

  • メソッドの間
  • メソッド内のローカル変数とその最初の文の間
  • メソッド内のロジカルセクションを区切り、可読性を向上させる

空白は次の状況で使用する。

  • キーワードの後に括弧が続くとき、空白で区切る
  • 引数リストではカンマの後に空白を置く
  • ドット(.)を除くすべての二項演算子は、そのオペランドを空白で区切る。単項のマイナス、インクリメント(++)、デクリメント(–)などの単項演算子は、そのオペランドの間に空白は置かない
  • for分の中の式は、空白で区切る

3.2.1 ブロック文での空白

// Good
if (condition) {
  doSomething();
}

// Bad: 空白で区切らない
if(condition){
  doSomething();
}

// Bad: 括弧内の前後に空白を置く
if ( condition ) {
  doSomething();
}

3.3 switch文

3.3.1 インデント

  • 各case文はswitchキーワードの位置から1レベルだけインデントする
  • 各case文の前後に余分が行があり、次のcase文に続ける

3.3.2 caseからcaseに続けられる

意図的な素通りであれば、それは許容される

switch (condition) {
  case 'first':
  case 'second':
    doSomethingA();
    break;
  case 'third':
    doSomethingB();
    /* 下に落ちる */
    default:
      doSomethingC();
}

3.3.3 default

// Good
switch (condition) {
  case 'first':
    doSomethingA();
    break;
  // default なし
}

// Bad: デフォルトのアクションで何も実行しないときでもdefaultを含める
switch (condition) {
  case 'first':
    doSomethingA();
    break;
  default:
    // 何も実行しない
}

3.4 with文

with文は使用しない。

3.5 forループ

// Good
let values = [1, 2, 3, 4, 5],
  i,
  len;
for (i=0, len=values.length; i < len; i++) {
  process(values[i]);
}

// Good
let values = [1, 2, 3, 4, 5],
  i,
  len;
for (i=0, len=values.length; i < len; i++) {
  if (i == 2) {
    break; // 以後イテレーションしない
  }
  process(values[i]);
}
// Good
let values = [1, 2, 3, 4, 5],
  i,
  len;
for (i=0, len=values.length; i < len; i++) {
  if (i != 2) {
    process(values[i]);
  }
}

// Case by case
let values = [1, 2, 3, 4, 5],
  i,
  len;
for (i=0, len=values.length; i < len; i++) {
  if (i == 2) {
    continue; // このイテレーションをスキップするだけ
  }
}

3.6 for-inループ

// Good
let prop;
for (prop in object) {
  if (object.hasOwnProperty(prop)) {
    console.log('name is ' + prop);
    console.log('value is ' + object[prop]);
  }
}

// Bad: インスタンスプロパティだけが得られるようになっていない
let prop;
for (prop in object) {
  console.log('name is ' + prop);
  console.log('value is ' + object[prop]);
}
// Bad: エラーが起きる可能性がある
let values = [1, 2, 3, 4, 5],
  i;
for (in in values) {
  process(items[i]);
}

変数、関数、演算子

4.1 変数の宣言

ECMAScript 5までは、var
ECMAScript 6 からは、let, const

それぞれ挙動が異なる。
var で変数宣言した場合、その変数はJavaScriptエンジンによって巻き上げられる。つまり関数の中のどこで変数を定義したとしても、関数の冒頭で宣言したのと同じになる。

  • 関数の最初の文でローカル変数を定義する
  • var文は結合する
  • 初期化されていない変数は最後に書く

4.2 関数の宣言

関数宣言はJavaScript宣言によって巻き上げられる。

  • 関数は常に使用するより前に宣言しておく
  • 関数宣言はブロック文(ブロックとは { } で囲まれたコード)の中に出現させない

4.3 関数呼び出しにおける空白

// Good
doSomething(item);

// Bad: ブロック文のように見える
doSomething (item);

4.4 即時関数呼び出し

即時関数呼び出しが発生することが明確にするために、関数を括弧でくくる。

// Good
let value = (function() {
  return 'Hi';
})();

// Bad: 最後の行までみないと即時関数かわからない
let value = function () {
  return 'Hi';
}();

4.4.1 strictモード

グローバルスコープに “use strict” を指定するのは避ける

// Good
function doSomething() {
  'use strict';
  // コード
}

// Bad: グローバルstrictモード
'use strict';

4.5 等価性

なるべく、== と != ではなく、 === と !== を利用する

// 数値の5と文字列の5
console.log(5 == '5');    // true
console.log(5 === '5');    // false

// 数値の25と16進数の25
console.log(25 == '0x19');    // true
console.log(25 === '0x19');    // false

// 数値の1とtrue
console.log(1 == true);    // true
console.log(1 === true);    // false

// 数値の0とfalse
console.log(0 == false);    // true
console.log(0 === false);    // false

// null と undefined
console.log(null == undefined);    // true
console.log(null === undefined);    // false

4.6 eval()

eval()は、受け取ったJavaScriptコードの文字列を実行する。タスクを実現できる手段が他に有る限り、eval()を避ける。

4.7 プリミティブラッパー型

String、Bookean、Numberのプリミティブラッパー型は使用しない。

// Good
let s = 'string text';
let n = 123;
let b = true;

// Bad: プリミティブラッパー型を使用している
let s = new String('string text');
let n = new Number(123);
let b = new Boolean(true);