TechFeed 7.8.0リリース!パフォーマンスの大幅な向上とリファクタリング

こんにちは、TechFeedの白石です。

大変お待たせしました!前回の7.7リリースから実に2ヶ月が経過してしまいましたが、ようやく新バージョンのリリースにこぎつけました。

Web版
Android版 
iOS版

今回のリリース7.8の目玉は、とにかくパフォーマンスです。起動速度、データ読み込み速度、スクロールの反応など、全般的な速度が改善しました。Androidユーザーの方は、起動速度が今までに比べて半分くらいになっていると思いますし、iOSユーザーの方は、データの初期表示がこれまでに比べて数秒早まりました。

ほか、細かいバグフィックスが(ほんとに)多数含まれているので、必ず早めのアップデートをお願いしますね。

技術者向けの話題

TechFeedのユーザーはほとんどがエンジニアだと思うので、今回のリリースについて、技術的な側面も述べさせていただきます。今回のリリースでは、以下のようなことを行いました。

・リファクタリングして、技術的負債を(多少)返済
・モバイルアプリの単体テスト導入
・CrossWalkを外して起動速度改善
・Angular CLI導入

とはいえ、ひとつひとつを詳細に語るとそれだけでかなりのボリュームになってしまいます。AngularやIonic、Cordovaと言ったキーワードに関連するイベントなどがあれば、そういったところでより詳細を述べさせていただきたく(イベント主宰者の方、よければお声がけください)。

リファクタリングして、技術的負債を(多少)返済

先ほども軽く触れましたが、TechFeedはAngular、Ionic、Cordovaと言った要素技術の上になりたっています。Angularは主張が強めのフレームワークで、レールに則って開発するだけでそれなりに整理されたコードになるのですが、TechFeedの内部は相当なコード量でかなり複雑化しており、コードを触るのがだいぶおっくう&修正に時間がかかる状態になっていました。そこで、今後の開発を加速するためにも、一旦リファクタリングに投資しておいたほうがいいと判断しました。

AngularのServiceの初期化方法を変更

JavaScriptアプリの初期化処理は、カオスになりがちです。処理の殆どが非同期なのと、依存関係にある様々なサービスが呼び合いまくるのがその主な原因。TechFeedも、APIリクエスト、ローカルストレージ呼び出し、Cordovaプラグインの初期化など、様々な非同期処理とイベントが絡み合い、何が起きているのかプログラマーも把握できない状態になっていました。

そこで、これまで init() のようなメソッドを定義して明示的に呼び出していたのを、「(Promiseを駆使して)必要になった時に一度だけ初期処理が自動で走る」ようにしました。具体的には、サービスのpublicなメソッドは必ず(一度だけ)初期化処理を行うという規約を定めて徹底することにしました。

class SomeService {
 private ready(): Promise<void> {
 // 一度だけ初期化処理を行う
}
public async method1() {
 await this.ready();
 …
}
public async method2() {
 await this.ready();
 …
}

上のready()というメソッドが、一度だけ行われる初期化処理です。実際には、ready()の呼び出し忘れが怖いので、ちょっとしたメタプログラミングを行って、publicなメソッドの呼び出し前には必ずready()が呼ばれることを保証しています。

これにより、カオスと化していて、もはや開発者自身が把握できなくなっていたアプリの初期化処理を一掃しました。サービスの呼び出し元は、何も考えずにメソッドを呼び出すだけで、初期化処理が完了していることが保証されます。ついでに初期化処理中に行っていた、保守的で無駄な待ち合わせもなくなったので、パフォーマンスにも好影響が期待できます。

「@Subscribe」というデコレータを作り、イベントの購読処理を宣言的に書けるように

単体テストの話とも絡むのですが、Service間のデータのやり取りは、AngularにおいてはObservable (EventEmitter) を使用するのが基本です。が、他のサービスのイベントを購読したいというだけのために、コンストラクタで他のサービスのDIをたくさん書いていて、単体テスト書く時にモックのセットアップが大変です。可読性も低く、コードがとっちらかる原因になっていました。

class SomeService {
 constructor(private userService: UserService) {
 // イベント購読のためだけにDI
 userService.login$.subscribe(() => this.onLogin());
 }
 // ログイン時の処理
 private onLogin() {
 …
 }
}

そこで、「他のサービスのイベントが発生した時に、自動的にメソッドを呼び出す」というデコレーター(@Subscribe)を作り、以下のような感じで使えるようにしました。

class SomeService {
 constructor() {
 }
 // ログイン時の処理
 @Subscribe(‘login’)
 private onLogin() {
 …
 }
}

これにより、以下のような効果が得られました。

・可読性が上がる
・イベント購読のためだけのDIが不要になり、依存関係が整理される
・イベント購読が、そのサービスにとって本質的な処理なのか、非本質的な処理なのかを考えるようになった。本質的な処理なのであれば、やはりDIで実装し、単体テストの対象とすべき。

モバイルアプリの単体テスト導入

恥ずかしながら、これまでTechFeedのモバイルアプリでは、単体テストを一切導入していませんでした。UIの仕様が激しく変わり続ける中、テストコードのメンテナンスで開発に時間が取られてしまうのを恐れたためです。

ただ最近では、コードのデグレとその対応に追われることも多くなっていたのと、基本的な仕様が固まった部分も増えてきたため、クリティカルな部分に絞って単体テストを導入するようにしました。

ここについては、AngularのテスティングフレームワークやKarma、TypeScript、Webpack、Mochaを使用しているということ以外は、それほど語ることはありません。ただ、モックのフレームワークとしてtestdouble.jsを使用したのですが、これは便利。慣れると、これなしで単体テストを書くのは考えられないほどです。TypeScriptに完全対応しているのも◎。

CrossWalkを外して起動速度改善

CrossWalkというのは、独自ビルドしたWebKitをCordovaアプリケーションで使用するためのCordovaプラグインです。これにより、どんな古いAndroid端末であっても、最新のブラウザ機能が利用でき、OSやデバイスの違いを意識しなくて良いという大きなメリットがありました。

ですが、Android4.xのシェアが十分に低下した現在、CrossWalkはメンテナンスが終了してしまいました。そして、CrossWalkは前述のメリットが得られるトレードオフとして、バイナリサイズが大幅に増加し、起動速度が数秒単位で遅くなるというデメリットが有りました。

そこで今回、CrossWalkを外す決断をしました。お陰で起動速度は数秒の単位で短縮されました🎉

その代わりに、Android4をご利用の皆様は、今後TechFeedのアップデートが受け取れなくなってしまいました…ただ、変わらず古いバージョンは利用し続けられますし、新しい端末に移行する際には、サインインして頂ければデータや設定は自動的に引き継がれますので、どうかご容赦ください。

Angular CLI導入

TechFeedは、Angularがベータ版の状態から開発しておりまして、ビルドは自前のスクリプトで行っていました。それに、IonicがAngular CLIを公式にサポートしていないということもあり、ずっとAngular CLIは導入できずにいました(おまけにAngular CLIの前バージョンにはきっついバグがあって、使い物になりませんでしたし…)。

しかしおかげで、Webpackのバージョンはずっと2系(しかもベータ版)だわ、ビルド速度は遅いわで、いつか改善したいと思っていたところ。今回ビルドシステムを刷新し、Angular CLIを利用するようにしました。

おかげでWebpackも最新になり、ビルド速度も向上、バンドルサイズも小さくなりました。ただ、この対応に費やした2週間以上の工数を、ビルド速度改善で取り返せるのか甚だ不安ではありますがw

今後の開発加速にご期待下さい!

この2ヶ月は、技術的負債の返済と、ずっと懸念事項だった開発上のタスクを消化することにあてた期間でした。リファクタリングなど、ユーザーに対する価値が直接感じられない活動にリソースを投入するのは大きな決断でしたが、今後の開発を加速して、ユーザーの皆様に今後価値を素早く、クオリティ高く届けられるようにするための投資と割り切ることにしました。

今後、開発が加速するTechFeedにどうかご期待下さい。

Like what you read? Give TechFeed a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.