
【第 1 回 cocone tech talk・後編】大規模リファクタリング〜100万行超えのスパゲッティコードからリファクタリングする際やるべき9項目〜【イベントレポート】
-
2021年4月12日
こんにちは。cocone tech blog 編集長の N です。
https://connpass.com/event/207743/
https://techplay.jp/event/811842
2021/03/25 に「Java 開発者の Go プロジェクト 1 年の振り返り / 大規模リファクタリングについて」というオンラインイベントを開催しました。
弊社の『ポケコロ』のクライアントエンジニアから 1 名、『ポケコロツイン』のサーバーエンジニアから 1 名が登壇し、各プロジェクトでの取り組みをライトニングトーク形式でお話しました。
本記事はイベントレポートとして、トピックと話していた内容をまとめたものを前編・後編で分けたものの後編になります。
前半:
【第 1 回 cocone tech talk・前編】Java開発者のGoプロジェクト1年の振り返り【イベントレポート】
登壇者について

岡田大介さん
クライアント開発 / ポケコロ開発リーダー
株式会社リコーで約10年の勤務を経てココネ株式会社に入社。
最近の技術経歴としては Cocos2d-x, Unity, ReactNative など。それに付随する言語として、C++, C#, TypeScript などを経験。
趣味は卓球・麻雀・ゲーム(最近はゴーストオブツシマやJustDanceをプレイ中)など。
(ココネには卓球と麻雀台があります)
目次
始まり / 背景
- 10年ほど走ってきたスマホアプリで超スパゲッティコード
え、100 万行?(ステップ数でなく行数とはいえ。デッドコードもあるので実際にはそんなにないはず) - 何か機能を追加するとすぐに関係ないところでバグが出る
- 結果、弊社の他アプリと比べても 1.5 〜 2 倍の開発コストがかかる
- 修正したいが闇が深い!!
- サービスが最優先
- ビルド時間が長すぎる
- 直そうとチャレンジした人がなぜかいつも他の部署へ異動する
- 技術力とは別のほぼ職人芸
- 修正の際の影響度が読めない
長いこと手をつけられないまま来てしまった。
- 2018 年 6 月に Apple が iOS / Mac でグラフィックスAPI 「OpenGL」非推奨を宣言
- Apple社の「Metal」への対応が必須に
- アプリの状況
- Cocos2d-x version 2 ベース(Cocos の最新は 4 で Metal 対応されている)
- Unity にしようという声もあったが、アイテム・UI に弊社独自のファイル形式を使用、描画処理でダイレクトに OpenGL を使用( = V4に上げるのにコストがかかる)
つまり、Metal 対応するしかない。
もともと Unity のプロジェクトに居たが……
CTO「このアプリの開発リーダーと Metal 対応よろしくね」
岡田「はい。。。(まじか)」
他プロジェクトで Cocos2d-x V3を扱った経験があったということもあり、ポケコロのプロジェクトにアサイン。
開発リーダーをしつつ、Metal 対応に向けてリファクタリングも行うことに。
やるべきこと・やってよかったこと
計画について
(1)関係者に理解を求めること
- 真っ先にやるべきこと。
- 関係者 = 事業の責任者(事業部長・社長・各職種リーダーなど)、CTO、サービスへの決定権を持つ人
- なぜしなければならないのかの理解を求める
- リファクタリング「する」ことへのリスク
- リファクタリング「しない」ことへのリスク
- 計画の共有
- 開発以外の人からも計画や人員計画など色々なアイディアが出ることもあり、非常に重要
- 大規模なリファクタリングということで、当初の計画通りにはなかなか進まなかった。
(2)大きい計画は絶えず見直す
大規模なリファクタリングということで、当初の計画通りにはなかなか進まなかった。
- 当初の計画
- 4 ヶ月の見込み
- 年末から開発者全員(10 人以上)でサービス開発を止めて(アップデートは止めない)、Cocos2d-x V2 → Cocos2d-x V4(Metal)へ変換
- 最初の計画変更
- Cocos2d-x V2 → V3 へ変更
- まずは OpenGL である V3 へ移行
- いきなり Metal への変更はリスクが高い
- 色々検証したが、メモリリークや修正できない不具合などが発生してしまったため
- 次の計画変更
- 翌年 4 月から
- 次の次の計画
- サービス開発を止めることにリスクを感じたため、サービス開発を止めずに対応(プロジェクト開発側から提言)
- 次の次の次の計画
- 対応メンバーは少数精鋭の 10 人 → 約 4 人で
- 大人数だと意思の疎通が難しいため
- 対応メンバーは少数精鋭の 10 人 → 約 4 人で
- 見直し項目(これら全て必要)
- 目標
- 期間
- チーム体制
最初はとてもではないが見積もりきれない。絶えず見直しと関係者への共有を常に忘れないこと。
場合によってはゴールの見直しも必要。
特に期間は関係者への共有は必須。言いづらいけれども、必ず共有しなければ崩壊してしまうというところを感じた。
(3)小さい目標を立てていくこと
普通のプロジェクトでもそうだが、細かくタスクを管理し、達成具合を記載していくこと。
例)技術課題の管理
- TableView の差し替え 37 / 149 達成 など
- Popup 画面の対応 50 / 85
普通のプロジェクト以上に見える化が大事。周りから見ると何やっているか・何がどう変わったのかがわからないため。
重要に感じたのがモチベーションの持続。正直リファクタリングを長時間ひたすらやるのは精神的にくる。数字でゴールを見えるようにしないとつらかった。
(4)チーム体制
サービス開発を止めずに対応する(どうしてもサービス優先になってしまう)ということで、チームをサービス(Cocos2d-x V2)側・リファクタリング(Cocos2d-x V3)側の2チームに分割し、互いにリーダーを立てた。
リファクタリングということで、会社やプロジェクトの状況により、なかなか別のチームを作るのは難しいが、リファクタリングに集中できる環境を作るということは非常に重要。
開発について
(5)Debug機能の充実化 / 開発環境の充実化
長年続いたアプリということもあり、Debug 機能があまり充実していなかったため、真っ先に作った。
- Debug機能
- ログ機能
- 起動だけでログが 6,000 行出たりで役に立たなかったのでフィルタリング機能などを追加して改善
- 画面レイヤー構造の確認ツール
- 画面のレイヤー構造がわかりにくかったため
- リファクタリング中に画面がブロックされて固まってしまうことがあった
- Unity などでは充実しているが、cocos では充実していない
- アカウントの切り替え機能
- アカウントにより挙動が変わる機能があったため
- アバター確認
- UI 確認機能
- Android バックキー etc…
- ログ機能
- 開発環境
- マシンの差し替え( ポケコロのビルドマシンを強くしてチームの作業を快適にした話 )
- Rizen 9 マシンにしたらAndroid版のビルドが2倍早く
- M1 Mac もビルド時間の向上に
- マシンの差し替え( ポケコロのビルドマシンを強くしてチームの作業を快適にした話 )
バグは大量に出るので、原因究明できるようにしておく。今後の役にも立つ。
開発速度を早くする工夫を。
ビルドマシンは状況によりけりだが、よくできるならすべき。
(6)断捨離
不要な機能の削除・ソースコード(クラス、関数)の削除を徹底した。
例)201X年のエイプリルフールのコード
対応箇所をいかに減らすか。当然だがビルド時間の向上にも繋がる(開発効率アップ)
基本的なことかもしれないけれど重要
(7)常にマージ / 最新の状態を保つ
- Git で管理
- サービス側がリリースされるたびにマージして最新の状態を保つ
- 一度乖離すると非常に難しい(1, 2ヶ月空くとコンフリクトしまくる)
- ソースコードは意外となんとかなる
- Xcodeのプロジェクトファイルが結構厄介
- どこを直せば良いかわかりにくい
- マージツールとして mergepbx を使用
- XcodeGen も今後検討している
(8)テストできる設計を目指す
● interface の分離
- 既存の状態
- シングルトンの嵐
- 依存関係がすごい
- SOLID 原則に反しまくっている
- 色々な所でベースの Layer クラスを dynamic_cast して処理を判断している
しかし、全部は直せない。何年かかるかわからない。
- 目指す方向として
- せめてプラットフォーム部分は interface を分離
- シングルトンではなくサービスロケータに変更
- DI 的にするのは中途半端に終わり、さらなるカオスになりそうと判断
- 画面遷移の仕組みも修正

<最終目標の図>
interface を分離する。具象クラスであるシングルトンも自然に減るはず。その上に変更もしていく
最初はよくある形。

<最初の図>
Client::function { CP1AssetManager::getInstance().download("main_asset"); }
例では AssetManager というクラスでリソースを扱うものを示した。
シングルトンの getInstance() でリソースをダウンロードという形。
ここからとにかく interface 分離を目指す。

<開発途中、サービスロケータとシングルトンが同居している状態>
Init { IAssetManager assetManager = CP1AssetManager::getInstance(); CPLocator::provide(assetManager); }
Client::function { CPLocator::AssetManager() -> download("main_asset"); }
Client2::function { CP1AssetManager::getInstance().download("main_asset"); }
継承する形をとって、Locator に注入・アクセスするようにした。
少しずつ移植し、開発途中ではシングルトンとサービスロケータが同居する形に。

<サービスロケータへの置換が完了した状態>
全部サービスロケータに置き換えが完了したら、シングルトンをなくし、Cocos2d-x V3 用の CP2AssetManager に差し替えるといったような作業を繰り返して行った。
Init { // IAssetManager assetManager = CP1AssetManager::getInstance(); IAssetManager assetManager = CP2AssetManager::getInstance(); CPLocator::provide(assetManager); }
● Fat なクラスの修正
Fat なクラス:ベースのクラスの割に色々な機能が詰め込まれている状態。
対応1:まずとにかくinterfaceに分割。ベースとそれ以外を分ける。
対応2:一部しか使っていないものは具象クラスの方へ移動
対応3:Cocos2d-x V3 にしたことで使わなくなったものなどを削除
これで不完全ではあるものの、分離したことでテストができる状態に。
(9)いじらせない
ソースコードを書く以上、どうしてもだんだんおかしくなっていく(様々なレベルのエンジニアが参加するなどによる)。
- interfaceを分離
- ライブラリ化(モジュール化)
- コーディング規約で縛るのもアリではある
これで触らないような仕組みにしてしまう。
まとめ
- 一番重要なこと
- 大規模リファクタリングの場合、関係者への理解は必須(どうしてもやらないと大規模なリファクタリングは不可能)
- 痛感したこと
- 最初からリファクタリングは定期的にしておくべき
- 新規アプリで余裕がなくても、2 年過ぎたらやるべき(2 年過ぎてサービスが続いているなら、まだサービスが続く可能性が高い)
- プラットフォームは定期的にバージョンアップするべき(Cocos にしろ Unity にしろ NDK でも)
Q & A
Q1. 今は定期的にリファクタリングできているのですか?
A1. できていると偉そうには言えないのですが、いらないものやこのクラスの機能ではないだろうというものの削除は意識的に行うようにしています。
Q2. チームが分かれていたと思うのですが、今でもそうなのですか?
A2. そうです。さも終わったかのように発表してしまいましたが、このリファクタリングはまだ完全に終わっておらず、現在も継続して行なっています。
Q3. サービスチームもリファクタリングしてますか?
A3. やっています。
今(岡田が)開発リーダーということもあって、自分の方からサービス側のソースコードを消したりなどもしています。新しく来た方でそのあたりの意識が強い方がいらっしゃったりして、リファクタリングしてくださっています。
Q4. リファクタリング中にデグレが発生した場合、技術者としてかなり凹みます。デグレしてしまった話や対策など、ご意見やエピソードをお聞かせください。
A4. デグレはちょこちょこ起きていたので……。
正直、対策がどうこうではないのですが、定期的にアプリで遊ぶというところも当然ありますが、しょうがないのでソースコードを元に戻して1個1個やったりしました。いい方法ではないと思いますが……。
救いだったのはチームを分けてやっていたこともあり、世の中にリリースする段階ではなかったので、すぐに戻せる状態だったことですね。ものによってはマージでなく作り直さなければならなかったので、戻せる状態だったのが今回はよかったです。
Q5. Apple が OpenGL を非推奨にした時の感想
A5. やばいと思いましたが、会社的としてはチャンスでもあると思いました。こういうことがないとなかなか大規模なリファクタリングはできないので、凹みもしましたが、自分としてはアリなのかいう気持ちもありました。
スライド
以上、第 1 回 cocone tech talk・後編「大規模リファクタリングについて」のイベントレポートをお届けしました。
サービス開発と平行して大規模なリファクタリングを行なっていくのは大変ですね……。
すぐに新しいものを取り入れていけるようにするためにも、後に技術的負債を残さないためにも、こまめなリファクタリングは重要と感じました。
ココネでは一緒に働く仲間を募集中です。
ご興味のある方は、以下のリンクから是非ご応募ください。