シンプルシンプルデザイン

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 空行

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

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://www.simplesimplesdesign.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 を使うべきではないケース

// 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 オブジェクトリテラル

// 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行コメント
// 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 複数行コメント

// 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行の空行を必ず使う。

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

3.2.1 ブロック文での空白

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

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

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

3.3 switch文

3.3.1 インデント

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エンジンによって巻き上げられる。つまり関数の中のどこで変数を定義したとしても、関数の冒頭で宣言したのと同じになる。

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);
モバイルバージョンを終了