この記事は何?
普段はサーバサイドエンジニアとして仕事をしている筆者が、会社のスマホアプリ開発者養成集中講義でAndroidに入門したことで感じた感想を徒然なるままに書きなぐったもの。
筆者のスペックは下記のとおり
- Web企業でアドテクプロダクトの開発/運用してる
- メインの開発言語はScala。その前はJava
- フロントエンドは、サービスレベルではほぼ書いてない。管理画面程度
- おじさん(30代半ば)
この記事で書かないこと
いわゆる「入門記事」ではなく、「入門した感想」を綴った記事です。
なのでAndroidアプリ開発に関する具体的なテクニックなどには触れませんm(_ _)m
サンプルコードなんかも一切出てこない。
免責事項
あくまで初心者の感想として書きなぐったものなので、内容には誤りが含まれているかも知れません。
優しくコメントで指摘していただけると筆者が喜ぶかも知れません。
入門としてやったこと
まず4回の座学およびハンズオンと毎回出される課題で基礎を学び、最後に2泊3日の合宿にて、最終課題としてAndroidのネイティブアプリを一本書き上げる、というもの。
講師陣(社内の現役エンジニア)も、通常業務で多忙な中、企画、講義、課題のレビューなどなど、講座参加者のためにかなり手厚いサポートをしていただき、非常に充実した内容であった。
サーバサイドエンジニアから見たAndroidアプリ開発
サーバサイドエンジニアの視点からAndroid開発に触れ、その違いに戸惑ったところや苦労したポイントがいくつかあったので、振り返ってみたい。
画面の制御が基本コールバック関数で実装される
スマホアプリの画面制御の基本は、UIコンポーネントを配置して、そのコンポーネントの振る舞いをListenerに記述して登録するというパターン。
あるコンポーネントに対する操作が別のコンポーネントに影響を与える場合は、
- イベント発生元のコンポーネントに対して
- 発生するイベントの種類に応じたListener実装を登録し
- そのListenerのコールバック関数内で、操作対象コンポーネントに対する操作を記述する
という実装になる。
こういったコードが数多く登場することにため、以下の点に迷うことが非常に多かった。
### 副作用に対する考慮
普段は、Scalaを使って開発していることもあって、
- 可能な限り内部に状態を持たない
- 持つとしてもimmutableにして極力副作用を避ける
ということを、徹底しているとまでは言えないまでもそれなりに意識して書いている。
しかし、UIコンポーネント群が相互に作用し合って動作するスマホアプリでは、各コンポーネントが内部に状態を持ち、その状態を更新することが基本動作になる。
immutableにしたり、副作用を避けたりするメリットの一つに、バグを作り込みにくいということが挙げられるが、このへんは書いてて、「このコードいかにもバグりそうだなー」という予感を感じながらの実装であった。
操作対象のコンポーネントのスコープ管理が難しい
全体を通して一番難しいと感じたのがこれ。
あるコンポーネントに対するアクションが別のコンポーネントを操作する場合、当然ながら操作対象のコンポーネントがコールバック関数内でアクセス可能なスコープに存在する必要がある。
コールバックは、通常 XXListener
のような形でインターフェース定義が決まっており、それを実装する形になるので、作用先のコンポーネントをパラメータとして渡せることはほとんどない。
コールバック内で別コンポーネントをアクセス可能にする選択肢としては、下記のようなパターンが考えられる。
- すべてのコンポーネントを
public
なオブジェクトとして保持し、どこからでもアクセス可能にする - 作用先のコンポーネントがアクセス可能な場所で
XXListener
を無名内部クラスとして登録する -
XXListener
の実装クラスを作成し、そのListenerのメンバとして操作対象のコンポーネントを持たせる - コンポーネントの保持と、それに対する操作のインターフェースを持ったUI管理用クラスを用意し、直接コンポーネントをコールバックから操作せず、そのクラスに移譲する
まず1.については、セキュリティ的観点からも予期せぬバグを生み出す危険性からも採用するべきではない。
現実的な解としては、2.と3.と4.を状況によって使い分けることだと思われる。
2.と3.の違いについては、「Listener定義をどこでやるか」と言い換えることができるかも知れない。
2.は、例えばActivityクラス内に作用元、作用先両方のコンポーネントがメンバとして存在するようなケースでは最もシンプルな解決策となる。
ググって出て来るコードもこのパターンがほとんどだし、多くのユースケースではこれで事足りると思われる。
しかし、RecyclerViewでAdapterクラスをActivityとは別に用意している場合など、作用元のコンポーネントと作用先のコンポーネントの持ち主が違う場合にはそのままでは利用できないため、結局のところ親であるActivityからAdapterに対して作用先のコンポーネントを渡してやる必要があったりする。
3.については、Listenerに操作対象のコンポーネントを渡すことが前提となるため、Listenerの生成箇所のスコープ内に操作対象のコンポーネントが存在する必要がある点では2.と変わらないが、同じListenerを複数のコンポーネントに適用する場合には、Listenerさえスコープに存在していれば、操作対象のコンポーネントはスコープ内になくてもよいというメリットがある。
一方、Listener実装が独立したクラスとして切り出されたコードが増えることになるため、無駄に多用すると可読性を損なうことになるかも知れない。
3.はスコープの異なる複数のコンポーネントに対して同一のListenerを適用したい場合にのみ利用し、そうでない場合は2.を利用するのがよいのかな。
4.は、3.のような複数のコンポーネントに同一の振る舞いを持たせる場合で、かつ管理するコンポーネントが多い場合などに良いかもしれない。
コンポーネント本体を隠蔽して外部から操作可能なもののみを開放する事で、比較的大規模なアプリで想定外の使われ方をして起こるバグなんかを防ぐ効果もありそう。
いずれにしても、コンポーネントのスコープ管理については、クラス設計時に一定のポリシーを持って一貫した管理をしないと、すぐにスパゲッティ化してしまう危険をはらんでいると感じた。
似たような問題は、サーバサイドの開発でも起こることはあるが、比較にならないほど今回の課題の実装時にこの問題に悩む局面が多かった。
ネストが深くなりやすい
上記と関連するが、コールバックをインラインで実装する事が多かったため、コードのネストが深くなりがちだった。
あまりにも深くなる場合は、名前付き内部クラスとして切り出したほうがよさそう。
コールバック内で更にコールバックを書かなければいけなくなった場合などはもう悲惨。
こうなってしまったら、たぶんコード設計をそもそも見直したほうがいいのだろう。
講師に聞いたら、 RxJava などはそういった問題に対する一つの解決策となるようだ。
課題では使用しなかったが、このあたりは引き続き勉強していきたいと思う。
ライブラリの選択基準の違い
サーバサイドの開発において、ライブラリ選択の基準は何だろうか?
現場によって、また人によって異なるだろうが、筆者はだいたいこんなことを考えながら情報収集して選択している。
- 開発やメンテナンスが盛んでコミュニティが活発
- 世の中に実績が豊富で、情報が探しやすい
- 省リソース、高パフォーマンスで動作する
- 使い方がわかりやすくシンプル
- 短いコードでやりたいことが実現できる
しかし、Android開発においては、これに考慮すべき点が増える。
- メソッド数を大きく消費しないこと
- バイナリサイズが小さいこと
これらは、サーバサイド開発の現場において考慮されることはあまりないが、Androidアプリ開発では非常に重要である。
Androidアプリでは依存ライブラリも含めた総メソッド数が65536以内になるように抑える必要がある。
未使用のメソッドをビルド時に削除する方法もあるが、アプリ本体の実装をメソッド数を節約しながら作るようなことは通常あまりしたくないはずなので、ライブラリがメソッド数を消費することは極力避けたい、ということになる。
(参考)
Androidでメソッド数が65536を超えた時の対処方法
また、バイナリサイズについても、アプリ本体のサイズに直結するため、やはり小さいに越したことはない。
この結果、Javaのサーバサイド開発においてデファクトとされているようなライブラリでも、そのままAndroidで利用できない(したくない)ケースも多い。
そして、Androidのために開発されたコンパクトなライブラリが世の中にたくさん作られており、コンパクトなだけでなく、Androidでの利用を意識した使い勝手になっていて、そういうものを利用したほうが、結果として早く実装できる。
サーバサイドの人間がAndroidアプリを作るときに、カジュアルに使い慣れたライブラリを使うのではなく、まずAndroidで同じ課題に対してよく利用されているライブラリがないか、まず確認したほうがよい。
Javaであるということ
Androidの公式開発言語はJavaである。(本当は「限りなくJavaっぽい何か」であり、様々な議論があるが、ここでは触れない)
普段Scalaで開発している筆者にとって、開発言語としてのJavaは「書けるんだけどだるい」と感じてしまう要素が多い。
(理由はたくさんあるけどここでは触れない)
Android NでようやくJava8が解禁されたとは言え、現実的に2016年12月現在まだまだ現役で利用されているKitkatやLollipopなどに対応したアプリを書こうとすると、基本的にはJava7以下での開発となり、LambdaやStream API、Optional型といった最新のJavaの恩恵も受けられず、オールドスタイルのJavaプログラミングでの実装を受け入れて開発するしかない。
「Java言語」で開発するならね。
でもJavaが動作するということは、Java以外のJVM言語が使えるということ。
以前Scalaの勉強会に参加したときに、ScalaでAndroidアプリを開発しているという人にもお会いしたことがある。
Scalaは、もちろんAndroid開発での選択肢の一つだし、個人的にも普段仕事で使っていて、難しいんだけど大好きな言語ではあるが、なにせ言語仕様自体が膨大なため、上述のメソッド数やライブラリのサイズの関係で不利な側面があるのは否めない。
今Android界隈ではKotlinが盛り上がっているらしく、少し調べてみると、かなりScalaの影響を色濃く受けていると思われ、かなり使い勝手が似ていると感じたため、最終課題はKotlinで書くことにした。
実際に使ってみると、KotlinがなぜAndroid開発者に受け入れられているのかが、初心者なりにもかなり理解できるものだった。
まず、基本的なことだが
- 定数を
val
キーワードだけで定義可能 - コレクションがデフォルトimmutable
- コレクション操作のAPIが豊富
この点だけ取ってみても、素のJavaで開発した場合に比べて、上述の副作用によるバグを生む余地を減らしてくれるし、コレクション操作の表現力で可読性を上げつつコードも短くできる。
次に、
- Null安全のサポート
については、オールドスタイルのJava開発で一度は必ず悩まされる NullPointerException
(NPE)に対する強力な解決策となる。
今回の最終課題では3日間かけてKotlinでAndroidアプリを作ったわけだが、その間一度もNPEにお目にかかることはなかった。
そして、
- 第一級関数が利用可能
- SAM (Single Abstract Method)変換
の組み合わせも大きい。
コールバックを数多く実装するAndroid開発において、関数をクロージャとしてオブジェクト化でき、かつSAM変換が加わることで、コード量も削減できるし、ネストも深くならず、とても相性がいい。
(参考)
Android開発を受注したからKotlinをガッツリ使ってみたら最高だった
2016年、KotlinでAndroid開発する方へ
Kotlinをプロジェクトに導入してわかったこと
(Null安全についてバズった記事)
null安全でない言語は、もはやレガシー言語だ
「Javaであること」は、開発言語としての魅力は今となっては決して大きくはないが、こういったJVM言語の登場により、より効率的、魅力的な開発環境が生まれてくることが何よりのメリットなのかも知れない。
# 最後に
本当に徒然なるままに書いてしまったので、薄い内容の割には長文になってしまった。
こんなまとまりのない記事を最後まで読んでくれた方にはお礼を申し上げたい。
同じようにサーバサイド開発メインのエンジニアがAndroid開発を始める際に、少しでも参考になるといいな。
また、Android開発の最前線を走る諸先輩方におかれましては、筆者が書いた稚拙な内容の誤りのご指摘や、感想に対するプロとしての見解などコメントいただけるとこれまたこの上ない喜びです。