個人的に書き溜めているTypeScriptコーディング規約です。
あまり網羅的ではありません。細かな事柄はESLintやPrettierでカバーする前提です。
下記のようなことが詳しめに書いてあります。
- ESLintで指摘されるんだけど、何が問題なのかわかりにくいもの
- ESLintで表現できないもの
- JavaScript未経験のメンバーがハマった落とし穴
記法について
理解だけしておいてその場その場で判断したい規約もあります。そのため、制約度合いを下記を用いて記します。
-
[MUST]
: 必ず行わなくてはならない。 -
[SHOULD]
: 行うべきである。合理的理由があれば行わなくていいが、当該項目の示唆を十分に理解し、慎重に重要性を判断しなくてはならない。 -
[OPTIONAL]
: 任意であり、行っても行わなくても良い。
これはRFC 2119を参考にしました。
変数
1. ローカル変数宣言にはconst
かlet
を使うこと [MUST]
理由: var
は関数スコープのため、予期せぬ結果を生みやすい。例えば、下記のように書くと、i
はすべて同じ変数を指すようになってしまう。
例:
// BAD
function varTest() {
var i = 31;
if (true) {
var i = 71;
console.log(i); // -> 71
}
console.log(i); // -> 71
}
2. 変数宣言には基本的にconst
を使うこと。変数に初期化時以外に代入する必要がある時のみ、let
を使うこと [MUST]
理由: 不変の値は変更できないようにすることで、予期しないバグを防げる。
3. 変数宣言は各行に一個だけの変数を宣言すること。ただし、分割代入はこの限りではない [MUST]
理由: 各変数が独立して定義されるので、メンテナンスがしやすい。
// BAD
let a: number, b: number;
// GOOD
let a: number;
let b: number;
// 分割代入はOK
let [a, b] = hoge;
4. ローカル変数は、できるだけスコープが小さくなるように、合理的な範囲で必要になる直前に宣言すること [MUST]
理由1: メンテナンス性の向上。
理由2: varしか使えない環境であれば、varは巻き上げ(hoisting)があるため、変数を関数の先頭で定義することの合理性はある。しかしletとconstが使えるので、その必要はない。参考 : MDN - varの巻き上げ(hoisting)
制御構文
5. if/for/do/while
文の本体は、必ずbrace{}
で囲むこと [MUST]
理由: 文の本体が2行以上になったときに改めてbrace{}
で囲おうとするとミスが発生しやすいため。
真偽値評価
6. 等価演算子(==
)は使わず、厳密等価演算子(===
)を使うこと。ただしヌルチェックの場合のみ== null
を使ってもよい [MUST]
理由: 等価演算子は暗黙の型変換を行うため振る舞いが予測しにくく、バグの原因になる.
例:
// BAD
hoge == "hoge";
// GOOD
hoge === "hoge";
7. number型、string型、any型をそのまま真偽値として利用しないこと [MUST]
理由: 数値や文字列をそのまま真偽値として使うと、下記のように予期せぬ結果を招くことがある。
- 数値は、0かNaN の場合は false。
- 文字列は、空文字 "" の場合は false。
関数
8. function式を避け、できるだけアロー関数式を利用すること [SHOULD]
理由: function式はクラスと相性が悪い。例えばクラスのインスタンスメソッド内でfunction式によって関数を定義し、その中にthisを書くと、そのthisはクラスのインスタンスにはならない(下記)。
// BAD
class Hoge {
private age:number = 0;
public foo (){
function bar() {
this.age++;// <-- エラー
};
}
}
アロー関数式にはこの問題はない。アロー関数式のthisは、「定義されているスコープにおけるthisと同じもの」になる。
例:
// GOOD
class NewHoge {
private fuga: number;
public foo () {
setInterval(() => {
this.fuga++;
}, 1000);
}
}
参考:
MDN - アロー関数
undefined / null
9. undefinedやnullを書くことが必要なときは、できる限りundefinedを使うこと。 [SHOULD]
理由: 一貫性のため。
配列
10. 配列を作成する際はリテラル構文を使用すること。配列の型定義も同様とする [SHOULD]
// BAD
const stack: Array<Something> = new Array();
// GOOD
const stack: Something[] = [];
理由: 一貫性のため。
オブジェクト
11. オブジェクトのプロパティにアクセスする場合はドット .
を使用すること [SHOULD]
例:
const hoge = {
fuga: true,
hoho: 28,
};
// BAD
const isFuga = hoge["fuga"];
// GOOD
const isFuga = hoge.fuga;
注記: TypeScriptで下記のように定義すると、hoge
はメンバーのないオブジェクト型({}
)と型推論されて、プロパティアクセスができなくなる。
// BAD
const hoge = {};
hoge.fuga = true; // error
この場合[]
を利用してのプロパティアクセスで解決するのではなく、型定義で解決すること。
// BAD
const hoge = {};
hoge["fuga"] = true;
// GOOD
const hoge: { fuga: boolean } = {};
hoge.fuga = true;
文字列
12. 文字列の定義にはシングルクオートを利用すること [MUST]
理由: 一貫性のため。
13. 文字列の合成にはTemplate literalを使うこと [SHOULD]
理由: +
などを利用して結合するよりも標準的でコードがわかりやすくなるため。
例:
const firstName = "hoge";
const lastName = "fuga";
const fullName = `${firstName} ${lastName}`;
型変換
14. stringへの型変換はString(hoge)
を使うこと [MUST]
例:
// BAD
const scoreString = myScore + "";
// GOOD
const scoreString = String(myScore);
理由: 暗黙の型変換を利用するよりも明確なため。
15 Number型へ型変換する場合はNumber
かparseFloat
を使うこと [MUST]
モジュールシステム
16. モジュールの読み込みには、標準のimport/exportを使うこと [SHOULD]
例:
// BAD
const hoge = require('./HogeHogeFugaFuga');
//GOOD
import { hoge } from "./HogeHogeFugaFuga";
17. アスタリスクインポートは用いないこと [SHOULD]
理由: アスタリスクインポートを使うと、利用していない要素までインポートするので、結合度が上がってしまう。
例:
// BAD
import * as Hoge from "./HogeHogeFugaFuga";
// GOOD
import { Hoge, Fuga, Hogehoge } from "./HogeHogeFugaFuga";
コメント
18. 下記の要素にはコメントを記載すること [SHOULD]
- クラス
- メンバ変数
- メンバ関数
- ファイル外部に公開(export)されている要素
- その他、必要と思われる場合
例:
/**
* ある時間のスケジュール
* @param time 時間
* @return スケジュール
*/
public schedule(time: Date): Schedule {
return this.scheduleService.schedule(time);
}
19. 関数のコメントでは、パラメタと戻り値の意味を明確にすること。ただし、パラメタや戻り値が明白な関数の場合、一行スタイルを利用してよい [SHOULD]
例: 複数行スタイル:
/**
* ある時間のスケジュール
* @param time 時間
* @return スケジュール
*/
public schedule(time: Date): Schedule {
return this.scheduleService.schedule(time);
}
例: 一行スタイル:
/** スケジュール */
public schedule(): Schedule {
return this.scheduleService.schedule();
}
20. コードからすぐに明白に読み取ることができるような内容は、コメントとして記載しないこと [SHOULD]
例:
//BAD
i++;// iに1を足す
21. 単一行コメントには//
を使うこと [SHOULD]
ソースコードのエンコーディング
22. ソースコードのエンコーディングは、BOMなしのUTF-8にすること [MUST]
理由: BOMありのファイルを正しく扱えないエディタやソフトウェアがあるため。
参考: Visual Studio Codeをデフォルトで利用していれば規約どおりになる。
ソースコードのフォーマット
23. インデントはスペース(ASCIIコード:0x20)で記述すること [MUST]
参考: Visual Studio Codeをデフォルトで使っていれば規約どおりになる。設定が変わっている場合、設定を変更する。
"editor.insertSpaces":true,
24. インデントはスペース2個とすること [MUST]
理由1: Reactプロジェクトとの相互運用性。JSXのTypeScript版であるTSXでスペース4つのインデントを使うと読みづらくなる。
理由2: 多くのJavaScriptのコーディング規約でインデント数は2が多いため(Google,Airbnb)。
ソースコードのスタイル
25. 1行の文字数の管理は、フォーマッタに任せること [MUST]
参考: 例えばPrettierを使う。
26. フィールド名やコメントの位置揃えは行わないこと。既存コードに合わせる必要もない [SHOULD]
理由: 位置揃えをする場合、変数を一つ追加しただけで全てを修正する必要がある。変更行が多くなりコンフリクトが起こりやすくなる。
例:
// BAD
let hoge;
const fuga;
// BAD
const hoge;// hoge
let fuga; // fuga
// GOOD
const hoge;// hoge
let fuga;// fuga
27. 制御構文やコールバックのネストは2回までを目安とし、それ以上の場合は関数に分割すること [SHOULD]
キャメルケースにする方法
28. キャメルケースにする方法は「Google Java Style Guide#s5.3-camel-case(日本語訳)」に従うこと。意味が分かりにくくなる場合、コメントで補うこと [SHOULD]
理由: キャメルケースにする方法を統一しておくことで、実装時に迷わない。
例: "supports IPv6 on iOS?" をローワーキャメルケースで表現したい時 → supportsIpv6OnIos
とする。
ソースファイル/ディレクトリのネーミング
29. ファイルから単一の要素(クラス、インターフェース、関数、定数、etc)をexportしている場合、exportしている要素の名前をファイル名にすること [SHOULD]
理由1: ファイル一覧を見るだけでクラスを発見できる。クラス名を覚えていればどのファイルにクラスがあるのかがすぐにわかる。
理由2: 新たな名前をファイル名だけのために導入しない。
例:
export class Hoge {
}
// このファイル名は、Hoge.ts
30. ファイルから複数の要素をexportしている場合、代表的な要素の名前をファイル名にすること [SHOULD]
31. ディレクトリ名はハイフンケース(hyphen-case)で命名すること [SHOULD]
例: "time edit"を表現したい時 -> time-edit
各要素のネーミング
32. クラス/インターフェースの命名には、アッパーキャメルケース(UpperCamelCase)を使うこと [SHOULD]
例: "color manager"を表現したい時 -> ColorManager
33. enumの命名規則は、クラスに準ずること。enumのメンバーの命名規則は、定数に準ずること [SHOULD]
34. typeの命名規則は、クラスに準ずること [SHOULD]
35. 変数と関数の命名には、ローワーキャメルケース(lowerCamelCase)を使うこと [SHOULD]
例: "is defined"を表現したい時 -> isDefined
36. 定数(実行前に確定しており、アプリの実行中常に不変であることを意図した値)には、コンスタントケース(CONSTANT_CASE)を使うこと [SHOULD]
例: "magic number"を関数で表現したい時 -> MAGIC_NUMBER
注記: ここでいう定数とは、C言語における#define
で定義するような、コードの実行前に確定している値を意図している。
つまり、const型で宣言された変数に格納されていたとしても、必ずしもコンスタントケースを使う必要はない。例えば、ローカル関数内で計算結果を一時的に保持するためのconst宣言された変数は、その内容はコードの実行前に確定しているとは言えない。そのため、コンスタントケースを使わなくてよい。
また、あるインスタンスがconst型で宣言された変数に格納されていたとしても、そのインスタンスのメンバ変数等が変更されうるならば、コンスタントケースを使うべきではない。
37. 型パラメタ名は、大文字アルファベット一文字とし、それに1個の数字が続くことができる [SHOULD]
例: T, T1, T2, E, X