こんにちは、Yahoo!天気・災害のエンジニア、大前です。Android版Yahoo!天気アプリの開発を担当しています。仕事はアプリ開発で、趣味もアプリ開発です。毎日楽しく開発しています!
Yahoo!天気アプリでは、2021年5月に雨雲レーダー機能の大幅アップデートを行いました。本記事では、Androidアプリエンジニアである私の視点から、天気アプリの開発の取り組みについて紹介し、ヤフーのアプリ開発の現場の様子を知っていただこうと思います。
ちなみに本記事は2021年7月31日に開催された「Developers Summit 2021 Summer」での発表内容を記事として書き起こしたものです。
- 発表資料 (外部サイト)
- アーカイブ動画 (外部サイト)
- CodeZine レポート記事 (外部サイト)
雨雲レーダーの大幅アップデート
Yahoo!天気アプリでは以前より主要機能の一つとして雨雲レーダー機能を提供しています。以下の画像が今回の大幅アップデート前後の画面比較です。UIをモダンなデザインに刷新し、ボトムシートを追加、いつ、どのぐらいの雨が降るのかを、わかりやすく表示するようにしています。
雨雲レーダーは雨雲の情報を地図上に表示する機能です。シークバーを操作し、時間ごとの雨雲の状態を知ることができます。しかし、多くの利用者の皆様にとって、この雨雲レーダーで本当に知りたい情報とは、現在地点やこれから移動する目的地といった特定の地点に、いつ、どのぐらいの雨が降るのか、ということではないでしょうか? 従来はそれを地図上に表示される雨雲の状態から読み取っていただいていました。
今回の大幅アップデートでは、雨雲の情報を計算によって整理し、特定の地点にいつどのぐらいの雨が降るのかを、
- 予想降雨量を視覚的にわかりやすく表示したグラフ
- 「まもなく雨が降ります」「しばらく雨が続きます」といったメッセージ
- どのぐらいの雨なのかを感覚的にわかるように「パラパラ」「ザーザー」といったオノマトペ
などのさまざまな表現方法を使って、必要な情報を誰にとってもわかりやすく提供できるように工夫しました。
開発体制
雨雲レーダーはYahoo!天気アプリの主要機能の一つではありますが、Yahoo! JAPANアプリの機能としても提供しています。大幅アップデートはYahoo!天気アプリとYahoo! JAPANアプリの両方を同時に開発しました。それぞれにAndroid版、iOS版が存在しますので、4つのアプリを同時に開発することとなりました。
チーム構成としては、Yahoo!天気アプリとYahoo! JAPANアプリそれぞれに開発チームがあり、Android版とiOS版は同一のアプリでもコードは共通化していないため、エンジニアチームとしてはAndroidチームとiOSチームに分かれています。
開発チームが4つに分かれていることもあり、1アプリあたりのエンジニア数としては非常に少人数という特徴があります。
開発に取り組むにあたり直面した問題
今回の開発で、Android版天気アプリは、開発前にある問題に直面しました。雨雲レーダーのUIを刷新し、新機能を追加するという開発ですが、従来の雨雲レーダーはすでに十分に多機能な状態でした。
こちらの画面を見てただければ分かる通り、
- マップ表示
- 地図レイヤーの表示
- 雨雲レイヤーの表示
- 時系列による表示の切り替え
- ドラッグ、ピンチなどによる操作
- 現在地への移動
- ズーム操作
- モード切替
- 時系列操作のシークバー
- SNSシェア
など、さまざまな機能が一画面に詰め込まれています。
また、雨雲レーダーはさまざまなモードを持っています。
- 雨雲レーダー
- 雷レーダー
- 雨雪レーダー(冬季のみ提供)
- 積雪深(冬季のみ提供)
- 台風の動き
このようにすでに十分に多機能な雨雲レーダーが抱えていた問題は、FatActivityです。Androidエンジニアであれば、FatActivityというワードを聞けば、どのような問題かすぐに理解していただけるでしょう。
ActivityとはAndroidでの画面を提供するコンポーネントです。この一つのクラスに、ほぼすべての機能が実装されてしまっているという状態です。アンチパターンにおける「神オブジェクト」などと呼ばれるものに相当するでしょう。機能が巨大になり、複雑で機能の全容を見渡せない状態になってしまっていました。機能の密結合が起こり、かんたんには引き剥がせない状態です。
継ぎ足し開発の限界に来ていました。
レガシーコードとの戦い
レガシーコードとの戦いは何も珍しいことではなく、おそらく世の中のすべてのソフトウェアが直面する問題だと思います。
ソフトウェアにおいて完璧な設計などというものは存在せず、時代背景や機能の規模に合わせて改善し、変化していくことが重要です。FatActivityは現在ではアンチパターンの代表例ですが、開発初期の非常に機能が小さい段階では、一つのクラスに全機能を実装するのはそれほど間違った設計ではなかったかもしれません。機能が増えていったときに、それに合わせて変化させられなかったために、はじめは小さかったはずの問題が大きく育ってしまいました。
天気アプリではレガシーコードからの脱却のため、開発案件の合間に全体的な構造の見直しを行っています。また、開発案件に取り組む場合は、その周辺を含めてリファクタリングを行い、着手したあとは着手する前よりもきれいなコードにしておく、という精神で改善に取り組んでいます。しかし、レガシーコードはまだ残っており、雨雲レーダーはその中でも大物の一つでした。
今回の大幅アップデートを成功させるためには大幅なコードの改善が必要となっていました。
リファクタリングをいつやるか
リファクタリングはしなければならない、問題はいつやるかです。
先延ばしにして良いことはまずありません。そのままにしておけば、難解で複雑なレガシーコードを理解しつつ開発することなりますので、開発速度は大幅に低下するでしょう。当然、バグの温床となり、たくさんの手戻りも発生するでしょう。
開発スケジュールがタイトだったこともあり、機能開発を優先することも考えましたが、機能開発のスタートを優先したところで、最終的には予定通りリリースできないと考え、リファクタリングを先に行うこととしました。
幸いにも、ヤフーではリファクタリングの重要性はエンジニアに限らず共通認識であり、提案してむげにされることはありません。ビジネス観点での数字に直接貢献することでなくとも、開発効率の改善などにつながるのであれば、きちんと評価されるという土壌もあるため、リファクタリングに取り組みやすい環境が整っています。
リファクタリングをしてからでないと開発をするのは厳しい状況であることを報告し、開発着手が遅れることも理解してもらった上で、機能開発に取り組む前にリファクタリングに着手しました。
リファクタリングの方針
リファクタリングに取り組むとなったとはいえ、開発リソースも時間も有限です。リファクタリングにゴールはなく、完璧な状態というものは存在しません。ついつい理想を追い求めてしまいますが、理想を追い求めすぎるとコストは無限に膨れ上がってしまいます。費用対効果を考え、適切な妥協点を設定することとしました。
全てが一つにまとまってしまっていることが何より問題であるため、機能ごとのできるだけ小さな単位に分割すること。
機能ごとに分割しやすくするため、リアクティブプログラミングをUIに導入すること。
そして、芋づる式に全体をリファクタリングしたい衝動をおさえ、雨雲レーダーの範囲に限定して行うこと。
という方針で取り組みました。厳密なアーキテクチャを導入するのではなく、その前段階、既存処理の見通しを改善できれば良いという程度のゆるい方針です。何をするにしても、ここまではできていないと身動きがとれないので、そこまでの改善を行うこととしました。
機能の分割
最初の分割点を見つけようとしましたが、コード量が多く、コードを眺めていても分割の基準が見えてきません。そこで、UIの全体を見て、明らかにこの機能は他と独立ししているだろう、という部分の目星をつけました。
その部分の処理を別のクラスに分離していきます。複雑に絡み合っているように見えても、実際に絡まっているところは、いくつかのメソッドコールやコールバック程度であり、思いの外かんたんに分離できました。大枠での分離が進んでいくと、コードの見通しが良くなり、それぞれの機能の中にも独立している部分が見えてきます。その部分をさらに分割し、小さな機能単位にまとめていきました。
あわせて、機能分割をやりやすくするためにリアクティブプログラミングを導入しました。リアクティブプログラミングというのは、データの流れに対する各モジュールのふるまいを実装していくプログラミングスタイルです。モジュールごとにデータが流れてきたときに、どうするのかを記述していきます。
このプログラミングスタイルを導入することで、各処理をイベントごとではなく、モジュールごとにまとめていくことができます。
今回問題となったFatActivityですが、画面を構成するコンポーネントに実装が集中してしまう要因の一つに、Androidのライフサイクル管理の難しさがあります。画面が閉じられれば、画面上のすべてのモジュールで処理を中断させる必要があります。画面を回転させた場合は、レイアウトを変えるために画面の作り直しが発生しますが、前の画面の状態を新しい画面に引き継ぐ必要もあります。
このあたりをうまく処理してくれるライブラリが公式で用意されており、これを採用しました。UI関連データを管理するためのViewModel
に、ライフサイクルを考慮したデータ監視が可能なLiveData
を配置します。各UIモジュールは、このLiveData
を監視する形で実装していきました。
比較的新しいライブラリですが、十分にノウハウが蓄積された非常に使いやすいライブラリです。
リファクタリングの結果
機能開発を遅延させてでもと、リファクタリングに取り組みましたが、結果的には成功でした。リファクタリングに使った時間はすぐに返済でき、レガシーコードに引きずられることなく、新機能の開発やブラッシュアップに集中して時間を使えました。
リファクタリングだけに使った時間としては1週間程度ですが、この期間内に必要な分をすべてやりきったわけではなく、大枠での分割とざっくりとした構造づくりまでを行い、あとは新機能の開発と並行して進めました。リファクタリングによって全体的なスケジュールにマイナスのインパクトを与えることはなく、むしろリファクタリングを行っていなければ、多くの手戻り、多くのバグに苦しめられ、リリース予定をどんどん後ろ倒しにせざるを得なかったでしょう。この判断は正解だったと思います。
また、コードの改善を行うという観点では、エンジニアの人数が少ないこともプラスに働いたと思います。同時並行で動いている案件がないため、大規模な変更もコンフリクトを恐れることなく行えました。
新機能の開発
新機能の開発では、最初から仕様を完全に決め、その仕様に従い作り切るという形ではなく、ある程度の方針が決まれば、実際に作ってみて、メンバーに配布し、フィードバックをもらうという形で開発を進めました。
百聞は一見にしかず、百見は一触にしかずどんなに事前に議論していても、触ってみて初めて分かることも多いものです。一部でも動くものができれば、メンバーに触ってもらってフィードバックをもらう。これを何度も繰り返しました。
触ってみてわかったことの例を2つ紹介します。
ボトムシートを引き上げたときに現れるグラフ表示部分、この部分はどのような操作に割り当てるべきかが議題に上がりました。実装前の議論では、シーク操作をしたいときはシークバーがあるのでそちらを操作するだろう。シートの上下移動ができればよいのではないか、ということになりました。
しかし、実装後、実際に触ってみると、シークバーがあったとしても、グラフ部分はシーク操作ができると思ってしまい、ここを操作しようとしてしまうことがわかりました。実装者である私も、シーク操作のためについつい触ってしまいました。
このようにグラフ部分はシーク操作に使えなければならないことがわかりました。それならばと、シーク操作ができるように作ってみると、今度はシートの上下操作をしたいときも、この部分を触ってしまうことがわかりました。
結果的に、上下方向であればシートの上下操作、横方向であればシークバーの操作、と、操作の方向に応じてふたつの操作を排他的に振り分けるのが、最も直感的な操作ができるとわかりました。
ボトムシートの高さは3段階あり、指を途中ではなすと、吸い付き動作をして、3段階のどこかの位置に収まるように動きます。その吸い付きのアルゴリズムはどうあるべきかが問題となりました。
はじめは指がはなれた位置から、一番近い位置に吸い付くように実装してみました。しかし、これでは直感的な操作はできませんでした。動かそうとしても移動距離が足りず、もとに戻ってしまうことが多かったです。
そこで、位置に加えて速度も考慮し吸い付く先を決めるようにしました。一部の人にとってはこれで直感的な動きになりましたが、中間位置を勢い余って通り過ぎてしまい、中間位置に合わせられない人もいました。
フリック、スワイプという画面上をはらう操作はセンサーから見た個人差が大きい操作です。絶対にこれが正しいという正解はありません。何度も試行錯誤し、最終的には加速度を加味して、ユーザーが操作しようとしている向きを計算に入れつつ、追い越ししてしまわないように、現在位置からユーザーの操作しようとしている方向へ1段すすめるという動作に調整しました。
4つのアプリの良い競争関係
今回はそれぞれ4つのアプリを開発することになり、コードの共通化も行われていないため、エンジニアリソースが分散することによるデメリットが多かったのではないかと思われるかもしれません。しかし、私はこの環境もプラスに働いたのではないかと思います。
4つのアプリの機能や動作はすべて完全に同じではありません。アプリごとに目的や機能の位置づけが異なりますし、OSごとに適切な表現方法が違います。それぞれが、よりよりものを目指す形で改善を行いました。
それぞれのアプリは適切な競争関係にあり、相互にフィードバックを行いながら、「お? これいいね」と思った良い部分は、自分たちのアプリに合わせて取り込んでいきました。コードは共通でないものの、それそれのリポジトリは見られる状態ですので、どのように実装されているのかをみて、良い部分を取り込んでいきました。
細かすぎて伝わらないこだわり
こちらは説明されなければ、いや、されてもわからないという細かすぎるこだわりですが、良い部分を取り込みながら改善させた部分ですので紹介します。
シートは3段階あり、それぞれで表示される要素が違います。シートを引き上げるにつれて下から新しい要素が出てくるのではなく、間に追加されていく形になっています。操作に合わせて適切にこの間をつなぐアニメーションを実装する必要があります。
どうでも良いことのように思われるかもしれませんが、スムーズなアニメーションが、使いやすさ、使い心地の良さにつながります。いろいろなアニメーションを試しながら、より自然なつながりが感じられるように作り込みました。
この動きはAndroid版とiOS版も少し異なっています。もし興味がありましたら、それぞれのアプリで、ゆっくりとシートを引き上げ、引き下げしながらどういう動きになっているのか、見比べていただくと面白いかもしれません。
リモートワークでのコミュニケーション
昨今の情勢からヤフーではリモートワークでの開発を行っており、全員が直接顔を合わせることのない環境です。
開発中のやりとりは、ほとんどSlackで行いました。検証用パッケージを作ると、そのパッケージをSlack上で共有し、各メンバーが手元の端末にインストールして確認します。フィードバックは単にテキストだけの場合もありますが、スクリーンショットや動画を組み合わせ、直接顔を合わせていなくても問題なく状況の把握ができきました。
Slackでのやりとりは、一堂に会した会議に比べて時間軸的に分散しますが、さまざまな業務を抱える各メンバーが、それぞれの都合のよい時間に確認をし、フィードバックをもらえました。天気情報は実際の天気との比較が重要ですが、リモートワークであるため、それぞれのメンバーの住んでいる地域が異なり、自然とフィールドテストを行えたことも大きなメリットとなりました。全員が出社している環境の場合、オフィスの地点での検証しかできなかったでしょう。
Zoomも活用しました。進捗(しんちょく)確認や方針ぎめなどのミーティングで使用する以外に、Zoomで接続してのバグバッシュも行いました。
パッケージを配布し、手元の端末にインストールしてもらった状態で、Zoomを接続します。テレビ会議で接続しているにもかかわらず、参加者は手元のスマホを操作しているという、一見やる気のない不真面目な会議のような状態から始まります。
しばらくすると、誰かが問題を見つけます。Zoomのカメラに端末を映したり、スクリーンショットをSlackで共有したり、その情報が共有されます。
再現方法もわからない状態での第一報ですが、それが共有されると、また別の人が再現方法がわかったと共有します。またある人は、報告されたバグをヒントに、別のバグを見つけます。短時間でも通常であれば見つからないであろうレアケースのバグまで見つけられました。
バグバッシュはエンジニアにとって自分のミスを見つけられる面白くないイベントと思われるかもしれませんが、大変楽しいイベントです。自分が一番にバグを見つけてやる! と参加者のモチベーションも高いです。
「バグを見つけた!」
「ホントだ! よく気づきましたね」
「次は自分が見つけるぞ!」
そんなやりとりが飛び交います。これを繰り返し行い、完成度を上げられました。
最後に
エンジニアの目線で、雨雲レーダー機能の大幅アップデートの開発の取り組みについて紹介いたしました。
機能の大幅な改善を行いましたが、これで完璧であるとは考えていません。表現の磨き込みや、低スペックの端末でも快適に使えるようパフォーマンスの改善にも取り組もうとしています。まだまだ改善の余地はあると考えておりますので、さらにわかりやすく、より便利に使っていただけるアプリを目指して改善に取り組んでまいります。
ここで紹介したとおり、当社はエンジニアが主体的に楽しく開発できる環境が整っています。もしご興味がございましたら、アプリ開発エンジニアの採用情報を御覧ください。一緒に楽しく開発しましょう!
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました