どうも。Androidエンジニアの谷口です。
これはGoodpatch Advent Calendar 2015 の16日目の記事として投稿しています。
昨日は @katsuhisaishii さんの「iOSでDynamic Typeを使ったときのFontのWeightとサイズの一覧」でした。
はじめに
今年のAndroidについて。
今年はAndroidアプリ開発者としてどんな年でしたか?
Material Designが発表されてから1年以上経ち、今年は具体的にアプリに適用していった年になったかと思います。大変だった!とか、新しいデザインに触れることができて楽しかった!などいろいろあったのではないでしょうか。
Androidにもやっとデザインのガイドラインと呼べるべきものが発表され(発表直後はsupport libraryもなくしんどかったですが)、新しいAndroidの可能性に触れることができた、変化の激しい年だったと思います。
GoogleからリリースされるMaterial Design対応の公式アプリがどういうものになるのかずっとずっと楽しみに待ち続けて、Google I/O Android appがリリースされた時はうれしかったのを覚えています。
その後予想していたよりもずっと早く他の公式アプリが連続してアップデートされ、UIチェックが毎回楽しみで仕方ありませんでした。
今回は...
開発するときにいつも迷ったり困ったりしていることを、少しみなさんと共有できたらと思っています。
それは パッケージ構成とクラス名などの命名について です。
開発するとき、いつもパッケージ構成やクラスや変数名、ViewのIDについてとてもとても迷います。
1人で開発しているときはいいのですが(よくない)、チームで開発となると各メンバーの開発経験や、ヘタすると好みなどに左右されて統一感のないアプリが誕生してしまいます。メンテきついです。
ということで、今回はGoogle I/O 2015 Android Appがどういう構成になっているのか、読んでみることにしました。命名方法などについて「そんなの当たり前やん」と思う方もいらっしゃるかと思いますが、再確認という意味も込めて読んでいただければと思います。
Google I/O 2015のソースコードはどこにあるの?
Google I/O 2015 Android Appのソースは以下で確認することができます。
https://github.com/google/iosched
公式のソースコードを公開してもらえるのは、本当にありがたいですね。
ちなみに
GithubにあるGoogle Samplesにたくさんのソースが公開されているので、
この中からピックアップしてソースを読んでみるのも勉強になると思います。
https://github.com/googlesamples
Google I/O 2015を読んでみよう。
パッケージ構成
おおまかに気になるところだけピックアップしてみました。
...
src/main
|--- assets --- licenses.html (オープンソースライセンス画面)
|--- java
|--- /com/google/samples/apps/iosched
|--- myschedule (私のスケジュール画面関連)
|--- explore (探索画面関連)
|--- settings (設定画面関連)
|--- social (ソーシャル画面関連)
|--- about (詳細画面関連)
|--- ui (UI関連)
|--- util (ユーティリティ関連)
|--- videolibrary (ビデオライブラリ画面関連)
|--- AppApplication.java (アプリケーションクラス)
|--- Config.java (定数関連)
画面の観点で見てみると、画面1つにパッケージが設定されていて、その中にFragmentやActivityが格納されています。
uiというパッケージもあって、その中にActivityがあることも確認できました。
もし小さなチームであれば、どちらかに統一したいところです。
カスタムView関連
com.google.samples.apps.iosched.ui.widget
このパッケージにまとめて配置されていました。
定数関連
com.google.samples.apps.iosched.Config.java
定数の管理方法もよく迷うことの1つです。
もちろん個別の機能に関連する定数は、わざわざ1箇所で管理する必要はないと思います。
ユーティリティ
パッケージ
com.google.samples.apps.iosched.util
このパッケージに機能ごとに分けてXxxUtils.javaというクラスが複数配置されています。
機能をまたいで使いたい処理はうまく共通化したいですね。単体テストも書きやすいですし。
クラス名
Applicationクラス
Googleは、
public class AppApplication extends Application {
私は、
public class アプリ名Application extends Application {
というふうにしています。
データ保持クラス
機能によって、いろいろな名前がつけられていました。
- XxxData
- XxxHolder
- XxxGroup
- データ名
※余談
みなさん、もうデータ保持するだけのクラスでsetter/getterって使ってないですか?
- 一昔前のBeansと呼ばれるタイプ
public class ItemGroup {
private String mTitle;
private String mId;
private ArrayList<SessionData> sessions = new ArrayList<SessionData>();
public void addSessionData(SessionData session) {
sessions.add(session);
}
/**
* Trim the session data to {@code sessionLimit} using the
* {@code random Random Number Generator}.
*/
public void trimSessionData(int sessionLimit) {
while (sessions.size() > sessionLimit) {
sessions.remove(0);
}
}
public String getTitle() { return mTitle; }
public void setTitle(String title) { mTitle = title; }
public String getId() { return mId; }
public void setId(String id) { mId = id; }
public ArrayList<SessionData> getSessions() { return sessions; }
}
- setter/getterなし
public class Marker {
public String id;
public String type;
public float lat;
public float lng;
public String title;
}
最近は後者の setter/getterなし で書いています。
レイアウトXML
XMLファイル名
Activity/Fragment
- パターン1
activity_xxx.xml / fragment_xxx.xml
- パターン2
xxx_activity.xml / xxx_fragment.xml
ファイルソートでActivity関連、Fragment関連がきれいに並ぶのでパターン1が好きです。
リストアイテム
list_item_xxx.xml
これは定番で言及するまでもないですかね...。
ViewのID
2パターン見られました。
- 役割 + Viewクラス名
<TextView
android:id="@+id/profile_email_text"
- 役割のみ
<TextView
android:id="@+id/name"
役割 + Viewクラス名 のほうがわかりやすく、変数も同じようにすることで可読性が上がるのではと考えます。
真似したいと思ったところ
ログ出力用のタグ文字列生成
ユーティリティ LogUtils
で、以下のように定義してあります。
public static String makeLogTag(String str) {
if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
}
return LOG_PREFIX + str;
}
/**
* Don't use this when obfuscating class names!
*/
public static String makeLogTag(Class cls) {
return makeLogTag(cls.getSimpleName());
}
使う側のクラスでは、まずstaticインポートした後に、
import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
このように書いてありました。
private static final String TAG = makeLogTag(MyScheduleActivity.class);
ログ用タグは、各クラスで毎回書くもの、かつ統一したいので、このやり方は真似したいなーと思いました。
例外の変数名
} catch (Exception ignorable) {
これも定番かもしれませんが、昔コメントで
// do nothing...
というように書いていた時期が私にはありました。変数名が ignorable
だとコメントも省略できるし、チームメンバーも「わざと無視しているんだな」と理解しやすいです(例外を握りつぶしていいという話はしていません)。
まとめ
品質の高いアプリをチームとして開発し保つためにどうすればいいのか、とても挑戦しがいのあるテーマだと思います。コード規約など作ってガチガチにしばるか、実装担当者の経験にまかせるか...。やっぱりその中間くらいがよいのかなと思います。個人的にはガチガチにしばられるのは息苦しくて、創造性に欠ける気がします。何事もバランスが重要ですよね。
永遠に答えの出ないテーマかもしれませんが、メンバーが経験してきた中でのベストプラクティスをチームで共有し、再構築していくのは楽しく思います。みなさんの意見を聞かせていただけたらうれしいです!
今後はもっとAndroidのインタラクションの方にも注力できたらなと思っています。
明日は @hi6484 さんです。お楽しみに。
Happy hacking!