TypeScriptコーディング規約
- はじめに
- アプリケーション構造
- 命名
- コメント
- 禁止事項
- 注意事項
- 推奨事項
- クラスのフィールドやメソッドなどには適切なアクセス修飾子を付与してください
- クラスのフィールドは原則 private にしてください
- ローカル変数はできるだけ狭いスコープで使用してください
- なるべく再代入は避けて、そのためにconstを活用してください
- なるべく引数の状態を変えないでください
- 配列の処理はArrayのメソッドを使用して簡潔に書くことを検討してください
- 配列やオブジェクト全体をコピーする場合はスプレッド構文を使用してください
- 複数行の文字列を定義する場合、テンプレートリテラルを使用してください
- 正規表現を使用する場合は正規表現リテラルを使用してください
- 1つのファイルにつき1つの export にしてください
- 値が存在しないことを表す場合は undefined を使用してください
はじめに
本規約はTypeScriptを使用したアプリ開発において、開発者が守るべきルールやより良いコードを書くための指針を解説しています。
前提
本規約が対象とするコードは、以下が実施されていることを前提としています。
- 静的解析ツールで検出できる規約違反は解消していること
静的解析ツールで検知できることはあらかじめ実施し、本規約ではより良いコードを書くためのガイド、あるいはコードレビューの指針となるように作成しています。
指標
本規約を書くにあたって、次の3つを指標としました。
- ランタイムエラーよりも静的解析エラー(コンパイルエラー + ESLintエラー)
- 変更可能な状態を減らす
- 状態の変更箇所を局所化する
本規約はこの指標に則ったコードを書くためにはどのような事に気を配れば良いのかを意識して作成しました。本規約を読み進める際も、また実際にコードを書く際も、この指標を意識しておくとより効果的でしょう。
ランタイムエラーよりも静的解析エラー(コンパイルエラー + ESLintエラー)
アプリを実際に動かさないと発見できない不具合よりも、静的解析時にエラーとなって発見できる不具合の方が素早く修正できます。 ランタイムエラーに頼らず、なるべく静的解析エラーで不具合を発見できるようなコーディングを心がけることで安心感をもって開発を進められます。
変更可能な状態を減らす
変更可能な状態とは、setter
などのメソッドによって再代入できる変数や、Date
のように値を変更できるオブジェクトで宣言された変数を指します。
// setterで変更できる変数(変更可能な状態)
let mutableState: string = '';
function setMutableState(value: string) {
mutableState = value;
}
// 変数は再代入できないが、値は変更できる(変更可能な状態)
const mutableState: Date = new Date();
function setDate(date: number) {
//値を変更している
mutableState.setDate(date)
}
変更可能な状態がある場合、現在の状態がどうなっているか頭の中で覚えながらコードを読み進めなくてはいけません。 人それぞれですが、一時的に頭の中で覚えておける情報量はそれほど多くありません。 なるべく変更可能な状態を減らすことで、読み手に負担をかけず保守しやすいコードになります。
状態の変更箇所を局所化する
setter
などで状態を変更することがありますが、状態の変更前後で頭の中で覚えている状態も変更しないといけません。
状態を変更するコードをなるべく近い場所にまとめることで、読み手に負担をかけず保守しやすいコードになります。
表記ルール
規約の解説中にコード例を掲載することがあります。
やってはいけない「禁止コード」の例は先頭に// NG
と一行コメントの形式で記載しています。
禁止コードとの対比として挙げている、やってもよい・ぜひやるべき「推奨コード」の例は先頭に// OK
と記載しています。
// NG
と// OK
のどちらも記載されていないコード例は、「推奨コード」か、もしくは単なる解説の補助として掲載しています。
コード例の中で、特に見せたい箇所以外は省略する場合があります。
その際は ...
と書いて省略を表しています。
// 省略の例
function someMethod(...) { // 引数の記載を省略
... // メソッド本体の記載を省略
}
また、縦に伸びているコード例を中略する場合は .
を縦に3つ書いて表していることがあります。
// 中略の例
const text: string = message.text;
.
.
.
return text;
コード例は本規約、特に「推奨事項」で挙げられている規約に準拠しているため、次のような特徴があります。
- クラスのフィールドには
private
を付けている - 変数には可能な限り
const
を付けている
他にも準拠している規約はありますが、詳細は推奨事項を参照してください。 なお「禁止コード」の例を示すため、一部のコード例では規約に従っていないものもあります。
アプリケーション構造
Application Architectureのアプリケーション構造に従ったディレクトリ構成にしてください
Application Architectureのアプリケーション構造に定義されている以下の3つに従って、ディレクトリを構成してください。
- 機能レイヤー
- 機能モジュール
- ステレオタイプ
命名
クラスや関数、変数の命名についての規約です。
クラス名は名詞としてください
クラス名は、そのクラスの特徴を端的に表す名詞とし、先頭の文字は大文字で開始してください。
型エイリアス名は名詞としてください
型エイリアス名は、その型エイリアスの特徴を端的に表す名詞とし、先頭の文字は大文字で開始してください。
コンポーネント名は名詞としてください
コンポーネント名は、そのコンポーネントの特徴を端的に表す名詞とし、先頭の文字は大文字で開始してください。
関数名は基本的には動詞から始まるように命名をしてください
関数名は、基本的に動詞から始まるように命名し、先頭の文字は小文字で開始してください。
ただし、真偽値を返すような場合は、is
やcan
など動詞以外から始まる場合もあります。
また、クラスのフィールドを返却するような単純な処理(いわゆるアクセサ)の場合は、その関数の戻り値の内容を端的に表す名詞としてもよいです。
変数は基本的には名詞で命名してください
変数名は、基本的に名詞とし、先頭の文字は小文字で開始してください。
ただし、真偽値を返すような場合は、is
やcan
など動詞以外から始まる場合もあります。
また、for
文等のループ構文において、カウンタとして使用される変数の名称には、 慣用的に使用される変数名(i, j, k ...
)を使用してもよいです。
一時的に使用されるスコープの狭い変数は意味の無い名前でも構いません。
// map関数の中だけで使用される変数
const priceList = items.map(i => i.price);
定数は全て大文字かつ単語間の区切りにアンダースコア(_
)を使用してください
定数とは、ステレオタイプのConstant
に該当するものです。
定数名は全て大文字かつ単語間の区切りにアンダースコア(_
)を使用してください。
データモデルの用語辞書を活用して命名してください
整理された用語辞書をもとにすると、統一感のある命名が出来ます。
是非、用語辞書を活用してください。 用語辞書が整理されていない場合は、まず用語辞書の整理をすることを検討してみてください。
コメント
コードの理解を助けるため、必要に応じて行コメントを記載してください
コードだけを読んで処理の内容を理解できるのが理想的ですが、複雑なロジックや、パフォーマンスのためにあえて特殊な実装をした場合は説明のコメントを記載してください。
禁止事項
計算式の途中でインクリメント・デクリメントをしないでください
計算式の途中でインクリメント・デクリメントをすると、該当の変数の値が分かりにくくなります。
計算式は計算式で済ませてしまって、そのあとインクリメント・デクリメントをするようにしてください。
// NG
let x: number = ...
const y: number = (x++ * height) / width;
// OK
let x: number = ...
const y: number = (x * height) / width;
x++;
フィールドを一時変数として使用しないでください
コレクションの要素を順次処理して必要な値を構築するメソッドなどで中間状態を保持するために一時変数を導入することがありますが、その場合にフィールドを使用しないでください。
フィールドを使用するとクラスが持つ状態を増やすことになります。 なるべく変更可能な状態は持たないようにするためにも、一時変数の用途でフィールドは使用しないでください。
一時変数の用途にはフィールドではなく、ローカル変数を使用してください。
// NG
// フィールドを一時変数として使用している
private itemNames: string[] = [];
public collectNames(items: Item[]) {
this.itemNames = [];
for (const item of items) {
this.collectName(item);
}
return this.itemNames.join(',');
}
private collectName(item: Item) {
this.itemNames.push(item.name);
}
// OK
public collectNames(items: Item[]) {
// 一時変数はローカル変数を使用して、メソッドに渡して引き回している
const itemNames: string[] = [];
for (const item of items) {
this.collectName(itemNames, item);
}
return itemNames.join(',');
}
private collectName(itemNames: string[], item: Item) {
itemNames.push(item.name);
}
戻り値がコレクションや配列の場合はnull
やundefined
を返さないでください
値がない状態を表すためにnull
やundefined
を使うことがあります。
ただし、戻り値が次に示すような配列やコレクションの場合は、値がない状態を表すためであってもnull
やundefined
を返さないでください。
- Array
- Set
配列の値がない状態というのは、多くの場合は配列が空であることを指します。
Array
やSet
はlength
プロパティが0なら空であると判断できるため、わざわざnull
やundefined
を返す必要はありません。
null
やundefined
を返す可能性があると、呼び出し元でnull
やundefined
のチェックをしなくてはならずコードが複雑になります。
// NG
function findItems(category: ItemCategory) {
const items = api.findItems(category);
if (items.length === 0) {
return null;
}
return items;
}
// OK
function findItems(category: ItemCategory) {
return api.findItems(category);
}
コンストラクタ内で自分自身のインスタンスメソッドを呼び出さないようにしてください
コンストラクタ内では、初期化前のundefined
値を参照してしまう場合があります。
そのため、コンストラクタからフィールドを参照するインスタンスメソッドを呼びだす場合、フィールドの初期化とメソッドの呼び出しの順番に注意する必要が出てきてしまいます。
// NG
class Foo {
private text: string;
private length: number;
constructor(text: string) {
// textが初期化されていないのでこの位置でcalculateLengthを呼び出すと以下のエラーになる
// 「Cannot read properties of undefined (reading 'length')」
this.length = this.calculateLength();
this.text = text;
//textが初期化された後のこの位置で呼び出すべき。
//this.length = calculateLength();
}
protected calculateLength() {
return this.text.length;
}
}
継承を伴うと話はさらに複雑化します。
次に示すNG例を見てください。
Foo
とBar
という2つのクラスが定義されています。
Bar
はFoo
を継承しています。
Bar
をインスタンス化すると、Bar
のコンストラクタの先頭でFoo
のコンストラクタが呼び出されます。
Foo
のコンストラクタではcalculateLength
を呼び出そうとしますが、Bar
でオーバーライドされているので実際にはBar
のcalculateLength
が呼び出されます。
Bar
のcalculateLength
ではtext
を参照していますが、この時点ではまだ初期化されていないのでエラーとなってしまいます。
//NG
class Foo {
private length: number;
constructor() {
//コンストラクタで自分自分のメソッドを呼び出している
this.length = this.calculateLength();
}
protected calculateLength() {
return 0;
}
}
class Bar extends Foo {
protected text: string;
constructor(text: string) {
super();
this.text = text;
}
protected calculateLength() {
//ここで以下のエラーになる
// 「Cannot read properties of undefined (reading 'length')」
return this.text.length;
}
}
このような複雑さを持ち込んでしまうため、コンストラクタ内から自分自身のインスタンスメソッドを呼び出さないようにしてください。
コンストラクタ内では基本的には引数をフィールドにセットするだけにしてください。 何かしらの処理を行いたい場合は、コンストラクタ内で処理を完結させるようにしてください。
//OK
class Foo {
private text: string;
private length: number;
constructor(text: string) {
this.text = text;
// コンストラクタ内で処理が完結している
this.length = text.length;
}
}
ただし、コンストラクタ内で長々と処理を書く必要が出てきた場合は、次に挙げるように自クラス以外で処理する方法も含めて検討してください。
- コンストラクタを呼びだす前にあらかじめ処理を行い、その結果をコンストラクタの引数に渡す
- コンストラクタ内で別の関数に処理を委譲し、その結果を使用する
try-catch 文を条件分岐のために使用しないでください
try-catch
文は例外を扱うためのものです。
条件分岐をしたい場合はif
文を使用してください。
// NG
try {
// codeに対するitemが無い場合にエラーをスローするAPI
await service.findItem(code);
return 'Items exist';
} catch {
return 'No items';
}
// OK
// codeに対するitemが存在するかチェックするAPI
if (await service.exists(code)) {
return 'Items exist';
} else {
return 'No items';
}
秘匿情報はログ出力やシリアライズされないように注意してください
パスワードのような秘匿情報はログに含まれないようにマスクするなど、注意をしてください。
また、インスタンスをシリアライズする場合も、秘匿情報が含まれないように注意してください。
クラスを不整合な状態にしないでください
クラス内に関連性のある複数のフィールドを定義した場合、それらのフィールド間で値の整合性を保つようにしてください。
// NG
class ItemList {
private items: Item[] = [];
private totalPrice: number = 0;
public add(item: Item) {
//この時点で合計値が変わるためtotalPriceを更新しなければいけない
this.items.push(item);
}
public save(service: ItemService) {
//保存時についでに合計値の計算をしている
this.totalPrice = 0;
for (const item of this.items) {
service.save(item);
this.totalPrice = this.totalPrice + item.price;
}
}
public getTotalPrice() {
//addの後にsaveを呼ばずにこのメソッドを呼ぶとItem追加前の合計値が返されてしまう
return this.totalPrice;
}
}
上記の例ではitems
を変更した際、同時にtotalPrice
を変更すると良いでしょう。
// OK
class ItemList {
private items: Item[] = [];
private totalPrice: number = 0;
public add(item: Item) {
//itemを追加してすぐに合計値の計算をしているので状態の整合性が保たれている
this.items.push(item);
this.totalPrice = this.totalPrice + item.price;
}
public save(service: ItemService) {
for (const item of this.items) {
service.save(item);
}
}
public getTotalPrice() {
//どのタイミングで呼び出しても正しい合計値を返す
return this.totalPrice;
}
}
もしくは合計値は状態として持たずにgetTotalPrice
内で都度計算するようにしても良いでしょう。
// OK
class ItemList {
private items: Item[] = [];
public add(item: Item) {
this.items.push(item);
}
public save(service: ItemService) {
for (const item of this.items) {
service.save(item);
}
}
public getTotalPrice() {
return this.items.map(item => item.price).reduce((price1, price2) => price1 + price2, 0);
}
}
名前と値が同じ定数は作らないでください
定数の名前と値が同じになっているものは、もっと良い名前がある場合と、そもそも定数にしなくて良い場合があります。
次に示すのは、もっと良い名前がある例です。 定数名が改行コードの値そのものになっています。
// NG
// この名前だともし改行コードがLFに変更となった場合に定数名も変更しなくてはいけない
const CRLF = '\r\n';
この場合は定数名を変えると良いでしょう。
// OK
// この名前だともし改行コードがLFに変更となった場合でも定数名はこのままで良い
const LINE_BREAK = '\r\n';
次に示すのは、そもそも定数にしなくて良い例です。 あるAPIから返却されるの項目名を定数にしたものです。
// NG
const ITEM_CODE = "itemCode";
もし項目名が変わるとそれに追随して定数名も変えることになり、定数化している意味が薄まります。
別の名前を付けるとしたらFIELD_01
といったものしか考えられず、意味ある名前にはなりません。
この場合は定数にしなくて良いでしょう。
グローバル変数は定義しないでください
グローバル変数を新たに定義することは禁止します。
プロトタイプは拡張しないでください
既存のコンストラクタ関数に対するプロトタイプオブジェクトの変更を禁止します。
注意事項
禁止とまではいきませんが、不具合の防止や保守性を損なわないために、注意事項を定めています。
メソッドには名前から想像できない処理を実装しないでください
メソッド名から想像できない処理が実装されていると、そのメソッドの使用者は混乱します。
//NG
//メソッド名からは「未読通知を取得する」処理だと想像できるが、
//実際には「既読状態への更新」も行っている
function findUnreadNotifications() {
const notifications = service.findUnreadNotifications();
//未読から既読へ更新している
for (const notification of notifications) {
const alreadyReadNotification = {...notification, status: Status.ALREADY_READ};
service.update(alreadyReadNotification);
}
return notifications;
}
//OK
//メソッド名から想像できる通り「未読通知を取得する」処理を行っている
function findUnreadNotifications() {
return service.findUnreadNotifications();
}
//「既読状態への更新」は別メソッドとしている
function updateToAlreadyRead(notifications: Notification[]) {
for (const notification of notifications) {
const alreadyReadNotification = {...notification, status: Status.ALREADY_READ};
service.update(alreadyReadNotification);
}
}
実装中はメソッドを使うのは自分だけなので大丈夫だと思っていても、コードレビューや保守など実装以降のフェーズになると他人の目に触れる機会があります。
誰が見ても自然に映るよう、「名は体を表す」ようなメソッドを実装してください。
単一のメソッドに複数の要素を詰め込まないでください
単一のメソッドに「取得」「更新」「チェック」といった複数の要素を詰め込まないでください。
複数の要素を詰め込んだメソッドの名前は、execute
やupdate
など抽象的なものになりがちです。
アプリケーションのコードは「プレゼンテーション」や「ビジネスロジック」、「データアクセス」といったレイヤーに分かれています。 それぞれのレイヤーによって抽象度は異なるので一概には言えませんが、ビジネスロジックでは単一のメソッドには複数の要素を詰め込まず具体的な名前を付けるよう努めてください。
未使用コードは残したままにしないでください
試行錯誤や性能改善の過程で使用されなくなったメソッドや変数、デバッグ用のコードなどは残したままにせず削除してください。
ただし、次のような未使用コードは例外として扱います。 これらは削除せずに残しておいてください。
- フレームワークの制約で、必ず定義しないといけないメソッドや変数
- 自動生成されたコードに含まれる未使用メソッド・変数
クラスは大きくなりすぎないようにしてください
クラスが大きすぎると内容の把握が難しくなり、保守性が低くなります。 そのような場合は、クラスを分割することを検討してください。
フィールドはクラスの状態を表すものですが、中でも変更可能なフィールドが多いとクラス全体の把握が難しくなります。 なるべく変更可能なフィールドを減らすか、分割して別のクラスに移動してください。
関数は大きくなりすぎないようにしてください
関数が大きすぎると処理の把握が難しくなり、保守性が低くなります。 そのような場合は、関数を分割することを検討してください。
最も簡単なのはある程度の塊ごとに関数へ切り出すことです。
また、関数の引数の個数も多くなりすぎないようにしてください。
可能な限り型アサーションは使用しないでください
型アサーションはある型として扱っている値を強制的に異なる型として扱うようにする仕組みで、「コンパイルは通っているけれど、実行時にエラーが出る」といった状況を引き起こす原因になります。
入力値は共通部品を用いてチェックしてください
入力値にはいくつかの種類があり、アプリケーション外部から受け取る場合もあります。 次に3つの例を挙げます。
- 画面でユーザが入力した値
- プッシュ通知から受け取った値
- ディープリンクから受け取った値
これらの値は必ず入力仕様に沿ってチェックをしてください。 ライブラリやアプリケーション内の共通部品が提供している機能を使用して入力値チェックを行ってください。
なお「アプリケーション外部からの入力値」というとAPIから取得したデータも該当しそうですが、自分たちのシステム内のAPIからは、既にチェック済みの信頼できる値が返却されているはずです。 このことから、自分たちのシステム内のAPIから取得したデータは入力値チェック対象外です。
エラーハンドリングはアプリケーションアーキテクチャに従って統一的にコーディングしてください
エラーハンドリングはプロジェクトで統一されていることが重要になります。 プロジェクトのアプリケーションアーキテクチャに従って統一的にコーディングしてください。
ループのネストはできれば二重までにしてください
ループのネストが深くなるとコードの可読性が低下します。 可読性は主観によるものなのでこの指標は絶対ではないですが、本規約ではループのネストは二重までと定めます。
推奨事項
より良いコードを書くために、推奨事項を定めています。
クラスのフィールドやメソッドなどには適切なアクセス修飾子を付与してください
フィールドやメソッドなど、アクセス修飾子を付与できる場所では適切なアクセス修飾子を選択してください。
アクセス修飾子の種類と公開範囲を次に示します。
アクセス修飾子 | 公開範囲 |
---|---|
(なし) | public と同様 |
public | 全てのクラスからアクセス可能 |
protected | 自分自身、サブクラスからアクセス可能 |
private | 自分自身のみアクセス可能 |
みだりにpublic
で宣言せず、必要がなければ狭い範囲になるようアクセス修飾子を付与してください。
クラスのフィールドは原則 private にしてください
クラスのフィールドはクラス外に露出するべきではありません。
原則private
としてください。
例外的に、フレームワークの制約でprivate
以外にしなくてはならない場合は適切なアクセス修飾子を付与してください。
また、抽象クラスを作成してサブクラスで参照させたいフィールドがある場合はprotected
にしてください。
ただし、その場合でもみだりにフィールドを公開するのではなく、メソッドを併用することでフィールドをprivate
にできないか検討してください。
ローカル変数はできるだけ狭いスコープで使用してください
ローカル変数のスコープができるだけ狭くなるように利用する場所と近い位置で宣言をしてください。
// NG
const text: string = ...
callMethod1();
callMethod2();
callMethod3();
.
.
.
callMethodN();
//textを使わない処理が延々と続いた後に初めてtextを使う処理が登場
printText(text);
// OK
callMethod1();
callMethod2();
callMethod3();
.
.
.
callMethodN();
//textを宣言してすぐに使用している
const text: string = ...
printText(text);
また、特定のブロック内でしか使用しないローカル変数は該当のブロック内で宣言してください。
// NG
// if文のブロック内でしか使用されないのにブロック外で宣言されている
const text: string = ...
if (isSuccess(result)) {
printText(text);
}
callOtherMethod();
return;
// OK
// if文のブロック内で宣言されている
if (isSuccess(result)) {
const text: string = ...
printText(text);
}
callOtherMethod();
return;
なるべく再代入は避けて、そのためにconstを活用してください
コードは「変更可能な状態」が少なければ少ないほど、全体の把握がしやすく理解しやすい傾向にあります。
変数を再代入するということは、知らないうちに「変更可能な状態」を作っていることになります。
let value: string = 'hello';
...
// 変数の再代入をすると「変更可能な状態」となり、コードを読んでいる途中で
// 「今この変数の値は何か?」を常に気にする必要が出てくる
value = 'world';
コードを読むときに把握すべきことを減らしましょう。 そのために変数の再代入は避けましょう。
変数の再代入を避けるためにconst
を活用してください。
変数を宣言する際にconst
を付けると、その変数は再代入不可になります。
const value1: string = 'hello';
...
// 再代入不可なので次のコメントアウトしているコードはコンパイルエラーになる
// value1 = 'world';
//既存の変数に再代入せずに、新しい変数を導入する
const value2: string = 'world';
クラスのフィールドにはreadonly
を付けて再代入不可に出来ないか検討してみてください。
フレームワークの制約などで必ずsetter
を定義しないといけない場合もありますが、そうでない場合はなるべくフィールドも再代入不可にしてください。
なるべく引数の状態を変えないでください
コードは「状態を変更する箇所」が局所的であればあるほど、全体の把握がしやすく理解しやすい傾向にあります。
引数の状態を変更してしまうと、状態を変更する箇所が広がってしまうことになります。
// ※これはやらない方が良い非推奨コードの例
//
// 消費税計算をした後に引数自身に税をセットしている
// なるべくなら消費税計算をするだけにとどめて、itemに税をセットするのは呼び出し元が行った方が良い
function calculateTax(item: Item) {
const tax = item.price * taxRate;
item.tax = tax;
return tax;
}
クラスのprivate
メソッドやexport
していない関数であればモジュール内に閉じているので影響範囲が限られています。
しかし、public
やprotected
などのクラス外に公開されるメソッドや、export
している関数ではなるべく引数の状態を変更しないことを推奨します。
配列の処理はArrayのメソッドを使用して簡潔に書くことを検討してください
Array
はfilter
やmap
、join
といったメソッドを使用して各要素に対する操作を小さく設定できます。
そのため、各要素に対してどのような処理をするのかが分かりやすくなる傾向にあります。
メソッド | 説明 | コード例 |
---|---|---|
filter | 条件に合う要素だけに絞り込む | ids.filter(x => x % 2 == 0) //偶数だけに絞り込む |
map | 要素を変換する | items.map(i => i.price) //priceだけの配列に変換する |
join | 全要素を連結する | labels.join(',') //要素をカンマ区切りの文字列に変換する |
その他のメソッドはArrayで確認してください。
Array
のメソッドを使用したコード例とfor-of
文を使用したコード例を次に示します。
どちらも従業員のリストから職種がプログラマーの従業員だけに絞り込んで平均年齢を算出しています。
Array
のメソッドを使用したコード例の方が、どのような処理を積み重ねて結果を得ているのかが分かりやすく感じないでしょうか。
// Arrayのメソッドを使用したコード例
const employees: Employee[] = ...
// 職種がプログラマーだけに絞る
const programmers = employees.filter(emp => emp.jobCategory === 'programmer');
const sum = programmers
// 年齢を抽出
.map(emp => emp.age)
// 集計する
.reduce((sum, age) => sum + age, 0);
// 平均を算出
const average = sum / programmers.length;
// for-of文を使用したコード例
const employees: Employee[] = ...
let sum: number = 0;
let programmerSize: number = 0;
for (const employee of employees) {
//職種がプログラマーだけに絞る
if (employee.jobCategory === 'programmer') {
//年齢を抽出して一時変数へ足し込む
sum += employee.age;
//平均を求めるため分母となる数をインクリメント
programmerSize++;
}
}
// 平均を算出
const average = sum / programmerSize;
必ず簡潔になるとは限りませんし、コードの読みやすさ・分かりやすさは主観によるものなので強制ではありませんが、配列を処理する際はArrayのメソッドを使用することを検討してみてください。
配列やオブジェクト全体をコピーする場合はスプレッド構文を使用してください
配列やオブジェクト全体をコピーする場合はスプレッド構文を使用するのが最もシンプルです。
// OK
// 配列の場合
const values: number[] = ...
const copied: number[] = [...values];
// OK
// オブジェクトの場合
const value: Item = ...
const copied: Item = {...value};
スプレッド構文はシャローコピーを行います。 ディープコピーをしたい場合はループしながら各要素に対してもコピー処理を行う必要があります。
// OK
const values: Item[] = ...
const temp: Item[] = [];
for (const item of values) {
temp.push(copyItem(item));
}
例に示したケースではArrayのメソッドを使用すると、より簡潔なコードになります。
// OK
const values: Item[] = ...
const copied = values.map(copyItem);
複数行の文字列を定義する場合、テンプレートリテラルを使用してください
複数行の文字列を定義する場合、テンプレートリテラルを使用できないか検討してください。
テンプレートリテラルは、バッククォート(`
)で開始し、バッククォート(`
)で終了します。
テンプレートリテラル内では、以下のような特徴があります。
- 改行文字を記述する必要が無く、改行で表現できる
- シングルクォート(
'
)やダブルクォート("
)を使用する際、エスケープシーケンスが不要
テンプレートリテラルを使用しない場合、改行文字を埋め込んだ文字列を定義し、行ごとの文字列を連結する記述が一般的です。
//NG
const text = '緯度は\'34.7\'です。\n' + '経度は\'135.4\'です。';
テンプレートリテラルを使用することで、改行文字やエスケープシーケンスの記述が不要になり、可読性に優れた記述が可能になります。 先ほどの文字列をテキストブロックで記述した場合、次のようになります。
//OK
const text = `緯度は'34.7'です。
経度は'135.4'です。`;
テンプレートリテラル内で変数の値を使用したい場合は、${}
を使用することで文字列を置換することで実現できます。
const latitude = '34.7';
const longitude = '135.4';
const text = `緯度は'${latitude}'です。
経度は'${longitude}です。`;
正規表現を使用する場合は正規表現リテラルを使用してください
正規表現を使用する場合は、可能であれば、文字列引数ではなく、正規表現リテラルを使用してください。
ただし、正規表現の内容が動的に変わる場合は、RegExp()
コンストラクタを使用してください。
1つのファイルにつき1つの export にしてください
アプリ内の各ファイルは、1つの論理的なModule
を表すべきです。
無関係なさまざまな関数やシンボルをエクスポートするようなModule
は避けてください。
多くの場合、これは1つのファイルにつき1つのexport
を持つことが良いことを意味します。
ただし、export
する関数と関係性が強い定数など、同じファイルに定義することにより保守性が高くなると考えられる場合は、例外としても構いません。
また、export
はnamed export
とし、ファイル名はexport
しているものと同じ名前にしてください。
例外的に複数のexport
がある場合は、そのModule
を表す代表的なexport
対象の名前をファイル名にしてください。
なお、Barrelに関してはこの規約の対象外とします。
値が存在しないことを表す場合は undefined を使用してください
TypeScriptには、値が存在しないことを表すための方法としてundefined
とnull
の2つがあります。
言語仕様上は、以下のような違いがあります。
- 変数に初期値が設定されていない場合は
undefined
になる。null
は明示的に設定しないとnull
にはならない。 undefined
は変数、null
はリテラルである。typeof
演算子の結果がundefined
はundefined
になる。null
はobject
になる。
しかし、これらの違いを考慮して開発者がundefined
とnull
を使い分けていくのは非常に難しいです。値が存在しないことを表すという大きな意味合いとしては同じと捉えることができるため、どちらかに統一することを推奨します。
本規約ではundefined
を使用することを推奨します。