Slide 1

Slide 1 text

© LY Corporation ステップバイステップで進める Yahoo!知恵袋のフロントエンド リアーキテクト LINEヤフー株式会社 津村光輝

Slide 2

Slide 2 text

© LY Corporation 自己紹介 2 2022/04 新卒入社 2022/06-2023/12 Yahoo!しごとカタログ 2023/01-現在 Yahoo!知恵袋 経歴: 中華料理、中国語、釣り、ボドゲ マイブーム: 名前: 津村 光輝

Slide 3

Slide 3 text

© 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日時点のデータ

Slide 4

Slide 4 text

© LY Corporation 4 フロントエンドチームが抱える問題 4 問題はたくさんある コードを置く 場所が不明確 バグの検知 がしづらい コーディング規約/ レビュー観点がない コンフリクトの嵐 不具合対応に 時間がかかる

Slide 5

Slide 5 text

© LY Corporation 5 何をいつ解決するか 何: 開発効率や品質の影響が大きい問題 5 いつ: 解決に必要な時間/分割可能性に依存 • コスト小 • コスト大/分割可能 • コスト大/分割不可能

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

© LY Corporation 10 コスト大/分割不可能な問題 10 バグの検知がしづらい コードを置く場所が不明確 ???

Slide 11

Slide 11 text

© LY Corporation 11 コスト大/分割不可能な問題 11 バグの検知がしづらい コードを置く場所が不明確 ???

Slide 12

Slide 12 text

© LY Corporation 12 問題点 12 controllers/detail.js • ルーティング • 受け取ったリクエストを元にレスポンスを返す • リクエストパラメーターのバリデーション 本来あるべき処理 コードを置く場所が不明確

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

© LY Corporation 18 解決策: リアーキテクト 18 リファレンス実装の追加 • 今後のリアーキテクトが進めやすくする • 実装をしているときに迷いづらくする コードを置く場所が不明確

Slide 19

Slide 19 text

© LY Corporation 19 成果: リアーキテクト 19 • コードの見通しが良くなった(controllerの行数が1100行から120行に!) • リファレンス実装を元に新アーキテクチャでの実装が進んでいる • ユニットテストが書きやすくなった コードを置く場所が不明確

Slide 20

Slide 20 text

© LY Corporation 20 コスト大/分割不可能な問題 20 バグの検知がしづらい コードを置く場所が不明確 ???

Slide 21

Slide 21 text

© LY Corporation 21 バグの検知がしづらい 21 単純なミスに実行時になるまで気づきづらい(JavaScriptで書かれているため) • タイプエラー/オブジェクトのプロパティの上書き ユニットテストのメンテナンスが困難 • コードコピー/mockの乱用 バグの検知がしづらい

Slide 22

Slide 22 text

© LY Corporation 22 22 単純なミスに実行時になるまで気づきづらい(JavaScriptで書かれているため) • タイプエラー/オブジェクトのプロパティの上書き ユニットテストのメンテナンスが困難 • コードコピー/mockの乱用 バグの検知がしづらい バグの検知がしづらい

Slide 23

Slide 23 text

© LY Corporation 23 23 TypeScript化 • でも一切TypeScriptがないところから一気に全部対応するのは難しい、、、 バグの検知がしづらい 解決策

Slide 24

Slide 24 text

© LY Corporation 24 24 TypeScript化 • でも一切TypeScriptがないところから全部一気に対応するのは難しい、、、 導入したルール: 以下のファイルはTypeScriptで書く • 新しく作るもの/処理を切り出す場合/更新頻度の高いもの バグの検知がしづらい 解決策

Slide 25

Slide 25 text

© LY Corporation 25 TypeScript化の困難性 25 膨大な量の型づけ • 一個のファイルから呼び出している別ファイルの処理にも芋づる式で型が必要 • データの変換を重ねていると型をつけるのが大変、、、 バグの検知がしづらい

Slide 26

Slide 26 text

© LY Corporation 26 TypeScript化の困難性 26 膨大な量の型づけ • 一個のファイルから呼び出している別ファイルの処理にも芋づる式で型が必要 • データの変換を重ねていると型をつけるのが大変、、、 並列して進む開発 • TS化している途中に、元のJSファイルに修正が入る バグの検知がしづらい

Slide 27

Slide 27 text

© LY Corporation 27 27 controllers/detail.jsをコピーして、controllers/detail.tsを作成する バグの検知がしづらい 解決策: TypeScript化 (1/6) コピー controllers/detail.ts controllers/detail.js

Slide 28

Slide 28 text

© LY Corporation 28 28 バグの検知がしづらい //ts-nocheck controllers/detail.ts 解決策: TypeScript化 (2/6) • controllers/detail.tsの最初の行に// ts-nocheck を追加する

Slide 29

Slide 29 text

© 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)

Slide 30

Slide 30 text

© 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)

Slide 31

Slide 31 text

© LY Corporation 31 31 開発は日々進んでいくので定期的に追いつき作業を行う • コピーした当初のdetail.jsと最新のdetail.jsのdiffを見比べて追いつき バグの検知がしづらい 追いつき controllers/detail.ts controllers/detail.js 解決策: TypeScript化 (5/6)

Slide 32

Slide 32 text

© LY Corporation 32 32 controllers/detail.jsを削除する バグの検知がしづらい 追いつき controllers/detail.ts controllers/detail.js 解決策: TypeScript化 (6/6)

Slide 33

Slide 33 text

© LY Corporation 33 成果: TypeScript化 33 • 実行時になって、はじめて発覚するエラーが減った • 安心してリファクタを行いやすくなった • ユニットテストで保証する範囲がわかりやすくなった バグの検知がしづらい

Slide 34

Slide 34 text

© LY Corporation 34 34 単純なミスに実行時になるまで気づきづらい(JavaScriptで書かれているため) • タイプエラー/オブジェクトのプロパティの上書き ユニットテストのメンテナンスが困難 • コードコピー/mockの乱用 バグの検知がしづらい バグの検知がしづらい

Slide 35

Slide 35 text

© LY Corporation 35 35 • ユニットテストがメンテナンスできていない • コードコピーで肥大化(1つのテストファイルで27000行!) バグの検知がしづらい 原因

Slide 36

Slide 36 text

© LY Corporation 36 36 • ユニットテストがメンテナンスできていない • コードコピーで肥大化(1つのテストファイルで27000行!) • ユニットテストは通るけど、実行時にエラーになる • mockの乱用 • テストケースの不足 バグの検知がしづらい 原因

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

© LY Corporation 39 39 バグの検知がしづらい イメージ 解決策: ユニットテストの実装&方針の変更

Slide 40

Slide 40 text

© LY Corporation 40 40 成果: ユニットテストの実装/方針の変更 バグの検知がしづらい • ユニットテストが書きやすく/読みやすくなった • ユニットテストの不足箇所がわかりやすくなった • 失敗してほしいテストが失敗するようになった

Slide 41

Slide 41 text

© LY Corporation 41 41 どのタイミングでどの問題を解決するか • 問題の種類でいつ解決するかを分類 • 大きな問題でも小さく分割できる場合は、少しずつ進める どこに何を書くべきかのルールが決まっていない リアーキテクトで解決 • コードの置き場所のルールを整備 • リファレンス実装の追加(かなり重要) バグの検知がしづらい • TypeScript化で解決 • ユニットテストの実装&方針の変更で解決 まとめ

Slide 42

Slide 42 text

© LY Corporation 参考・引用 Yahoo!知恵袋、サービス開始20周年を記念した特集サイトを公開 https://www.lycorp.co.jp/ja/news/release/007875 回答のつかない質問を減らすために https://chiebukuro.yahoo.co.jp/topic/ai/answer.html typescript-eslint https://typescript-eslint.io/ type-fest https://github.com/sindresorhus/type-fest 42

Slide 43

Slide 43 text

© LY Corporation ご清聴ありがとうございます!

Slide 44

Slide 44 text

© 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を使って、意図せぬ関数の戻り値の型の変更を防ぐために、 戻り値の型を書くことを必須にした バグの検知がしづらい