Slide 1

Slide 1 text

Sansan株式会社 部署 名前 型情報を⽤いたLintで コード品質を向上させる Sansan技術本部 Bill One Engineering Unit 江川 綾

Slide 2

Slide 2 text

写真が入ります 江川 綾 Sansan株式会社 技術本部 Bill One Engineering Unit 2021年 Sansan新卒⼊社。 ⼊社以来、Webアプリケーション開発エンジニアとして インボイス管理サービス「Bill One」の開発に従事。 現在は、Bill Oneにおける機能開発に加えて、 プロダクトの技術的改善に向き合っている。 @erm1116_ @erm1116

Slide 3

Slide 3 text

About Bill One

Slide 4

Slide 4 text

Bill Oneは、Sansan株式会社が提供するインボイス管理サービスです。 郵送やメールといったさまざまな⽅法・形式で届く請求書をオンラインで⼀括受領し、素早く正確にデータ化。 請求書をクラウド上で⼀元管理することで、アナログで⾮効率な請求書業務をデジタル化します。 インボイス制度や電⼦帳簿保存法にも対応し、⽉次決算業務を効率化することで、企業経営における意思決定のスピードを加速します。 ※⽉次決算業務 毎⽉の営業成績、財政状況を明らかにするために⾏われる業務。経理担当者が⾏う業務で、毎⽉の数字の締め処理作業として発⽣します。

Slide 5

Slide 5 text

技術スタック フロントエンド / BFF サーバーサイド インフラ データベース ドキュメント React express ツール chromatic GitHub Copilot

Slide 6

Slide 6 text

アーキテクチャ Email Cloud Load Balancing Backend Cloud Run Database Cloud SQL Static Files Cloud Storage Cloud Tasks API Gateway Cloud Load Balancing API Client User Logging Error Reporting Cloud Build Bill One Entry Management / Developer Tools Cloud Functions Monitoring Authentication Auth0 Login Screen Frontend / BFF Cloud Run Pub/Sub

Slide 7

Slide 7 text

このコードは意図した通りに動く?

Slide 8

Slide 8 text

このコードは意図した通りに動く? 7

Slide 9

Slide 9 text

forEach()のコールバックにasync関数を渡している console.logによる出⼒は以下のようになる - “Done” が先に出⼒される - 逐次処理の完了に依存した処理を書くと 期待しない挙動になる - 数値が順不同で出⼒される - 順序依存したコードを書くと期待しない挙動になる forEach()は同期関数を期待するので Promiseの解決を待たない。 → async関数を渡した場合は意図しない挙動になりうる (Unhandled Rejectionの発⽣によりプロセスが強制終了する恐れもあるため危険) 8

Slide 10

Slide 10 text

forEach()のコールバックにasync関数を渡している console.logによる出⼒は以下のようになる - “Done” が先に出⼒される - 逐次処理の完了に依存した処理を書くと 期待しない挙動になる - 数値が順不同で出⼒される - 順序依存したコードを書くと期待しない挙動になる forEach()は同期関数を期待するので Promiseの解決を待たない。 → async関数を渡した場合は意図しない挙動になりうる (Unhandled Rejectionの発⽣によりプロセスが強制終了する恐れもあるため危険) 9 チーム開発でどうやって未然に防いでいく?

Slide 11

Slide 11 text

- Typed Linting について - Rust製 Linter におけるTyped Lintingの現在地 - Typed Linting とどう付き合っていくか 今⽇話すこと

Slide 12

Slide 12 text

Typed Linting について

Slide 13

Slide 13 text

- 型情報を⽤いて静的解析を⾏うこと - 多くの Lint ルールは1つのファイルごとに⽣成された 抽象構⽂⽊(AST)をもとに構⽂違反を警告する - このとき、例えば他ファイルからインポートされたコードの情報を理解することはできない - Typed Linting では、ASTに加えて型情報を⽤いた解析が可能となる Typed Linting (Type-Aware Linting) とは import { bar } from “bar.ts”; … foo.ts AST-based Linting Typed Linting foo.ts (AST) foo.ts (AST) foo.ts (Type Information) Type Type Type Type Type

Slide 14

Slide 14 text

- ESLint で TypeScript 対応するためのツールである typescript-eslint を⽤いて⾏うことができる - 設定ファイルで画像のように設定する - recommended | strict | stylistic は プロジェクトに合わせて選べばOK - 必要な個別のルールのみ有効にしてもよい - Bill Oneでは⼀部のルールしか有効にしていない - 後からこうした Lint ルールを組み込むのは⼤変なの でプロジェクト⽴ち上げのタイミングでやっておき たい Typed Linting を有効にするには

Slide 15

Slide 15 text

- 型情報をもとに静的解析できるため、意図しない型が指定されている箇所を特定できる - 例えば、以下は recommended で適⽤されるルールの⼀部 - @typescript-eslint/no-unsafe-* - any型が紛れ込み、型安全性を低下させるようなパターンを防⽌するためのルール - @typescript-eslint/unbound-method - thisの束縛によるランタイムエラーを避けるためのルール - @typescript-eslint/no-floating-promises - ハンドルされていない状態のPromiseが⽣まれることを防⽌するためのルール - ほかにも有⽤なルールが提供されている(ルール⼀覧) Typed Linting でできること

Slide 16

Slide 16 text

再掲: forEach()のコールバックにasync関数を渡している Promise returned in function argument where a void return was expected. @typescript-eslint/no-misused-promises

Slide 17

Slide 17 text

Lint の実⾏時間が増加する - 型情報を⽤いて静的解析を⾏う = TypeScript を利⽤した型情報の取得 + ルールの評価 ≒ tsc によるビルド時間 - 型情報の取得⾃体に時間がかかるため、改善には TypeScript のコンパイル時間の改善に 向き合う必要がある(Performance | TypeScript Wiki) Typed Linting の問題点

Slide 18

Slide 18 text

Rust製 Linter における Typed Lintingの現在地

Slide 19

Slide 19 text

- 代表的なものとして、Biome, oxlint などがある - Biome: web開発におけるツールチェインの⼀部としてLinterを提供 - oxlint: JSツールチェインのインフラ(Oxc)としてLinterを提供 - それぞれのベンチマークによるとESLint に⽐べて Biome は最⼤15倍早く、oxlint は50~100倍速いとされている Rust製 Linter

Slide 20

Slide 20 text

- 型情報を取得するために TypeScript Compiler (tsserver) を利⽤する場合、 typescript-eslint と同様にパフォーマンスの問題が発⽣する - 更にこのとき、取得できた型情報と AST の構造には差分があるため、 AST のマッピング処理が発⽣することになりメンテナンスコストにつながる - 上記より、 Biome や oxlint では、TypeScript Compiler API の部分的な実装として、 ⼀部の型推論に対応することで Typed Linting を実現しようとしている - Biome: https://github.com/biomejs/biome/issues/3187 - oxlint: https://github.com/oxc-project/oxc/issues/3105#issuecomment-2100859582 - が、進捗はあまりなさそうに⾒える Rust製 Linter における Typed Linting

Slide 21

Slide 21 text

いずれにせよ、現状は Rust製 Linter では 型情報を⽤いたLint は利⽤できない そのため、Typed Linting による安全性の恩恵を加味したうえで Lint ツールの選定を⾏う必要がある Rust製 Linter における Typed Linting https://github.com/typescript-eslint/typescript-eslint/issues/10022 (翻訳&要約) 「Typed Linting とはこういうもので、なぜそれを有効にする必 要があるのか」がうまく伝わっていない。 多くのユーザーが Typed Linting を使わず、Biome や oxlintなど に移⾏する理由の⼀端はそこにあると思います。

Slide 22

Slide 22 text

Typed Linting とどう付き合っていくか

Slide 23

Slide 23 text

現状は他のRust製 Linter で型情報を⽤いた Lint が利⽤できないため、 Typed Linting のためには typescript-eslint を利⽤する必要がある → typescript-eslint におけるパフォーマンス問題をどう緩和・改善できるかが重要となる Typed Linting とどう付き合っていくか

Slide 24

Slide 24 text

1. typescript-eslint でできるパフォーマンス改善を⾏う 2. 適⽤する typescript-eslint のルールを環境ごとに切り替える 3. Lint ツールを併⽤する * ここで紹介している⼿段はあくまでアイディアであり、実施を推奨するものではありません Typed Linting とどう付き合っていくかのアイディア

Slide 25

Slide 25 text

1. typescript-eslint でできるパフォーマンス改善を⾏う - Profiling したうえで適⽤するルールの⾒直し - TypeScript のビルド時間の改善 (Performance | TypeScript Wiki) - projectService を有効にする - サードパーティのプラグインの設定を⾒直す(Third-Party Plugins) - 注意: 実⾏速度改善のために ESLint のキャッシュを有効にしたい気持ちに駆られるが、 ESLint のキャッシュはファイル単位で⾏われるため、ファイルをまたいだ解析を⾏う Typed Linting との相性が悪く利⽤しないことが推奨とされている Typed Linting とどう付き合っていくかのアイディア

Slide 26

Slide 26 text

2. 適⽤する typescript-eslint のルールを環境ごとに切り替える - FlatConfig を活かし、実⾏環境に応じて適⽤するルールを切り替える (CI環境を例に挙げているが、エディタ内でのみ特定ルールを無効にすることもできる) - そもそも素早いフィードバックを得るための Lint なので、 個⼈的にはできるだけ取りたくない⼿段だとは考えている Typed Linting とどう付き合っていくかのアイディア

Slide 27

Slide 27 text

Typed Linting とどう付き合っていくかのアイディア 3. Lint ツールを併⽤する - Typed Linting 以外を Biome or oxlint で実⾏することで、並列実⾏による全体実⾏時間の削減 - 厳密にはカスタムルールは ESLint が必要など、細かいルールの使い分けが発⽣する - 全体のボトルネックが型情報の取得の場合はそこまで改善しない可能性もある - 複数 Lint ツールの使い分け・ルール管理により複雑性は増加 - カスタムルールは ESLint が必要など、細かいルールの使い分けが発⽣する - 併⽤に対して、ツールごとに思想が異なっているようにみえる - Biome: ESLint からの“移⾏”を推奨(Migrationコマンド を提供) - oxlint: ESLint との“併⽤”を想定(ルールの競合を防ぐためのプラグインを提供) - 個⼈的には Linter 単体で⾒るとルールのメンテナンスの観点から oxlint の⽅が ベターという考え。⼀⽅ Formatter としても利⽤することを考えると Biome になりそう。

Slide 28

Slide 28 text

まとめ

Slide 29

Slide 29 text

- 型情報を⽤いて静的解析(Typed Linting)を⾏うことで コード品質を⾼める仕組みづくりができる - Rust製 Linter において 型情報の取得は現状未対応なため、 これを加味してプロジェクトに合わせた Lint ツールを選定する必要がある - 現状は Typed Linting のためには typescript-eslint を利⽤するしかない まとめ

Slide 30

Slide 30 text

No content