大規模な Android アプリをリファクタリングする前にしたこと

メリークリスマス!!この記事は、はてなエンジニアアドベントカレンダー2016の25日目の記事です。昨日は id:tarao による 開発速度と品質のトレードオフの判断基準の合意 でした。

僕は id:funnelbit と言う者です。はてなでは Android アプリを担当しています。

突然ですが、ソフトウェアというものは腐ります。腐るというのは、メンテナンス性が失われたり、機能追加が困難になってくることを指します。原因は複雑なコードが絡み合ってしまったり、ライブラリのバージョンが古すぎたり、もはや世間では時代遅れのライブラリをつかっていたり等です。特に長期間問題を放置すれば腐りやすくなります。

こうなってくるとエンジニアとしてはリファクタリングを考えるようになります。リファクタリングには規模の大小があり、リネームやモデルの修正程度で済むものから、アプリ全体の設計を変更せざるを得ないケースまで様々です。後者の場合、しっかりと計画を立てなければ、適切なリファクタリングを行うことは困難になります。そして僕は今、まさにこの大きなリファクタリングに直面しているのです。

この記事では大規模なリファクタリングを始めるにあたり、どのような準備が必要だったかを紹介します。

1. なぜリファクタリングをするのかを明確にする

僕の関わっているプロダクトは、これまで長きにわたり設計を変えることなく、機能追加を行って来ました。しかしユーザへの機能追加を優先すると、コードのアーキテクチャを最適化するのは後回しになりがちです。アーキテクチャの最適化はどうしてもコストが掛かるもので、もし着手した場合、その間新機能の追加速度も落ちてしまいます。重きを置くのはユーザ体験や利便性の向上であったため、軽いリファクタリングは挟みつつ、機能追加をメインに開発を進めて来ました。皆様の支えもあり、おかげさまで5年以上経過した今も、多くの方々にご利用いただいております。本当にありがとうございます。

しかし5年以上経過すると、そろそろコードの状態を整理したくなります。実際、以下のような状況が目につくようになり、無視できないようになって来ました。

  • 非推奨のライブラリを使っており、将来のセキュリティにリスクがある。ライブラリを変更するためには、コードの大幅な変更が必要である。
  • 機能追加が非常に困難な箇所が見受けられ、計画的なリリースに支障をきたすレベルである。
  • 各所が時代ごとに別の書き方をされているため、どのような書き方をすればよいのか明確な基準がなく、どう書くか?でまず悩む時間が発生し、工数増加の原因となる。統一するにしても、まずはどう統一すればよいのかから考える必要があるし、その後書き換える必要もある。

このままただ単に機能を追加していけば、エンジニアリングが滞るだけではなく、ユーザ体験の低下にも繋がりかねません。この危機感はディレクターにももちろん共有しました。その結果、大規模なリファクタリングタスクが認められ、今回着手することになりました。

このリファクタリングには一つの目標を定めました。「今後5年間は変更に耐えうるコード」です。誤解しないでいただきたいのは、5年後には腐敗してよいと言うことではありません。しかし5年後にはまた新しいパラダイムが登場していることでしょう。それはどうしようもありません。しかしそのような状況になっていたとしても、分解して再構築しやすいようなコードを目指したいと考えました。プロダクトの価値を次の5年につなげるためのリファクタリングと言うことです。

2. リファクタリングする期間を確保する

まず必要なのは当たり前ですが「リファクタリングする時間」を取ることです。この時間ではリファクタリング以外のことはしません。アプリのコードの規模にもよるかと思いますが、少なくとも大規模なリファクタリングの場合、連続的かつ長期間設けたほうが良いです。例えば「1日の数時間だけリファクタリングする」というのは余り良くないと思います。大規模リファクタリングはそんな単純なものではありません。

とはいえ、この段階で工数を弾き出すのは非常に困難です。まだ「リファクタリングをやる」ということしか決まってません。しかし大雑把な期間であればある程度は出せるはずです。

3. ドメイン知識を得る

ここからが本番です。まずは正しいドメイン知識が必要です。

全画面を把握する

まずはどんな画面がアプリに存在しているかどうかを把握します。コードをみたり、思いつく限りの操作を行って、とにかく色んな画面を起動し、1つずつスクリーンショットを取るなどして記録します。特別な導線の場合に特別な View が現れることも考えられるので、とにかく起動しまくります。わかったつもりになっている画面も全てです。また、それぞれの画面でどのような Model・View を使うべきかを議論し、記録していきます。そうすると、他の画面と何を共通化出来るのかが分かってきます。

条件によって分岐する View をすべて把握する

「ここはこの API を叩いたときには通常表示だが、とある API の場合のみ、別な表示になる」という箇所をすべて記録します。わかったつもりになっている箇所もすべて記録します。スクリーンショットを取り、まとめます。

正しい名前を付ける

画面、View それぞれに正しい名前を付けます。この作業は最低でもエンジニア・デザイナー全員で行います。特に View の名前を付けるためには、どのような意図でその View のデザインをしたのかというデザイナーの意見が不可欠です。正しい名前が決まったら即座に記録します。

4. 現状の問題点を見極める

ただ単に「クソコード」と揶揄するのではなく、冷静に問題点を分析していく必要があります。言うまでもなくこれはエンジニア全員でやります。 問題のあるコードには必ず不吉な匂いが漂います。そこから何故こうなっているのかを探し出し、解決方法を探ります。以下は僕が直面した数例です。

継承が多い

歴史の層が積み重なり、多重の継承をしている箇所があります。継承が必要なく、移譲で解決できる箇所が散見されます。これは過去にはそれでうまく行っていたが、時がたつに連れてうまく行かなくなっていくケースが多いかと思います。

抽象化が間違っている

正しい抽象化がなされているのかどうか精査してください。例えば、ただ処理をまとめるだけの抽象クラスは確実に破綻します。

名前と役割に違和感がある

名前と役割が一致してない、という方がわかりやすいですね。機能としては良いが、ネーミングが好ましくなかったり、ネーミングの割には機能が豊富すぎて、本来の役割を逸脱してそうだったり、様々なケースがあります。

アーキテクチャが統一されていない

これは歴史的経緯が大きいです。古い箇所は古いアーキテクチャで、新しい箇所はやたらモダンであったりします。古い箇所でモダンにすれば解決するかもしれませんが、古い箇所は複数のコードが絡み合っていることが多く、まずは処理を整理することが求められると思います。

クラス内に処理がまとまってはいるが、追うのが難しい

確かに一クラスに処理がまとまっているのですが、追うのがかなり難しくなっている箇所があります。過度な抽象化や共通化、複雑な Builder パターンなどが該当します。

パッと見で「やばい」と思ってしまうクラスがある

冗談抜きで開いた瞬間に「これは」と思ってしまうクラスに出会うことがあります。これは一言では表せないことが多いです。例えば定数が大量に存在したり、非同期処理が複雑に絡んでいたり、色んな箇所から呼ばれていたり。解決するには、それぞれがどこでどう使われているのかを把握し、正しい設計に落とし込む必要があります。

5. ライブラリを選定すると同時に、設計を決める

ここまで来たら、何が問題かはだいたい分かっているはずです。が、ここが最も難しい所です。 近年の Android 開発では有用なライブラリが多々ありますが、採用することで設計が左右されるものがあります。例えば Rxjava を採用するとリアクティブプログラミングを全面的に取り入れることになりますし、Realm を採用した場合は Realm の監視を中心とした設計になってくるはずです。

このようなライブラリを選ぶと、設計をある程度確定せねばなりません。しかしそのライブラリから発生する設計が本当に有用なのかは、しばらく考えてみないとわかりません。まるでパズルのように、このライブラリを採用したパターンA、パターンB、やっぱり採用せずに別パターン... などを繰り返し、議論し、納得できる設計に落とし込んでいきます。

6. 小さなプロジェクトで試す

経験上、いきなり新しい設計を既存のアプリに組み込むと必ず不備が出てきます。「やっぱりこうしとけばよかった」となると、組み込んだすべての箇所に変更を加えなければなりません。そこを放置して先に進むと新たなレガシーが爆誕です。

組み込む前に完璧な設計を行うのは非常に難しいですが、少しでも完璧に近づける事はできます。最もいい方法は小さいプロジェクトを作って、新しい設計を試すことです。プロジェクトは Github で管理し、うまく行かなかった設計についてはプルリクを投げ、レビューし、修正します。修正内容は Github に残るので、設計の経緯を追うことができます。

7. 設計をドキュメント化する

小さなプロジェクトでチーム全員が納得できる設計を作ったら、次に行うのがドキュメント化です。命名規則パラダイム、クラスごとの規約や制約を書き出します。ドキュメントには「なぜこのような規約が設けられているか?」が分かるように、経緯を織り交ぜて読み手が納得できるように書いていきます。ドキュメントも Github で管理し、変更する必要がある場合はプルリクを出し、変更の経緯が分かるようにします。

ドキュメントは、コードを作る時、コードレビューのときに利用します。記載しておくことで「分かった気になっていた」「勘違いしていた」を無くします。誰かがチームを抜けても、新しい人が入ってきても、ドキュメントは常に存在するので属人化を防ぐことができます。

8. 作業状況を可視化する準備

エンジニアは自分で作業してるので、今自分がどれぐらい進んでいるのかが分かります。しかしディレクターにはそれはわかりません。またエンジニアにとっても、取り掛かっているタスクの進捗はわかりますが、それが計画全体のどの位置にいるのかを把握するのは難しいものです。

そこでリファクタリングを進める前に、ある程度タスクをリスト化するなどして、今自分が全体でどの位置にあるタスクをこなしているのか、あとどれ位タスクをこなせば終わるのかを把握できるようにします。そうでなければ、エンジニアははて亡き荒野をひたすら進むことになり、ディレクターもうまく助けることができなくなってしまいます。僕の所属しているチームでは、それぞれのタスクに目安の時間を設定し、バーンアップチャートを作り、順調に進んでいるのかどうかを他人が把握できるようにしています。

9. ここからが本当のスタートです

ここまでで準備は終了です!しかし本当のリファクタリングはここからです。言うまでもなく大変な作業が待ち構えています。 ざっくりと紹介しましたが、細部については書ききれていません。実際にどんな設計にしたのか?どのようにドキュメント化したのか?どのようなクラスから着手したのか?リリースのタイミングは?

幸いにも、僕は運良く国内最大の Android カンファレンスである DroidKaigi 2017 でこれらについて話す機会が与えられました。タイトルは「大規模アプリのリノベーション」というものです。

droidkaigi.github.io

もしもご縁がありましたら、聴講していただけると幸いです。またぜひ会場では皆様と交流し、リファクタリングについての知見を交換できたらなと思っています。どうかよろしくお願いいたします。

はてなエンジニアアドベントカレンダー2016 はこれにて終了となります。それでは皆様、良いお年を。