読者です 読者をやめる 読者になる 読者になる

Mercari Engineering Blog

We're the software engineers behind Mercari. Check out our blog to see the tech that powers our marketplace.

"High 意識 Android Team" のチームワーク - その 2

Mercari Android チームの @tsuyogoro です。US 版 Mercari Android アプリの開発を担当しています。

先日の “High 意識 Android Team” のチームワーク - その 1 に続き、今回は 2016 年秋頃におこなった US 版 Mercari のカメラ機能 (日本版メルカリとは別物) の開発を通して得られた知見などについてご紹介します。

US 版カメラのご紹介

その前に、日本版メルカリのカメラ機能からご紹介しておきます。なお、昨年秋までは US 版 Mercari も同じカメラ機能を搭載していました。

f:id:tsuyoyo:20170330181015p:plain:w200

このような画面で、赤いシャッターボタンを押すと商品の写真の撮影をすることができます。 フラッシュとオートフォーカスという必要最低限の機能を持ったシンプルなカメラですね。

f:id:tsuyoyo:20170330181123p:plain:w200

なお、このような画面で画像をギャラリーから選択することもできます。

カメラはユーザが売りたい物を出品する際の非常に重要な機能であり、 「面倒だなぁ」とユーザが感じてしまう事は “出品される商品が少なくなってしまう” という問題に直結します。

更なる出品数増加を目指し、カメラおよび周辺の機能には下記の可能性があるのでは?と仮説を立てました。

  • 連続して写真を撮る事ができないが、そのようなニーズがあるのではないか
  • 撮影・ギャラリーからの選択・画像の編集でそれぞれ別の画面が立ち上がるので、機能の階層が深く見えてしまっているのではないか

これらを解決すべく、大幅な機能変更を行ったものがこちらです。

f:id:tsuyoyo:20170330185111p:plain:w200 f:id:tsuyoyo:20170330185129p:plain:w200

左右のスワイプ操作によって、撮影機能と画像選択をスイスイ切り替えることができます。 写真を撮影 or 画像を選択をすることで 4 つの四角 (スロットと呼んでいます) へ、画面の切り替わりなしにシームレスに画像が入っていきます。

f:id:tsuyoyo:20170330183544p:plain:w200

スロットの画像をタップすることで、これまた画面の切り替わり無しに編集モードに突入します。

結果は?

狙い通り、出品数を増加させることができました。しかも出品が未経験のユーザからの出品数が増えたという嬉しい結果となり、本施策はまさに成功したと言えるでしょう。

開発時の苦労と挑戦

そんな成功の裏側には苦労と挑戦がありました。

開発当時、並行して進んでいた各プロジェクトの都合により、本機能は San Francisco (SF) および London オフィスのメンバーにコードレビューを依頼して進めてくことになりました。機能の分量が多いこととタイトなスケジュールにより、3 拠点にまたがったとしてもリソースを多く割いて進めていく事がベストと判断した背景もあります。

また、コードレビューを通して技術的なノウハウを濃いレベルでシェアすることができるメリットもあり、今回の挑戦を通して得られた知見の一つとなりました。

Tokyo <-> SF <-> London 体制の問題点

ズバリ “すれ違わない二種類の時差” です。

時差のある拠点間で共同開発を進める場合 “お互いに稼働している時間に、如何に濃いコミュニケーションを取るか” が非常に重要になります。ところがそのすれ違う時間帯が、SF は日本時間で朝なのに対し London は日本時間で夜になるのです。よって (日本から見た場合) SF と London が両方稼働している時間が無い事になります。

体制の問題をさらに大きくする技術的な問題点

カメラ機能を実装した事がある方ならお分かりになると思いますが、Android の カメラ周りはいわゆる “コールバック地獄” なのです。写真を撮る部分だけ抽出しても下記の通りです。

camera.autoFocus(new Camera.AutoFocusCallback() {
      @Override
      public void onAutoFocus(boolean success, Camera camera) {
          // フォーカスが取れたので写真を撮る
          camera.takePicture(new Camera.ShutterCallback() {
              @Override
              public void onShutter() {
                  // シャッターがおりたのでUIの状態を変える
              }
          }, new Camera.PictureCallback() {
              @Override
              public void onPictureTaken(byte[] data, Camera camera) {
                  // 撮った写真を保存するけど、これも非同期だ
              }
          }, new Camera.PictureCallback() {
              @Override
              public void onPictureTaken(byte[] data, Camera camera) {
                  // 撮った写真を保存するけど、これも非同期だ
              }
          });
      }
});

このようなコードは、見通しも悪いですしコードレビューをするのも大変ですよね。例え隣同士の席であっても大変なのですから、8,300 km (SF) および 9,600 km (London) 離れていたら尚の事です。

ちなみに、今回は 4 系端末をサポートするため Camera2 は利用しませんでした。まだまだ 4 系の端末を使っているユーザがたくさんいるためです。この状況下で開発スピードの事も考えると、まだ Camera1 が有力ですね。

課題に対する挑戦 1 - RxJava 活用

前 post で触れました Screen Hero によってコードレビューにおけるコミュニケーションは大分改善は出来ているものの、コールバック地獄にはその改善を全て潰すくらいの威力があります。そこで考えたことは Screen hero を用いて共有する 1 スクリーン上で、瞬間的に共有する情報を如何に増やすかという事で、そのために活用した手段が RxJava (& Retro lambda) です。下記のように、複雑に絡み合うカメラ処理の全体像を 1 スクリーンに詰め込むことができました (一部改変) 。

takePictureSubscription = cameraPreview.takePicture()
        .doOnSubscribe(() -> {
            toggleCameraOptionsClickability(false);
            toggleShutterButtonAvailability(false);
        })
        .timeout(SHUTTER_TIME_OUT_IN_SECOND, TimeUnit.SECONDS)
        .observeOn(Schedulers.io())
        .flatMap((data != null) ? processAndSavePictureData(data) : Single.just(null))
        .observeOn(AndroidSchedulers.mainThread())
        .doOnUnsubscribe(() -> {
            cameraPreview.startPreview();
            toggleCameraOptionsClickability(!isSlotFull());
            toggleCameraOptionsAvailability(!isSlotFull());
            toggleShutterButtonAvailability(!isSlotFull());
        })
        .subscribe(
                savedFile -> {
                    if (savedFile != null) {
                        String filePath = savedFile.getAbsolutePath();
                        addPhoto(new MultiModePhotoPickerSlotView.SlotEntry(filePath));
                },
                e -> // エラーのToastを表示
        );

適度に大きな情報を共有することにより、コードレビュー時のコミュニケーションの濃さを更に上げ、品質に妥協すること無くスピードを維持する事が可能になりました。

課題に対する挑戦 2 - “疎” なコミュニケーション

( こちらはチャレンジしたものの上手く行かなかった、学びの話です )

2 つの時差を考慮しなくてはいけないため、各拠点に対するコミュニケーションに依存が発生する (例えば SF と議論したことを London が知らないと話が進まない…etc) と破綻する事が予想されました。よって SF と London それぞれに対して “疎” なコミュニケーションを取ることにチャレンジしました。具体的には、下記の通りです。

  • 設計の全体像を wiki にまとめ上げて、進め方のプランを最初に作成
  • これを開発初期の段階で各拠点メンバーへシェア
  • 全体像を押さえてもらった上で撮影パートと画像選択 (ギャラリー) パートのコードレビュー担当を拠点ごとに分けた

これは最初は上手く行ったのですが、最終的には課題の残る結果で終わりました。 というのも中盤以降から複数の Pull Request 間に依存が生まれ始めてしまい “疎” なコミュニケーションが厳しくなっていったのです。 最高のスタンドプレーの条件として挙げた “他のメンバーから見た時に状況が容易に把握できる状態にある” がだんだん崩れてしまった感触がありました。

“こういうコミュニケーションを取って進めていきたいから、こういう設計をする” という コミュニケーション driven な設計思考が大事だなと学んだ経験です (次以降の post で、この課題に対して取り組んだ話を書く予定です) 。

まとめ

昨年秋にリリースした US版 Mercari の新しいカメラ機能の開発の裏側をご紹介しました。

“公安 9 課モデル” に則り、技術的なスキルとマネジメントスキルを最適なバランスで組み合わせてチームのアウトプットにつなげていくという “High 意識 Android Team” の実態が垣間見えれば幸いです。

もっと深い話を聞きたい、という方は是非下記よりご連絡下さい。いつでもお待ちしております!!

www.wantedly.com

メルカリでは Android エンジニアを絶賛大募集中です。

ソフトウェアエンジニア(Android)/ Software Engineer(Android) / メルカリ

Tips : 非同期処理を RxJava の Observable にする

非同期の Camera#autoFocus を RxJava の Observable に変換する例を紹介します。ポイントだけ掻い摘んだので、実際には Exception のハンドリングなどが必要です。

private Single<Boolean> kickAutoFocus() {
    PublishSubject<Boolean> subject = PublishSubject.create();
    camera.autoFocus(new Camera.AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {                
            subject.onNext(success);
        }
    });
    return subject.first().toSingle();
}

Observable でなく Single となっているのは、onNext を 1 度しか呼ばないことが分かっているためです。