13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1 年間のリファクタリング振り返り:562 件の PR から見るフロントエンド改善の軌跡

Last updated at Posted at 2025-12-19

はじめに

昨日 12/19(金) は @moneyan9 さんでした

これは Hubble Advent Calendar 2025 の 20 日目の記事です

自己紹介

フロントエンドチームの akihiko.KIgure a.k.a グレさん です

主にリファクタリング業務を担当しています

今回は、2024 年 12 月 01 日から 2025 年 11 月 30 日までの 1 年間で行ったリファクタリング作業について、データを基に振り返ります

開発環境

  • フロントエンドのリポジトリは Nx でモノレポ管理しています
  • フレームワークは Angular を使用しています

なぜリファクタリングに取り組んでいるのか

私たちの Hubble プロダクトは、「依頼・作成・レビュー・締結・管理」を一気通貫で支援する AI 契約業務・管理クラウドサービスです
企業の重要な契約書や文書を扱うため、システムの信頼性と保守性が極めて重要です

システムの信頼性と保守性を維持するために最新の技術を取り入れつつ、堅実な実装が必要になります

具体的にリファクタリングは何をしたのか

この 1 年でのリファクタリングでは、以下の作業を実施しました。

  • Container / Presentational パターンの見直しと Container コンポーネントの廃止
  • Signal 対応(Observable ベースから Signal ベースへ移行)
  • @Input/@OutputSignal への移行
  • テストの @testing-library/angular への移行
  • タイポ修正などの細かい改善

なぜ Container やめて Signal にするのか

Hubble で Container コンポーネントを採用した主な理由は、Hubble のアーキテクチャにおいて、RxJS を使った実装方針が最も適していたからです。

Async Pipe を実装することで、実装ミスを最小限に抑える

  • 極力 subscribe を使わない方針
  • unsubscribe し忘れを防ぐため
  • メモリリークのリスクを削減
  • Presentational コンポーネントにロジックを持たせない方針
  • 上記理由で、Container コンポーネントが必要でした

Signal の登場で、上記の方針がより簡易に実現できるようになりました

  • Async Pipe を使うためだけに存在していた Container コンポーネントが不要
    • RxJS を使った実装の負債の返済が可能
  • 複雑なコンポーネント構成の簡素化が可能
  • データバインディングの簡素化
  • 明示的なサブスクリプションの必要性が軽減
  • メモリリークのリスクを大きく低減

Jest > Testing Library の移行

  • 古いテスト実装を @testing-library/angular を使ったテスト実装に修正しました。
  • 実際のユーザー操作に近い形でテストができるようになり、テスト実装も簡潔になりました。

どのようにリファクタリングしたのか

作業基本方針

この基本方針に沿って作業しました

  • 1 コンポーネント 1 プルリクエストで作成
    • 目的:影響範囲の限定
    • 目的:レビュアー負荷の軽減
    • 例外:Signal 化に伴う親コンポーネントのテスト修正等
  • 既存挙動に関わるロジックの破壊的変更をしない
    • 理由:リファクタリングで、挙動が変わってしまっては本末転倒になるため
  • テストが無いコンポーネントは、基本的に追加する
  • AI(Cursor, Devin) の活用
    • Cursor のルールファイルの活用
    • Devin を活用した様々な作業の自動化
      • チケットの自動作成
      • 単純な修正内容の PR の自動作成
      • devin bot による PR のレビュー

リファクタリング前における影響範囲・実装調査の重要性

影響範囲や実装調査を雑に行うとプルリクエストを作り直すことになります

  • 共通コンポーネントか否か
  • 複数コンポーネントで構成されたコンポーネントかどうか
  • 親コンポーネント・子コンポーネントの INPUT / OUTPUT
  • 状態管理の有無
  • API

リファクタリング粒度

リファクタリングの粒度が粗いと莫大な差分を生み、レビュアーに負荷を掛けます

  • 1 コンポーネントでも実装によっては、3 ~ 5 つの PR に分けました
  • @Input に getter, setter が実装されている場合は、@Input のみの PR を作成しました
  • 具体例として、下記のように分割しました
    • @Output のプルリクエスト
    • @HostBinding, @HostListener のプルリクエスト
    • @Input のプルリクエスト
    • @ViewChild, @ContentChildren のプルリクエスト
    • この順番は、影響度・重要度を考慮しました
    • 既存挙動に関わる破壊的実装変更は、このタイミングで行いました
      • ※必要に迫られた時のみ
具体例(サンプルコード)
export class HogeComponent {
  @HostBinding('class') classes =
    'flex flex-col flex-nowrap overflow-hidden';


  @Input() set hoge(hoge: hogeModel | null) {
    if (hoge === null) {
      return;
    }
    ......
  }

  _fugaStatus: FugaStatus = FugaStatus.piyo;
  @Input() set fuga(fugaStatus: FugaStatus) {
    this._fugaStatus = fugaStatus;
    ......
  }
  get fugaStatus() {
    return this._fugaStatus;
  }

  @Output() poyo = new EventEmitter<string>();
  .....
  @ViewChild('uploadFile') uploadFile!: ElementRef;
  .....
  @ContentChildren(CellComponent) cells!: QueryList<CellComponent<T>>;
  .....
  @HostListener('window:beforeunload', ['$event'])
  beforeUnload(e: Event) {
    ....
  }

}

Signal の untracked の重要性

Signal の effect(副作用) を実装する上での注意

effect(副作用)の実装を適切に行わないと無限ループが発生します
値を読みたいだけで、依存してほしくない場合は、untracked を使います

リファクタリングの見送り判断の必要性

コンポーネントによっては、意図的にリファクタリングを見送りました

  • UI デザインの変更予定がある
  • 子コンポーネントを先に着手した方が、変更検知など影響が少ないため
  • 抜本的な実装見直し が必要な場合

リファクタリングは、コンポーネントの最小単位から着手

  • 親コンポーネントを先に Signal 化した場合、Angular ライフサイクルの差異からバグが起きたことがありました

リファクタリングを実施した結果

対象 Nx プロジェクト: 57
248 件の Container コンポーネントの削除を達成しました!
8,254 行のコード削減に成功しました!

項目 数値
総 PR 数 562
追加行数 81,621
削除行数 89,875
純減行数 8,254
変更ファイル数 4,813

リファクタリングを実施して良かったこと

実装の簡素化

リファクタリング前後で、コードの見え方が別世界です
シンプルな実装になったので、実装の意図が把握しやすくなりました
コードジャンプも格段に減りました

テストの重要性

  • テストや CI で、実装ミスに気づける
  • 他のメンバーのテスト実装コストが削減できる

タイポ撲滅

後回しにしがちな作業ですが、気持ちが悪いコードを払拭できました

脆弱性回避

最新コードにリファクタリングしているので、脆弱性の要因になりにくいコードになりました

AI の活用の効果を実感

  • 生産性の向上: 手作業では数日かかる作業を数時間〜 1 日強で完了
  • 品質の向上: 一貫したパターンでリファクタリングを実施し、人的ミスを削減
  • 学習効果: AI の提案から新しいパターンやベストプラクティスを学習
  • レビュー負荷の軽減: AI による事前チェックで、レビュー時の指摘事項が減少
  • 不具合調査の時間短縮: AI による不具合調査で、調査時間が減少

まとめ

本記事では、1 年間で実施した大規模なリファクタリング作業について振り返りました。

主な成果

  • 248 件の Container コンポーネントの削除を達成
  • 8,254 行のコード削減に成功
  • 562 件の PRを通じて、段階的にリファクタリングを実施

重要なポイント

  1. 影響範囲・実装調査の徹底: リファクタリング前の調査を丁寧に行うことで、PR の作り直しを防ぐ
  2. 適切な粒度での分割: 1 コンポーネントでも必要に応じて複数の PR に分割し、レビュー負荷を軽減
  3. テストの重要性: 既存挙動を保証し、実装ミスを早期に発見
  4. Signal の適切な活用: untracked の使用など、Signal の特性を理解した実装が重要
  5. AI ツールの活用: Cursor や Devin を活用することで、生産性と品質の両立を実現

本記事が以下の方々の参考になれば幸いです

  • Angular をモノレポで開発している方(Nx を利用中の方)
  • Angular の最新機能(Signals、Control Flow、inject())への移行を検討している方
  • Container/Presentational パターンから Signal ベースのアーキテクチャへの移行を検討している方
  • 大規模なリファクタリングプロジェクトを計画している方
  • フロントエンドのコード品質向上に取り組んでいる方
  • AI 開発ツール(Cursor、Devin)を活用したリファクタリングに興味がある方

おわりに

リファクタリングは、地道な作業です。
プロダクトにどのような貢献をしているか、不安になる時もありました

そんな中、他のチームやメンバーからのありがたい言葉で自信が持てました

  • 最新のコードベースへの移行が早く、開発速度が低下していないことを非常に感謝してます(EM)
  • テストがあって助かった(フロントチーム)
  • プロダクトが安定して動いているからこそ、お客様に自信を持って勧められます(マーケ・セールスチーム)

今後も貢献していきます!(決意表明)

ここまで読んで頂きまして、誠に有難うございました!

明日 12/21(日) は @kyohei-0xcc さんです!
よろしくお願いします!

13
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?