Upgrade to Pro — share decks privately, control downloads, hide ads and more …

巨大なSPAの技術的負債と向き合い続けるテクニック(2023年秋)

shibe23
November 20, 2023
8k

 巨大なSPAの技術的負債と向き合い続けるテクニック(2023年秋)

2023/11/21 開催の「技術的負債に向き合う Online Conference」(主催 : ファインディ株式会社)で登壇したスライドです。
https://findy.connpass.com/event/297813/

Chatworkのフロントエンドは一画面で18万行を超えるSPAです。プロダクトとしては12年目であり、技術的負債とも向き合う必要があります。 このセッションでは、Chatworkが機能開発を行いながら、安全かつ段階的に移行するために取り入れている手法や、技術的負債についての向き合い方をご紹介します。

shibe23

November 20, 2023
Tweet

Transcript

  1. 「Chatwork」のWebFrontendの特徴 • 巨大なSPA、かつ利用中ほとんどリロードされずに使われるアプリケーション • 双方向性があり、ユーザーからのアクション以外で状態が変わる • JavaScriptだけで18万行 • CSSも1万7千行 ◦

    Styled-Componentを利 用しているため、実際に はもっと多い 巨大なコードベース • ブラウザベースのアプリケー ションとしては珍しく、URL単 位でアプリケーションとして分 割されていない • 再読み込みのない一枚のページ で全UIが動作している 純粋なSinglePageApplication • Webサイトベースの「入力 -> 確認 -> 完了」と情報が一方向な Webアプリではない • Google SpreadSheetのような GUIアプリとしての複雑さを持 つ GUIアプリとしての複雑性 4
  2. 技術的負債の状況 • jQuery -> React + DDD -> React +

    Reduxの3世代に渡るアーキテクチャ • jQuery製のUIとReact製のUIが混在している ◦ 全体のうち約2万3000行がjQuery製 • 認知的複雑度(Cognitive Complexity)の合計は、およそ9,000 ◦ jQuery世代はおよそ5,000 • 各世代ごとの詳細は下記を参照ください 組織フェーズを見据えたWEBフロントエンドのアーキテクチャと変遷(JS Conf 2021)
  3. 技術的負債は何によってもたらされるか • ソフトウェアは常にアップデートされ続ける • 周辺の環境の変化に追従する必要があるため「何もしない」という選択肢はない ◦ (例: セキュリティアップデートやインフラのサポート終了) • 「あるべき姿」自体が変化したり、そこに至る道のりが険しくなることで、理想に到達するまでの時

    間が延びる 原因 例 時間経過 • 技術的なパラダイムシフトが起きた • ライブラリのアップデートに追従する必要がある 目的不確実性 • 事業フェーズやユーザー数の増減に伴う要求の変化 • 市場への解像度が高まったために、コードに表現するべきものが変化した • 仕様が本質的に複雑、または整理されていない 方法不確実性 • その時点ではベストなコードだったが、後に「もっとこうすればよかった」が判明した • スキル不足や時間へのプレッシャーなどの理由からコードの内部品質を確保できなかった
  4. 技術的負債についての考え方 • 技術的負債は「アプリケーションのあるべき姿と現状との差分」 ◦ 現状からあるべき姿に到達するまでに掛かる時間(= リソース)と向き合っている • 日々変化する状況が技術的負債を産み続ける ◦ jQuery全盛期にReactの登場は予測できなかった

    ◦ 現在のベストプラクティスも、新しい技術の登場によってあるべき姿から遠ざかる ◦ 要件の変化やエンジニアの経験によってもあるべき姿が変化していく • 負債は無くすものではなく、様々な要因を加味してコントロールするもの ◦ 技術的負債を抱えたコードでも、仕様変更によって丸ごと不要になるケースもある ▪ 必ずしも負債がゼロであることが適切とは限らない 技術的負債と継続的に向き合う仕組みづくりが重要
  5. jQuery製のUIとReact製のUIを混在させる • 段階的にリファクタリングするためにUIごとにReact化したい • ReactDOM.renderを使った時の課題 ◦ Context APIを全てのレンダリングツリーに流し込む必要性 ◦ デバッグ時にレンダリングツリーごとにしかプロファイルが取れない

    • ReactDOM.createPortal で同一の親コンポーネントを指定してマウントするようにした • 部分的な置き換えにおける注意点 ◦ イベントハンドラの多重登録 ◦ 再描画の度にjQueryで作成したDOMが残り続けることによるメモリリーク … React … jQuery
  6. TypeScriptのStrict modeを有効にする • jQuery時代のコードはany型を一部許容している ◦ 一方で、普段のコードを書く時に暗黙的なany型の混入を防ぎたい • strictを有効にするために、TypeScript Compiler APIを使って型エラーが起きている箇所に

    @ts-expect-error を自動で追加した ◦ 新規で@ts-expect-errorを書かないようにpre commit時にチェックしている • 型エラーが隠蔽されるケースがあるので、トレードオフには注意
  7. 技術的負債と継続的に向き合う = 小さく試して改善し続ける • コードを適切に処理できれば確実に不具合を回避することはできるか? ◦ できない ◦ 負債の影響は積み重なって予期しない不具合として現れる •

    リリースしなければ本当に問題がないかは確認できない ◦ 失敗の影響を最小限に抑えなければ、リファクタリングは重たいプロセスになってしまう • “どうコードをきれいにするか”も大事だが、 “小さく失敗できる体制をつくる“ことが最も重要
  8. コミットハッシュごとのスナップショットを生成する • PR作成時 / 更新時 / mainブランチへのマージ時にビルドしたjsファイルをS3にアップロードする • コミットハッシュをURLにパラメータとして付与することで、指定したバージョンでアプリを開いて 検証できる仕組みがある

    ◦ バージョンごとの現象確認が容易 • リリースブランチへマージされた履歴がjsファイルとして既に生成されている、というのがポイント commit hash: c63df1b164b73d71… main commit hash: f632892477af9be29… commit hash: ebbd2436080a165c… commit hash: 8238f9b315116c5c0…
  9. 技術的負債の地層化 • 設計としての技術的負債はjQuery -> React + DDD -> React +

    Redux と積み重なっている • 各世代を返済しきらずに新たな設計思想を追加すると負債が「地層化」する ◦ 新たなライブラリを導入したい、状態管理などの設計方針を改善したいなど理由はさまざま • あるべき姿に向かうコストを払いきれなくなった時点で負債の返却が止まる ◦ 負債返却のためのプロジェクトが長期化してコストが膨らんでしまう ◦ ボトムアップの改善が様々な要因で途絶えてしまう • 「Chatwork」のWebFrontendでは一定の地層化を許容しているとも言える ◦ 必要な分だけ段階的に返済できる ▪ リプレースせずに中身を入れ替え続けている ◦ 地層を重ねる弊害が大きくなるのであれば、集中したリファクタリングも必要になってくる • 規模の大きさに苦慮している一方、一定の認知負荷を許容しつつ事業的な要求に応えている状態
  10. ACL層のメリット / デメリット • ACL層によって旧コードとの依存関係は整理しやすくなった • 一方でACL層は「一時しのぎ」とも言えるため、そのままだと肥え続ける ◦ ACL層を定期的に整理するなど、地道な改善は必要 ◦

    旧コードへの依存状況が可視化されるため、負債状況の目安にもなる 変更回数が多いファイルTOP10 (参考元) git でファイルごとのコミット数を取ってきて,プロジェクト中のホットなファイルを割り出すという試み