Data Binding Library と Google Analytics for Firebase の依存関係の件
Data Binding Library と Google Analytics for Firebase が古いライブラリを参照してるのでメモ。
手元にある support library の version として "27.0.2" を採用しているプロジェクトで、普通にビルドすると lint で下記のように怒られる。
Incompatible Gradle Versions
../../build.gradle: All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes). Found versions 27.0.2, 25.2.0. Examples include com.android.support:animated-vector-drawable:27.0.2 and com.android.support:support-media-compat:25.2.0
gradlew :modules:app:dependencies とかやると下記のような依存関係がわかる。
com.android.databinding:library:1.3.1 - com.android.support:support-v4:21.0.3 com.google.firebase:firebase-analytics:11.8.0 - com.google.android.gms:play-services-basement:11.8.0 - com.android.support:support-v4:25.2.0
ぐぬぬ、、、
本質的な解決じゃないけど下記のように設定してlintエラーを回避するくらいしかできんよな、、、(´・ω・`)
implementation "com.android.support:support-v4:27.0.2"
RxJava2のnullの扱いとOptional
RxJava 2.0 で Observable による null の emission が非サポートになったので、その点に関してのメモ。
Javaでnullをemitしてみる
Observable<String> source = Observable.create(emitter -> { try { emitter.onNext("Alpha"); emitter.onNext("Beta"); // io.reactivex.exceptions.OnErrorNotImplementedException: The mapper function returned a null value. emitter.onNext("Gamma"); emitter.onNext("Delta"); emitter.onNext("Epsilon"); emitter.onComplete(); } catch (Throwable e) { emitter.onError(e); } }); source.map(s -> s.length() >= 5 ? s.length() : null).subscribe(s -> System.out.println("RECEIVED: " + s));
emitter.onNext("Beta"); のところで例外がスローされる。
JavaでOptionalを使ってみる
Observable<String> source = Observable.create(emitter -> { try { emitter.onNext("Alpha"); emitter.onNext("Beta"); emitter.onNext("Gamma"); emitter.onNext("Delta"); emitter.onNext("Epsilon"); emitter.onComplete(); } catch (Throwable e) { emitter.onError(e); } }); source.map(s -> Optional.ofNullable(s.length() >= 5 ? s.length() : null)).subscribe(optional -> System.out.println("RECEIVED: " + (optional.orElse(null))));
Optional でラップすることにより null を emit していないので例外はスローされず、以下のように出力される。
RECEIVED: 5 RECEIVED: null RECEIVED: 5 RECEIVED: 5 RECEIVED: 7
kotlinで普通にSequencesを使った場合
val source = listOf("Alpha", "Beta", "Gamma", "Delta", "Epsilon") source.map { s -> if (s.length >= 5) s.length else null }.forEach { s -> println("RECEIVED: " + s) }
普通に null が扱えるので、例外はスローされず以下のように出力される。
RECEIVED: 5 RECEIVED: null RECEIVED: 5 RECEIVED: 5 RECEIVED: 7
ThinkPad Compact Bluetooth Keyboard with TrackPointの接続が切れるようになった時のメモ
java や kotlin でもポインタを扱いたい件
多角的に検証してないけどとりあえず思い付きをメモしてみた。
状態を持つ Session オブジェクトを UI から操作する場合を考えてみる。
Session が単一の interface を持つ場合は、特定状態では実行不可能なメソッドを叩くと実行時例外がスローされるように作ることが多い。
しかし、そもそも実行不可能なメソッドを叩けなくするというアプローチもある。状態遷移を起こすメソッドの戻り値を状態遷移イベントにして、その中に次の状態用のインタフェースを入れておくとか。
この場合、状態用のインタフェースの参照を UI が保持してしまうと、Session 側で破棄されても UI 側で使えてしまう。もしそれがポインタであれば、Session 側で ポインタの値を null にするなり新しい値にしておけば、古い参照を使用することが不可能になる。
実際問題として正しいコードを書く分にはポインタがあろうがなかろうが関係ないのだが、assertion のコストが全然違う。UI側が古い参照を叩いてしまうというバグについては、ポインタが使える場合はヌルポで落ちるだけなので何もしなくてもよいということもある。しかしポインタが使えない場合は古いものを叩いた時に実行時例外をスローするような仕組みが必要となり、それを入れないとエラーと気づかないまま間違った動作をしてしまう可能性がある。
現状だと開発者に対するルールで縛るしかなさそうな気がする、、、。
interface 仕様をコードで記述する
とりあえず絵の餅を描いてみるw
interface 上に定義されている関数について、document に仕様を記述し、実装クラスに実装するというのは結構普通に行われるが、その仕様がほぼ純粋に複雑なロジックだった場合、以下のような問題がある。
- ドキュメント中に書かれた自然言語によるロジック説明が非常にわかりづらい上に曖昧。
- ドキュメント中のロジックが正しい保証が得られない。
- 複雑なロジックであればあるほど、ドキュメントとコードが乖離する可能性が高まる。また、乖離したかどうかを確認する機械的な手法が無い。
- 疑似コード方式でロジックを検討するにしても、疑似コードは実装フェーズの成果物である実装クラス上で行うものなので、要求開発時には行えない。要求開発時に行うためにモジュールの外部インタフェースのドキュメントとして記述するとしても、実装との乖離が無いように保守する必要があり、また、どれだけコストをかけても実装との乖離が無いことを証明する方法がない。*1
上記のような問題を扱う工数をかけるより、kotlin であれば interface 上にソースコードを書いてしまうほうがラクチンな気がする。以下案:
- 仕様レベルに属する interface のロジック仕様を記述する抽象クラスを作成する。
- 仕様インタフェースレベルのドキュメントには概要を記述するが、ロジックは一切記述せず、抽象クラスへのリンクを見せるだけにする。*2
- 抽象クラスは Xxx を implements して XxxBase という命名で統一。(Xxx は仕様レベルに属する interface の名前)
- 抽象クラスに疑似コードを記述するという作業を全てのモジュールインタフェースに対して行い、その後、仕様レベルのロジックのみ実コードに置き換えていく。*3
- 仕様レベルで決定する必要が無いロジックは TemplateMethod として実装クラスに丸投げする。*4
- 切り出したいロジックがある場合は、必要に応じて別関数として切り出す。*5
上記の成果物までを要求開発フェーズの後半に含めることにより、低コストかつ高精度で要求開発の精度が上がるんじゃないだろうか。予想可能な一定の工数を支払う代わりに予想不可能な実装フェーズでの手戻りを減らせるという効果が期待できるかも。特に、開発後期に大人数を投入してリードタイムを削減する場合、設計への手戻りコストが非常に高くつくので、この方法は有効な気がする。
絵に描いた餅おしまいw
*1:そもそも、このような保守は、進捗と品質のトレードオフという概念が存在する組織では成り立たない。品質の全責任を負い、品質以外の一切の責任を持たず、開発部門の全権限を上回る権限を持つ品質管理部門が無い限り無理。
*2:意味的に結合しているので依存しても問題ない。っていうか依存させるべき。
*3:疑似コード作成者はその疑似コードに対する実コードを書いてはいけないというルールがあるといいかもですね。同一の人物やペアが実装することが常態化すると疑似コードの品質維持が難しい気がする。もしくは、レビュー参加者からランダムで実装者を選ぶということにすれば、全レビュー参加者が気を抜けなくなる気がするw
*4:例えばソートする必要がある場合など、ソートアルゴリズムは非機能要件さえ満たせれば何でもいいので仕様記述に含める必要が無い。っていうか含めちゃいけない。
*5:構文的かつ意味的にも重複しているコードの切り出しは必要だが、それ以外のコードの切り出しは細かく切り出し過ぎないように注意する。メソッドは短いほうが良いというのは過去のプラクティスであり、別メソッドに切り出す理由がメソッドの長さだけであるならすべきではない。選択や反復のブロックも、目視するのに困難なレベルの複雑さ以外の時は切り出すべきではない。
疑似コード(pseudocode)について
ふと思ったのでメモ。
疑似コードをプロジェクトで正式に採用したことはないんだけど、要求開発のツールとしてドメインモデリングを採用する際に、インタフェース設計の後に一段階入れても面白いかも。予想されるメリットは以下:
- インタフェース設計のみより多くのフィードバックが期待でき、コストもそれほど大きくない。*1
- リリース後の保守コストがゼロ。*2
- 工数の見積もりが高精度で立てられ、かつ、この活動によって、工数の読みづらい実装時の手戻りを削減できる。*3
- ソースコードのシンタックスエラーを気にして不必要に細かいところまで考えてしまうことが無くなる。
- 実装時にソースコード上のコメントを考えることや、実装後に後付けすることが無くなる。結果として、実装に準じるようにうまくコメントを書くという本来と逆の思考により不適切なコメントを書くことを避けられる。
- レビュー時に、実装技術の細かい話の話になったり、レビュー者が実装技術の細かい点につい意識を持って行かれるようなことが無い。*4
- ダメな案を抱えてしまうリスクの排除。*5
要検討な点:
- 疑似コードを利用するかどうかの判断が必要になる。*6
- 書式をある程度一般化する必要がある。*7
- 複雑だが本質的なロジックの場合、ソースコードを直接書いたほうが厳密かつ分かりやすいよね。そういう場合にどうするか。*8
- どこまで疑似コードで掘り下げるかの判断基準が必要。*9
*1:インタフェース設計フェーズを拡張するだけで行けると思われ。
*2:リリース時には疑似コードはすべて実際のコードに置き換わっているため。
*3:固定金利と変動金利のスワップのようなもので、リスク管理手法としてはかなりおいしいと思われる。
*4:設計レベルのレビュー時にインデントのスタイルとかスペースの入れ方とかfor/while/forEach等のどれを使うべきかとかを話しても意味がない
*5:コーディングまで完了させてしまうと、心理的にそのコードを捨てづらくなる。数行の疑似コードであれば捨てやすい。
*6:setter/getterなどに作っても意味が無いので。しかし、setterにバリデーションがあったらどうなの?とか考えだすと、かなり明確な判断基準がないと運用が難しそう。っていうか、全部疑似コードを書くと決めてしまったほうが早いかもですね。単純なgetterならお決まりの文を1行入れるだけなのでコストはかからんし、後でバリデーションロジックなどが入るかもしれんし。そもそも疑似コードを書くという行為によって『バリデーションいるんじゃね?』的な気付きを得られる可能性が増すという考えもあるよね。
*7:順次・選択・反復系をどう表すかなど。たぶん、いくつかのパターンの例を示しておく程度でもいい気がする。形式化しすぎると本末転倒だし。
*8:例えば、独自サービスのポイント付与ロジックとか、大半はプログラミング言語の syntax で記述したほうがわかりやすいと思われる。
*9:インタフェースからほぼ自明な処理は疑似コードは書かず、そうでないものはすべて機械的に実装に書き換えられるレベルの粒度まで掘り下げてもいいかも。しかし、ドメインモデルのインタフェース直下ではなく派生したルーチンとかクラスが必要になる場合は要検討だけど、直観的にはそこまではやらんほうがいいと思われる。そこまでやったらもう要求設計フェーズじゃないよねwww。ドメインモデル以外については、臨機応変にやるのがたぶん正解。作業者のレベルや経験値やコード資産の流用などによる影響が大きすぎるので。