はじめに
昨日 12/19(金) は @moneyan9 さんでした
これは Hubble Advent Calendar 2025 の 20 日目の記事です
自己紹介
フロントエンドチームの akihiko.KIgure a.k.a グレさん です
主にリファクタリング業務を担当しています
今回は、2024 年 12 月 01 日から 2025 年 11 月 30 日までの 1 年間で行ったリファクタリング作業について、データを基に振り返ります
開発環境
なぜリファクタリングに取り組んでいるのか
私たちの Hubble プロダクトは、「依頼・作成・レビュー・締結・管理」を一気通貫で支援する AI 契約業務・管理クラウドサービスです
企業の重要な契約書や文書を扱うため、システムの信頼性と保守性が極めて重要です
システムの信頼性と保守性を維持するために最新の技術を取り入れつつ、堅実な実装が必要になります
具体的にリファクタリングは何をしたのか
この 1 年でのリファクタリングでは、以下の作業を実施しました。
- Container / Presentational パターンの見直しと Container コンポーネントの廃止
- Signal 対応(Observable ベースから Signal ベースへ移行)
-
@Input/@OutputのSignalへの移行 - テストの
@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 で、実装ミスに気づける
- 他のメンバーのテスト実装コストが削減できる
後回しにしがちな作業ですが、気持ちが悪いコードを払拭できました
最新コードにリファクタリングしているので、脆弱性の要因になりにくいコードになりました
- 生産性の向上: 手作業では数日かかる作業を数時間〜 1 日強で完了
- 品質の向上: 一貫したパターンでリファクタリングを実施し、人的ミスを削減
- 学習効果: AI の提案から新しいパターンやベストプラクティスを学習
- レビュー負荷の軽減: AI による事前チェックで、レビュー時の指摘事項が減少
- 不具合調査の時間短縮: AI による不具合調査で、調査時間が減少
まとめ
本記事では、1 年間で実施した大規模なリファクタリング作業について振り返りました。
主な成果
- 248 件の Container コンポーネントの削除を達成
- 8,254 行のコード削減に成功
- 562 件の PRを通じて、段階的にリファクタリングを実施
重要なポイント
- 影響範囲・実装調査の徹底: リファクタリング前の調査を丁寧に行うことで、PR の作り直しを防ぐ
- 適切な粒度での分割: 1 コンポーネントでも必要に応じて複数の PR に分割し、レビュー負荷を軽減
- テストの重要性: 既存挙動を保証し、実装ミスを早期に発見
-
Signal の適切な活用:
untrackedの使用など、Signal の特性を理解した実装が重要 - AI ツールの活用: Cursor や Devin を活用することで、生産性と品質の両立を実現
本記事が以下の方々の参考になれば幸いです
- Angular をモノレポで開発している方(Nx を利用中の方)
- Angular の最新機能(Signals、Control Flow、inject())への移行を検討している方
- Container/Presentational パターンから Signal ベースのアーキテクチャへの移行を検討している方
- 大規模なリファクタリングプロジェクトを計画している方
- フロントエンドのコード品質向上に取り組んでいる方
- AI 開発ツール(Cursor、Devin)を活用したリファクタリングに興味がある方
おわりに
リファクタリングは、地道な作業です。
プロダクトにどのような貢献をしているか、不安になる時もありました
そんな中、他のチームやメンバーからのありがたい言葉で自信が持てました
最新のコードベースへの移行が早く、開発速度が低下していないことを非常に感謝してます(EM)テストがあって助かった(フロントチーム)プロダクトが安定して動いているからこそ、お客様に自信を持って勧められます(マーケ・セールスチーム)
今後も貢献していきます!(決意表明)
ここまで読んで頂きまして、誠に有難うございました!
明日 12/21(日) は @kyohei-0xcc さんです!
よろしくお願いします!