DDDでの失敗談

近々同僚とLT大会をするので話すことをMarkdownで書いてみたので投稿。

DDDでの失敗談

DDDを1年半くらい実践してきて色々成功したこと失敗したことがあった。 この場では失敗談を共有してアンチパターンへの対処法をお伝えしたい。

ドメインしっかりと設計すれば変更少なくてすむだろう→そんなことはなかった

ドメインの設計見直しタイミングは以下のケースがあった。

  • 仕様を固めていく上で他の仕様も変わるケース
  • ユーザテストの意見反映で仕様が変わるケース
  • 実装を進めていてより良い設計を思いついたケース

意外とコロコロ変わる。 悩んでベスト設計を目指すのではなく、ベター設計で速度を優先したほうが良い。

ユビキタス言語決めたら変更はないだろう→変更入りまくり

  • 名前が学術名で難しいので分かりやすい名前に変えた
  • デザイン進めて行く上で変わった
  • 他アプリでの名称違うからそっちの反映
  • 監修担当から変更要望反映

ユビキタス言語は変わる。 変わらないように努めるべきだが変わる可能性は充分にあることを留意する。

通信も結局はデータ操作だからData層に入れられそう→Domain層でHttpステータスに応じて色々したい…

  • 通信処理もDB操作と同じくset/getのイメージで、returnもValue/nullで扱えそう。通信結果はData層内で隠蔽できそうだと思った。
  • でもHttpステータスに応じてアクション変えたいね。
  • やっぱりドメイン層で扱いたい。

通信結果もビジネスロジックとしてDomain層で扱いたい要望はあるので隠蔽しすぎないこと。 個人的にはCleanArchitectureの図のように、Data層/Web層は分離しとくと分かりやすくて良いと思う。

ビジネスロジックどこ書けばいいかわからん…とりあえず専用クラス作ってそこにメソッド書くか→Entityがただのデータの入れ物化

ビジネスロジックを書く際は既存のEntity/ValueObjectで担当できるか検討する。 担当出来ない場合、サービスクラスとして実装する。UseCaseには書かない。

まとめ

  • ドメインはアプリを作っていく過程で変わる可能性は充分にあるし、後日より良い設計を思いつく可能性が高い。設計に迷ったら時間を掛けすぎず一旦思いついた限りの内容で実装進める。
  • ユビキタス言語はプロジェクト初期に確定するのは難しい。変わらないように努力しつつ、後々変わる事も意識しておく。
  • ドメインは通信結果も意識する。Data層に押し込めすぎないようにする。
  • 振る舞いを考えるときは既存のEntitiy、ValueObjectで探すこと。安易にロジックをUseCaseなりに押し込めない。

Kotlinの単一式関数は責務を制約する時に便利

単一式関数とは、通常のreturnで返却する式↓を

fun method(type: Type) : Int {
  return when(type) {
    Type.TypeA -> 0
    Type.TypeB -> 1
  }
}

代入式で省いて書ける↓

fun method(type: Type) = when(type) {
  Type.TypeA -> 0
  Type.TypeB -> 1
}

といったもの。英語ではSingle-Expression functions。 kotlinlang.org

この書き方で何が変わるんだ、シンプルになるとは言いつつもそこまで変わってない。returnしてもいいじゃないかと思っていたけど一つ使い所を思いついた。 余分な処理を挟んでほしくない箇所に制限を掛ける、例えばMapper。

余分な処理を挟んでほしくない場合にすごい便利

Mapperでは↓のように実装すると思う。名前の通り責務は変換するだけ。

fun map(type: Type) : OtherType {
  return OtherType(type.name)
}

↑このコードで問題なのは、returnの前に何か処理が書けるということ。 引数のtypeではなく、DB内のtypeを読み込んでOtherTypeにmapする。と言ったことが出来てしまう。

fun map(type: Type) = OtherType(type.name)

↑単一式によって処理を挟める隙間が消えた。コードを挟むには単一式を変更して中括弧が必要になる。

あんまり変わってない?

言うほど確かに変わってない。でも小さな制約だけどコードレビュー時に役に立つと思う。 単一式では責務が限定的で分かりやすい。単一式から通常の関数に書き換えられたということは注意するコードの可能性があると思う。

引数のtypeではなく、DB内のtypeを読み込んでOtherTypeにmapするなんてする?

実際にそういうコードを見てしまったので今回の案を思いついた。 責務を限定してエラーで表現してくれるのは良いと思う。

Android開発者はそろそろ32GBメモリ以上のPCに移行を考える時期

最近AndroidStudioのビルド速度の遅さと補完時にフリーズするのに耐えられず、会社のPCを買い替えてもらった。 メモリ8GBの2012年頃MBP→CPU4Ghzメモリ32GBのiMacへ。 買い替えの際になぜMBPではなくiMacを買ったのか、その経緯を書く。 結論から言うと現在のiMac上でのメモリ使用率は20〜22/32GBであり、メモリ32GBほどのマシンを買ったほうがよい。 Macの話だけどWindowsでも同様だと思う。

AndroidStudioの推奨スペック

AndroidStudioの推奨スペックは公式サイトには以下の記述がある 3 GB 以上の RAM、8 GB を推奨、さらに Android エミュレータ用に 1 GB

では推奨スペックで業務利用に耐えられるのか?全くもってそんなことはない。

8GBはかなり辛い

AndroidStudioの推奨スペック以上には達しており、小規模アプリの初期のコード量が少ない場合は問題ない。 ただし、開発後期に進むに連れて厳しさが目立ってくる。 具体的に言うとレイアウトXMLの補完に10秒ほどかかったり。 アプリ実行に2分30秒かかったり。 メモリ使用率は12/8GBで超えた分はストレージをスワップメモリとして使用していた。

16GBはギリギリ?

16GBMBP使ってる同僚の話だとそこまで困ってはいないようだ。まだ使えるレベルなんだと思う。 現状のMBPが積める最大サイズ。

32GBは快適

iMacとMBPを直接比較するのはどうなんだというのもあるが、やっぱり32GBiMacは快適。 ビルド時間は2分30秒→9秒へ。View周りの凝った実装するにはビルド→実機転送を繰り返すのでこれくらいの速度は欲しい。 補完でフリーズすることもない。

iOSはどうなのか?

iOS開発者のiMacアクティビティモニタを見たら同じく20GB/32GB程度だった。

まとめ

CPU性能も関係しているし具体的な数値ベースで調査を行ったわけではないけど、私と同僚のMBPで比較しても快適さが違うのでメモリの差が大きく関係してそうだなぁという肌感の印象。 MBPにも近々32GB積めるようになるらしいのでそれを買うのもいいかもしれない。話はすこし逸れるがディープラーニングもしたいならCUDAの関係でNVIDIA GPUが搭載されるようになるのを待つのもありかも。 PCは高い金額なので買うなら良いPCを買おう。

16GBを使ってる人→まだ使える。
これからPC買う人→32GBPC買おう。
8GBを使ってる人→32GBPCを買おう。

Nexus6PのBootloaderでFAILED (remote: oem unlock is not allowed)

Android O Previewも出たことだしNexus6Pに入れてみようとしたのだけどfastboot flashing unlockで見慣れないエラーが出た。

$ fastboot flashing unlock
FAILED (remote: oem unlock is not allowed)

調べてみるとこんなサイトが見つかった 要約するとアプリ起動中に開発者向けオプションから設定する必要があるらしい。

設定アプリ->開発者向けオプション->OEMロック解除ON

↑設定後、fastboot flashing unlockが実行出来てAndroid O Previewの書き込みすることができた。 以前はこのオプション無かったと思うんだよなーAndroidNからかな?

AndroidStudioのプレビュー表示にView.isInEditModeが便利

AndroidStudioのプレビュー表示は便利だけど、稀に良い感じに表示してくれない時がある。例えばカスタムViewの初期化時にレイアウトパラムを変更したときだ。xml上のtools:visibilityなどを使いたくなるが、コード上なので使えないケースがある。 そういった時にView.isInEditModeをつかうと便利。 プレビューではisInEditMode == trueが適用されているので、if (isInEditMode) { return }などでプレビュー時には該当の初期化処理をしないなどすればよい感じにプレビュー表示してくれる。

経緯

私のケースでは全てのButtonのアニメーションを一括で変更したかった。 しかしスケールアップアニメーションを正しく表示するのにclicpChildren/clipToPaddingを全てのレイアウトで適用するのが面倒だったので、Viewから親のViewGroupのclicpChildren/clipToPaddingを全て書き換えるということを行った。(これはこれでどうなのかという話もあるが)

つい先日のDroidKaigi2017で発表があったSmoke and Mirrors in Androidでも同様の処理をしていたので真似をしてみたのだ。

github.com

しかし、clicpChildren/clipToPaddingを書き換えるとプレビュー上のViewが正しく表示されなくなった。 実行時は問題ないのだけどプレビュー時に正しく表示されない。 そこで if (isInEditMode) { return } を利用した。

Admob広告がなかなか表示されなかった

久しぶりにAdmobを導入しようとしたら2点ハマった。 1つ目はgradleの設定。 2つ目は広告が表示されない。

gradleの設定

ガイドどおりに設定するとapply plugin: 'com.google.gms.google-services' を設定するとnot foundエラーになる。 プロジェクトルートのdependenciesに以下を設定することでいけた。 classpath 'com.google.gms:google-services:3.0.0'

広告が表示されない

テスト広告は表示されるけど本番用の広告が表示されない。 これは時間の問題だった。StackOverflowにも書いてあった。 30分ほどしたあと表示された。

広告SDK周りは変更が多いのと挙動が変わって毎回面倒だったりするね。

DroidKaigi2017に参加して感じた今後のAndroid開発

少し前ですが3/9〜10に行われたDroidKaigi2017に参加してきました。 講演内容の具体的な内容は以下の記事などまとめられているのでそちらを。

qiita.com

全体的な雑感

DroidKaigiは初めて参加したのですが、今までのAndroid開発からの変化を大きく感じ取りました。
あんざいゆきさんの講演で述べられていましたが、大規模高品質アプリが求められる中、1人での開発から複数人での開発にスタイル変化しつつあると。
そこでMVVMやMVPアーキテクチャやDDDなど設計力が求められるようになったと。
実際私の会社でも個人開発は消え、複数人開発のプロジェクトしか残っておらず同じ認識を持っています。

  • 複雑化したアプリに対応するためのMVVM/MVP、設計指針のためのDDD。
  • 各種アーキテクチャを取り込むことでレイヤー化されたクラス構造へのアクセス単純化の為のRx。
  • 規模が大きくなるとCIなどでビルド/リリースの自動化。
  • 見た目の良いViewを提供するためConstraintLayoutでの描画高速化
    といった風潮を感じ取りました。

あとはKotlinの登場ですね。
Javaのラッパー言語であるKotlinをAndroidでも採用するかどうか、皆悩んでいるようでした。
私は業務でKotlinを利用しているのですがドメインモデルが簡潔に書けるのでオススメです。コードがかなり綺麗になります。

まとめ

従来のJava+オレオレMVC+Retrofit+他OSSで作ればAndroidアプリ開発できていた時代と比べてみると、大きな変化を感じました。
個人的にはDDD、Kotlin、RxJava、CI、ConstraintLayout、MVVMの順番でキャッチアップしていかないとこの先辛いなぁという印象。

おまけ

サイバーエージェントさんがアンケート結果をアップしてくれていました。 設計が割れている所と、Kotlinへの関心からAndroid開発のスタイル変化を強く感じます。

f:id:paraya3636:20170325150817p:plain