はじめに
エンジニアになって2年が経過した私が最近気をつけられるようになってきたことについての備忘録です。1年後くらいに振り返って懐かしむことを目的としています。
序盤は「気をつけられるようになってきたこと」というには抽象度が高すぎる内容になってしまいましたが、大目に見てください。
型定義ちゃんとやる
TypeScriptに依存する内容です
私自身、TypeScriptを触り始めた当初は
「型定義?何それおいしいの?」
「定義するメリットがよくわからん、時間かかるし...」
などと愚民丸出しで斜に構え、隙あらばany型に逃げようとしていました。
しかし、強いエンジニアほどany型に逃げないことに気づき、型定義と向き合う覚悟を決めたのでした。
仕様書の代わりになる
以下の型なしTypeScriptファイルを読んでみます
export class GetOrder {
async execute() {
const orders = await this.getOrdersFromExternalApi();
return this.prepareOrders(orders);
}
private async getOrdersFromExternalApi () {
// 何かしらの処理
return orders;
};
private prepareOrders (orders) {
// 何かしらの処理
return preparedOrders;
}
}
読んだとき、得られる情報は
- まず外部APIから何かしらの情報を取ってきているっぽい
- その後に何か整形をして返却しているっぽい
くらいです。
例えば「注文日の形式がおかしいからYYYY-MM-DDにしておいて」と言われたとき、わざわざ外部APIのドキュメントを見に行ったり、実際に実行してみて変数ordersの中身を見るのは面倒です。
型を定義しておけば、どのプロパティを修正すれば良いのか一眼でわかります。
interface ExternalApiOrder {
id: number;
itemId: number;
amount: number;
orderedDate: number; // 注文日(秒)
status: string;
}
interface PreparedOrder {
id: number;
itemId: number;
amount: number;
orderedDate: string; // 注文日(YYYY-MM-DD)
status: string;
}
export class GetOrder {
async execute(): Promise<PreparedOrder[]> {
const orders = await this.getOrdersFromExternalApi();
return this.prepareOrders(orders);
}
private async getOrdersFromExternalApi (): Promise<ExternalApiOrder[]> {
// 何かしらの処理
return orders;
};
private prepareOrders (orders: ExternalApiOrder[]): PreparedOrder[] {
// 何かしらの処理
return preparedOrders;
}
}
TypeScriptの型定義により、
- オブジェクトがどのようなプロパティを持っているのか
- 関数が何を受けとり、何を返却するのか
がわかるようになりました。
さらには、入出力さえ分かればいいという場合には型情報だけ見れば良いようになっています。
型を定義することで、「コードを仕様書として機能」させ、「将来的な変更をより容易にする」ことが理解できました。
開発効率が上がる
エディタによってはTypeScriptの補完機能が効くため、オブジェクトのプロパティを表示してくれます。
また、そのオブジェクトに存在しないプロパティが指定されていた場合、実行前に警告してくれます。
実行してからエラーになるより遥かに短い時間でコードの問題に気づくことができます。
などなど、型を定義しておくことによるメリットはこれ以外にもたくさんあります。
安易にany型に逃げていた私ですが、今では極力anyを撲滅し、TypeScriptの恩恵にあずかっています。
TypeScriptをフル活用するため、まだまだ深堀りを続けていく予定です。
単体テストちゃんとやる
正直、ずっと単体テストを面倒くさがっていました。
書くのに時間がかかるし、面白味はないし、地味だし...などとまたも自身の無知をひけらかしていました。
しかし、メリットを理解してからは単体テストに楽しさを見出せるようになりました。
ここでは単体テストを書くことのメリットについて触れておきます。
修正コストが下がる
単体テストのカバレッジが高い場合、「テストをパスする = 仕様を満たしている」と解釈できます。
軽微な修正が必要になった場合は、コード修正→単体テストによって、変更後も仕様を満たしているか確認することができます。
逆に単体テストが薄いもしくは書かれていない場合、
- いま書かれているコードが仕様を満たしているのかわからない
- これから加える変更が仕様を満たしているのかわからない
となり、本来単体テストで担保されるべき箇所まで結合テストで担保する必要が出てきます。
一般的に結合テストは単体テストより大きなコストがかかるため、薄い単体テストはそれだけで開発効率の低下を招きます。
単体テストのカバレッジを上げることで、修正のコストが下がることが分かりました。
仕様書の代わりになる
単体テストのコメントを見ると、対象の関数や関数群がどのように振る舞うべきかが理解できます。
上の項で既に述べてしまいましたが、カバレッジの高い単体テストは仕様書の代わりになります。
describe('外部APIから取得した注文情報の加工', () => {
describe('注文日が秒数で入力された場合', () => {
it('YYYY-MM-DD形式に変換すること', () => {
// テストコード
});
});
});
上記のテストコードだけでも、前章のprepareOrdersの仕様をある程度知ることができます。
機能追加や改修を行う際には、単体テストをカバレッジ高く書いておくことで、次回以降に改修するメンバーが楽になることが分かりました。
その他細かいところ
マジックナンバーを使わない
すごく基本的なところではありますが、急いでいたりするとついついやりがちなマジックナンバー。
経験豊富なエンジニアのコードでもたまに見かけます。
単純に何かわからない数値は読んでいてストレスになりますし、その数値について誰かに質問しに行く時間も無駄です。マジックナンバーを使っていないか、最近ではPullRequestを出す前にセルフレビューしています。
コメントを書く / 書かない
-
翻訳コメントを書かない
翻訳コメントはその名の通り、英語を訳しただけのコメントです。
コードを読めばわかるコメントは基本的に無駄なので、書かないようにしています。
-
要約コメントは必要に応じて書く
先ほど翻訳コメントは書かないと言いましたが、関数があまりに長い場合は、それを要約するコメントを書く場合があります。
もちろん長すぎる関数を作らないことが先決ですが、他のメンバーが書いたコードに理解不能なものがあれば、それを訳したコメントを書いておいてあげた方が全体像の把握が早く進むと思います。 -
コメントを書かなくても読めるコードが理想
とはいえ、やはりコメントがなくても読めるコードが理想です。
関数の長さや命名、適切な責務の分離が行われていれば、コメントを書く必要はないはずです。
コメントのいらないコードを目指して勉強します。
おわりに
まだまだ半人前以下の雑魚ですが、始めた頃に比べれば気をつけられることも増えてきた気がしています。
1年後の自分がもっとレベルの高い「気をつけられるようになったこと」を書いてくれることを願い、この記事をおわりにしたいと思います。
最後までご覧いただきありがとうございました。