はじめてのしゅうだんかいはつ あるいはAndroidの暗黙知のこと
先月ぐらいからバイトでAndroidのプログラムを書いている。
結構、言葉になってない暗黙知がたくさんあったので、その経験として、メモを残す。プログラミングそのものの話も含む。
三人で分担して開発していた。
分担
自分: Android開発初心者。プログラミングはある程度慣れてる。ロジックとネットワークとバックグラウンド処理
A : Android開発初心者。iPhoneアプリ開発経験ありだが、プログラミングは慣れてない。AndroidのUIデザイン担当
B : サーバー・データベース担当。PHP結構出来る。
問題。このバイト先ではAndroid開発経験者がいない。スキル的にも自分より上の人が最初はいなかった。途中で入ってきたスキルがある人がいたが、このプロジェクトには関わっていない。というわけで、自分とAがほぼゼロから開発してきたことになる。
かなり時間がかかっていて、あまり開発効率がよかったとは言えない。あくまで経験として自分は割り切っていた。社長はそうじゃないっぽいが。まあそのうちリリースする。
BとはネットワークでHTTPでJSONをもらうだけの関係。最初にアクセス用のクラスを書いて、あとはそのテストコードを定期的に確認していた。自分はアクセスログだけ見せてもらっている。このクラスはAndroidに依存しなかったので、Groovyでテストを書いた。
で、自分とAが結構密接に連携してて、Bがほとんど初心者だったので、彼の描いたコードを自分がリファクタリングしたり、いろいろ口をだしていた。
とはいっても、ちなみに自分もAndroid開発経験がはじめての初心者なのだけど、まあコードを書いてきた経験はあるので、それ任せで力押しの開発だった。Java、大学の授業で習って以来だった。
とにかくフィールドメンバを減らす コードを短くする
共同開発だと、どこに触れてどこに触らないかを明確にする必要がある
「全部フィールドにすると便利」「全部staticだと便利」は甘え。なんでもかんでもpublic staticにしない。挙動を変えない限りで、ひとつひとつフィールドメンバから落としていく。
クラスのstaticメンバにするとしたら、初期化をきっちりと行う。アクセス時にnullであるとエラーが起こる。フィールドメンバの初期化だけはきっちり。
フィールドメンバは、フィールドメンバにする必要性が生じたときに、はじめてフィールドメンバにする。
自分の開発経験的に、Java自身への習熟で、どれだけフィールドメンバを減らせるか、短く簡潔なコードにできるか変わっていった。基礎が大事。
同じコードを二度書かないという強い決意
共通に何度も出現するルーチンを関数に押し込める。短いコードほど潜在的なバグが減る。
短くすることが目的になってはならないが、それでも短いコードを書くという強い意思が必要。
コピペを自分の色に染めること
ネットから拾ってきたコードは、独立して動くように書き換える。外部に依存する部分をコードを切り落とす。
フィールドメンバは可能な限り除去する。ネットに転がってるJavaのサンプルコードは、不必要なフィールドメンバがあることが多い。
中身の挙動がわからなくても、副作用がないように、メソッドの中に押しこんで抽象化してしまう。
ダミーデータ
多少面倒でも、ダミーデータを最初に準備しておくと開発が進む。そしてダミーデータの形式をきっちりクラスで決めしまうといい。
サーバー側が常に正しい挙動をしているとは限らない。サーバー側がCakePHPだったっぽいのだけど、何かの動作を試すと何かのControllerエラーで頻繁にバグってた。
今回のプロジェクトはサバクラ型なので、ダミーデータのjsonを吐くようにしたので、サーバー側が落ちてても開発が進んだ。
しかしダミーデータに固執してはいけない。将来的に必要な拡張の余地は残しておく。
で、実際のどうなるか
共通パーツに切り離せないものは、無名クラスに押し込んでしまえばいい、って話になった。
非同期でTwitter#getTimeline(int uid)という操作でJSONObjectを取得したいとする(実際はそんなコードではないけど、サンプルとして)
class TwitterTimelineView extends Activity{ //... private LinearLayout mainw; private ProgressDialog dialog; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mainw = new LinearLayout(); dialog = new ProgressDialog(this); setContentView(mainw); } public void onStart(){ super.onStart(); final int uid = 321431; new AsyncTask<Integer,Integer,JSONObject>() { protected void onPreExecute() { dialog = MainActivity.dialog; dialog.setTitle("Now Loading..."); dialog.show(); }; protected JSONObject doInBackground(Integer... params) { return Twitter.getTimeline(uid); // 時間がかかる操作 } protected void onPostExecute(JSONObject json) { Log.d("DEBUG","AsyncTask "+json.toString()); mainw.addView( getTimelineView(json)); dialog.dismiss(); } }.execute(); } private LinearLayout getTimelineView( JSONObject json ){ // jsonのデータを分解してパラメータをセットしたviewをつくる return linearLayout; } }
- onStartなのは遷移してくるたびに更新したいから
- mainw がフィールドメンバなのは、初期化用にとりあえず見せておき、非同期タスクからonCreate以外の名前空間から触る必要があるため。
- final int uid で引数を間接的に受け渡す。(実際のプロジェクトにはPreferenceから読み込んでいる)
- 今回の非同期操作は使いきりなので、AsyncTaskを無名クラスで宣言して、そのままexecuteしてしまう。これをクラスにしたりすると、無駄にクラスが増えてややこしくなる。
- もし非同期のアラート画面を自作したり、細かなオプションを付けて何度も使うのなら、クラスとして独立させる。
- 非同期タスクを飛ばす前に、非同期タスクから触れるフィールドメンバが、必ず初期化されていることを確認する。
- getTimelineView では、フィールドメンバにアクセスしない。極端な話フィールドメンバをnullで埋める可能性がある場合、あとの動作が動く保証がない。
- 正直mainwという名前は良くないかな…
「ほとんど共通パーツで部分が違うもの」をメソッド化する。
必要以上にファイルを増やさない。増やしすぎると、どこにアクセスしていいかわからない。
連番ファイルはやめる。不必要にlayoutのxmlを増やさない。
Myなんとかって、テストコードじゃないんだからそういう意味が分からない名前は付けない。
デバッグ用のLogは便利だが使い終わったら破棄する。放置すると、どこでのLogなのかわからなくなる。それでタグを増やしたりすると本末転倒。
Android特有の問題
なにのコンストラクタか、どのApplicationContextか、で初期化されてるものが変わってくる。thisとは何か?を常に意識する。
デバッグするときは、何が初期化されてて、何が初期化されてないかを常に気にする必要がある。
テストコード
最初にテストコードを書くべきだったとは思うが、手探りの開発の場合、そもそも前が見えないので、テストコードに縛られると、それに縛られて開発が停滞する。そもそもテストコードなんて書けないと思われる。
見通しがたった時点で、中途でテストコードを書く習慣をつけること。
git
開発メンバー誰もgitに慣れてなかった。
git自身の操作に振り回されるので、gitの練習と割りきって、手動でバックアップしつつmergeに挑むこと。merge失敗しても泣かない。出来る限りコンフリクトを減らしたいので、変更分が少ないときは目視でmergeしていた。あんまりgitの恩恵を受けていない…。次のプロジェクトではRedmineを使う。
まとまってないけど、これはペアプロで話していた内容を思い出して、そのまま頭の中身を吐き出したもの。
というわけで、今回のプロジェクトはテスト的な意味合いが強かったので、次に生かせるように、自分も含めてできるだけアジャイルな環境が作れるような種を蒔いた。
でも、これ以上Android触れたい気分じゃない。まだこのプロジェクト終わってないけど!