nashcft's blog

時々何か書く。

穏やかに週末をむかえる

直近の日記としてのブログで忙しいとかつらいとかニャーンとか言ってた原因の切羽詰まった状況が木曜で終わり、その開放感に任せてTDD写経のブログを書き上げた後、とても穏やかな気持ちでむかえた今週最後の平日はそれなりな進捗と共にとても穏やかに過ぎ、帰る前にちょっとバタバタしたのをとても穏やかな気持ちで対応して帰りしなに寄り道してラーメンを食べて帰宅して諸々の後に今がありこれから寝ます。Rxの勉強はやり損ねました。土曜日は幕張までガルパンを観に行きます。おわり。

Kotlin で『テスト駆動開発』を進める (第1章 - 第4章)

せっかく『テスト駆動開発』を読むのだから写経をしながら、それにただ写経するだけというのもなんだし他の言語でやろうということで学習がてら Kotlin で取り組んでいたのだが、これが中々面白かったので取り組んだ過程を、ある程度整理した上で*1書いてまとめる事にした。

大雑把にいうと以下の記事に影響を受けており、章の区切りなどもこちらに沿うようにしている。

qiita.com

レポジトリは↓

github.com

なぜ過程を書くのか?

もともと『テスト駆動開発』の内容は、新訳版の翻訳者である t_wada さんのブログ記事にもある通り、著者 Kent Beckテスト駆動開発の手法を用いながらインクリメンタルにプログラムを書き進めていく時の思考の推移を辿っていくような構成になっている。

本書の第I部、第II部は、ペアプログラミングのような語り口で、そのときどきの思考の言語化を挟みながら、コードがだんだん育っていくという構成を採っています。

この「思考」が、使用した言語によって変化させられる様であるとか、その結果として書き上げられるコードの形にも表れるところがとても興味深く感じられたので、私自身が辿った思考の流れも残しておけば誰かにとって参考になったり興味深い読み物になったりするのではないかなあと思ったからである。

進め方

Qiita の記事の方針に沿って本の内容と変わらないところは TODO リストとコードだけで進め、Kotlin特有の何かや思考が本の内容と異なった部分に関してはそれについて書いていく。

事前準備

プロジェクトのセットアップは各自適当にやってください。私はこれを参考に IntelliJ IDEA で build.gradle を書いていたら色々サジェストしてくれたので結果的に このようになった。

第1章 仮実装

最初の TODO リストは以下の通り:

  • [ ] $5 + 10CHF = $10 (レートが 2:1 の場合)
  • [ ] $5 * 2 = $10

早速テストクラスを作る。

// MoneyTest.kt
// package, import は省略

class MoneyTest {

    @Test
    fun testMultiplication() {
        val five: Dollar = Dollar(5)
        five.times(2)
        assertEquals(10, five.amount)
    }
}

このレベルだとJavaと殆ど差異がない。ここでの Kotlin 的要素といえば以下のようなものがある:

  • デフォルトの公開レベルは public (classfun の前に修飾子をつけていない)
  • 変数宣言時に val をつける
    • val/var があり、前者は再代入不可、後者は再代入可
  • 型は後置表記
    • 型推論が効くので省略が可能。以後明示する必要がなければ全部省略する

以後進行にあまり関係のない Kotlin 要素はスルーしていくので各自適当にググってください。

最初のテストを書いたところで TODO リスト更新:

  • [ ] $5 + 10CHF = $10 (レートが 2:1 の場合)
  • [ ] $5 * 2 = $10
  • [ ] amount を private に
  • [ ] Dollar の副作用をどうする?
  • [ ] Money の丸め処理をどうする?

まずはコンパイルエラーを直す。 Dollar クラスに amounttimes(Int) があれば良い。

// Dollar.kt
class Dollar(var amount: Int) {

    fun times(multiplier: Int) {}
}

Kotlin はコンストラクタで引数に受けた値をそのままフィールドに代入するだけ、という場合はクラス名の後の() に修飾子つきの引数を記述するとそのまま値を入れてくれる。今回 amount はそれに当てはまるので Dollar(var amount: Int) となった。

とりあえずコンパイルが通って、次はテストが red になる。
テストを通すには amount10 を返してくれればいいのでとにかく10を返すようにする。
テストが green になったらリファクタリングtimes() のなかで amount を変更するようにする。

class Dollar(var amount: Int) {

    fun times(multiplier: Int) {
        amount = 5 * 2
    }
}

数字のハードコーディングになっているところをメンバや引数に置き換えて重複を排除していく。この辺は本の内容と一緒。

class Dollar(var amount: Int) {

    fun times(multiplier: Int) {
        amount *= multiplier
    }
}

この間コードを修正するたびにテストを実行して green であることを確認し続ける。ここまでくれば TODO リストの項目を1つ完了にできる。

  • [ ] $5 + 10CHF = $10 (レートが 2:1 の場合)
  • [x] $5 * 2 = $10
  • [ ] amount を private に
  • [ ] Dollar の副作用をどうする?
  • [ ] Money の丸め処理をどうする?

第2章 明白な実装

本の内容と変わりがないのでスキップ。

class Dollar(val amount: Int) {

    fun times(multiplier: Int) = Dollar(amount * multiplier)
}

amount に再代入しなくなったので var から val に、また times() は値を返却する1行の関数になったのでブロック表記から式表記に変更できた。

TODO リスト更新:

  • [ ] $5 + 10CHF = $10 (レートが 2:1 の場合)
  • [x] $5 * 2 = $10
  • [ ] amount を private に
  • [x] Dollar の副作用をどうする?
  • [ ] Money の丸め処理をどうする?

3章 三角測量

Value Object パターンのお話から始まり、DollarValue Object として扱うための TODO (equals(), hashCode()) がリストに項目が追加される。
これらの事情は Kotlin も一緒なので本のコードとほぼ同様に equals() 実装して、最後にまた TODO リストを更新。

  • [ ] $5 + 10CHF = $10 (レートが 2:1 の場合)
  • [x] $5 * 2 = $10
  • [ ] amount を private に
  • [x] Dollar の副作用をどうする?
  • [ ] Money の丸め処理をどうする?
  • [x] equals()
  • [ ] hashCode()
  • [x] null との等値性比較
  • [ ] 他のオブジェクトとの等値生比較
class Dollar(val amount: Int) {

    fun times(multiplier: Int) = Dollar(amount * multiplier)

    override fun equals(other: Any?) = amount == (other as? Dollar)?.amount
}
class MoneyTest {

    // ...

    @Test
    fun testEquality() {
        assertTrue(Dollar(5) == Dollar(5))
        assertFalse(Dollar(5) == Dollar(6))
    }
}

上記のコードで Dollar 同士を equals() ではなく == で比較するように実装しているが、Kotlin の ==Java== とは異なり equals() を用いた比較の糖衣構文でだからある。ちなみに equals()== は全く同じ機能ではなく、リファレンスによると a == ba?.equals(b) ?: (b === null) と同等の判定を行うとのこと。== に関しては後でちょっと話題になる。
あと Kotlin は基本的に null を扱う場合は ? が最後についた型や演算子を使ってNPEを吐かないように実装しないとコンパイラに怒られ、結果的に equals() の実装は null との等値性比較をクリアしている。

ところで Value Object を作るたびに equals() とか hashCode() とかいちいち実装しなきゃいけないとか等値性比較で色々考慮しなきゃいけないの大変面倒で、Kotlin に来てまでジャバ界のつらみを背負いたくないとなるが、そこはさすが最近の言語ということで、 data class というものがあり、これは以下の特徴を持っている:

  • Data class として定義するとそのクラスに適した equals(), hashCode() が自動的に付与される
  • toString()"<Class name>(<field>=<value>[, <field>=<value>...])" という出力のものが実装される

Dollar をただの class から data class に変更すると、テストをそのままにさっき実装した equals() を捨てることができ、さらにおまけでこの章の最後に追加された TODO の項目も潰せるようになる:

data class Dollar(val amount: Int) {

    fun times(multiplier: Int) = Dollar(amount * multiplier)
}
  • [ ] $5 + 10CHF = $10 (レートが 2:1 の場合)
  • [x] $5 * 2 = $10
  • [ ] amount を private に
  • [x] Dollar の副作用をどうする?
  • [ ] Money の丸め処理をどうする?
  • [x] equals()
  • [x] hashCode()
  • [x] null との等値性比較
  • [x] 他のオブジェクトとの等値生比較

Data class, Value Object を作る手間がだいぶ省けるので地味に嬉しい機能だ。

第4章 意図を語るテスト

ここはやることに変わりなし。 Dollar(val amount: Int)Dollar(private val amount: Int) とすると amount の公開レベルを private にできる。

  • [ ] $5 + 10CHF = $10 (レートが 2:1 の場合)
  • [x] $5 * 2 = $10
  • [x] amount を private に
  • [x] Dollar の副作用をどうする?
  • [ ] Money の丸め処理をどうする?
  • [x] equals()
  • [x] hashCode()
  • [x] null との等値性比較
  • [x] 他のオブジェクトとの等値生比較

この時点でのコード:

class MoneyTest {

    @Test
    fun testMultiplication() {
        val five = Dollar(5)
        assertEquals(Dollar(10), five.times(2))
        assertEquals(Dollar(15), five.times(3))
    }

    @Test
    fun testEquality() {
        assertTrue(Dollar(5) == Dollar(5))
        assertFalse(Dollar(5) == Dollar(6))
    }
}
data class Dollar(private val amount: Int) {

    fun times(multiplier: Int) = Dollar(amount * multiplier)
}

ここまでのまとめ

テスト駆動開発』の第4章までを Kotlin で進めてみた。ここまでは基本的に Java と変わりなく、違いといえば言語間の記述方法の違いと data class くらいなので、基本的な流れが変わらない分この記事単体では面白さに欠けるものかも知れない。これはクラス単体の表現の仕方に Java と Kotlin で変わりがないことの表れだとも言える。
次回以降は複数のオブジェクトの関係や抽象化などの話が加わり、Kotlin でできる表現の方法と Java のそれとの違いが見えるようになって、環境の違いによる思考や実装の変化というこの一連の記事で示したいことを見せることができる、はず。

続き:

nashcft.hatenablog.com

*1:実際に取り組んだ時は通り過ぎて暫くしてから、またこれを書いている最中に気づいたりより Kotlin っぽい書き方を知ったりしてコードを書き直したり記事の内容に盛り込んだりということがままあったので順番通りに書こうとすると脱線が激しく記事としてまとまりがなくなるため、それらを必要な地点で記述できるように並び順を直している。そのためレポジトリのコミットログとこのまとめの内容に乖離が発生しており、大雑把にいうと GitHub のレポジトリは1周目、この記事は2周目の世界として見ていただきたい。

+メモ書き

前回のブログで余裕があるとか言ったな、あれは嘘だ。嘘だったんだ... Binder transaction buffer の記事修正は今週末にやろう...

先週も結局ヒイヒイ言いながら開発タスクをやっつけてて、今週も厳しそうな雰囲気がある。そんなこんなもあり昨日今日とバス係数についてふわふわと考えているような状態になっていて、それは色々な事があるからです。

ところで先月頭に始めた『テスト駆動開発』の写経会、最近は参加者各位の多忙により全く進められておらず、とはいえ皆の予定が合うまで延期というのではつまらなくなってしまい先週末から1人で進め始めてしまう事にした。

github.com

レポジトリ名の通り Kotlin で進めているのだが割と面白いことになっていると感じているのでElmで進めている方がやっているようにブログに過程をまとめようと思う。章の区切りも合わせようかな。第一弾は今週末までに書く。

今回ブログを書いたのは日記的なものを書くというよりもTODOを書き残しておこうという理由で、今週末までにやることを以下にまとめておく

  • Binder transaction buffer のサイズについて再調査 & 記事の加筆修正
  • Kotlin で『テスト駆動開発』をやった過程についてブログを書く (1~4章?)
  • TDD_kotlin: 14章を終わらせる
  • "Reactive Android Programming" の写経レポジトリを作成して chapter 1 まで終わらせる

オッ、なんか増えましたね...

久々の日記的な

色々あり3週間弱もの間日記的なものを書かないでいた...

先々週の土曜日までは JJUG CCC の発表資料を作っていたとか、先週は業務でタスクが突然押し寄せてきたので残業しまくっていたとかそういう言い訳をすることもできるが実際それを言い訳に忙しいから落ち着いてから再開すればいいやとか考えてた。
とまあそんな感じで最近は細かいバグ取りタスクを捌くことに気を取られていたので他のことはあまりやっておらず、でも休日には怪獣惑星を観るなどはしていた。ところでバグ捌きをしているとQAチームとのコミュニケーションが増えるのだけどここもまたニャーン

ニャーン

先週で怒涛のようにバグを潰したので今週は結構落ち着いていて割と余裕がある雰囲気。今日は息抜きに社内wikiを眺めていたら若者がいつの間にか日報を始めていたのに気づいて最新の分までさらりと読んでいた。日報って1人でやってると虚無になってしまうので私も日報仲間になろうかなーと思っている。

前回の記事のことを思い出したけど、あれは後から見直して正しくなさを感じたので調べ直して修正する予定。業務の方では Intent に保存するデータ量を雑に記録して適当な上限を設けるなどしてなんとなく解決した。感覚的には端末間で binder transaction buffer のサイズにばらつきがあるという方がしっくりくるのだけど、とりあえずAndroid One S2は600KBくらい放り込むとTransactionTooLargeExceptionを吐いて死ぬがXperia系は問題なく処理する。別件でXperia系に苦しめられていてどちらかというとXperia系の方がいい加減なのではないかという気持ちになりつつある。

とりとめがないけどおしまい

Binder transaction buffer は1MBとは限らないかもしれない話

今開発しているアプリで特定の端末・条件でとある画面への遷移時に画面が真っ暗になって操作を受け付けなくなったり、該当する中で古い端末だとアプリが強制終了したりしてしまうという現象に出くわして、ログを眺めてみたら以下の例外が出続けていた:

Exception thrown launching activities in ProcessRecord{(略)}
    android.os.TransactionTooLargeException: data parcel size 645548 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(Binder.java:615)
        at android.app.ApplicationThreadProxy.scheduleLaunchActivity(ApplicationThreadNative.java:893)
        at com.android.server.am.ActivityStackSupervisor.realStartActivityLocked(ActivityStackSupervisor.java:1330)
        at com.android.server.am.ActivityStackSupervisor.attachApplicationLocked(ActivityStackSupervisor.java:892)
        at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:7116)
        at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:7194)
        at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:542)
        at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3032)
        at com.android.server.am.ActivityManagerServiceEx.onTransact(ActivityManagerServiceEx.java:594)
        at android.os.Binder.execTransact(Binder.java:565)

それでリファレンスを読んだりQiitaの記事を漁ったりして、上記の例外は Binder transaction buffer に対して上限を超えたサイズのデータをやりとりしようとした時に発生するっぽいことがわかった。

読んだ記事: qiita.com

qiita.com

確かに問題の画面遷移では Intent にそれなりなサイズになるデータを1つ保持させるのでそれかなーと納得しかけたが、ちょっと変なことに気がついた。
読んだページの中で Binder transaction buffer の上限は1MBと書かれているが、上の例外には data parcel size 645548 bytes とある。つまり読み方が間違ってなければだいたい650KB弱のデータで音を上げていることになる。さっきの「それなりなサイズのデータ」以外に保持させている大きなデータは無いし、そうなると transaction buffer の上限が1MBならばこれで too large と判断されるのはおかしいはず。

それで気になってググってみたら StackOverFlow の以下の記事がすぐに引っかかった:

stackoverflow.com

The limit is supposed to be 1MB but it varies by device from little less than 512KB up to almost a full 1MB.

質問は2013年、上で一部引用したベストアンサーの回答も2015年とやや古いが、端末によって transaction buffer のサイズが異なるとある。それで "binder transaction buffer size vary" とかでさらにググってみたら他にもいくらか引っかかる様子。

https://www.neotechsoftware.com/blog/android-intent-size-limit

リファレンスはまるで一律1MBだと定めているように読めるのでなんだかなーという気持ちになった。リファレンスの記述は以下の通り:

The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process.

さてどうやって対応したものかね...

JJUG CCC 2017 Fall に参加した

20分枠で Eclipse Collections の紹介をした。

slides.com

発表時の様子をコミッタの1人である @itohiro73 さんがまとめてくださっている。

togetter.com

発表後の振り返り

余談

このライブラリは今勤めている会社のサーバサイドチームで Stream API の代わりとして1年半くらい使われていて、最近はチーム加入後に取り組むハンズオントレーニングの題材にも組み込まれるようになった。
プロダクトの中で両方が混在していると混乱するだろうということで Stream API を使わないように規約で決まってるので面白がって以下のようなやりとりをすることがある。

Eclipse Collections と私と今後

まとめのところで何かコントリビュートするぞと口走ったので来年末くらいまでに何かできたらいいなあと考えている。Immutable なコンテナでできることを増やしていきたいなあとか。
仕事ではサーバチームから Android アプリ開発チームに移動して使う機会がなくなってしまっているのがネックかなー...

日記

チームの人からのサーバサイドAPIの挙動に関する質問を聞いていたらAPIの不備が見つかったのでシュッと直してプルリクを投げた。
大した規模の修正ではなかったのでその場ですぐプルリクにしたのだけど、弊社では基本的にタスクをチケット化するのが決まりで、チケットも作らずにこういうことをしていると見えないタスクを生み出してしまい、誰が今どのくらいタスクを持ってるかとか誰がどの期間で何をどのくらいやったかといったトラッキングを正確にできなくてよくないですねということが考えられる。でも縦割りチームだとチームをまたいだ作業は記録しても見えにくいし開発タスクはGitHubの草でも見たらいいじゃーんというか、そういう行動データがJIRAだったりGitHubだったりetcだったり散っちゃっててそもそも上手くトラッキングできてないじゃーんウワーつらい眠いのでおわり。