WorkManagerの内部を見てみた

f:id:qurangumio:20210907153258p:plain

はじめに

皆さん、こんにちは!mikanでAndroidエンジニアをしているgumioです。

twitter.com

今回はmikan androidにWorkManagerを導入しようとした際に色々面白かったので、それを紹介しようかなと思います。

こちらの記事は以下のような流れになっています。

  1. WorkManagerを知らない人向けに簡単な概要とメリット
  2. WorkManagerのユースケース
  3. WorkManagerの内部挙動
  4. 検証してtoolなどを使って覗いてみた
  5. さいごに

WorkManagerを知らない人向けに簡単な概要とメリット

Android Jetpackの仲間です。

developer.android.com

あるバックグラウンドタスクを実行するScheduler APIです。

今までバックグラウンドタスクに利用されていたFirebaseJobDispatcherGcmNetworkManagerJobSchedulerなどがあったが、WorkManagerを利用すれば以下の図のように内部で分岐してくれるので、API levelとかを意識する必要がないメリットがある + 電池寿命も考慮されており、電池寿命が伸びるメリットもあると公式は言うております。

f:id:qurangumio:20210907150508p:plain
WorkManagerの内部分岐について

ちなみにWorkManagerはAPI level 14から機能するので、安心してください。

WorkManagerの全体的なメリットとしては

  • API level 14から気にせずに機能する
  • シンプルで一貫性がある
  • 信頼性の高い非同期タスク
  • 電池寿命がよくなる
  • RxとCoroutineのサポートもされている

今回WorkManagerの詳細な使い方などは日本語ドキュメントが充実しているため、紹介しないです。

WorkManagerのユースケース

Coroutineとはどこで使い分けたらいいの?どういう時にWorkManagerを使うべき?とか少なからず思いますよね。

androidドキュメントににバックグラウンドタスクのタスクのガイドが用意されてますので、これに従うでいいと思います。

developer.android.com

僕なりの解釈としてはこんな感じだと思いましたが、どうでしょう?

  • 短時間 or 長時間かかって、裏側で必ず動かしてほしいタスク
    • 厳密には書かれていませんが、10 分以上は実行できるらしい
  • 電源が急に落ちる、いきなりプロセスキルされても必ず実行されたい 延期させたい: ネットワークがオフラインになっている、充電が充分にない場合に延期させて、条件を充分に満たした場合に実行したい

公式でも以下のように述べられてます

WorkManager は、ユーザーが画面から移動した場合、アプリが終了した場合、デバイスが再起動された場合でも、確実に実行する必要がある作業を対象としています。次に例を示しますログやアナリティクスをバックエンド サービスに送信するアプリデータをサーバーと定期的に同期する

mikanでもログを送るときに使用しようとしています。

アプリデータをサーバーと定期的に同期するはfetchしてくるイメージだと思います。アプリのデータをpushすることもできなくはないですが、WorkManagerが入力を受け付けれるをデータ量は10KBまでとなっているので、あまり何かを載せるのは期待できません。

それでは、次に実際の挙動を見ていきましょう!

WorkManagerの内部挙動

さて、今回一番紹介したい部分で、WorkManagerの謳い文句の

「ユーザーが画面から移動した場合、アプリが終了した場合、デバイスが再起動された場合でも、確実に実行する必要がある作業」

これって、結局どうやってるんだろう??ってなりました。 そこで内部の動きを簡易ではありますが、追ってみます。

まず、簡単なWorkManagerの流れのおさらいですが

  1. 行いたいタスク(Worker)を定義する
  2. このタスクの実行方法とタイミングを決めるRequestを作って依頼する
  3. WorkManagerを通じて、システムに2.のRequestを飛ばす(enqueue)

すごくシンプルですね。

このとき、WorkManagerのStateは以下の図のようになっていて、一度きりのRequest(OneTimeWorkRequest)と定期的なRequest(PeriodicWorkRequest)で少し異なりますが、終わりがあるかないかの違いだけで基本は同じです。

以下、処理の状態  |  Android デベロッパー  |  Android Developers より

f:id:qurangumio:20210907150506p:plain
一度きりのRequestのとき

f:id:qurangumio:20210907150503p:plain
定期的なRequestのとき

それでは、enqueueされたあとのRequestたちはどのように扱われてるのでしょうか?

WorkManager basics. Getting started with WorkManager | by Lyla Fujiwara | Android Developers | Medium より

f:id:qurangumio:20210907150513p:plain
enqueueされたあとの流れ

  • Internal TaskExecutorが、requestされたものをWorkManager DB(Room)にWorkRequest情報を保存します
    • Internal TaskExecutorはsingle threadのExecutorで、実際にenqueueされたrequestをさばく役割を持っています
  • WorkRequestのConstraintが満たされると(すぐにでも可能ですが)、Internal TaskExecutorはWorkerFactoryにWorkerを作成するように指示します
  • その後、defaultのExecutorは、mainThreadからWorkerのdoWork()メソッドを呼び出します

developer.android.com

なるほど!RequestされたものがDBに保持されていたんですね。このDBにはRoomが使われてました!! Requestされたものは制約が満たされるまではDBでおとなしくしてるんですね。この制約は端折っていましたが、Requestを作るときに指定することができます。

例えば、ネットワークがオンラインのときかつ、充電中のときのみ実行される制約が設定されます。

val constraints = Constraints.Builder()
  .setRequiredNetworkType(NetworkType.CONNECTED)
  .setRequiresCharging(true)
  .build()

先程のStateでRUNNINGになってたとしても処理が完了する前に電源落とされたり、オフラインになったりするとENQUEUEEDに戻り、またDBに大人しく返ってたんですね。

改めて、最初の謳い文句を再度見たときに、なるほどとなるのではないでしょうか?

「ユーザーが画面から移動した場合、アプリが終了した場合、デバイスが再起動された場合でも、確実に実行する必要がある作業」

検証してtoolなどを使って覗いてみた

最後に少しだけ挙動を確認してみて、終わりにしようと思います! 以下のような1...10を出力する簡単なWorkerを作成します。

→ 例ではWorkerではなく、CoroutineWorkerを使用していますが、こうするだけでdoWorkはsuspend関数となり、内部ではCoroutineが使われるようになりますb

class TestWorker(
  appContext: Context,
  workerParams: WorkerParameters
): CoroutineWorker(appContext, workerParams) {
  override suspend fun doWork(): Result {
    repeat(10) {
      Log.d("TestWorker", it.plus(1).toString())
      delay(1000L)
    }
    return Result.success()
  }
}

次にこのWorkerをRequestします。一度きりでネットワークに繋がってるときのみの制約にしています。

補足: 前述したようにWorkerをRequestするときは、一度きりと定期的で分かれます。

また、実行条件の制約をつける場合はConstraints.Builderで指定し、setConstraintsにsetしてあげる必要があります。

val testWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<TestWorker>()
  .setConstraints(
    Constraints.Builder()
     .setRequiredNetworkType(NetworkType.CONNECTED)
     .build()
   ).build()

最後に適当なボタンを用意して、enqueueするようにして、実行してみましょう 途中でオフラインにしてみます。

f:id:qurangumio:20210907150527p:plain
実行中にオフラインにして、キャンセルされてる

Workerがキャンセルされてることが確認できますね。 このとき、最近機能が充実してAndroid StudioのApp Inspectionで覗くことができるので、見てみましょう!!

App Inspectionとは、RoomやWorkManagerといったdebugをしづらいものをAndroid Studioがdebugしやすいように用意してくれたサポートtool郡のことを指します。

まずはDB Inspector

f:id:qurangumio:20210907150523p:plain
DB InspectorでWorkManagerが作成しているworkdbを見ている

どうやら、内部でWorkManager用にworkdbというのが内部で自動作成されていることがInspectorからわかりますね。これが先程の内部挙動で紹介したRequestされたWorkerを保持しておくために必要なDBということが予測できます。

色んなtableがありますが、試しにWorkSpecというtableを開いてみましょう!

名前の通り、各Workerの詳細情報を持っていることが伝わりますね。

f:id:qurangumio:20210907150534p:plain
workdbのWorkSpecテーブルを見ている

次にArctic Fox Canary 3で新たにWorkManagerのdebug用に追加された、Background Task Inspectorを見てみましょう。

f:id:qurangumio:20210907150538p:plain
Background Task Inspectorを見ている

最後のWorkerが途中でオフラインにされたWorkerなのですが、ENQUEUEDになっていることがわかります。 この状態でアプリをオンラインにして再度立ち上げたら、再度Workerが動き出して無事に処理されました!!

さいごに

いかがだったでしょうか? 今までWorkManagerとは関わる機会がなかったのですが、今回導入することをきっかけにキャッチアップをし、気になるところなど、学んだことを書いてみました。

mikan androidではこのように新しめな技術を気軽に試して、導入できるかどうかを検証するのも業務の一環です。まだまだやりたいことがたくさんあり、メンバーが足りないので少しでも興味を持った方は是非お話しましょうー!

herp.careers

チームでスキーマ設計した時のハマりポイントと勘所

f:id:nixii_squid:20210903185934j:plain

はじめに

こんにちは。mikanでバックエンドエンジニアをしているnixiiです。

今年の7月にmikanにジョインし、そこから1ヶ月ちょっとが経ちました。最近は本格的に開発にもコミットするようになり、本エントリテーマにあるようなスキーマ設計を議論したり、がっつりコードを書いている日々です。

先日出した入社エントリから早くも1ヶ月が経ったと思うと、時の流れは早いな〜と実感しています。

note.com

こちらの入社エントリでは、私の自己紹介や、なぜmikanにジョインしたのかについて色々と深ぼって書いています。是非ちらっと読んで頂けると嬉しいです!

。。と前置きが長くなりましたが、今回は「チームでスキーマ設計した時のハマりポイントと勘所」と題して、私を含めバックエンドチームがスキーマ設計を行った際に出会ったつまづきと、そこから得た学びをシェアしていきます。

そもそもスキーマ設計とは

スキーマ(Schema)は広義には「図式・構造」などと訳され、データベース分野においては「DB(データベース)の構造」を指します。

例えばRDB(リレーショナルデータベース)であれば、現実で扱いたいデータに応じてテーブルを定義し、必要なカラムも考慮した上で、テーブル同士の関連についても考えることになると思います。こういったDBの構造(= スキーマ)の設計を考えることが、即ち「スキーマ設計」です。

特にテーブル間のリレーションなど、実世界をデータモデリングしているものは「概念スキーマ」、それをRDBであればSQL文などで表現したものを「外部スキーマ」と呼びますが、本エントリでは便宜上、両者スキーマの設計を合わせて「スキーマ設計」と呼んでいます。

ハマりポイント

今回mikanでは、英単語アプリで新たな教材をアプリ上で扱うため、その教材に応じたスキーマを新たに設計する必要がありました。

スキーマ設計を進めていくうち、こうした設計タスクなどでよく出会うハマりポイントに、今回バックエンドチームもハマってしまっていました。具体的には以下の2つが挙げられます。

  1. チーム内で設計への見通しが異なっていた

  2. 検証フェーズについて考慮漏れしていた

「チーム内で設計への見通しが異なっていた」点について

スキーマ設計の工数としては、元々チームでは1日程度と見積もっていました。が、実際には素案レベルでのスキーマをチーム内で仕上げ、ビジネスサイドと認識をすり合わせつつ調整するために、結果としてトータルで3, 4日以上かかってしまっていました。

元々1日で完了すると踏んでいた理由として、じつは過去すでに類似したスキーマを作っており、このスキーマが流用出来ると考えていた点があります。しかし実際には、このスキーマは今回RDB向けに設計しようとしていたフォーマットとは異なり、Cloud Firestore向けに設計していたものでした。

Cloud FirestoreはNoSQLのドキュメント指向データベースで、key-value値が入れ子構造のようにドキュメントとして保存されていく構造です。これは各テーブルを必要に応じて正規化したり関連させることでデータモデルを表現するRDBとは大きく構造の異なるデータベースで、当然出来上がるスキーマも自ずと違ってきます。

このように前提の異なるスキーマを再利用するのは実際にはかなり難しく、チーム内で議論しつつ、RDB向けにほぼスキーマを設計し直す形となりました。結果として工数が膨らみましたが、元々資産として参考にする予定だったスキーマについて、事前にチーム内で目線を合わせることで気づけていた点でもあり、走り出し時点でチームで認識を揃える重要さを体感しました。

「検証フェーズについて考慮漏れしていた」点について

本来スキーマ設計をチームで進める場合、議論しつつスキーマ設計の素案を作成するだけでなく、「作成したスキーマが実際に解決したい現実の問題を扱えるのか」を検証する必要もあります。

しかし、設計に着手時には後者について考慮しておらず、着手中に検証フェーズの必要性に気づいた結果、1~2日ほど工数が膨れていました。今回に限らず、新規事業でのPoCや実装におけるテストなど、考えたコト・作ったモノに対して正しく機能するかどうかを担保するための検証フェーズは不可欠なものだと思いますが、常にその点も考慮に入れるべきだと振り返りました。

チーム内で認識を揃え、問題解決にあたる際には解決案の検証まで行うというのは非常に基本的な事ながら、日々それを常に徹底することの大切さと難しさを痛感した週でした。

スキーマ設計の勘所

スキーマ設計に限らないのですが、こうしたチームレベルで考え、検討した上で設計するタスクでは、事前にチーム内で

  • 今明らかになっているものは何か
  • まだ不明瞭なものは何か
  • 現在やろうとしている事は何か

について認識を揃えておくだけでも、予期せず工数が膨らむといった事態は避けやすくなるだろうと振り返りました。

今回の例だと、「明らかに過去の資産を今回の設計に活かせる」と考えていたものの、実はチーム内でその点について触れることで「実は明らかではなく、活かせるかどうかは不明瞭だった」という事が把握出来たためです。

中でも「一見明らかだけど、明らかだと思い込んでいて実際には不明瞭だった」というパターンは結構曲者だと思っていて、上記はこのパターンにチームで早期に気づくための勘所でもあるなと思います。

また、現在やろうとしている事についても認識を揃えることで、本当にそれらを完了すれば設定したゴールに辿り着けるかもチームで確認出来ます。今やっている事をこなすことで設定したゴールに到達出来るのか、最初にチームで目線を合わせるのもこうしたタスクでは重要だと思いました。

mikanでは現在ほぼフルリモートで開発を行なっていますが、今回のリモートでのスキーマ設計の議論ではmiroというオンラインホワイトボードのようなツールを使ってみました。ホワイトボード上で図を書きつつ議論するような使い方が出来る点で、非常に使いやすいな〜と感じています!

f:id:nixii_squid:20210903185650p:plain

(miroを使って概念スキーマの議論をしている画面)

他にも必要に応じてZoomにiPadを繋いでイメージ図を書いたり、チーム内で必要に応じてサッと情報を共有していくのもこうした設計の議論では大切だなと実感しています。

コロナ禍前ではオフラインで実際のホワイトボードを前にワイワイ議論出来ていたのですが、オンラインでこういった設計の話を進める上では正直やっぱりハードルが上がったなと感じています。

一方で、お互いにこうしてツールや通信環境をより良くしたり、進める上でのちょっとした障害が無いかどうか都度フィードバックをし合うなど、ハードルを下げる方法はまだまだ沢山あるな〜と思いました。

さいごに

私がmikanチームにジョインしてから度々思うのが、こうやって自分たちが過去やったこと・現在やっていることへの振り返りと、そこから学びを得ようとするカルチャーが徹底されているなという事です。

良い結果を得るために出来たこと、芳しくない結果になった経緯などを、次により良い仕事をやり切るためにチームでシェアするというmikanのカルチャーは個人的にすごく好きで、他のメンバーの振り返りも見ていて非常に勉強させて貰えたりします。

チームにとって良い影響を与える事を継続し、プロジェクトの進行を妨げる要因になりそうなものを排除する上でも振り返りはとても重要だと考えていて、個人・チームレベルのどちらでも日次や週次で振り返り、かつチーム内から色んなフィードバックをもらえるというのはすごく良い環境です。

最後になりますが、このような環境で働いてみたいと少しでも思った方、英語が好きだったり教育を変えていきたいという想いのある方、是非一緒に働いてみませんか?

以下の私のTwitterに気軽にDMして頂いても構いませんので、ぜひ気軽に声をかけてみてください!

twitter.com

mikan.link

参考にした記事

Firebase Documentation Cloud Firestore

入社して4ヶ月経つのでぶっちゃけてみる

f:id:iiizukkeyiii:20210826134024p:plain

はじめに

こんにちは。 mikanでAndroidエンジニアをしております、zukkeyといいます。

今年の4月に入社して、大体4ヶ月ぐらいが経過しました。 まだ入社して4ヶ月しか経っていないのか、という驚きと共に、4月に入社エントリーを書いたときから、何ができたんだろうかと振り返っていこうと思います。

ちなみに、入社してすぐに書いたエントリーについてはこちらになります。

note.com

入社してからやっていたこと

入社してから次のようなことをやっていました。

  • Android開発
    • 運用
    • 機能実装
    • 設計
  • 業務改善
    • Ktlint、自動レビューアサイン、PRテンプレート見直し
  • デザイン
  • 広報活動(採用)

Androidエンジニアなので、まずはAndroid開発で何をやってきたのかをお話しさせていただこうと思います。

入社する前から副業で手伝っていたリデザインの開発を主に行っていました。

mikan AndroidiOSアプリとは異なり、なかなか置き換えるための人的リソースが少なく、代表の@kazさんや副業メンバーで回している状況でしたので、以前自分が触った時と同じままの古いデザインでした。

そのため、Androidのメイン画面を新しいデザインに変更したいという要望もあり、新デザインに変更する(リデザイン)をやっていました。

f:id:iiizukkeyiii:20210826133555p:plain

リデザインの開発では、具体的に次のようなことをやっていました。

  • 画面を跨いでの作業が必要な箇所の大きい改修
    • Fragmentの状態保持の仕組みを入れる
    • 教材一覧画面でカルーセルのindexが保持されておらずリセットされていたのを保持する仕組みを入れる..etc
  • デザイン改修
    • Proラベルの改修やボタンのBackgroundカラーがMaterialComponentsで変わっていた部分を修正
    • タブレットのデザインが崩れていたところを直す..etc

他にも、業務改善や全体として必要そうなところもやっていました。

  • チームになったことで必要になった業務改善
    • Ktlintを入れてフォーマットを整える
    • GitHub Actionsを用いた自動レビューアサイン機能を導入
    • チームができたことにより、プルリクテンプレートの見直し
  • その他
    • 今まではなかなか取り組めていなかった既存のバグ対応(優先度の高いものから着手して着実に数を減らしてきて、今では残すところ7件)
    • アプリケーションの全体の設計を含めてたたき台を作って導入し、今も今後のやりたいことを見据えてブラッシュアップ

設計に関しての記事もまとめているので、よかったら見ていってください。

mikan-tech.hatenablog.jp

mikanでは、担当領域だけでなく手を挙げれば他の領域もやっていけるので、自分の新たなチャレンジとしてデザインタスクもやっていました。

このエンジニアブログのアイキャッチ画像作成にも自分が関わりました!

f:id:iiizukkeyiii:20210826133618p:plain
作成したアイキャッチ画像集

他にも、mikanの認知を広める活動の一環として、広報ブログを開始したり採用面でもお声がけさせていただくことをやっていたりしています。

note.com

mikanに入社してよかった?

総じていうとよかったなー、と思ってます。

よかったと思う点は次のとおりです。

  • いい人で構成されている
  • 施策を決める前の段階から進められる
  • 何でこれやるの?への納得感が大事にされている
  • 状況に応じて柔軟に対応している
  • ベンチャーでありつつ、ベンチャーという理由を言い訳にしない

一番入ってきてからいいなと思ったことは、Qの初めにやる施策案出しがあります。

分業されているところだと、この機能を作るためにはどう実装するのか、現在の状況から実現可能か、いつまでにやり切れるかにばかり集中していて、これってそもそも何のために作ってるんだっけという部分に声を上げられなかったり、提言してもあまり変わらなかったりします。

最初からどういう課題があるかを探しながら、案を出して納得感を大事にして上流から関わることができるのが、一番楽しいところだなと思っています。

他にも、ベンチャーでありながらも柔軟に対応していて、ベンチャーだからできないというようなことがないのも嬉しいところかなと思っていたりします。

昨今のコロナウイルスの状況を鑑みて、適宜リモートにしたり、ワクチン接種まで用意していただいたりして、無事にワクチン2回目摂取も終えることができました。

また、病気・ケガを事由とした有給休暇を年10日間、年次有給休暇とは別で付与されるSick Leaveを設けたり、「安心キット」という人事制度まで整ってきて驚くばかりです。 ベンチャーだからという理由はなく、正直ここまでやるとは思っていなかったので、すごいなぁと思いました。

mikan.link

逆に、もうちょっとここどうにかしたいなーって思うところもあります。

Androidは、今はまだ複数人で開発するためのアーキテクチャ設計をやっていたり、根幹となる技術選定と検証をしていたりする土台作りの段階で、まだ長期的に見据えて時間を割く必要があり、施策をバンバン打って事業伸ばしていくぞー、というよりも前の段階にいます。

自分としては、開発の成果をユーザーからフィードバックをもらっていけるのがとても楽しく感じるので、もどかしい気持ちが多くあります。

早く土台作りを終わらせて次に進みたい、という気持ちと、長期視点で立つときちんと作らないと後で大変なことになるな、という思いがせめぎ合って、まずは目の前の課題を片付けないといけないので、モヤモヤしているところではあります。

人が増えれば解決しそうな気もしますが、今度はマネジメント部分の問題も出てきそうで、今後どうなるかはまだわかりません。実装に突き抜けていたり、マネジメントの経験が豊富であったり、いろんなバックグラウンドを持った仲間がmikanには必要な気がしており、今後の進展が楽しみでもあります。

今はとにかく目の前の課題を先に終わらせて、次行かなくてはという気持ちで走っています。

入社してから気づいたmikanの文化

副業していたときにはあまり気づいてなかったmikanの文化について紹介させていただこうと思います。

Good&New

mikanでは毎日10時から朝会があります。ラジオ体操を終えた後に、Good&Newについてみんなで雑談する場があります。

Good&Newとは、その場で当てられた人がここ最近で嬉しかったことや楽しかったことを共有して、それについて話すということをやっています。

最近は、自粛期間ということもあってなかなかいい話題が見つかりづらいところあったりしますが、Netflixで見た映画の話とか家での過ごし方を共有しながら和気藹々とやっています。

f:id:iiizukkeyiii:20210826133701p:plain

mikan LT

月に1回程度、好きなこととか趣味とかを話す場があります。

ジャンルは何でもよくて、コミュニケーションの活性化のためにやっている催しです。

準備するのは大変ですが、聞いてる分には結構楽しくやっています。

締め会

月次で一回あり、月の振り返りとプレミアムバリューカードを送る会があります。

中でもプレミアムバリューカードは、mikanにある4つのバリューのうち体現している方に1人1枚だけ送ることができ、日々の感謝の気持ちを伝える場となっています。

日々の感謝を伝える方法として、バリューカードを送るのもあるのですが、月次で贈られるものは1枚だけの限定なので嬉しいところがありますね。

おわりに

いかがだったでしょうか?入社からのmikanの雰囲気が少しでも伝われば嬉しいと思います。

もうちょっとここどうにかできないかなーという部分でも出てきたところで、施策をバンバン打ってグロースさせるという方向に早く持っていくためにも、Androidエンジニアが増えると嬉しいなぁと思っております。

もちろん、他の職種も応募しています!

さいごに、mikanでは、一緒に英語学習をより良いものにしていくべくサービスを作っていく仲間を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください!

herp.careers

初めてQA担当になってみて知った「現場をアゲルQAの行動」を紹介

f:id:hocchan-mikan:20210806152254p:plain
現場をアゲルQAの行動を紹介します

こんにちは。mikanでQAを担当するほっちゃんです。

本格的な夏が始まり、今日も汗だくで保育園の送迎をしております。

先日、QAもブログを書いてみない?とお誘いがあったので

  1. mikanのQAの仕事内容紹介
  2. 現場の雰囲気を良くするQAの行動とは?

についてお話したいと思います。

mikanのQAの仕事内容

QAの仕事内容を簡単に説明すると

プロダクトの品質保証をするために、テストをしたりテストを作ったり

他にもコードを書いて自動化したり、他部署の人を巻き込んでテストするなど、品質保証のためならあらゆることをする仕事です。

現在mikanで私はテスト実施の他に以下のような業務をしています

  • QAサイクル決定
  • テストケース作成
  • QA実施への他チーム巻き込み
  • 不具合再現
  • QA結果の効果測定

ある1日の流れ

私のある1日の様子をご紹介します。

毎日同じスケジュールではないのですが、FirebaseのCrashlyticsの確認は毎朝行い、クラッシュしない率が99%になるよう、どんな不具合が起きているか探ったりテストケースを変えたりしています。

f:id:hocchan-mikan:20210806152301p:plain
1歳の息子にあわせてこんなスケジュールで働いていますがmikanは柔軟に働きやすい!!最高です

育児をしながらの仕事は大変と思われるかもしれませんが、mikanでは子供の急な熱や就業時間を柔軟に対応してくれるので、子供や夫との時間も取りながら働くことができて、とても感謝しています。

 

なぜ「現場の雰囲気を良くするQA」について調査したのか

実は、私はQA専任にはなったことがなく、現場がどんなQAを欲しているのか分かりませんでした。

mikanにもQA組織はなかったので、まずは社内中に「どんなQAが理想ですか?」と聞きに行きました。

今日紹介する現場の声は、そのヒアリングした中で得た内容になります。

 

数々のヒアリング結果の中で今回のテーマを選んだ理由とは?

それは、社内の他の職種の人が輝いて見えたからです(笑)

困ったときはお互いフォローしあいながら専門的な仕事もバンバンこなすところが素敵だと思いました。

と、同時にQAは知名度の低さから以下のような寂しさがあり、いいなー。みんなかっこいいなー。と思いながら仕事をしています。

  • エンジニアになりたいです!というインターンは来るのにQAやりたいです。と来る人がいない
  • 職種のプルダウンにQAがないアンケートを見かける
  • 転職サイトのQA紹介でよく「縁の下の力持ち」と書かれている
  • Q&Aと勘違いされる

同じように思っているQAの方がいましたら、一緒に会社を盛り上げていけるキッカケになったらいいな。と思いこのテーマを選びました。

 

実際に聞いた「現場の雰囲気を良くするQAの行動とは?」

それでは、いよいよヒアリングした声の紹介をしていきます。

mikanで働く皆さんは、前職で様々な現場を経験してきた人たちなので、過去の経験からも含めて回答していただきました。

さらにレベルアップするためのプラスαの行動もヒアリングしましたのでぜひご覧ください。  

1.「テストケース作成」中に現場を盛り上げる行動

仲間が書いたドキュメントにフィードバックをする

この声はドキッとしました。あの人輝いているね。と言われたければ、誰よりも相手を褒め称えあっていくことが重要なのかなと思いました。

  • 作った仕様が分かりやすいと言われたときうれしかった。(エンジニア)
  • FAQを見て操作方法が分かりやすいと言われ、頑張って書いた苦労が吹き飛んだ。(CS)  
仲間に感謝する

これは、自分も子育て中に体験があるのですが、子供ってすっごい喜ぶんですよ。目を輝かせてきゃーっ!と。

いやいや、そんな喜ばなくても。といいながら、その反応が良いとまたやっちゃう。

なので感謝することで現場の雰囲気がアガるのも納得です。

  • QAを作成したり運用することに関するアドバイスや情報を渡したとき「いつもありがとうございます!」と返してもらい少しでも力になれていると感じることができた。(エンジニア)

 

プラスアルファでレベルアップさせるなら…
# デザインや仕様にもQAをしてきた観点から意見を出してみる
  • 「バグじゃなくて、体験の品質まで保証します!」とQA担当者に言われたとき心強かった。(デザイナー)  

私もQAマンならではの視点で仕様の指摘ができたらなと思います。

 

2.「QA実施への他チーム巻き込み」中に現場を盛り上げる行動

仲間が見つけたバグにエールを送る

「ナイスQA!」という声かけをご存知ですか?私はこのおもしろい声掛けをmikanに入って初めて知りました。部活みたいで楽しいなと感じました。

自分がこれを言われたとき、とても嬉しかったのを覚えています。開発者側もバグが出たら嫌だと思うのではなくこの声掛けをしているのが素敵だなと思います。

  • バグを見つけたときにQA担当者から「ナイスQA!」と言ってもらえた場合は嬉しいです。(Bizdev)

 

分かりやすい不具合報告

この意見は、実際私に対して頂いたもので、何回か試行錯誤して生まれたオペレーションを褒めて頂けたので、とても嬉しかったです。

  • 何度もQAしていただいて、丁寧に結果を伝えてもらってるのが非常に助かっております。(エンジニア)
  • 再現手順を細かに書いて頂き、動画も付いていてバグ修正のときにすぐ再現できて助かるなーという感じでした。(エンジニア)

 

プラスアルファでレベルアップさせるなら…
# 重要なバグを見つけたら開発者だけでなく関係ある部署に確認を入れてみる
  • 「現在こちらのバグを発見しましたが、ユーザーさんからすでにお問い合わせ来ていますか?」とQA担当者から共有があり、問い合わせに備えることができた(CS)

この意見をもらったとき、ハッとさせられました。「この仕事は私には関係ない」と思わず、常に高い視点を持ち、いろいろな人の動きをよく見て、連携していくことの大切さを学びました。  

 

3.「不具合再現」中に現場を盛り上げる行動

難しいテストを代わりに実施し本業に集中してもらう

mikanの助け合いの文化のおかげで私もこのような行動ができたのかなと思います。最近は、手順書に残して誰でもできるようにすることにも挑戦しています。

  • 仕様をまったく知らなかった機能をサクッとQAしてくださり、丁寧に仕様について動画付きで教えてくれたことです。神だと思いました。(CS)

 

プラスアルファでレベルアップさせるなら…
# 仲間の想像を超えるパフォーマンスについて考えてみる
  • 同じテストケースをあらゆる端末で確認してくれていたことがあって感動した。(エンジニア)
  • デザインと実装を比べてズレを指摘してくれたのは助かった。(デザイナー)

これも貴重な意見でしたが、仕事に対する、そして人に対する想像性ですよね。人生において一番大事かもしれないです。想像力を働かせて動けるようになりたいと思いました。  

4.もっと現場を盛り上げるならこんな行動

バグ修正に苦しむ人を応援する

なぜ私がこの行動を行ったかと言うと、自分が昔バグ修正をしていたからかもしれません。ほんとバグ修正って気が滅入るんです…

  • 難しいバグに立ち向かっているときは思い悩みがちですが、QA担当者に「修正方法を次から次へ思いついてすごいですね」と応援されると、気力が湧きます。(エンジニア)  
エンジニアとの協力

こちらも自分の行動に対して頂いた意見になります、社内でどんなQAが理想かについてヒアリングした結果を元に行動できたからよかったのかなと分析しています。

  • 開発が佳境のタイミングで余裕がない時に急な依頼でもすぐにQAをしてもらえたこと。(エンジニア)
  • 開発の仕様を渡すだけでテストケースを作ってテストまでしてくれたことがめちゃくちゃ助かりました。(エンジニア)

 

技術で助ける

今後の私の目標にもなりますが、QA環境が整ったら、次はここらへんにもチャレンジしてみたいと思っています。

  • 自動化でテストケースを網羅されることで安心感がありました。(エンジニア)
  • バグ調査を依頼したときに期待通りのアウトプットを提出してもらいバグ調査時間を短縮できた。(エンジニア)
  • バグが出た部分のログ情報を送ってもらえて助かったことがある。(エンジニア)  

 

まとめ

以上が「現場の雰囲気を良くするQAの行動とは?」に対する現場の生の声でした。

できるだけ技術ではなく、明日からちょっとの工夫で変えることができるものを紹介してみましたが、いかがだったでしょうか。

私はまだまだこの声全てに対応できていませんが、まずは小さなことから始め、試した結果良かったものは、またこのブログで発信していこうと思います。

実際に実践してみて「反応があった。」などの連絡を頂けるとさらに嬉しいです。

 

valueを送り合うmikanの文化

私が集めた現場の声ですが、いくつかの声は私自身が実際にもらったものでもあります。 実はmikanでは下記のように4つの行動指針が定義されており、皆このValueを元に行動をしています。

f:id:hocchan-mikan:20210806152308p:plain
mikan 4つのvalue

このValueに合致した行為を見かけたとき、仲間から感謝の気持ちを込めたValueカードが送られる仕組みがあります。

こんなふうに、Slackに届き、社内中の人がその内容を見ています。

f:id:hocchan-mikan:20210806152242p:plain
valueカードはSlackのvalueチャンネルに届き社員全員が見ています

これを見ると、「xxさんはいつも仲間を助けていてすごいな」とか「xxさんはいつも結果のために逆算した計画を立てていてすごいな」など、とても勉強になります。

私もこのカードをもらうと、もっと仲間や会社に貢献しようと元気になります。(家事と育児もこういう仕組みで誰か褒めて欲しい…)

このような、お互いを尊敬し気持ちよく働ける環境に興味があれば、ぜひ遊びにきてください。

mikanには他にも働きやすい文化がたくさんありますので、カジュアルにランチやお茶でもして情報交換してみませんか?

mikan.link

mikanのCSってどんなことしてるの?チーム加入3ヶ月の私が紹介します!

f:id:NobukoTokushige:20210730143532p:plain

はじめに

皆さん初めまして、こんにちは。mikanでCS(Customer Success)を担当しておりますnobukoです。

私は今年の4月入社ですのでまだまだ見習い中の身であります!

常に向上心と探究心を持ったmikanメンバーに囲まれながら刺激のある毎日を過ごし、個人的にはアニメ・ワンピースの一員に加入したような気持ちで毎日楽しく業務に励んでいます。

今回は、そんな見習い中の私がCSチームでどんなことをしているかについてご紹介します!

その前に私自身の紹介を少しだけお話ししますね。


<プロフィール>

なまえ: Nobuko

あだな: のぶちゃん(mikanメンバーの8割方はこのあだ名で呼んでくれています)

住んでいるところ: 鹿児島県(完全リモートのためメンバーとオフラインでお会いしたことがありません・・・いつかリアルでメンバーに会える日を夢みています✨)

ファミリー: 4人家族で3歳半と1歳半の子どもがいます👦👧

趣味: 最近はもっぱら映画鑑賞です

ストレス発散方法: 走る🏃‍♀️・運転中に大きめのボイスで歌う

嫌いな家事: 皿洗い


<大学卒業後>

大学卒業後地元に戻り、地方銀行に就職。

窓口業務や生命保険、投資信託販売業務を経験→2年間ほど勤務した後、退職。


<mikanに入社したきっかけ>

私がmikanに入社したのは、大学時代の友人で現取締役のmizoさんが声をかけてくれたことがきっかけです。

久々の社会復帰に不安だらけでしたが、私のバックグラウンドを理解したうえでの勤務体系提案など、事前に相談させてもらったことで安心して業務を行う姿を思い描くことができました。

おかげで現在とても働きやすい環境で充実した毎日を過ごせています!!!

業務内容

さて、まずはCSチームがどんなことをしているか業務内容について紹介していきます! CSチームの通常業務や定例MTGはこのような感じです!!

<通常業務>

①お問い合せ対応

mikanではメインのお問い合わせ管理ツールとしてHelpshiftを使っています。

App Store/Google playストアのレビュー返信

③開発チームへの不具合報告

④FAQの作成や変更

mikan.helpshift.com

<定例MTG

①CSタスク確認

その日のタスクを確認したり共有事項を確認するMTG(毎朝開催)

②CS定例MTG

主に1週間の振り返りを行い次の週に向けての目標・スケジュール等を確認するMTG(週1開催)

③CS開発提案

CSチームと開発チームで不具合を共有し、今後の調査・改善についてなど話し合うMTG(週1開催)

現在、4月入社の私もCSチームのメンバーに頼りながらやっと少しづつ業務に慣れてきました。 最初は初めてのことだらけ+完全リモートワークのため戸惑うことも・・・

しかし、事前にオンボーディングがしっかり準備されていたことや、わからないところがあればSlackやZoomですぐに回答してもらえる環境が整っていていたためスムーズに業務に取り組むことができました!

予想外の業務!!!

続いて、私がmikan CSチームに入って予想外だった業務についてお話しします!

mikanで働く前の私はCS=お客様対応、コールセンターのイメージが強くそのままのイメージで入社すると、ところがどっこい!!!!!

こんなこともするの???とびっくりした業務がいくつかありました。

その1. Figmaでの簡単な画像作成

mikanではFAQに掲載する画像をCSチームがFigmaを使って作成しています。

私自身はまだこの業務にチャレンジできていないのですが、これを習得した暁にはもう『CS兼デザイナー』を名乗っていいのではないかと思っています。(嘘です)

その2.Redashでユーザーさんの情報を検索

CSチームではユーザーさんの状態を確認するためにRedashというログデータ可視化ツールを使って、情報検索する作業も行います。

機械系に疎い私はRedash画面の開発系な感じを見た時から「えーーーこれ私にできるの?」

と苦手アンテナが発動しましたが、教えてもらうと簡単に検索することができました!

その3.SQLiteで特定の教材を使用可能にする操作

以前「英単語アプリmikan」では、サブスクリプション形式(mikan PRO)とは別に特定の教材を買い切り購入できるサービスを提供していました。(現在は取り扱っていません)その教材が何らかの不具合で使用不可になった場合、暫定対応で教材を利用できるよう対応するのは、もちろん開発チームの出番!!!!!!!

ではなくmikanではCSチームの出番です。

当時SQLiteを知らなかったため、またまた脳内で苦手アンテナが発動していました・・・。

しかし、操作方法をZoomで画面共有しながらわかりやすく丁寧に教えていただいたので現在は問題なく操作できています。

その4.プッシュ通知の作成

CSチームでは毎日19:50に配信している通知の作成も担当しています。

こちらの業務も予想外ではありましたが、どのような通知にすればユーザーさんに学習を促せるだろうかなどと考えながら、毎週当番制で英語のことわざを用いたり、英単語を紹介するなどの工夫して通知を作成しています。

f:id:NobukoTokushige:20210730143003p:plain
プッシュ通知作成時の下書き画像

CSチームに入って私が感じたこと

最後に私がCSチームに入って感じた雰囲気やチームの強みをお話しします!

チームの雰囲気

私がCSチームに加入して感じた雰囲気は、和気藹々(わきあいあい)です。 その中でもチームメンバーそれぞれが意見を持ち、どんな意見でも発言しやすくメンバーが肯定的に受け止めてくれる雰囲気が印象的でした。 これは前職の銀行員では味わうことのなかった雰囲気でとても安心したのを覚えています。

それぞれの意見を共有し、ユーザーさんにとってBestな状態は何かと模索する中で、最適なサポート環境を提供できるのではないかと思っています。

MTG時には「和気藹々」な雰囲気ですが、業務中では一転して下記のような印象を持っています。

①業務に取り組むスピードが早いこと

具体的な例を挙げると、毎朝のタスク確認で割り振られたタスクがあると午前中、下手をすると割り振られた直後には完了していることが多々あります。

一見簡単に聞こえるかもしれませんが、お問い合せ対応や不具合対応をしながら別タスクに取り組むのは時間管理等が大変で私個人的には対応までに時間がかかってしまうことが多いのです。そこをパパッとこなしてしまうCSチームメンバーにはいつも驚かされます。

②他チームの進捗に気を配り先を見越して周知等の準備を整えていること

こちらも具体的な例を挙げると、まず他チームがどのような状況にあるのか、どんなことに取り組んでいるのかをNotion、Slack上で積極的に情報収集を行います。

そこで例えば、新機能がリリースされるとわかると「どのような機能なのか」「従来と何が違うのか」「ユーザーさんにはどのように伝えればいいのか」などの詳細について開発チームにヒアリングを行い、それに伴って必要になるであろうFAQ等の作成に即座に取り組みます。この間のスピード感は入社から3ヶ月経った今でもびっくりします。

チームの強み

私が思うチームの一番の強みは「ゆずらない・満足しない」ところだと思います。

一見、少し強めなイメージを持たれるかもしれませんが、ユーザーさんの声を一番聞いているCSチームだからこそのユーザーファースト精神を表した言葉です。

ちなみにmikanには「With User」というValueがあって、まさにそれを体現しているのではないかと思っています。

前Qでは、ユーザーさんからの要望を何とかしてもっと社内に届けられないかということで、今までにきている要望の件数などを遡って全て集計し、ドキュメントにまとめ各チームへの提案書として提出しました。

日々ユーザーさんから要望を多くいただくのですが、いろんな事情でその要望を実現まで持っていくことはなかなか難しいことも多いです。

ですがどれだけのユーザーさんが望んでいるか数値で表したり、実際の声を再度提示することによって社内で再認識できたのではないかと思います。

f:id:NobukoTokushige:20210730142921p:plain
各チームへの提案書をSlackにて投稿した画像

これまでの要望の数値を出したりする作業は大変でしたが、これこそ「ユーザーさんの声をプロダクトに届ける」というCSの役割をゆずらず、妥協せず実行できたと思っています。

これからもmikanが進化していく中で、しっかりとユーザーさんの声を社内に届けることがCSチーム使命と肝に銘じて業務にあたっていきます💪

まとめ

今回は主に4月入社の私がCSチームの業務・雰囲気についてお話ししました。

mikan CSチームではCS業務を超えて色々なことにチャレンジする機会がたくさんあること、和気藹々としたとても働きやすい環境で仕事ができるという雰囲気は感じていただけたでしょうか?

CSチームではこれからもユーザーファーストの精神で、皆様により良い環境でmikanを使っていただけるよう励んでいきます!

そして最後になりましたがmikanでは一緒に働く仲間を募集しています!

少しでも興味を持たれた方はご連絡お待ちしております🍊

mikan.link

角丸が尖る問題の直し方

f:id:mizonit:20210717002316p:plain

こんにちは。 英単語アプリmikanのiOS開発をやっている、aviciidaです。

最近嬉しかったことは、iOSDC 2021に提出したレギュラートークのプロポーザルが採択されたことです。

ど文系でエンジニア未経験の状態から開発の世界に入り、かれこれ2年が経ちましたが、ようやくこれで自分もエンジニアです、と胸を張って言えるなと思って嬉しいです。

角丸が尖ってしまう問題を解決する方法

今回は、iOS開発で以前に詰まってしまった「角丸が尖ってしまう問題」をどうやって解決したか、という話をしたいと思います。

そもそも、どんな問題なのか

写真を見てもらうとわかりやすいと思います。

f:id:aviciida:20210716152412p:plain

学習のボタンが尖ってしまってますね。

発生しやすいパターンは

  • 小さな端末(iPhone SEなど)
  • 多重構造(UIView in UICollectionView in UIView in UIViewみたいな)

という感じでした。

mikanではこれを「ラグビーボール問題」と呼んでいました。

端的に言うと、cornerRadiusをheight / 2にしている時に、height / 2がobjectの実際のheight / 2よりも大きくなっていることが原因です。

そもそも、cornerRadiusと角丸の関係はどうなっているかというと、 あるobjectの角っちょを、cornerRadiusを半径とした円で切り抜くよ、そこが角丸になるよ、ということです。 cornerRadiusを、そのobjectのheigt / 2に指定すると、このようになります。

f:id:aviciida:20210716152457p:plain

しかしそれがheightの半分を超えてしまうと f:id:aviciida:20210716152522p:plain

このように、先っちょが尖ってしまいます。

なぜこのようなことが起きるのか

これも簡単に言うと、viewのcornerRadiusを指定するために高さを取得しようとするとview.frame.heightで取得すると思いますが、その値が間違っているということです。

例えば、この画像の例でいくと、

f:id:aviciida:20210716152412p:plain

本当は、view.frame.height31.0になるはずでした。

しかし、このviewの(Auto Layoutの制約がresolveされる)layoutSubViews()view.frame.heightをprintしてみると f:id:aviciida:20210716152645p:plain

Interface Builderで指定したサイズのままになってしまっています。

対策① CustomViewを作る

layoutは親viewから子viewに、という順番なので、今回問題になっているviewのサイズは、1番親のviewのlayoutSubview()時点ではまだ決められていないのでは、という仮説を立てました。

問題のviewは、階層構造のかなり下の方にありました。

f:id:aviciida:20210716152659p:plain

そこで、問題のviewをCustom Viewに切り出して、そこのlayoutSubviews()であれば、正しいheightを取得できるのではと考えました。 結果...

f:id:aviciida:20210716152708p:plain

無事取得できました!

対策② layoutIfNeeded()を呼び出す

親のviewを生成しているところでlayoutIfNeeded()を呼び出し、viewのクラス内でlayoutIfNeeded()をオーバーライドし、その中でheightを取ることによってでもうまく取得することができました。

layoutIfNeeded()のドキュメントを見てみると

Use this method to force the view to update its layout immediately. 
When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints. 
Using the view that receives the message as the root view, this method lays out the view subtree starting at the root. 

(雑に翻訳)
レイアウトの更新を強制的に行うために呼び出す。
Auto Layoutを使用している場合は、レイアウトエンジンが、制約の変更に対応するために必要なviewのポジションの調整を行う。
この処理が走ったら、処理対象のviewをroot viewにして、それよりも下の階層にあるviewがレイアウトされる。

つまり、強制的に制約を更新してレイアウトを行ってくれるfunctionなので、これをオーバーライドした中でheightを取得すれば正しい値が取れるということかと思います。

まとめ

ということで、今回は「ラグビーボール問題」をどうやって倒したかを解説しました。

要は、多重階層になっているviewの子viewのheightがlayoutSubviews()でも間違った値が入ることがあるので、そういう時には子viewのCustom Viewを作ったり、layoutIfNeeded()を呼び出したりして正しいheightをとりましょうね、という感じでした!

今見ると当たり前では?という感じですが、当時はまだ経験が浅く、かなり倒すのに時間がかかってしまいました。

mikanは絶賛採用中です! ご興味ある方は、お気軽にご連絡ください! mikanの採用情報

参考にした記事

Subview frame is incorrect when creating UICollectionViewCell

UIKitのView表示ライフサイクルを理解する

RemoteConfigを使ってABテスト振り分けしていくのをやめた話

f:id:hoshitocat:20210709173512p:plain

こんにちは。mikanでサーバーサイドエンジニアをしております。星(@hoshitocat)です。 最近ハマっているアニメは「不滅のあなたへ」です。先週はこれを観てうるっときてしまいました... OP曲の宇多田ヒカルさんの曲もよくて、おすすめです!

さて、mikanではGrowthのための施策やプロダクトの方向性を決める際にABテストを行っています。 これまでABテストの振り分けにはRemoteConfigを使っておりましたが、 最近振り分けロジックを変更したので、それについてご紹介したいと思います。 なぜRemoteConfigをやめたのか?みたいな話から、これから使おうと思っている方やすでに使っていて、課題感ある方に少しでも参考になればと思っております。

RemoteConfigを使った振り分け

RemoteConfigは、Firebaseのコンソールなどからあらかじめパラメータを設定しておいて、アプリ起動時などにその設定値を取得し、 それに応じてアプリの挙動などを制御することができるようなサービスになります。(ざっくり理解、詳細は公式ページをご参照ください。)

この設定値は、RemoteConfigで振り分けて返すことができ、以下のように50%のユーザには true を返したいけど、残り50%は false にしたいなどの分類が可能です。 これまでmikanでは、この機能を使ってABテストの振り分けを行っていました。

f:id:hoshitocat:20210709173518p:plainf:id:hoshitocat:20210709173516p:plain
remoteconfigのconditionsとparameters

ABテストを実施する際もそのまま使うだけという導入コストの低さゆえに、mikanではRemoteConfigを昔から使っていました。 また、分析にはログをAthenaで分析1し、ABテストの結果などを判断しているのですが、そのログに ab_flags という、RemoteConfigから取得された設定値を入れることで、 あるユーザがABテストのどのABフラグを持っているかで判断しています。

次にRemoteConfigを使ってよかったメリットとデメリットを示します。

メリット

  • 管理が楽
    • リッチなコンソール
    • GUIで設定項目を簡単に編集できる
  • 非常に導入が簡単
    • SDKを入れるだけ

デメリット

  • ヒューマンエラーを防ぐためには専用の仕組みを作る必要がある
    • コンソールから設定しておりましたが、 レビューの体制を作りたいと思い、 管理用のリポジトリを用意し、コードで管理していました。
    • 自動化にはRemoteConfigのAPIを叩いて実装していました。
  • 通信環境が悪いユーザのバイアス
    • 通信環境が悪いとRemoteConfigから値を取得できないため、 その場合はABの振り分けがデフォルト値にアサインされてしまい、 ABテストの結果に影響してしまうことになります。
  • サーバーサイドのAB振り分けロジックには利用できない
    • RemoteConfigはあくまでクライアントから利用することを想定されているため、 クライアントSDKを使った振り分けは可能だが、 サーバーサイドのAPIなどで、ユーザのAB振り分けを行うことはできません。

上記にあげた以外に、RemoteConfigにはキャッシュがあり (デフォルトだと12時間詳細はこちら) 一度値を取得し振り分けられたら、キャッシュが消えるまではそのままの値が保持されます。 これは便利なのですが、値を変更しても最大で12時間たたないとその変更後の値にはならないため注意が必要です。 (もちろん取得時間などは調整可能です)

RemoteConfigを使わずに実装した

デメリットであげたもので、特に以下2つ解消するためにABの振り分けロジックを考えました。

  • 通信環境が悪いユーザのバイアス
  • サーバーサイドでも使える振り分けロジック

前提として、ABテストの結果の分析にはAthenaを使っているので、Athenaで分析可能な振り分けを行う必要がありました。

結果として以下のようなアルゴリズムで実装しました。

  1. Firebase AuthのUIDとABテスト固有の文字列を連結
  2. ①の文字列をハッシュ化
  3. ②から先頭4バイト取得
  4. 10進数に変換
  5. ④の絶対値を取得し、100で割った余りを使いABテストの振り分けを行う

サーバーサイドとクライアントと分析基盤で同じ値が取得できることを確認するためにそれぞれで実装を書いてみました。

サーバーサイドで利用するGo言語で実装

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"

    "crypto/sha1"
)

func main() {
    salt := "cdf3fa31-4cb5-4663-91a8-9274f722e232" // ABフラグ
    uid := "00wOT95NyMZvnprfjN4uU0AJ67C2"          // Firebase Auth UID(開発環境のもの)

    // saltとuidをくっつけた文字列をバイト配列に変換し、SHA1でハッシュ化
    value := sha1.Sum([]byte(salt + uid))

    // ハッシュ化されたバイト配列から先頭4バイト取得(32bit符号付き整数へ変換するため)
    // 先頭4バイトからint32へ変換
    var intValue int32
    buf := bytes.NewReader(value[:4])
    binary.Read(buf, binary.BigEndian, &intValue)

    fmt.Println(intValue) // ABテストの振り分けに利用する10進数
}

iOSアプリで利用するSwiftの実装

import UIKit
import CommonCrypto

let salt = "cdf3fa31-4cb5-4663-91a8-9274f722e232" // ABフラグ
let uid = "00wOT95NyMZvnprfjN4uU0AJ67C2"          // Firebase Auth UID(開発環境のもの)
let str = salt + uid
var hash: [UInt8] = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
let strLen = CC_LONG(str.lengthOfBytes(using: String.Encoding.utf8))
CC_SHA1(str, strLen, &hash)
let raw = hash[0...3].reduce(0) { $0 << 8 + Int32($1) }
print(raw)

Androidで利用するKotlinの実装

package org.kotlinlang.play

import java.nio.ByteBuffer
import java.security.MessageDigest

fun main() {
    val salt = "cdf3fa31-4cb5-4663-91a8-9274f722e232" // ABフラグ
    val uid = "00wOT95NyMZvnprfjN4uU0AJ67C2"          // Firebase Auth UID(開発環境のもの)

    val target = salt + uid
    val targetBytes = target.toByteArray()
    val sha1 = MessageDigest.getInstance("SHA-1")
    val sha1Result = sha1.digest(targetBytes)

    // 分析基盤の処理上、4byteまでしか受けつけれないため、揃えるために先頭4byteを取得するようにしてます
    val sliceResult = sha1Result.slice(0..3).toByteArray()
    val result = ByteBuffer.wrap(sliceResult).int
    println(result)
}

分析に使うAthenaでの実装

SELECT 
from_big_endian_32(
  SUBSTR(
    SHA1(
      CAST(
        'cdf3fa31-4cb5-4663-91a8-9274f722e232'      -- ABフラグ
        ||
        '00wOT95NyMZvnprfjN4uU0AJ67C2' AS VARBINARY -- Firebase Auth UID(開発環境のもの)
      )
    ),
    1,
    4
  )
)

メリット

  • オフラインでもクライアント側に振り分けロジックを記載すれば振り分けを正確に行うことが可能
  • サーバーサイドでも同じようなロジックでABテスト可能

デメリット

  • 振り分け時にABテスト時に割合を徐々に拡大する必要がある場合に対応できない
    • ABテストの比率50%:50%でテストしたいものだったので、 今回はクライアント側で振り分けロジックの実装をしました。 しかし、5%からはじまり徐々に拡大していく必要のあるABテストの場合はサーバー側で比率を調整し、 それをクライアントから取得するといったように実装しないと、アプリのアップデートありきの割合調整 になるため、比率調整が難しくなってきます。
    • 比率だけRemoteConfigから取得することもできましたが、 その場合どうしても、オンラインで通信環境が良いユーザのバイアスが含まれてしまいます。
  • 集計が複雑になる
    • UIDからハッシュ化などでユーザごとに振り分けがどちらなのか計算する必要があるため、集計が少々複雑になります。 (分析用ログに振り分けに利用したABフラグを付与することで回避できます。)

まとめ

今回紹介した方法では、RemoteConfigを使ったABテストのバイアスがかかってしまう問題を解消できました。 また、共通のロジックを使いサーバーサイドでもABテストができるようになりました。

しかし、管理側で比率をいつでも調整できないことは、今後の課題になります。 RemoteConfigだと通信環境が悪いユーザをABテストしづらいです。 特にオフラインのユーザはサーバーから比率を取得したり、ABフラグを取得することができないためどうしようもないのですが、 通信環境が悪い程度なら、通信コストをもっと抑えた実装をすることで、ABテストを実施できるかもしれません。 ここは調査していきたいです。

最後になりますが、mikanでは現在積極採用中です!

ABテストの分析やその機構の実装に興味がある方、英語が好き、日本の英語教育を変えたいと思っている方は、ぜひ一緒に働きましょう!!

mikan.link


  1. 弊社で分析してくれている石塚がこれまでの分析基盤の歴史を書いてくれています。また、これらのためのインフラ構成図もあります。