Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

ステップバイステップで進めるYahoo!知恵袋のフロントエンドリアーキテクト

Koki Tsumura
November 23, 2024
3.7k

 ステップバイステップで進めるYahoo!知恵袋のフロントエンドリアーキテクト

Koki Tsumura

November 23, 2024
Tweet

Transcript

  1. © LY Corporation 自己紹介 2 2022/04 新卒入社 2022/06-2023/12 Yahoo!しごとカタログ 2023/01-現在

    Yahoo!知恵袋 経歴: 中華料理、中国語、釣り、ボドゲ マイブーム: 名前: 津村 光輝
  2. © LY Corporation 機能 • AI回答機能 • 企業公式Q&A機能 • 商品に関するQ&A機能

    3 Yahoo!知恵袋の紹介 • 2004年からあるQ&Aサイト(今年20周年 ) • 登録利用者数5,200万人、質問総数2億8,000万件、回答総数6億5,000万件以上 フロントエンドチーム/システムについて • フロントエンドチーム: 6人 • 行数: 380,000行程度 • 利用技術: Node.js、React、Express.js 等を利用 ※2024年4月3日時点のデータ
  3. © LY Corporation 4 フロントエンドチームが抱える問題 4 問題はたくさんある コードを置く 場所が不明確 バグの検知

    がしづらい コーディング規約/ レビュー観点がない コンフリクトの嵐 不具合対応に 時間がかかる
  4. © LY Corporation 6 何をいつ解決するか 何: 開発効率や品質の影響が大きい問題 6 いつ: 解決に必要な時間/分割可能性に依存

    • コスト小 → スキマ時間に解決 • 例: ABテストの仕組み、feature flag、コーディング規約 • コスト大/分割可能 • コスト大/分割不可能
  5. © LY Corporation 7 何をいつ解決するか 何: 開発効率や品質の影響が大きい問題 7 いつ: 解決に必要な時間/分割可能性に依存

    • コスト小 → スキマ時間に解決 • 例: ABテストの仕組み、feature flag、コーディング規約 • コスト大/分割可能 → リファクタリングデー • 例: デザインシステムのコンポーネントの実装 • コスト大/分割不可能
  6. © LY Corporation 8 何をいつ解決するか 何: 開発効率や品質の影響が大きい問題 8 いつ: 解決に必要な時間/分割可能性に依存

    • コスト小 → スキマ時間に解決 • 例: ABテストの仕組み、feature flag、コーディング規約 • コスト大/分割可能 → リファクタリングデー • 例: デザインシステムのコンポーネントの実装 • コスト大/分割不可能 → 専用の時間を確保 • 例: リアーキテクト、TypeScript化、ユニットテスト改善
  7. © LY Corporation 9 何をいつ解決するか 何: 開発効率や品質の影響が大きい問題 9 いつ: 解決に必要な時間/分割可能性に依存

    • コスト小 → スキマ時間に解決 • 例: ABテストの仕組み、feature flag、コーディング規約 • コスト大/分割可能 → リファクタリングデー • 例: デザインシステムのコンポーネントの実装 • コスト大/分割不可能 → 専用の時間を確保 • 例: リアーキテクト、TypeScript化、ユニットテスト改善 ↑ここについて話します
  8. © LY Corporation 12 問題点 12 controllers/detail.js • ルーティング •

    受け取ったリクエストを元にレスポンスを返す • リクエストパラメーターのバリデーション 本来あるべき処理 コードを置く場所が不明確
  9. © LY Corporation 13 問題点 13 • 各層との連携 • データの加工

    • 業務ロジック • ページに表示する文言に関するロジック controllers/detail.js 特定の関数が肥大化しやすく、処理が追いづらい/品質が下がる • ルーティング • 受け取ったリクエストを元にレスポンスを返す • リクエストパラメーターのバリデーション 本来あるべき処理 それ以外の処理 コードを置く場所が不明確 viewModel applicationService
  10. © LY Corporation 14 14 リアーキテクトする • ドキュメント/ルールの整備 コードを置く場所が不明確 解決策:

    リアーキテクト controller controller: • ルーティング • リクエストを元にレスポンスを返す • リクエストパラメーターのバリデーション applicationService model utility
  11. © LY Corporation 15 15 リアーキテクトする • ドキュメント/ルールの整備 コードを置く場所が不明確 解決策:

    リアーキテクト controller controller: • ルーティング • リクエストを元にレスポンスを返す • リクエストパラメーターのバリデーション applicationService: • 各層との連携(処理の進行役)を行う • データの加工/様々な業務ロジック model utility
  12. © LY Corporation 16 16 リアーキテクトする • ドキュメント/ルールの整備 コードを置く場所が不明確 解決策:

    リアーキテクト controller controller: • ルーティング • リクエストを元にレスポンスを返す • リクエストパラメーターのバリデーション applicationService: • 各層との連携(処理の進行役)を行う • データの加工/様々な業務ロジック model: データとふるまいをまとめたオブジェクト utility: 繰り返し色々な箇所で利用する関数
  13. © LY Corporation 17 17 リアーキテクトする • ドキュメント/ルールの整備 • リファレンス実装

    コードを置く場所が不明確 解決策: リアーキテクト controller controller: • ルーティング • リクエストを元にレスポンスを返す • リクエストパラメーターのバリデーション applicationService: • 各層との連携(処理の進行役)を行う • データの加工/様々な業務ロジック model: データとふるまいをまとめたオブジェクト utility: 繰り返し色々な箇所で利用する関数
  14. © LY Corporation 19 成果: リアーキテクト 19 • コードの見通しが良くなった(controllerの行数が1100行から120行に!) •

    リファレンス実装を元に新アーキテクチャでの実装が進んでいる • ユニットテストが書きやすくなった コードを置く場所が不明確
  15. © LY Corporation 26 TypeScript化の困難性 26 膨大な量の型づけ • 一個のファイルから呼び出している別ファイルの処理にも芋づる式で型が必要 •

    データの変換を重ねていると型をつけるのが大変、、、 並列して進む開発 • TS化している途中に、元のJSファイルに修正が入る バグの検知がしづらい
  16. © LY Corporation 28 28 バグの検知がしづらい //ts-nocheck controllers/detail.ts 解決策: TypeScript化

    (2/6) • controllers/detail.tsの最初の行に// ts-nocheck を追加する
  17. © LY Corporation 29 29 バグの検知がしづらい //ts-nocheck • controllers/detail.tsの最初の行に// ts-nocheck

    を追加する • controllers/detail.tsから呼び出しているファイルに型をつける *.d.ts *.ts detail.tsから呼び出されているJSファイル detail.tsから切り出したTSファイル controllers/detail.ts 解決策: TypeScript化 (3/6)
  18. © LY Corporation 30 30 バグの検知がしづらい //ts-nocheck detail.tsから呼び出されているJSファイル detail.tsから切り出したTSファイル controllers/detail.ts

    *.d.ts *.ts • // ts-nocheckを外す • 型がつけられていないものは、ひとまずanyにする(TODOコメントつき) 解決策: TypeScript化 (4/6)
  19. © LY Corporation 33 成果: TypeScript化 33 • 実行時になって、はじめて発覚するエラーが減った •

    安心してリファクタを行いやすくなった • ユニットテストで保証する範囲がわかりやすくなった バグの検知がしづらい
  20. © LY Corporation 36 36 • ユニットテストがメンテナンスできていない • コードコピーで肥大化(1つのテストファイルで27000行!) •

    ユニットテストは通るけど、実行時にエラーになる • mockの乱用 • テストケースの不足 バグの検知がしづらい 原因
  21. © LY Corporation 37 37 ユニットテストの実装&方針の変更 • テスト対象のコードを小さくする(リアーキテクトとセット) • パラメタライズドテストの導入

    • mockの初期化を行う関数の作成 • 基本はinputに応じたoutputの確認 • 副作用が発生する関数以外は基本的にmockしない バグの検知がしづらい 解決策: ユニットテストの実装&方針の変更
  22. © LY Corporation 38 38 ユニットテストの実装&方針の変更 • テスト対象のコードを小さくする(リアーキテクトとセット) • パラメタライズドテストの導入

    • mockの初期化を行う関数の作成 • 基本はinputに応じたoutputの確認 • 副作用が発生する関数以外は基本的にmockしない バグの検知がしづらい 解決策: ユニットテストの実装&方針の変更
  23. © LY Corporation 40 40 成果: ユニットテストの実装/方針の変更 バグの検知がしづらい • ユニットテストが書きやすく/読みやすくなった

    • ユニットテストの不足箇所がわかりやすくなった • 失敗してほしいテストが失敗するようになった
  24. © LY Corporation 41 41 どのタイミングでどの問題を解決するか • 問題の種類でいつ解決するかを分類 • 大きな問題でも小さく分割できる場合は、少しずつ進める

    どこに何を書くべきかのルールが決まっていない リアーキテクトで解決 • コードの置き場所のルールを整備 • リファレンス実装の追加(かなり重要) バグの検知がしづらい • TypeScript化で解決 • ユニットテストの実装&方針の変更で解決 まとめ
  25. © LY Corporation 44 Appendix: TypeScriptの導入+α 44 optional propertyを避ける 起こり得ない状態を作れないようにする

    オブジェクトのプロパティを上書きできないようにする 以下を利用した。大きな配列やオブジェクトの場合は、eslint-disableする場合もある • @typescript-eslint/prefer-readonly • @typescript-eslint/prefer-readonly-parameter-types • type-festのReadonlyDeep 関数の戻り値の型を明示的にする @typescript-eslint/explicit-function-return-typeを使って、意図せぬ関数の戻り値の型の変更を防ぐために、 戻り値の型を書くことを必須にした バグの検知がしづらい