Infinito Nirone 7

白羽の矢を刺すスタイル

チームで育てるAndroidアプリ設計の一般販売が始まりました。

告知記事です。

Peaks のクラウドファンディングで執筆し先日電子版を先行リリースした「チームで育てるAndroidアプリ設計」の一般販売が始まりました。 これまではクラウドファンディングで出資いただいた方にのみ電子版・書籍の配送手続きをしていましたが、本日からクラウドファンディングに参加していない一般の方々にも電子版・物理本の購入をいただけるようになりました。

peaks.cc

チームで育てるAndroidアプリ設計とは

改めてこの書籍が何なのかを紹介すると、大小様々な規模のチームで継続的にAndroidアプリの開発をすすめていく中で直面するアーキテクチャの成長痛を乗り越えるためのノウハウを詰め込んだ本です。

アーキテクチャは一度整えればそれで終わるものではなく、プロダクトの成長やチームの成長とともに少しずつ形を変えていくものであるという考えのもと、最初の一歩としてどのようにアーキテクチャを選定しチームに根付かせていくか、またアプリの成長にともなって徐々に現れるひずみをどのように解消していくのか、実際の方法論を交えつつも根本にある思想や考え方、行動指針を示すことで、特定のチームにおける実例を他のチームにも活かせるプラクティスとしてまとめています。

新規事業の立ち上げから運用にいたるまでの比較的小規模なチームにおける事例を @kgmyshin さんが担当し、すでに成長を続けてきたサービスをさらに拡大していく比較的大規模なチームにおける事例を自分が担当しました。それぞれ4章分の内容があります。そして最後の章では大小それぞれの事例を振り返り、規模によらない共通点や規模によって異なるポイントをまとめています。

クラウドファンディング開始当初の意気込みなんかは次の記事に書いてありますのであわせてどうぞ。

blog.keithyokoma.dev

オンライン輪読会

実はこのプロジェクトはまだ続いていて、出版後にオンラインで輪読会を開催します。

techbookfest.connpass.com

全9回、各回で書籍の1章分の内容を輪読します。初回は早速明日4/27の22時からで、1週間ごとに読みすすめる予定です。 YouTube で配信予定で、アーカイブもあるので当日参加できない方もあとからご視聴いただけます。

チームで育てるAndroidアプリ設計の一般販売が始まりました。

告知記事です。

Peaks のクラウドファンディングで執筆し先日電子版を先行リリースした「チームで育てるAndroidアプリ設計」の一般販売が始まりました。 これまではクラウドファンディングで出資いただいた方にのみ電子版・書籍の配送手続きをしていましたが、本日からクラウドファンディングに参加していない一般の方々にも電子版・物理本の購入をいただけるようになりました。

peaks.cc

チームで育てるAndroidアプリ設計とは

改めてこの書籍が何なのかを紹介すると、大小様々な規模のチームで継続的にAndroidアプリの開発をすすめていく中で直面するアーキテクチャの成長痛を乗り越えるためのノウハウを詰め込んだ本です。

アーキテクチャは一度整えればそれで終わるものではなく、プロダクトの成長やチームの成長とともに少しずつ形を変えていくものであるという考えのもと、最初の一歩としてどのようにアーキテクチャを選定しチームに根付かせていくか、またアプリの成長にともなって徐々に現れるひずみをどのように解消していくのか、実際の方法論を交えつつも根本にある思想や考え方、行動指針を示すことで、特定のチームにおける実例を他のチームにも活かせるプラクティスとしてまとめています。

新規事業の立ち上げから運用にいたるまでの比較的小規模なチームにおける事例を @kgmyshin さんが担当し、すでに成長を続けてきたサービスをさらに拡大していく比較的大規模なチームにおける事例を自分が担当しました。それぞれ4章分の内容があります。そして最後の章では大小それぞれの事例を振り返り、規模によらない共通点や規模によって異なるポイントをまとめています。

クラウドファンディング開始当初の意気込みなんかは次の記事に書いてありますのであわせてどうぞ。

blog.keithyokoma.dev

オンライン輪読会

実はこのプロジェクトはまだ続いていて、出版後にオンラインで輪読会を開催します。

techbookfest.connpass.com

全9回、各回で書籍の1章分の内容を輪読します。初回は早速明日4/27の22時からで、1週間ごとに読みすすめる予定です。 YouTube で配信予定で、アーカイブもあるので当日参加できない方もあとからご視聴いただけます。

val age = 0x20

やったね僕も二十歳(16進数)になりました!

去年の自分はなにか目標でも立ててたのかと思って振り返ったら何もありませんでした。代わりに DroidKaigi がコロナ禍の始まりとかちあって中止となったことで、それまでのいろいろな準備をせめて何らかの形でアウトプットしようという供養エントリを書いていました。

blog.keithyokoma.dev

まだまだコロナ禍は収まる気配はなく、DroidKaigi も平年のような大きな会場を利用しての開催予定をたてるには難しい状況ですが、Podcast を始めたり2021年版公式アプリを公開したりと少しづつ活動の幅を広げてきています。

自分自身はというと、去年は思ってもいませんでしたが新しい会社で働いていて引き続き Android アプリをもりもり作っています。 今は Android エンジニアが自分ひとりということもあり、とにかく Android アプリの実装に必要な判断を自分ひとりでやっている状況なので、はやくコードそのものだけでなく設計やら作り方など含めてレビューしてくれる仲間を求めています。もちろん単にアプリを作るだけじゃなくて、いろんなアイディアを話し合う仲間でもあってほしいですが。 スタートアップということでまだまだ足りないものだらけですが、着実に前進していて楽しく仕事をしています。新しいもの、足りてないものをもりもり作るのが好きな人は声をかけてもらえれば、いつでもお話できます。

あとはクラウドファンディングでの技術書執筆プロジェクトもすすめています。リリースがだいぶ近づいており、佳境という感じです。絶対にリリースするぞ!

干し芋

ViewBinding を用いた View の操作をユニットテストする

ViewBinding を使うと View のアクセスを簡略化 & @NonNull を標準として堅牢にロジックを作れるようになります。 ここでこの ViewBinding を用いて次のような View の操作をメソッドとして定義したクラスを作ったとして、そのクラスにあるメソッドをユニットテストすることを考えてみます。

例えば、次のような TextView がおいてあるレイアウトを定義してみたとします。

<ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >

  <TextView
    android:id="@+id/sample_text"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    />

</ConstraintLayout>

ViewBinding では上記の XML から FragmentSampleBinding のような名前の ViewBinding クラスが生成されます。これをつかって View の操作を司るクラスを次のように定義してみます(特に MVP パターンを推奨するつもりはなく、ただ View の操作を司るクラスの命名として Presenter を用いています)。

class SamplePresenter(
  private val binding: FragmentSampleBinding
) {

  fun setSampleText(text: String) {
    binding.sampleText.text = text
  }
}

シンプルですね。findViewById の必要がなくなり、かつ Null Safe に View へアクセスできます。

ここで setSampleText 関数に渡した文字列が sample_text を ID とした TextView のテキストとして設定されることをユニットテストしてみます。 ユニットテストでは Robolectric を使わず、モックフレームワークとして mockk を使う場面を想定しています。

ユニットテストの準備として FragmentSampleBinding をうまくモックするとテストの見通しがよくなりますが、 ViewBinding で自動生成されるクラスの構成の問題で素直にモックが生成できません。

次にその自動生成コードを示しますが、モックの生成で問題となるのは各 View のフィールドです。 mockk ではモック対象のクラスが持っているフィールドを mock できず、かつ ViewBinding が持っている View のフィールドは final なのであとから代入することもできません。

(${module}/build/generated/data_binding_base_class_source_out/ 配下に生成されます)

public final class FragmentHomeBinding implements ViewBinding {
  @NonNull
  private final ConstraintLayout rootView;

  @NonNull
  public final TextView sampleText;
}

これを回避するには、ViewBinding クラスが持っている bind メソッドを経由して各フィールドに mock を差し込みます。

bind メソッドは次のような実装になっています。

public final class FragmentHomeBinding implements ViewBinding {

  @NonNull
  public static FragmentHomeBinding bind(@NonNull View rootView) {
    int id;
    missingId: {
      id = R.id.sample_text;
      TextView sampleText = rootView.findViewById(id);
      if (sampleText == null) {
        break missingId;
      }

      return new FragmentSampleBinding((ConstraintLayout) rootView, sampleText);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

この定義をもとに mock を差し込む方法を次に示します。 bind メソッドに渡す rootView は ConstraintLayout に明示的にキャストされるため、mock するときも ConstraintLayout としてモックを作ります。また基本的に XML 内の View はすべて rootView から探索するため、 XML 上の親子関係によらずレイアウト内の View をすべて rootView から返すようにモックします。

class SamplePresenterTest : BehaviorSpec() {
  init {
    Given("SamplePresenter") {
      val sampleText: TextView = mockk()
      val rootView: ConstraintLayout = mockk {
        every { findViewById<TextView>(eq(R.id.sample_text)) } returns sampleText
      }
      val binding = FragmentSampleBinding = FragmentSampleBinding.bind(rootView)
      val presenter = SamplePresenter()

      When("Set text to sample_text view") {
        presenter.setSampleText("foo")

        Then("`foo` is set") {
          verify { sampleText.text = eq("foo") }
        }
      }
    }
  }
}

レイアウト内にある要素が増えるとその分だけ View のモックが手間になりますが、モックを作るファクトリメソッドを定義しておき 必要に応じてテストケース固有のモックを差し込めるようにしておくと潰しの効く構成になります。

2020振り返り

なんやかんややってたら2020年も終わってしまうので振り返っておきます。来年になにか活かせるかどうかは分からない。

技術者視点で DX (Developer Experience) を上げていく活動

ここの DX はデジタルトランスフォーメーションではなく、デベロッパーエクスペリエンスのほうです。

技術を使ってサービスやプロダクトなど何らかの形のものを作るとき、作り始めた時点ではすごくモダンでイカした設計とツールで最高のプロダクトが作れる気になっていたものが、 日々普通に開発を進めているうちに、あるいは何もせず放っておいたとしても、気がつくとレガシーでちょっと身動きが取りづらいイケてない状態になっちゃうのはよくある話で、 まずは気がついたらそうなっちゃったっていう状況にはまらないためにどんなことをしたらいいんだっけっていうのをあれこれ試したのが2020年のはじまりでした。

いや本当はもっと前からそういうことはやっていたんだけど、DroidKaigi 2020 で予定していた発表はそのまとめとして、どんなことを見て考えて実行したのかを整理していました。

speakerdeck.com

その延長ってことを考えていたわけではないけれど、今もチームで育てる Android アプリ設計というクラウドファンディングプロジェクトで執筆をしています。絶賛原稿進捗中です。

リモートワーク環境の改善

当初は自宅の作業環境がキャンプチェアでしたが、いい加減なんとかしないと腰が爆発しそうだったのでデスクとチェアから揃えることにしました。 とりあえずチェアはリクライニングができてガッツリ背中を倒せるものであればいいやと思い、ニトリでちょうど良さそうなものを見繕ってきました。

ディスプレイは以前に使っていたものを使おうとしたところ、解像度が低すぎて使い物にならないことに気がついたので、LG 27UK650-W 27 インチ 4K UHD IPS LED モニターを買いました。 今の所特に不満なく使えていますが、デスクの場所が部屋の隅でシーリングライトの位置から遠くすこし暗く感じたため、ディスプレイの上に引っ掛ける BENQ のディスプレイライトを追加したり、 椅子をガッツリ倒したときの目線とディスプレイが合わせられるようにすることと、ディスプレイの下に空間を作って収納として使えるようにすることをねらって広めのディスプレイスタンドを置いたりしました。

その他、キヤノンの一眼レフをウェブカメラ化したり、良いマイクを買ったり、自宅に STF を建てて端末管理を一括化したり、Nest Hub Max をディスプレイ脇において作業用に活用したり、会社用と個人のMacBook Pro を並べて立てておけるようにしたりしてリモートワーク環境の改善をした結果、 こんなかんじで作業環境が整いました。

もうすこし欲を言うなら、作業専用の部屋を分けられるといいのですが、そうすると都内では家賃がマッハで上がっていくしかないので難しい…

ワーケーション

年末、軽井沢へワーケーションしに行きました。

blog.keithyokoma.dev

これがすごく良かった。自宅にあるような作業環境ではないものの、いい具合に速度の出るWiFiと電源があれば作業ができて、周りには美味しいものがたくさんあってリフレッシュも気軽にできる。疲れたらいつでも温泉とサウナに入れるのが最高です。 そしてなにより、スーパーのツルヤの品揃えが完璧を超えてきている…近所にツルヤがほしい…

運動

変わらずサイクリングは続けていて、今年は赤城山や霞ヶ浦などを走りました。

https://www.strava.com/activities/4239858105

ただ圧倒的に長距離を走る回数が減ってしまったのが残念。コロナ渦で仕方ないとはいえ、もう少し頑張りたいところです。

また最近はランニングもはじめました。走り込みが久しぶりすぎて、5~6km はしっただけで脚がパンパンになって筋肉痛が厳しいですが、もうすこし続けていけばいい具合に走れるようになると思っています。 ちゃんと痩せるぞ…

その他買ってよかったもの

軽井沢ワーケーションをしてきた

12/13~12/16にかけて星野リゾート BEB5軽井沢でワーケーションをしてきました。

BEB5軽井沢

初日の日曜日は移動日で、車を借りて軽井沢まで行きました。シーズンとしては雪が降ってもおかしくないところを走るため、レンタカーもスタッドレスタイヤのオプションをつけて借りました。

BEB5軽井沢はこんなかんじ。いい雰囲気ですね。

f:id:KeithYokoma:20201217212530j:plain

f:id:KeithYokoma:20201213180406j:plain

f:id:KeithYokoma:20201214184517j:plain

建物は2階建てで、1Fには受付のほかに居室とカフェ、いくつかの作業スペース、2Fには居室と自販機や洗濯機コーナーなどがあります。 また中庭にはこたつや簡易的な暖炉があり、冬でなければ外で作業もできそうです(冬はとても寒いし少しでも水たまりがあればカチカチに凍る)。暖炉を囲んで会話をすれば Fireside Chat が成立します。

日曜日は流石に人も多めではありましたが、それでも3密を避けるため多くのスペースが確保されており、かなり広々としていました。

今回の居室には2段ベッドがあり、下はソファー兼ベッド、上は2つのベッドがあります。下のソファーで一つの電源、上の2つのベッドもそれぞれに電源と読書灯がついています。

f:id:KeithYokoma:20201213174007j:plain。・。

ハルニレテラス

BEB5軽井沢から北へ5分くらい歩いたところにある商業施設で、パン屋さんやカフェ、レストランなどがあります。パン屋さんとカフェは朝早くから開いているので、朝の散歩ついでに朝食をとりに出かけるのにちょうどよいです。

f:id:KeithYokoma:20201215191503j:plain

f:id:KeithYokoma:20201217234307j:plain

f:id:KeithYokoma:20201217234321j:plain

初日はイタリアンレストランで食事をしました。地産地消ということで、軽井沢やその近辺で育った野菜とワインをいただきました。

f:id:KeithYokoma:20201217234338j:plain

f:id:KeithYokoma:20201217234353j:plain

f:id:KeithYokoma:20201217234405j:plain

ハルニレテラスからさらに少し北に進むと、ステーキ屋さんや温泉があります。

f:id:KeithYokoma:20201217235558j:plain

f:id:KeithYokoma:20201217235615j:plain

f:id:KeithYokoma:20201217235638j:plain

温泉の泉質は草津に近い感じで、もしかすると肌が弱いとすこしヒリヒリするかもしれません(自分には不快ではないもののすこしヒリつく感じがありました)。 またサウナ完備で、コロナ禍の情勢を踏まえソーシャルディスタンスが確保できるよう一度にサウナに入れる人数が制限されています。水風呂は非常に冷たく、一度入れば外気温が氷点下でもなんとなく暖かく感じます。

f:id:KeithYokoma:20201214215353j:plain

ハルニレテラスから温泉まで、ひととおり星野リゾートが開発しているのでおなじ WiFi アクセスポイントでインターネットに繋がります。

ワーク

使うテーブルにもよりますが、カフェ側の長テーブルにはすべての席に電源があります。WiFi の速度もいい具合に出るので、一通り仕事がしやすい環境になっています。ワーケーションとして実際に仕事をしたのは月曜と火曜の2日間でしたが、そのどちらも自分用の電源が確保できる長テーブルで仕事ができました。ミーティングなどで機密性の高い会話をする必要があるときは居室にもどってミーティングに参加するといった使い分けが可能です。ただし居室でミーティングに参加するにはすこし明るさが足らず、特に居室のソファーではだいぶ暗いので、もうすこし明るさを確保できる位置取りをしたほうがいいかもしれません。

f:id:KeithYokoma:20201215103303j:plain

長テーブルもソーシャルディスタンスに配慮し隣の席との距離が確保されています。かなり広々と使えるので、多少荷物が多くても気になることはありません。むしろ、ランチなどでホテルから出るときに荷物を一気にその場でまとめられるので、機動力があがります。

今回は車での移動を主としたので、ランチもいろいろなところに出かけられました。 BEB5軽井沢自体は中軽井沢駅に近いのですが、軽井沢駅も車でそう遠くないので、軽井沢駅周辺のランチスポットで食事をとることが多かったです。 ちなみにBEB5軽井沢から旧軽井沢駅近辺のランチスポットに向かうと、いい具合にラウンドアバウトを通れたり、軽く雪化粧した白樺の木々を眺められる山道を通ったりすることができます。

f:id:KeithYokoma:20201214135055j:plain

f:id:KeithYokoma:20201215132205j:plain

その他

中軽井沢には近くにツルヤというスーパーマーケットがあります。ここはプライベートブランド商品が充実しており、かつ地元企業との協力により様々な商品を取り扱っています。 ビールはヤッホーブルーイング、コーヒーは丸山珈琲、それ以外にもドライフルーツは長野県産の果物を使っていたり、ワインも長野のワイナリーでつくったものを扱っていたりと、地元に根ざしつつとても美味しい食品をいくつも作っています。 お惣菜も充実していて、なにより安い!ツルヤが近所にほしいと切実に思いました。

最終日は軽井沢のアウトレットの南にある西武のホテルでビュッフェランチをしました。平日ということもあり、非常にひろいビュッフェで片手で数えられる組のグループが食事をしていました。こちらも長野の食材を使用した料理がおおく、デザートも小布施牧場の牛乳を使ったジェラートが楽しめるなど、とにかく地元推しで美味しい食事が楽しめる最高のリモートワーク環境でした。また行きたいどころか住むのもありなのではと思うほどでした。

おわりに

軽井沢には新幹線でもいけますが、BEB5軽井沢に宿泊してワーケーションをするなら車で行ったほうがよさそうです。なにより、食事の選択肢が広がります。東京のようにあらゆるものが24時間営業とはいかず、コンビニでさえも深夜は閉まるくらいですが、それが特に気にならないくらい他のスーパーやレストランが充実しています。 また BEB5軽井沢での宿泊の特典として、温泉の利用料金が平日無料、休日は半額となる点もとても良いです。気分を変えたいときに散歩以外に温泉に浸かる選択肢もできるので、キンキンに冷えた水風呂につかってから全集中の呼吸へ移行することもできます。

非日常での仕事は楽しくかつ適度に集中ができ、環境の整った自宅とは違った進捗を出すことができます。

季節としては夏は避暑、春や秋も涼しく、冬は寒いですが豪雪地帯ではないので気軽にワーケーションをするにはもってこいの場所のようです。なにより食事が美味しい。この記事も気がつけば食事の画像がほとんどでした。しかしご飯が美味しいのはとても重要で、いろんな食事を楽しめたからこそなおのことワーケーションが充実したように感じます。

劇場版 SHIROBAKO のエンディング

この記事は SHIROBAKO ADVENT CALENDAR 2020 10 日目の記事です。

とにかく劇場版 SHIROBAKO のエンディングが素敵なのだ。多分にネタバレを含むので、まだ劇場版を見ていない方はいますぐ U-NEXT にサインアップして見てほしい。何ならテレビ放送の分まで配信されているので、それをみてから劇場版を見てほしい。今なら初月無料(劇場版 SHIROBAKO は課金が必要だけど 1 週間見放題)だし(一応申し添えておくと、自分は U-NEXT の回し者でもなんでもない)。

続きを読む

自宅に STF (Smartphone Test Farm) を立てて検証端末を管理する

コロナ禍のなかで検証用の Android 端末をどう調達・管理するのかという話題を DroidKaigi.fm #4 でしてきました。その会話の中で、自分の場合は検証端末を全部自宅に持っていて、STF を使ってつないでいるという話をしたのでそのあたりを少し掘り下げてみようと思います。

STF とは

Smartphone Test Farm という OSS ソフトウェアで、これをつかうことで Web ブラウザから STF サーバにつながっている Android 端末を操作できるようになります。adb による接続もできるので、端末を自分の PC に USB ケーブルでつなぐことなくアプリのデバッグが可能です。

github.com

本来は社内ネットワーク内などで STF サーバを立てておき、社内から誰でもブラウザで端末を使えるようにするようなユースケースで使うものですが、自分の場合は自分で会社の検証端末の管理もすることになったので、半ばファイルサーバにしかなっていなかった Mac mini に STF サーバを立て、これを中心に検証端末の管理をしつつ、いつでも使いたい端末をケーブルの抜き差しの手間なく使えるようにしました。

STF を立てる

STF の README にある通りの手順で STF サーバを立てます。事前に Requirements にあるものを準備しておきますが、Node.js が 8.x であることに注意する (これより新しいバージョンでは動かないものがあるよう) 以外は、Installation の手順通りでよいはずです。

検証端末を STF サーバに接続する

開発者オプションの USB デバッグが ON になっていれば、USB ケーブルで STF サーバにつなぐだけで STF が自動で端末を認識してブラウザから使えるようにしてくれます。

一方で、端末を管理するうえで問題になるのは、端末を繋ぎっぱなしにしておくとバッテリーの寿命が早まってしまうことです。とくに検証端末はいつでも使えるようにしておきたいので、STF サーバに繋ぎっぱなしにしておきたいところですが、そうするとずっと充電状態となってしまいます。USB ハブのなかには、電源タップのように ON/OFF スイッチのついたものがあり、これをつかって適度に ON/OFF を切り替えてできる限りバッテリーに負担のないようにしたいところですが、USB の口すべてにスイッチが付いているのは面倒です。そこで自宅ではできる限りたくさん USB の口を確保しつつ、ON/OFF スイッチは1つで全体をコントロールするものとしてこの USB ハブを使っています。

検証端末の保管場所をつくる

1台や2台程度ならどうということもないですが、5台以上になってくると、そのへんに積んでおくわけにはいかなくなってきます。今回は東急ハンズで仕入れた木材を適当に加工して簡単なスマホラックを作りました。

ひとつのラックで3台ほど格納できるように木材をあわせます。下からケーブルを通して差し込めるようにしてあります。

f:id:KeithYokoma:20201120184530j:plain

これを4個並べて運用しています。

f:id:KeithYokoma:20201120184541j:plain