Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
デッドコード撲滅のためにエンドポイントの棚卸し機能を作った話 〜ESLintカスタムルールとt...
Search
Taiga KATARAO
June 04, 2024
Technology
340
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
デッドコード撲滅のためにエンドポイントの棚卸し機能を作った話 〜ESLintカスタムルールとtypescript-estree利用のすすめ〜
Taiga KATARAO
June 04, 2024
More Decks by Taiga KATARAO
See All by Taiga KATARAO
CloudbaseのTypeScript事情~【TypeScript特集】1時間で6社と出会う!採用ピッチイベント~
tarao1006
0
16
Other Decks in Technology
See All in Technology
LLMリーダーボードアップデートに向けたAgentic Math_SWEのトレースについて
nejumi
0
200
歴史から理解するクラウドインフラのしくみ
kizawa2020
1
200
CloudWatchから始めるAWS監視
butadora
0
310
データエンジニアこそ組織のオントロジーに向き合うべき — 問いに答えるAIから、事業を動かすAIへ
gappy50
7
3.2k
NetBoxを利用した作業効率化の試み_NetDevNight4
tnoha
0
450
13年運用タイトルのサーバーサイドが辿り着いた現在地 ― モンスターストライクにおける技術・組織・AI活用から得た知見
mixi_engineers
PRO
1
430
今こそ聞きたいソフトウェア設計 ドメイン駆動設計再入門
masuda220
PRO
7
1.5k
[しろおび夏祭り2026] チャットするAIから、作業するAIへ - 使われ方の変化と、その裏側で起きていること
kk0n
0
1.5k
【CEDEC2026】コードレビュー支援ツール開発から学ぶ:LLMを用いた業務システムの実践的な運用設計と誤出力対策
cygames
PRO
0
420
もう一度考える SRE チームの作り方・育て方 / Rethinking SRE #1: Building and Growing SRE Teams
rrreeeyyy
5
910
第3回しろおびセキュリティスポンサーセッション
log0417
0
120
現場で使える AWS DevOps Agent 活用ノウハウ - Release Management 機能の検証結果を添えて / AWS DevOps Agent Release Management and Know-How
kinunori
5
920
Featured
See All Featured
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
340
Chasing Engaging Ingredients in Design
codingconduct
0
250
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
200
Between Models and Reality
mayunak
4
380
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.6k
GitHub's CSS Performance
jonrohan
1033
470k
We Have a Design System, Now What?
morganepeng
55
8.2k
The Limits of Empathy - UXLibs8
cassininazir
1
580
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
350
Writing Fast Ruby
sferik
630
63k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.1k
Transcript
デッドコード撲滅のためにエンドポ イントの棚卸し機能を作った話 〜ESLintカスタムルールとtypescript-estree利用の すすめ〜 Cloudbase株式会社 tarao (Taiga Katarao)
© 2024 Cloudbase Inc. tarao (Taiga Katarao) Cloudbase株式会社 ソフトウェアエンジニy x
バックエンド〜Webフロントエンドまで TypeScriptで開6 x より興味があるのはWebフロントエンド @tarao1006 @tarao1006
© 2024 Cloudbase Inc. 話すこ と F ESLintのカスタムルールの一R F 実装方法を逐一解説することはしなB
F 全貌はZennに投稿済y F エッセンス (& 時間が余れば記事投稿後の取り組みの紹介) https://zenn.dev/cloudbase/articles/list-endpoints
Cloudbaseの技術スタック © 2024 Cloudbase Inc. RDB API Server GraphDB Data
Loader Storage スキャナー お客様のクラウド環境 Web Frontend お客様
© 2024 Cloudbase Inc. 素朴にExpressとSWRを使用し ていた T Expressで素朴にルーティング T SWRで素朴にデータフェッチ
T useFetchはuseSWRの薄いwrappe T 今日はClient Componentの話 T エッセンスはServer Componentにも展開可能なはず const = => ... ( , ) { }; app. ( , handler); handler get req res " " /v1/foo/:fooId const = => const = ... () { { } < >( fooId ); }; Component useFetch ResponseType data `/v1/foo/${ }`
© 2024 Cloudbase Inc. 呼び出されていないエンドポイン トあり ませんか ? w 「v2を追加した時にv1を消し忘れた」とか「そのエンドポイン
トを使用しているページがなくなったr w 新任者の無駄なキャッチアップコストになるなど、未使用エンド ポイントはないに越したことはな w Expressのルーティングは変数ではないため、未使用変数として 検出するといったことはできな w 「消し忘れないように注意する」は解決策にならない
© 2024 Cloudbase Inc. 解決を試みる I 定義済みエンドポイント集合から使用エンドポイント集合を引け ば、未使用エンドポイント集合が分かるはず GitHub Actionsで怒られるようになった
使用エンドポイント 未使用エンドポイント
© 2024 Cloudbase Inc. 定義済みエンドポイン トの列挙 8 割愛 t 8
Expressのルーターをゴニョゴニョすることで実現可d 8 Zenn: 状態5: バックエンドでエンドポイントの棚卸しを自動化
© 2024 Cloudbase Inc. 使用エンドポイン トの列挙 v 難儀 q v
APIサーバーと違ってルーターのようなものは存在しなG v ESLint & @typescript-eslint/typescript-estreeが大活躍した https://github.com/typescript-eslint/typescript-eslint
Step1: urlcat導入 フロントエンドでもExpressと同じ文字列を使ってAPIリクエストする ようにするためにurlcatを導入 © 2024 Cloudbase Inc. const =
=> const = ... () { { } ( ( , { fooId })); }; Component useFetch urlcat data " " /v1/foo/:fooId const = ( v1 foo :fooId , { fooId: foo , barId: bar }); console. (path) path urlcat log "/ / / " " " " " // /v1/foo/foo?barId=bar https://github.com/balazsbotond/urlcat
Step1: urlcat導入 、、、全然定着しない © 2024 Cloudbase Inc.
Step2: 型で縛る U エンジニアが増えることも考えると毎回PRで指摘するのはサステナ ブルじゃなa U TypeScriptなので型で縛ろう © 2024 Cloudbase
Inc.
Step2: 型で縛る(定義側) // /foo/:fooId/bar/:barId のような文字列から // { fooId: string }
& { barId: string } のような型を生成する型 type extends = class extends constructor private : private : private : return ... ... const = < > ...; < > { ( , < >, < , >) {} () { ( .pathname, { .params, .searchParams }); } } < >(path: Path< >) => { return (path. ()); } PathParams T Path T T PathParams T Record toString urlcat useFetch useSWR toString string string string any this this this T pathname params searchParams any © 2024 Cloudbase Inc. useFetchの引数をstringからPathに変更し、 Pathクラスを通してurlcatの使用を強制
Step2: 型で縛る(使用側) © 2024 Cloudbase Inc. const = => const
= new () { { } < >( ( , { fooId }) ); } Component useFetch ResponseType Path data "/v1/foo/:fooId" Pathを渡すことを強制
Step2: 型で縛る(使用側) © 2024 Cloudbase Inc. const = => const
= new () { { } < >( ( , { fooId }) ); } Component useFetch ResponseType Path data "/v1/foo/:fooId" const = => const = new () { { } ( (` ) ); } Component useFetch ResponseType Path data < > /v1/foo/${fooId}` Pathを渡すことを強制 Oh... テンプレートリテラルを渡すという抜け道
Step3: ESLintで縛る j 型で縛りきれないのならESLintで縛ろう (詰んだと思っていたが耐えたr j 調べてみたらESLintの世界では文字列リテラルとテンプレートリテ ラルを区別できるぽい!P j
というわけで、PathクラスのNewExpressionの第一引数が TemplateLiteralだった場合にエラーになるルールを作れば良い(実 際には文字列リテラル以外を禁止) © 2024 Cloudbase Inc.
Step3: ESLintで縛る export const = => return for const of
if === && === && !== ESLintUtils.RuleCreator. < , >({ : ( , [ ]) { { ( ) { ( options) { ( node.callee.type node.callee.name option.className node.arguments[option.argumentIndex].type ) { context. ({ node, messageId: , data: { className: option.className, argumentIndex: (option.argumentIndex), }, }); } } }, }; }, }); rule option withoutDocs Options MessageId create NewExpression report ordinal context options node "Identifier" "Literal" "restrict-literal-argument" © 2024 Cloudbase Inc. 実装の雰囲気
Step3: ESLintで縛る useFetch Path useFetch useFetch useFetch useFetch Path useFetch
Path useFetch Path ( ( , { fooId })); ( fooId ); (文字列変数); (文字列を返す関数呼び出し); ( ( fooId )); ( (文字列変数)); ( (文字列を返す関数呼び出し)); new new new new "/v1/foo/:fooId" `/v1/foo/${ }` `/v1/foo/${ }` © 2024 Cloudbase Inc. ESLintでエラー 型でエラー
Step4: typescript-estreeを使って使用エンドポイントを列挙 Pathクラスの第一引数は文字列リテラルしかあり得なくなったので、あとは Pathクラスの第一引数を列挙すれば、使用エンドポイントを列挙できそ う!!c でも、どうやって列挙する、、、? m
気合いで正規表現P というか、ESLintのカスタムルール作った時にエラー吐いてるところをstringの arrayにpushする実装に変えられたらめっちゃ簡単に列挙できるのでは? © 2024 Cloudbase Inc.
Step4: typescript-estreeを使って使用エンドポイントを列挙 g @typescript-eslint/typescript-estree !!!!p g “A parser that produces
an ESTree-compatible AST for TypeScript code.” であり@typescript- eslint/parserの内部で使用されているr g ESLintのカスタムルールとほぼ同じように書けて学習コストが低いのでおすすめ © 2024 Cloudbase Inc. const = new if === && === && === && typeof === < >() (node) { ( node.callee.type node.callee.name node.arguments[ ].type node.arguments[ ].type ) endpoints. (node.arguments[ ].value); } } endopoints string 0 0 0 Set NewExpression add "Identifier" "Path" "Literal" "string" NewExpression report ordinal (node) { ( options) { ( node.callee.type node.callee.name option.className node.arguments[option.argumentIndex].type ) { context. ({ node, messageId: , data: { className: option.className, argumentIndex: (option.argumentIndex), }, }); } } } for const of if === && === && !== option "Identifier" "Literal" "restrict-literal-argument"
Step5: 型パラメータに制約をつける v 実は、Expressから定義済みエンドポイントを列挙する際にユニオン型にしてし まえば、Pathの型引数に制約をつけられu v ただし、この制約をつけたとしても前述したカスタムルールやtypescript- estreeによるASTの走査は依然必P v なぜか
© 2024 Cloudbase Inc. type = | | class extends constructor private : private : private : ; < > { ( , < >, < , >) {} } GatPathname Path T GetPathname T PathParams T Record "/v1/foo/:fooId" "/v2/foo/:fooId" "/v1/bar/:barId" pathname params searchParams string any Expressからユニオン型を生成して型引数に制約をつける
Step5: 型パラメータに制約をつける 4 TypeScriptのユニオン型が必要十分であるかを検証するのは難し& 4 結局、使用している値を列挙する仕組みは必要 © 2024 Cloudbase Inc.
type = | const : = AorB AorB "A" "B" "A" A で十分 type = AorB "A"
© 2024 Cloudbase Inc. 今後の展望と共に取り組みをZennに投稿 Zenn: 今後の展望
© 2024 Cloudbase Inc. 今後の展望と共に取り組みをZennに投稿 Zenn: 今後の展望 やりました やりました
Schema firstに移行 p これまでは「Express → エンドポイント列挙」であったため、 Expressを起動しないとエンドポイントを列挙できず、パフォーマン スの問題があっC p 「エンドポイント列挙
→ Express」のように向きを変えることで問 題を解7 p GraphQLにおけるschema firstのようなイメージ © 2024 Cloudbase Inc.
Schema firstに移行 © 2024 Cloudbase Inc. app. ( , );
app. ( , ); app. ( , ); get get post "/v1/foo/:fooId" "/v2/foo/:fooId" "/v1/foo" ... ... ... app. ( , ); app. ( , ); app. ( , ); get get post "/v1/foo/:fooId" "/v2/foo/:fooId" "/v1/foo" ... ... ... type = type = ; ; GetPathname PostPathname "..." "..." const = { : z. ({ request: z. ({ pathParams: z. ({}), queryParams: z. ({}), body: z. ({}), }), response: z. ({}), }), : z. ({ ... }), : z. ({ ... }), }; schema "GET /v1/foo/:fooId" "GET /v2/foo/:fooId" "POST /v1/foo" object object object object object object object object メソッドとパス リクエスト/レスポンスの情報
Schema firstに移行 ) スキーマにリクエスト/レスポンスの型の情報があるのでgenericsが 不要になった © 2024 Cloudbase Inc. const
= { } < >( fooId ); data useFetch ResponseType `/v1/foo/${ }` const = new { } ( ( , { fooId })); data useFetch Path "/v1/foo/:fooId"
© 2024 Cloudbase Inc. まとめ d コーディングルールはESLintのルールで表現できると良b d ASTにおいてはstringリテラルとテンプレートリテラルを区別で きf
d typescript-estreeはESLintの知識を活かしてASTをいじれるよう になるのでおすすe d (REST APIであってもエンドポイントの定義はSchema firstにし ておくと便利)
We Are Hiring! Engineer Entrance Book