今回はJavaのコーディングスタイルに関する話題です。
スタイルにはいろんな考え方がありますが、本エントリでは「読むときのことを考える」という原則に従ったスタイルを考えて行きます。
「プログラムは書いている時間よりも読まれている時間の方がずっと長い」とFacebookの中の人が言ったとか言わないとか。また「昨日の自分は他人」とも言いますね。たとえ自分が書いたプログラムでも、後から読み返してみると「何でこんなコーディングしてるんだろ・・・」と思うことは良くあります。自分も含めた将来の読み手に対し、適切な情報を与えることで読みやすいプログラムを書くように心がける必要があるでしょう。
考え方の基本は「読み手に課す前提を減らす」
「読み手に課す前提」とは何でしょうか。それは、ある箇所のコードを読んでいるときに考慮しなければならない事柄を減らす、ということです。
次のコードを見て下さい。
public Object someMethod(String s) { String os = cnv(s); ... } public String cnv(String s) { ... }
someMethodを読み解くには、いろんな可能性を考える必要があります。
- publicだからどこから呼び出されているか分からないな・・・
- 返り値がObject型なんだけど、実際にはどの型のオブジェクトが返ってるんだろう・・・?
- インスタンス変数に手を加えてる可能性ってあるのかな・・・?
- cnvメソッドは何をやってるんだろう?副作用はないの?インスタンス変数に手を加えるようなことしてないよね?
- cnvメソッドまでpublicなんだけど名前から言ってユーティリティっぽいんだけど、このクラスの外から呼び出している箇所ってあるの?
安易なpublic宣言のおかげで、読み解くのがとてもたいへんなことになっています。しかし、このコードが次のようになっているとどうでしょうか。
void someMethod(final String s) { final String os = cnv(s); ... } private static String cnv(final String s) { ... }
- someMethodはパッケージ外から呼び出されていることはない!→呼び出し元を洗い出す範囲が減った!
- 戻り値はない!→インスタンス変数に何らかの変更を加えているはず!
- cnvメソッドはprivateかつstaticだから、インスタンス変数に手を加えることは無いしこのクラスの外から呼び出されることはない!
先のコードに比べ、考慮しなければならない事柄が相当に減ります。
読み手に課す前提を減らすためのスタイル
読み手に課す前提を減らすには、以下のようなスタイルを採用すると良いでしょう。
アクセス修飾子はprivateが基本!
特にpublicはどこからでも参照可能になり影響範囲が広大です。本当にpublicでないといけないのか、自問しましょう。
変数のスコープは最小限に
コードを読む際に考慮すべき変数が少ない方が読みやすいです。グローバル変数よりもインスタンス変数を、インスタンス変数よりもローカル変数を選択しましょう。
staticメソッドを好む
staticメソッドの中ではthisが参照出来ません。staticメソッドの中ではインスタンス変数に変更が入らないことが保証されるわけで、考えなければならないことがかなり減ります。
これは後にもう少し詳しく解説します。
ちなみに・・・あくまでstaticメソッドが有用なのであって、static変数はむやみに使わないように気を付けて下さい。特にfinalでないstatic変数が必要な局面はほぼありません。finalでないstatic変数を宣言したくなったら「何かがおかしいのでは?」と自問して下さい。
変数やメソッドの名前の単語を極力省略しない
書くのは面倒ですが読みやすいです。
変数の数を少なくする
ただでさえ変数の中の値は目に見えないため、人間にとって把握が難しい存在です。その上数が増えると飛躍的に複雑さが増し、手に負えなくなります。クラスにまとめるなどして、シンプルに管理出来ないか?などの手法を考えましょう。
インデントを減らす
インデントは何らかの条件分岐の結果で生じている場合がほとんどです。よって、インデントが深いということは複数の条件が組み合わされている、ということであり、人間の頭では把握が難しい状況です。
また、インデントを抜ける際には「今までどんな条件の下で処理が実行されてたんだっけ」と考えなおす必要があります。
これは後にもう少し詳しく解説します。
変数は宣言と同時に初期化し、finalを付ける
先にも書いたように、変数の値は目にみえないため、人間にとって把握が難しい存在です。ましてや値がどんどん変わって行ってしまうような変数は、相当理解が難しいと言えます。
final変数は、2度と値が変わらないことが保証されています。これは読み手に大きな安心感を与えます。
これは後にもう少し詳しく解説します。
3つの具体例
前項で紹介したスタイルのうち、あまり普及していないような気がするけれどもコードの読みやすさへの貢献度の高いスタイルを3つピックアップし、少しだけ掘り下げてみましょう。
staticメソッドを好む
スタイル具体例1つ目はstaticメソッドについてです。
メソッドにstaticを付けると、読み手に対し「このメソッドの中ではthisを使っていませんよ!」と知らせていることになります。thisを使っていないということは、少なくとも自クラス内のインスタンス変数やインスタンスメソッドを使っていないということになります。これは読み手にとって、考えなければならないことをかなり減らす効果があります。
以下のコードを見て下さい。
private String cnv(String s) { if (s == null) { return "★"; } return "★" + s; }
このメソッドはthisにアクセスしていないのですが、全てのコードを読むまでそのことが分かりません。しかし次のように書き換えるとどうでしょうか。
private static String cnv(String s) { if (s == null) { return "★"; } return "★" + s; }
コードを読むまでもなく、thisにアクセスしていないことが明示されます。
eclipseのコンパイラ設定
実はeclipseのコンパイラ設定にはthisにアクセスしていないメソッドを「staticを付けることが出来るよ!」と教えてくれる設定があります。ぜひオンにして積極的にstaticを付けるようにしましょう。
変数にfinalを付ける
スタイル具体例の2つ目は変数にfinalを付けることです。これは手軽で効果が高いスタイルである上にプログラムの腕を向上させる効果もあるため、強くおすすめします。
プログラムを書く側は、変数にfinalを付けなくても、実際に値が変わることはないことを確信出来ます。しかし読み手はその確信を得るのに、変数のスコープにある全てのコードを読む必要があるのです。
しかしfinalの付いた変数は、その変数の宣言を見ただけで、値が変わらないという確信を得ることが出来ます。これは読み手にとってとてもありがたいことなのです。
更に、変数の宣言と共に初期化するように心がけると値に何が入っているか分かりやすくなります。
以下のコードを見て下さい。
private Map<String, String> cache; /** * コンストラクタ */ public SomeClass() { this.cache = new HashMap<>(); ... }
cacheという変数の値はどこからか書き換えられる可能性があり、読み手はこのことを意識してコードを読む必要があります。また実際にcacheにどんな値が入っているのか、コンストラクタを見るまで分かりません。
しかし次のように書くとどうでしょうか。
private final Map<String, String> cache = new HashMap<>(); /** * コンストラクタ */ public SomeClass() { ... }
finalが付いているおかげで、この変数の値が決して書き換えられないことが分かります。また宣言と同時に初期化しているため、値に何が入っているか一目瞭然です。
finalを忘れがちな変数
メソッド/コンストラクタ引数
void someMethod(final String s) { ... }
catch
try { ... } catch (final RuntimeException e) { .... }
ローカル変数
void someMethod(final String s) { final String os = cnv(s); ... }
クラスによっては全ての変数にfinalを付けることが出来ます。
finalを付けられないこともある、が・・・
for文のループ変数にはfinalを付けられません。が、forループの多くは拡張for文に置き換え可能であり、拡張for文のループ変数にはfinalを付けられます。
for文を使ってListの全要素にアクセスするコードは以下のようになります。
final List<String> ss = getStringList(); for (int i = 0; i < ss.size(); i++) { System.out.println(ss.get(i)); }
ループ変数はインクリメントされていくため、finalを付けることは出来ません。
これを拡張for文に置き換えると次のようになります。
for (final String s : getStringList()) { System.out.println(s); }
拡張for文に置き換えたことで、変数にfinalを付けられる上に変数ssが不要になっています。シンプルになり読みやすくなったと思います。
eclipseの設定に頼ってはいけない
実はeclipseには保管アクションの中に「finalを付けられる変数に自動で付けてくれる設定」があります。しかしこの機能に頼ることはおすすめしません。なぜなら、finalを付けられない変数には付けてくれないためです。
保険として設定する分には構いませんが、基本的にはプログラマが意思を持ってfinalを付ける必要があります。
インデントを減らす
最後のスタイル具体例はインデントについてです。
インデントが深くなる典型的なケースはif、for、while、tryによるものでしょう。これらはいずれも何らかの条件分岐であると考えられます。つまりインデントを付けられたコードを読む際、読み手は「ここのコードが実行されている条件は何だっけ・・・?」と考えながら読むことになります。
例えば、次のようなコードを目にしたことはありませんか?
void someMethod(String s) { if (s != null) { // とても長いコード ... ... ... ... ... ... ... ... ... ... ... s = .....; } else { // ん?このelseの条件って何だったっけ? s = ""; } ... }
長いコードブロックが終わるとおもむろに現れるelse。これを読み手が目にすると、「ん?このelseの条件は何だっけ?」と、ifのコードブロックの一番上まで戻らざるを得ません。
このコードは次のように書くとぐっと読みやすくなります。
void someMethod(String s) { if (s == null) { s = ""; } else { // とても長いコード ... ... ... ... ... ... ... ... ... ... ... s = .....; } ... }
が、更に工夫して、次のように書けばもっと読みやすくなります。
void someMethod(String s) { s = cnv(s); ... } private static String cnv(final String s) { if (s == null) { return ""; } // とても長いコード ... ... ... ... ... ... ... ... ... ... ... return .....; }
インデントがほぼ無くなってしまいました。someMethodメソッドの中を見る限り、条件分岐が無くなっていて、コードを追いやすくなっていると思います。
条件分岐を別メソッドに切り出すようにすると、インデントの多くを無くすことが出来ます。
まとめ
ここまで「読むときのことを考える」という原則のもとで、様々なスタイルを考えて来ました。
スタイルを理解するにはその裏にある原則を理解することが必要です。例えば過去のエントリ「Java @Override アノテーション活用法」で紹介したスタイルは、読みやすさに加えて「ミスは極力コンパイルエラーとして捕捉する」という原則に沿ったスタイルです。
このエントリでは具体的なスタイルについて解説しましたが、原則を理解することでより良いスタイルを自分で選択出来るようになると思います。ぜひ日々考えながらプログラミングするようにしてみて下さい。