■イベント TypeScriptを活用した型安全なチーム開発 https://sansan.connpass.com/event/292695/
■登壇概要 タイトル:チーム開発における自作ESLintルールの活用と戦略 登壇者:技術本部 Digitization部 Bill One Entryグループ 薩󠄀田 和弘
■Digitization部 エンジニア 採用情報 https://media.sansan-engineering.com/digitization
チーム開発における⾃作ESLintルールの活⽤と戦略TypeScriptを活⽤した型安全なチーム開発Sansan 株式会社Digitization部 Bill One Entry Group&⽥ 和弘
View Slide
!⽥ 和弘Sansan株式会社Digitization部 Bill One Entry Group2021年 Sansan中途⼊社。以降Bill One Entry Groupとして、請求書のデータ化に向き合う。
はじめに(ES)Lint、使っていますか?
はじめに(全員の⼿が挙がる予定✋)(ES)Lint、使っていますか?
ESLintを使うと⾔っても、さまざまな使い⽅がある。- 検索したままにとりあえずルールを⼊れてみる- @typescript-eslint- eslint-plugin-react- 強⼒なルールプリセットを使う- eslint-config-airbnb- XO- チームの癖に合ったルールを設定する/外すはじめに
- ESLintのルールを「作る」- ⾃作ESLintを使う前に確認するべき点- ⾃作ESLintの活⽤と戦略本⽇話すこと
ESLintを「使う」から「作る」へ
Lint- コードのパターンによって検証を⾏う- 静的解析である- バグを事前検知する
環境変数に現在の環境を記載している。それを利⽤する⽅法。ルール例- nodeEnvは内部的にNODE_ENVの値を取得する- 挙動の違いにより、ヒヤリハットが発⽣× NG- process.env.NODE_ENV○ OK- nodeEnv()
作り⽅process.env.NODE_ENVASTを探索し、特定のパターンが現れたらエラーを上げる
実装(実際にはもうちょい多いです)MemberExpression(node: MemberExpression & Rule.NodeParentExtension) {const { object: parentNode, property: targetProperty } = node;const { object: parentObject, property: parentProperty } = parentNode;if (parentObject.name === "process" &&parentProperty.name === "env" &&targetProperty.name === "NODE_ENV") {// エラーレポート}}
⾃作ESLintルールを作る前に
⾃作ESLintルールを作る前に1. すでに同じルールが作られていないか
- ESLintの持つデフォルトのルールで 266 個- eslint-plugin-から始まるnpm packagesは 6500 個以上すでに同じルールが作られていないか⼤体の場合解決するためのルールは存在する
すでに同じルールが作られていないかファイル名に関するルール⾃作してみたが、調べてみると存在した。- eslint-plugin-unicornのルールの1つ- 他にもファイル名に関するLintは多かった× NG- snake_case_file_name.ts- camelCaseFileName.ts○ OK- kebab-case-file-name.ts
⾃作ESLintルールを作る前に2. ESLint以外で解決できないか?
- JavaScriptのエコシステムはかなり成熟している- TypeScriptの型チェックで⼤概は事⾜りるESLint以外で解決できないか?ハンマーだけを持っていたら全てが釘に⾒える
ESLint以外で解決できないか?expressのハンドラの型はデフォルトで⾮常に緩い× NG ○ OK(req: Request) => {req.params // => any}(req:Request) => {req.params // => { userId: string }}最初はESLintでRequestの型引数を強制しようと思ったが...。
ESLint以外で解決できないか?我々は習慣的にexpressのRequestHandlerを利⽤していたtype RequestHandler =express.RequestHandler;const handler: RequestHandler = (req) => {req.params // never}より厳しい型のRequestHandlerを⽤意することで解決した。
ESLint以外で解決できないか?加えて@types/express/index.d.ts に下記を記載すると、RequestHandlerがdeprecated扱いになり誤って使うことはないdeclare module "Express" {/** @deprecated */interface RequestHandler {}}
⾃作ESLintルールの活⽤と戦略
⾃作ESLintルールの活⽤と戦略- ESLintでなければならないルール- 今まで誰も書いていないルール
Lintとは- コードのパターンによって検証を⾏う- 静的解析である- ロジックに関してはテストで回避する- バグを事前検知する- 今まで発⽣したバグや、チーム内で統⼀するべきものに対して⾏う- フォーマット等例外もあるESLintでなければならないルール
今まで誰も書いていないルール我々のチームではArray#reduceを使うケースが有る。⼀⽅でArray#reduceで第⼆引数の値を誤って利⽤してしまう場合がある。これを早期発⾒したい。const sum = 0;[1, 2, 3].reduce((acc, val) => {return sum + val; // sumではなくacc}, sum);// => expected: 6, actual: 3
今まで誰も書いていないルールArray#reduceは「利⽤してはいけない」というルールはよく⾒かける。が、第⼆引数を利⽤してはいけないというルールはなかったため作った。
最も機会があるのは「チーム内で発⽣したインシデント」に対するルール⾃作ESLintルールの活⽤と戦略
チーム内で発⽣したインシデントエラーが発⽣した場合、res.sendがされていなかった。結果動作が想定した挙動にならなかった。× NG ○ OKconst handler = (req, res) => {try {const result = someFunction();res.send(result);} catch (e) {return;}}const handler = (req, res) => {try {const result = someFunction();res.send(result);} catch (e) {res.sendStatus(500);}}
チーム内で発⽣したインシデント我々のチームの習慣に合わせてルールを作成。全てのフローでsendが送られていることを確認している。
チーム内特有のバグ・事象は、仕組みとして⽤意しないと、⼈間が注意するしか無い。- 古参メンバーがレビューなどを通して⾒つけるしか無い- 新⼈メンバーはドキュメントやレビューなどを通して知るしか無い仕組み化できるのであれば、⼩さな落とし⽳でも避けられる。チーム内で発⽣したインシデント
- ESLintのルールを作るという発想を持つ- 作るルールは考える- すでに同じルールが作られていないか- ESLintで本当に解決すべきか?- チーム特有の事象はベスト- ESLintは基本はコードのパターンに対する注意- インシデント・ヒヤリハットの再発防⽌策として仕組み化できないか、1つの⼿札として使うまとめ
ハンマーを増やす
ESLintを⾜がかりに「エコシステムを活⽤する」- VSCode / Vim / Emacsの拡張・プラグイン- TypeScript Language Service Plugin- 意外に使い所は少ないので使ったことはないけど...。ハンマーを増やす
VSCode拡張紹介js-teleporterJavaScriptのテストと実装を⾏き来する。やってることはディレクトリ検索なのでJavaScript以外でも使えはする。
VSCode拡張紹介js-test-outlineテストケースをリストアップする。jest拡張機能にも似たような機能があるが、そちらは単純にリストアップするわけではない。