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