モバイルDevOpsカレンダーの2日目です。
お仕事では Android を書いているので、 Android の DevOps について書こうと思います。
開発と運用の苦しみ
一日目に tnj さんが書いてくださいました通り、
モバイルの開発・運用をする上で、我々エンジニアが目指すところがあります。
全ての 人間に頼らず解決できる仕事 を生まれる前に消し去りたい。
我々開発者は、なぜか日々苦労してコードを生産しています。
そして、のちのちにそのコードのメンテナンスで苦しむ羽目になります。
苦しい思いはしたくない!!
何が我々を苦しめるんだ!?!?!
我々が書いたコード だ!!!!
となれば、そう、 メンテナンスすべきコードがなければ苦しまずに済む のです!
人間が書いて管理するコードが減ればいい!
でもプロダクトが出来上がらないとお客様に価値提供なんてできない!
収入もない、生きて行けない! どうしよう!
思い出してください。
我々はエンジニアです。
コンピューターを使えばいいのです。
人間に頼らず解決できる部分は、 コンピューターにコードを書かせてしまえばいい のです!!!
コード生成の使い所
本題です。
Javaでの自動コード生成といえば apt 1 が有名ですが、
apt を使ってコードを生成する方法については
Android Advent Calendar の 12/21 に shiraji さんが詳しく書いてくださると思うので、
その方法を適用する場所、使い所について提案します。
自分のプロジェクトのどこに自動生成が適用できて、どれくらい仕事が楽になりそうか、考えながら12/21を待ちましょう。
楽しみですね!
さて、自動生成が適用でき、効果が大きいのは以下の2パターンです。
- マッピング
- 設定値の型安全化
それぞれの詳細と、使いどころのヒントになるような具体的な事例を紹介します。
マッピング
1対1対応 や 1対n対応 を作るのに使用でき、かつ書くべきコードを減らすことができます。
具体的には、
- ある型 A から別の型 B へ変換する
- ロジックを集めて1つのロジックを形成する
など。
このパターンは人間が書いても同じようなコードが並ぶことが多いので、自動生成によって書かずに済むようにしましょう。
具体例: ButterKnife
言わずと知れた、 findViewById
をいちいち書かなくても済むようにしてくれるライブラリ。
クラスメンバに @Bind
アノテーションを付けることで、 layout の id から、任意のクラスのメンバへ のマッピングが行われます。
@OnClick
も同様に、 layout idとメソッド のマッピングを行います。
@Bind(R.id.hoge_button)
Button hoge;
...
@OnClick(R.id.hoge_button)
public void doSomething(Button b){ ... }
実際のマッピング( findViewById
とキャストをしてメンバに代入する , setOnClickListener
を書く)は、
よく見かける以下のメソッドの呼び出しで行われます。
ButterKnife.bind(this);
この裏では以下のことが起きています。
1.(あらかじめaptで、 findViewById
しまくる ViewBinder クラスを対象クラスごとに生成しておく)
2. this
インスタンスのクラス名を元に対応する ViewBinder を動的に読み込んで実行する
生成されるコードを開発者が一切意識しなくてもいいのが特長です。
(そのかわり、生成済みクラスのロードにリフレクションを使っているので、ProGuardの設定が必要です)
具体例: POJO -> Map<String, String>
私が携わっているプロジェクトでは POJO 2 に検索クエリなどを格納していて、
APIクライアント(Retrofit)に渡す際に Map<String, String>
に変換しています。
クエリの設定項目が10個以上あり手で書くのがバカらしくなったので、
変換コードを自動生成するようにしました。
@ToQueryParam
public class SearchQuery {
@Param(name = "hoge_hoge", adapter = StringToStringAdapter.class)
public String hogeHoge;
@Pram(name = "fuga_fuga", adapter = FugaToStringAdapter.class)
public Fuga fugaFuga;
...
↓
// SearchQueryConvertor クラスが生成される
Map<String, String> rawQuery = SearchQueryConvertor.convert(searchQuery);
// {hoge_hoge=xxxxx, fuga_fuga=yyyyyy} という形になる
各 POJO を変換するためのクラスが自動生成されます。
Dagger2 と同じように生成されたクラスを開発者が import する必要がありますが、
その代わりに ProGuard の設定を考える必要がありません。
(ProGuard 対策をしなくて良い方をとった理由などについては、 Roppongi.aar #2 で発表した資料があるので、そちらを御覧ください → Designing Auto Generated Codes)
設定値の型安全化
単純に設定ファイルをロードすると、パースの際に例外が出たり、 int や String などの汎用的すぎる型になっていたりして扱いに困ることが多々あります。
// 動的に設定を読むと困ることは結構ある
try {
// ファイル名、ファイルパスを書き間違えるかも?
// 読み込んだデータの形式は? JSON? CSV? YAML?
// 文法ミスがあってパースに失敗したら?
// "100" <- これどうやって扱う? いちいち Integer.valueOf() 使う?
String settings = loadSettings("./path/to/very_important_setting.pro");
} catch (Exception e) {
// もし読み込めなかったらどうするの? デフォルト値は? どこに書く?
}
// 生じる例外が RuntimeException だと try-catch で囲んでなくてもコンパイル通るけど、
// 実行時に本当にそこ例外出ない? 大丈夫?
そこで、あらかじめ必要な情報を収集し、適切なデータ型にすることで型安全に値を扱えるようにできる、というわけです。
クラッシュする可能性がある箇所を減らすことで「落ちないアプリ」になり、品質向上に役立ちます。
// 設定や環境情報から、こういうクラスが出来上がってくれると、↑の問題が解決する
public final class VeryImportantSetting {
public static final int HOGE = 100;
public static final String FUGA = "String Value";
...
もし設定ファイルに文法のミスがあってもコンパイル中にエラーが出るので、ストアに apk を上げてしまう前にミスに気付くことができます。
また、あらかじめ設定値を Java の定数とすることで、設定をロードするコードを書かなくても済みます。
(もちろん、動的に設定を変えることが難しくなるので、ビルド時に確定する設定に限られます)
具体例: R.java, BuildConfig.java
共に Gradle の Android Plugin によって自動生成されるjavaファイルです。
R.java
は res
ディレクトリ内のファイル名や XML 要素の ID などに、一意の整数を割り当て public static final
なフィールドにしたものです。
これによって、 R.id.hogehoge
が特定の View 要素や Drawable のファイルを特定したりできるようになっています。
BuildConfig.java
では BuildConfig.DEBUG
や BuildConfig.VERSION_NAME
など、
build.gradle
で宣言した内容が public static final
なフィールドとして参照できるようになっています。
具体例: Genum
拙作の、YAML ファイルから enum のソースコードを生成するライブラリです。
まだ Gradle Plugin がありませんが、近いうちに作る予定です…!
サーバーとクライアントで共通の定数値を使う必要がある場合、
サーバーの設定ファイルからクライアントでも使用可能な定数を生成できます。
まとめ
自動コード生成の使い所を、二つ紹介しました。
- マッピング
- 設定値の型安全化
どちらも人間がコードを書かないことで開発が楽になったり、品質向上ができたりする箇所です。
コンピューターに仕事を任せて、上手に仕事を減らしましょう。
そして定時にはダッシュで帰りましょう!!
おまけ: コード自動生成以外でコードを減らす方法
java.lang.reflect.Proxy を使って動的に処理を決める
Ruby の method_missing
みたいなことをやる方法です。
Javaでは全く定義のされていないメソッドを呼びだすことはできないので、
interface でメソッドの形だけを作っておいて、実装は動的に生成する、ということをやります。
具体例: Retrofit
言わずと知れた、便利なAPIクライアントメーカーです。
interface のメソッドとREST APIをアノテーションで対応させておくと、
REST APIにアクセスするメソッドを自動的に作ってくれます。
public interface GitHubService {
@GET("/users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.build();
GitHubService service = retrofit.create(GitHubService.class);
実はコード生成はしておらず java.lang.reflect.Proxy を使っていて、
叩かれたメソッドに付いているアノテーションで指定されたAPIへリクエストを飛ばす仕組みになっています。
apt と置き換えることもできます。
どちらがいいかは場合と好みによるようです。
地道にリファクタリング
「やっぱりこれかよ!」と思った方、ごめんなさい。
どんなライブラリやテクノロジをつかったところで、簡単なことを難しく書いているようではコードの量も質も上がりません。
難しいところは難しく書く以外にないのですが、 簡単なことを簡単に書く を心がけることでソースコードは減ります。
(既存コードでされていたわけのわからない処理を1週間かけて解きほぐしてリファクタリングしたところ、1000行減ったこともありました。
逆に言えば、簡単なことを難しく書いてしまうと、メンテナーにそれだけのダメージが行くということです )
ソースコードが減れば管理するものの量が減るので、問題が起きる可能性も低くなり、幸せになることができます。
まずはツールでもなんでも使ってコードを減らしましょう。
そして、あとは人間が簡単なことを簡単に書けば、メンテナンスしやすいコードになります。
願わくば、あなたとあなたの後任が、楽しくメンテナンスできますように…!!
明日は kiy0p0n がDeplygateコマンドについて書いてくれるそうです。