これは Android Open Source Project にある Contribute > Code Style Guide(2009-01-04 時点)の日本語訳です。
最終更新: 2010-05-21
※ こちらはすでに古いバージョンです。最新版は コントリビュータのためのAndroidコードスタイルガイドライン 日本語訳 にあります。
ここに挙げたルールはガイドラインや推奨という位置付けではなく、厳格な決まり事です。そのため、 ここに挙げたルールを無視してはいけません。ただし、"need-to-use" の原則にのっとり認められた場合は、この限りではありません。
既存のコードのすべてがこれらのルールに従っているわけではありませんが、新しく書いたコードはすべてこれらのルールに従うべきです。
私たちは標準のJavaコーディング規約に従いますが、いくつかルールを追加しています。
AndroidのJavaライブラリおよびツールにも規約があります。時として規約が大きく変更されて、以前書いたコードが推奨されないパターンやライブラリを使うことになるかもしれません。このようなコードで作業するときには、これまでのスタイルを使い続けても構いません(一貫性を参照)。ただし、新しくコンポーネントを作る場合には、推奨されないライブラリを使ってはいけません。
すべてのファイルのスタイルに一貫性があると、プログラムは非常にメンテナンスしやすくなります。私たちは Sun が策定した Code Conventions for the Java Programming Language に書かれている標準のJavaコーディングスタイルに、いくつか例外と追加を加えたものに従います。Sun のスタイルガイドは幅広い範囲にわたって詳細な説明があり、Javaコミュニティでは広く使われているものです。
この Sun のスタイルガイドに加えて、私たちは次のルールに従います。
m で始めてください。スタティックフィールドは s で始めてください。XmlHttpRequest、getUrl() など。void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
}
}
これは絶対にやってはいけないことです。「こんなエラーが起こるわけない」とか「このエラー処理はそんなに大事ではない」と考えたのかもしれません。でも、このように例外を無視していると、いつか誰かがつまずいて、あなたのコードにある地雷を踏んでしまいます。信念をもって、コードにあるすべての Exception を処理する必要があります。どのように処理するかは、場合によって違ってきます。
空のcatch節を見たときには必ず気持ち悪いと思わなきゃいけないよ。最終的には、そうするのが正しい場合もあるだろう。たとえそうだとしても、少なくとも検討はしなきゃならない。Javaではこうした気持ち悪さから逃れることはできないよ。
例外を無視する代わりに、次のような手段をとることができます(以下、望ましい順に)。
void setServerPort(String value) throws NumberFormatException {
serverPort = Integer.parseInt(value);
}
void setServerPort(String value) throws ConfigurationException {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("Port " + value + " is not valid.");
}
/** Set port. If value is not a valid number, 80 is substituted. */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
serverPort = 80; // default port for server
}
/** Set port. If value is not a valid number, die. */元の例外が RuntimeException のコンストラクタの引数に渡されていることに注意してください。例外をラップするという考え方はとても役に立ちますが、このコードはJava 1.4以降でしか動きません。もしJava 1.3でもコンパイルする必要があるなら、元の例外を省略する必要があります。
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new RuntimeException("port " + value " is invalid, ", e);
}
/** If value is not a valid number, original port number is used. */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
// Method is documented to just ignore invalid user input.
// serverPort will just be unchanged.
}
}
try {
someComplicatedIOFunction(); // may throw IOException
someComplicatedParsingFunction(); // may throw ParsingException
someComplicatedSecurityFunction(); // may throw SecurityException
// phew, made it all the way
} catch (Exception e) { // I'll just catch all exceptions
handleError(); // with one generic handler!
}
このようにすべきではありません。ほとんどの場合、汎用の Exception や Throwable をキャッチするのは適切ではありません。しかも、できるだけ Throwable を使わないのが望ましいです。Throwable には Error 例外も含まれるためです。これは非常に危険です。予期せぬ例外(ClassCastException といった RuntimeException も含みます)が起こると、結局、アプリケーションレベルにあるエラー処理でキャッチすることになります。こうすると、誰がその失敗を処理するのかわかりにくくなります。また、もし誰かがあなたの呼び出しているコードに新しい型の Exception を追加しても、コンパイラは別のエラー処理が必要になったことを教えてはくれません。たいていの場合、異なるタイプの例外を同じ方法で処理しようとすべきではありません。
めったにないことですが、このルールには例外があります。テストコードやトップレベルのコードの場合、あらゆるタイプのエラーをキャッチしたいことがあります。エラーがUI上に現れるのを防いだり、バッチ処理を継続するためです。このような場合には、汎用の Exception(あるいは Throwable)をキャッチして、エラーを適切に処理しても構いません。ただし、そうする前に、よく注意して考えましょう。そして、なぜそこでそうしても安全なのか、理由を説明するコメントを付けておくべきです。
汎用の Exception をキャッチする代わりに、次のような手段をとることができます。
定義: ファイナライザとは、オブジェクトがガベージコレクトされるときに実行されるコードのことです。
賛成: クリーンナップするときに便利です。リソースが外部にあるときには特に便利です。
反対: ファイナライザがいつ呼び出されるか保証はありません。そもそもまったく呼び出されないことさえあるのです。
結論: ファイナライザを使ってはいけません。うまく例外処理することで、ファイナライザでやりたいことはたいていできます。どうしてもファイナライザが必要であれば、close() メソッド(のようなもの)を定義して、そのメソッドをいつ呼び出す必要があるのか、ドキュメントに明記しておきましょう。例えば、InputStream を見てください。この場合、ログがあふれそうにない限りは、ファイナライザで簡潔なログメッセージを出力するのはよいですが、必ずしも必要ありません。
ひとつ例外があります。X.assertTrue() を呼び出すだけであれば、ファイナライザを書いても構いません。
定義: パッケージ foo にあるクラス Bar を使いたいとき、2つのインポート方法があります。
import foo.*;import foo.Bar;#1 に賛成: インポート文の数を減らせます。
#2 に賛成: 実際にどのクラスが使われているのかが明確になります。
結論: Androidでコードをインポートするときは、必ず #2 のスタイルを使ってください。ただし、Java標準ライブラリ(java.util.*, java.io.* など)とユニットテストコード(junit.framework.*)についてはその限りではありません。
すべてのファイルの先頭には、まずコピーライト文を入れておくべきです。その後に、package 文と import 文を続けましょう。各ブロックの間は1行空けておいてください。その後に、クラスやインタフェースの宣言を書きましょう。Javadoc コメントには、そのクラスやインタフェースが何をするものなのかを書いてください。
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.foo;
import android.os.Blah;
import android.view.Yada;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Does X and Y and provides an abstraction for Z.
*/
public class Foo {
...
}
クラスと複雑なパブリックメソッドにはすべて、少なくともそのクラスやメソッドが何をするものかを説明する一文を Javadoc コメントとして入れなければなりません。コメント文は三人称の説明的な動詞で始めるべきです。例:
/** Returns the correctly rounded positive square root of a double value. */
static double sqrt(double a) {
}
/**
* Constructs a new String by converting the specified array of
* bytes using the platform's default character encoding.
*/
public String(byte[] bytes) {
}
setFoo() といった簡単な get/set メソッドの場合、「Fooを設定する」というだけの Javadoc を書く必要はありません。メソッドがもっと複雑な場合(制約があったり、重大な副作用があるなど)には、それについて明記しておかなくてはなりません。また、もしプロパティ "Foo" が何を意味しているのかわかりにくければ、それも説明しておくべきでしょう。
コードを書くときには、そのメソッドがパブリックであろうとなかろうと、すべてのメソッドに Javadoc を書いておくと役に立ちます。パブリックメソッドは API の一部なので、Javadoc を書かなくてはいけません。
現在のところ、Android では Javadoc コメントの書き方について何も強制していませんが、Sun Javadoc conventions に従うべきです。ローカル変数は初めて使うところで宣言すべきです。ほとんどの場合、ローカル変数の宣言はイニシャライザに入れておくべきでしょう。まだ変数を初期化するのに十分な情報がない場合には、その情報が得られてから宣言すべきです。
try-catch 文については、このルールから除外されます。
メソッドの戻り値によって変数を初期化しようとしていて、そのメソッドがどこかでキャッチされる例外を投げる可能性があるなら、この変数は try ブロックのなかで初期化しておかなくてはいけません。もしその値を try ブロックの外でも使う必要があるなら、try ブロックの前に宣言しておかなくてはいけません。ただし、この時点ではまだきちんと初期化できていないことに注意しましょう。
// Instantiate class cl, which represents some sort of Set
Set s = null;
try {
s = (Set) cl.newInstance();
} catch(IllegalAccessException e) {
throw new IllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
throw new IllegalArgumentException(cl + " not instantiable");
}
// Exercise the set
s.addAll(Arrays.asList(args));
この場合でも、try-catch ブロックをメソッドのなかにカプセル化することで回避できます。
Set createSet(Class cl) {
// Instantiate class cl, which represents some sort of Set
try {
return (Set) cl.newInstance();
} catch(IllegalAccessException e) {
throw new IllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
throw new IllegalArgumentException(cl + " not instantiable");
}
}
...
// Exercise the set
Set s = createSet(cl);
s.addAll(Arrays.asList(args));
よほどの理由がない限り、ループ変数は for 文のなかで宣言すべきです。
for (int i = 0; i < n; i++) {
doSomething(i);
}
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomethingElse(i.next());
}
IDE の設定とうまく合うよう、次のようにインポートすべきです。
もともとは、インポートの順序についてのスタイルはありませんでした。これは、常に IDE が順序を変えるのに任せるか、さもなくば、IDE にある自動のインポート管理機能を無効にして手動でインポートを管理しなければならない、ということを意味します。これはひどい話のように思えました。java-style が尋ねられると、望ましいスタイルはすべてここにあることでした。結局「順序をひとつ選んで一貫性をもたせる」ことが必要になりました。そこで、私たちはスタイルをひとつ選んで、javaguideをアップデートし、IDEがそれに従うようにしました。私たちが期待しているのは、IDEのユーザがコードを書いているうちに、余計なエンジニアリング作業をせずとも、すべてのパッケージがこのパターンに沿ってインポートされることです。
私たちの選んだスタイルは次のようなものです。
ほとんどの人がこれを優先度の低い問題だと考えているので、各自の判断にまかせることにしました。ただし、一貫性をもたせるようにしてください。
ブロックのインデントにはスペース4つを使ってください。タブを使ってはいけません。迷ったときには、まわりにあるコードを見て一貫性をもたせてください。
関数呼び出しと引数を含んだ行の折り返しは、スペース8つでインデントしてください。例えば、これは正しいインデントです。
Instrument iこれは正しくないインデントです。
= someLongExpression(that, wouldNotFit, on, one, line);
Instrument i
= someLongExpression(that, wouldNotFit, on, one, line);
m で始めてください。s で始めてください。例:
public class MyClass {
public static final int SOME_CONSTANT = 42;
public int publicField;
private static MyClass sSingleton;
int mPackagePrivate;
private int mPrivate;
protected int mProtected;
}
中括弧で行を始めてはいけません。中括弧はその直前のコードと同じ行に置いてください。つまり、次のようになります。
class MyClass {
int func() {
if (something) {
// ...
} else if (somethingElse) {
// ...
} else {
// ...
}
}
}
条件文には中括弧を付けてください。ただし、条件文全体(条件と本体)が1行に収まるのであれば、中括弧を付けなくても構いません(そうしなければならないわけではありません)。つまり、これも許されます。
if (condition) {
body; // ok
}
if (condition) body; // ok
でも、これは許されません。
if (condition)
body; // bad
コードにおけるテキスト行は、せいぜい100文字までにするべきです。
このルールについてはかなり議論があったのですが、結局は最長100文字のままとなりました。
例外: コメント行に100文字以上のコマンド例やURL文字列が含まれる場合には、100文字を超えても構いません。コピー&ペーストしやすくするためです。
例外: インポートの行はこの制限を超えても構いません。ただし、めったにないでしょう。この方がツールを書くのも簡単になります。
アノテーションは同じ言語要素にある他の修飾子よりも前に置くべきです。単純なマーカーアノテーション(例えば @Override)であれば、言語要素と同じ行に並べても置いても構いません。アノテーションが複数あったり、パラメータ付きのアノテーションの場合には、アルファベット順に1行ずつ並べるべきです。
Java 1.5 で定義された3つのアノテーションについて、以下が Android 標準のプラクティスです。
@Deprecated@Deprecated アノテーションを必ず付けなければなりません。@Deprecated アノテーションを使うときには、@deprecated Javadoc タグも付けなければなりません。そして、その代わりに使う実装について説明しておくべきです。@Deprecated メソッドはまだ動作がサポートされていることも意味していることを忘れてはいけません。
もし @deprecated Javadoc タグが付いた古いコードがあれば、@Deprecated アノテーションを追加しておきましょう。
@Override@Override アノテーションを必ず付けなければなりません。
例えば、もし {@inheritdocs} Javadoc タグを使っていて、クラス(インタフェースではなく)から派生しているときには、メソッドが親クラスのメソッドをオーバーライドしていることを示すために @Override アノテーションを付けなければなりません。
@SuppressWarnings@SuppressWarnings アノテーションは、どうしても警告を取り除けない事情がある場合にのみ使うべきです。警告を「取り除けない」と判断したときには、@SuppressWarnings アノテーションを付けなければなりません。すべての警告がコード上にある実際の問題を反映できるようにしておくためです。
@SuppressWarnings アノテーションが必要になったときには、その直前に TODO コメントを入れて、その警告を「取り除けない」事情について説明しておかなくてはいけません。こうすることで、やっかいなインタフェースをもつ問題のあるクラスだということがわかります。例:
// TODO: The third-party class com.third.useful.Utility.rotate() needs generics
@SuppressWarnings({"generic-cast"})
List<String> blix = Utility.rotate(blax);
@SuppressWarnings アノテーションが必要になったときには、コードをリファクタリングして、アノテーションが適用されるソフトウェア要素を他から分離すべきです。
頭字語や略語は単語として扱ってください。その方が読みやすい名前になるためです。
| よい | わるい |
XmlHttpRequest |
XMLHTTPRequest |
getCustomerId |
getCustomerID |
頭字語や略語そのものが名前の場合にも、このスタイルが適用されます。
| よい | わるい |
class Html |
class HTML |
String url; |
String URL; |
long id; |
long ID; |
頭字語について、JDK と Android のコードベースはいずれも、まったくと言っていいほど一貫性がありません。そのため、まわりのコードとの一貫性をもたせることは事実上不可能です。それでもグッとこらえて、どうか頭字語を単語として扱ってください。
このスタイルルールについて、もっと理由が必要であれば、 Effective Java Item 38 と Java Puzzlers Number 68 を読んでください。
コード中の TODO コメントは、一時的なものや、短期的な解決策、その場しのぎで完全ではないところなどに使いましょう。
TODO にはすべて大文字の "TODO" にコロンを続けたものにすべきです。
// TODO: Remove this code after the UrlTable2 has been checked in.
// TODO: Change this to use a flag instead of a constant.
もし TODO が「将来の日付に何かする」という形式であれば、具体的な日付が入っているか(「2005年11月までに修正」など)、もしくは具体的なイベントが入っているか(「すべての製造ミキサーがプロトコルV7を理解できるようになればこのコードを削除する」など)を確認しましょう。
最後に考えること: 一貫性をもたせましょう。コードを書いているなら、数分かけてまわりのコードを調べて、スタイルを決めましょう。もし if 節のまわりに空白があれば、あなたもそうすべきです。もしスターで囲まれた小さなボックス型のコメントが使われていれば、あなたのコメントもそうしましょう。
スタイルガイドラインがあるということは、コーディングに共通のボキャブラリがあるということです。これがあるおかげで、どうやって話すのかではなく、何を話すのかに全員が集中できるようになります。このガイドラインでは、全般的なスタイルに関するルールを説明しました。これでボキャブラリについてはわかったはずです。ただし、ローカルなスタイルも重要です。あなたの追加したコードが、まわりにある既存のコードと全然違って見えてしまうと、コードを読む人は不連続さを感じて、リズムが狂ってしまいます。これは避けましょう。
ログ記録は必要不可欠なものですが、適度に簡潔にしておかないとパフォーマンスにかなり悪影響を与えて、すぐに役立たずなものになります。ログ記録ファシリティには5つのレベルがあります。それぞれのレベルをいつどのように使うべきか、以下に説明します。
if (LOCAL_LOG) ブロックや if (LOCAL_LOGD) ブロックで囲んでおく必要があります。この LOCAL_LOG[D] はクラスやサブコンポーネントで定義され、こうしたログをすべて無効にできるようになっています。
そのため、if (LOCAL_LOG) ブロックにはアクティブな論理を入れてはいけません。ログのための文字列構築も if (LOCAL_LOG) ブロックのなかに入れておく必要があります。もし if (LOCAL_LOG) ブロックの外側で文字列構築しようとしているなら、ログ記録の呼び出しをメソッド呼び出しにリファクタリングすべきではありません。まだ if (localLOGV) を使っているコードもあります。これもまだ使えますが、名前付けは Android の標準に従っていません。
if (LOCAL_LOGV) ブロック(あるいは同等のもの)で囲んでおくべきです。こうすることで、デフォルトではコンパイルされなくなります。ログに関する文字列構築はリリースビルドに入らないよう、if (LOCAL_LOGV) ブロックのなかに入れておく必要があります。
注意: VERBOSE レベルを除いて、可能な限り、エラー報告はモジュール内で一度だけにすべきです。モジュール内の一連の関数呼び出しでは、いちばん内側にある関数だけがエラーを返すべきです。そして、同一モジュールにある呼び出し元がログを追記するのは、問題を切り分けるのに非常に有益なときにだけにすべきです。
注意: 複数のモジュールが連携している場合には、上位レベルのモジュールからやってきた不正なデータを、下位レベルのモジュールで検出することがあります。このときには、VERBOSE レベルを除いて、下位レベルのモジュールでのみ、その状況を DEBUG ログに記録すべきです。しかも、ログ記録するのは、呼び出し元からはわからない情報がある場合にのみにしましょう。具体的に言うと、例外が投げられたり(例外には関連する情報がすべて含まれているはずです)、ログ記録する唯一の情報がエラーコードに含まれている場合には、ログに記録する必要はありません。このことは、フレームワークとアプリケーション間のやりとりでは特に重要になります。サードパーティ製アプリケーションに原因があり、フレームワークがそれをうまく処理する場合には、DEBUG レベルよりも高いレベルでログ記録すべきではありません。モジュールやアプリケーションでは、同レベルあるいは下位レベルからやってきたエラーを検出したときにのみ、INFORMATIVE レベル以上でログ記録しましょう。
注意: 通常でも、ログを記録する必要のある状況が何度も発生する可能性があるときには、同じ(あるいはよく似た)情報でログがあふれないよう、ログ記録を制限する仕組みを実装するとよいでしょう。
注意: ネットワークの切断はよくあることなので、あらかじめ十分想定しておきましょう。特別な理由がない限り、これをログに記録すべきではありません。アプリケーションに重大な影響を及ぼすようなネットワークの切断は、DEBUG レベルか VERBOSE レベルでログ記録しておくべきです(リリースビルドでもログ記録しておくほど結果が深刻であり想定外であるかによります)。
注意: ファイルシステムへのアクセスあるいはサードパーティ製アプリケーションの動作によって、ファイルシステムがいっぱいになったとき、この状況を INFORMATIVE レベル以上でログ記録すべきではありません。
注意: 信頼できないソース(共有ストレージにあるファイルやネットワーク経由のデータも含みます)から不正なデータがやって来ることは十分想定されることであり、たとえ不正なデータを検出しても、DEBUG レベルよりも高いレベルでログ記録すべきではありません。(できるだけログを記録するするのを限定すべきです)。
注意: String オブジェクトに対して '+' 演算子を使うと、知らないうちにデフォルトのバッファサイズ(16文字)をもつ StringBuilder オブジェクトと、ごくわずかな一時的な String オブジェクトが生成されていることを覚えておきましょう。 つまり、デフォルトの '+' 演算子に任せるよりも、明示的に StringBuffer オブジェクトを生成した方がコストがかからない(しかも、実際にはずっと効率がよくなる)ということです。また、Log.v() の呼び出しは、リリースビルドでもコンパイルされて実行されることを覚えておきましょう。たとえログが読めなくても、文字列構築は実行されています。
注意: 他人に読んでもらうようなリリースビルドでも有効なログは、曖昧さのない簡潔なものにすべきです。そして、非常に理解しやすいものにすべきです。これには DEBUG レベルまでの、すべてのログ記録が含まれます。
注意: ログ記録は意味が通じる限り、1行に収めるべきです。1行が80文字から100文字であれば、まったく問題ありません。(タグの長さも入れて)130文字あるいは160文字を超えるのは、できる限り避けるべきです。
注意: 成功したことをログに記録するときには、VERBOSE レベルより高いレベルにしてはいけません。
注意: 再現困難な問題を調査するために一時的にログを記録するときには、DEBUG もしくは VERBOSE レベルにするべきです。そして、if ブロックで囲んで、コンパイル時にすべて無効にできるようにしておくべきです。
注意: ログ全体を通して、情報漏洩には注意しましょう。プライベートな情報をログに記録するのは避けるべきです。保護されている内容に関する情報は、絶対に記録してはいけません。フレームワークのコードを書いているときには、これは特に重要になります。ある情報がプライベートもしくは保護されている内容であるか否か、事前に知るのは簡単ではないためです。
注意: System.out.println()(またはネイティブコードの printf())を使ってはいけません。System.out と System.err は /dev/null にリダイレクトされます。したがって、いくら print 文を書いても見えません。にもかかわらず、呼び出しに伴って発生する文字列構築はすべて実行されてしまいます。
注意: ログ記録には鉄則があります。あなたのログが不必要に他人のログを押し出さないようにしてください。他人のログがあなたのログを押し出さないようにするのと同じようにね。
testMethod_specificCase1
testMethod_specificCase2
void testIsDistinguishable_protanopia() {
ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA)
assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK))
assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y))
}