effective java 勉強会

42
EFFECTIVE JAVA 第7章 メソッド 滝波 景

Upload: takinami-kei

Post on 15-Jan-2015

1.042 views

Category:

Engineering


6 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Effective java 勉強会

EFFECTIVE JAVA 第7章 メソッド

滝波 景

Page 2: Effective java 勉強会

項目38 パラメータの正当性を検査する

メソッド、コンストラクタを実装するときは

できるだけ早めにエラーを検出できるようにする

public static List<Integer> returnList(Integer[] numbers) {

//このチェックがないと呼び出し側でエラーになって、デバッグが大変に。

if (numbers == null) { throw new NullPointerException(); } //...//配列をリストに変換する処理

Page 3: Effective java 勉強会

あとになればなるほどエラーの原因が発見しづらい。

最悪なのは、メソッドは正常にリターンしてるのにいくつかのオブジェクトが不正のまま後になって、全く関係ないところでエラーになる。

Page 4: Effective java 勉強会

メソッド、コンストラクタを実装するときは

publicなメソッドに対してはJavadocの@throwsタグを

使って文章化

/** * 値が(this mod m)であるBigIntegerを返します。このメソッドは、 * remainderメソッドとは異なり、常に負でないBigIntegerを返します。 * * @param m 正でなければならない * @return this mod m

* @throws ArithmeticException m <= 0の場合 <=チェック内容を文章化 */ public BigInteger mod(BigInteger m) { if (m.signum() <= 0) { throw new ArithmeticException(); } ...//計算を行う }

Page 5: Effective java 勉強会

メソッド、コンストラクタを実装するときは

privateなメソッドはアサーションを用いて検査する

// 再起的ソートのためのprivateのヘルパー関数 private static void sort(long a[], int offset, int length) {

assert a != null; assert offset >= 0 && offset <= a.length; assert length >= 0 && length <= a.length - offset; ...//計算を行う

}

Page 6: Effective java 勉強会

publicなメソッドと違ってどんな状況でそ

のメソッドが呼ばれるか管理できるからOK

実行時に-eaオプションでアサーションを

有効にしない限りはアサーションの効果かもないし、コストもかからない。

Page 7: Effective java 勉強会

ただし

例外もある

Page 8: Effective java 勉強会

例えば

Collections.sort(List)

このListの中身は比較可能なオブジェクトでないと、

ClassCastExceptionがスローされる。

これはsortメソッドが行うことで、実装者がチェックすることではない。

なので、sortの前に比較可能のチェックを実装する必要はない。

チェックのコストが高い場合

現実的でない && チェックが暗黙に行われる場合

Page 9: Effective java 勉強会

項目38 必要な場合には、防御的にコピーする

Javaは安全な言語だけど、きちんと書かないと他のクラスから守ることができない。

防御的なコードを書こう

Page 10: Effective java 勉強会

//一見問題ないコード public class Period { private final Date start; private final Date end; /** * @param start 期間の開始 * @param end 期間の終わりで、開始より前であってはならない。 * @throws IllegalArgumentException start が end の後の場合。 * @throws NullPointerException start か end がnullの場合。 * */ public Period(Date start, Date end) { if (start.compareTo(end) > 0) { throw new IllegalArgumentException(start + " after " + end); } this.start = start; this.end = end; } public Date getStart() { return start; } public Date getEnd() { return end; } //残りは省略 }

Page 11: Effective java 勉強会

// Periodインスタンスの内部を攻撃 Date start = new Date(); Date end = new Date(); Period p = new Period(start, end); end.setYear( 77); // pの内部を変更する!!

一見すると、期間の開始と終わりの後には絶対こないように見えるけどDateは可変なので、

簡単に終わり(end)を変えられる。

Page 12: Effective java 勉強会

対策 コンストラクタで可変なパラメータのコピーしちゃう。

(ここでは新しいDateオブジェクトを作成)

// 修正されたコンストラクタ - パラメータの防御的コピーをする public Period(Date start, Date end) { this start = new Date(start.getTime); this end = new Date(end.getTime); if (this.start.compareTo(this.end) > 0) { throw new IllegalArgumentException(start + " after " + end); } }

さっきみたいにend.setYear(78); ってやっても

参照しているオブジェクトが違うから変更されない。

Page 13: Effective java 勉強会

注意

ちなみにオブジェクトをコピーするときはcloneを使ってはダメ。

Dateはfinalなクラスではないので、cloneメソッドがjava.util.Dateを返すとはかぎらない。

逆にいうと

finalでないクラスはcloneしてはいけない!!

※http://www.jpcert.or.jp/java-rules/obj04-j.html

Page 14: Effective java 勉強会

待って!!

Page 15: Effective java 勉強会

// Periodインスタンスの内部への第2の攻撃 Date start = new Date(); Date end = new Date(); Period p = new Period(start, end); p.getEnd.setYear(78); // pの内部を変更する!!

実はまだ、Periodクラスは攻撃から上手く防御できてない。

setterで変更できちゃう

Page 16: Effective java 勉強会

対策2 さっきの修正

getterもコンストラクタの値を元に新しいDateを返す

これで、Periodは本当に不変

public Date getStart() { return new Date(start.getTime); } public Date getEnd() { return new Date(end.getTime); }

Page 17: Effective java 勉強会

public class Period { private final Date start; private final Date end; public Period(Date start, Date end) { if (start.compareTo(end) > 0) { throw new IllegalArgumentException(); } this.start = start; this.end = end; } public Date getStart() { return start; } public Date getEnd() { return end; } //残りは省略 }

public class Period { private final Date start; private final Date end; public Period(Date start, Date end) { if (start.compareTo(end) > 0) { throw new IllegalArgumentException(); } this start = new Date(start.getTime); this end = new Date(end.getTime); } public Date getStart() { return new Date(start.getTime); } public Date getEnd() { return new Date(end.getTime); } //残りは省略 }

※ただ一番いいのはDateではなく、Date.getTimeで取得される基本型のlong値を使う方法。

変更前 変更後

Page 18: Effective java 勉強会

項目38 メソッドのシグニチャを注意深く設計する

メソッドを設計する上でのポイント

1.メソッド名を注意深く選ぶ

・標準命名規則に従う

・理解可能で同じパッケージ内の他の名前と

矛盾がないこと

2.便利なメソッドを提供し過ぎしないようにする

・あんまり多いと学習、使用、文章化、テスト、

保守のコストがかかる

・メソッドの多いインターフェイスは使用者を混乱させる

(よくある無駄に多いUtiltyクラスのメソッドが当てはまるのかな?)

Page 19: Effective java 勉強会

3.パラメータ型に関しては、クラスよりインターフェイ

スを選ぶ

・例えば、引数としてHashMapを使う場合でも、Mapを使う。

そうすると、HashTable,HashMap,TreeMap....といった感じで

どんなサブマップが使える。

OK public static void sample(Map<String, String> map)

NG public static void sample(HashMap<String, String> map)

4.booleanパラメータより2つの要素を持つenum型を

使用する。

・コードが読みやすくなるし、IDEの保管機能があるとより

書きやすい。

例)public enum TemperatureScale { FAHRENHEIT, CELSIUS }

Thermometer.newInstance(true)よりは

Thermometer.newInstance(TemperatureScale.CELSIUS)

Page 20: Effective java 勉強会

3.長いパラメータのリストは避ける

・4個以下が目標

・長いパラメータリストは覚えられないし、

その場合はドキュメントを都度参照しないといけない。

・同じ型が続くのは特に有害。

順番が違ってもコンパイルできて実行できちゃう。

対策1

メソッドを複数のメソッドに分割

例)java.util.List #indexOf メソッド

indexOf(検索対象オブジェクト, fromIndex, toIndex)

というメソッドではなく、

「subList(fromIndex, toIndex)」

「indexOf(検索対象オブジェクト)」

に分割されている。

Page 21: Effective java 勉強会

対策2

パラメータの集まりを保持するクラスを作成

public class Sample { public doSomething(String firstName, String lastName,String address, int number, Sting district) { //残りは省略 }

public class Sample { private String firstName; private String lastName; private String address; private int number; private String district; //残りは省略 } public class Sample { public doSomething(User user) { //残りは省略 }

Page 22: Effective java 勉強会

対策3 ビルダーパターン

// ■オブジェクト側 public final class Commodity { private final int id; // 必須 private final String name; // 必須 private final long lowestPrice; // オプショナル public static class Builder { private final int id; private final String name; private long lowestPrice; public Builder(int id, String name, long catalogPrice) { // 必須項目は Builder のコンストラクタで指定 this.id = id; this.name = name; } public Builder lowestPrice(long lowestPrice) { this.lowestPrice = lowestPrice; return this; } public Commodity build() { // パラメータ間の整合性は build メソッドで解決 if (lowestPrice > 100) throw new IllegalStateException(); return new Commodity(this); } } private Commodity(Builder builder) { id = builder.id; name = builder.name; lowestPrice = builder.lowestPrice; } // getter 省略 } // ■クライアント側 Commodity commodity = new Commodity.Builder(10, "hoge").lowestPrice(100).build();

詳しいことはこっちで

Page 23: Effective java 勉強会

項目41 オーバーロードに注意して使用する

class CollectionClassifier { public static String classify(Set<?> s) { return "Set"; } public static String classify(List<?> lst) { return "List"; } public static String classify(Collection<?> c) { return "Unknown Collection"; } public static void main(String args[]) { Collection<?>[] collections = { new HashSet<String>(), new ArrayList<BigInteger>(), new HashMap<String, String>().values() }; for (Collections<?> c : collections) { System.out.println(classify(c)); } } }

一見、問題なく Set,List,Unknown Collection

と表示されそう。

でも、実際はUnknown

Collectionを3回表示。

Page 24: Effective java 勉強会

どのオーバーロードされたメソッドが呼ばれるかは、コンパイル時に決まる。 パラメータのコンパイル時は Collection<?> なので、classify(Collection<?>)で実行されてしまう。

for (Collections<?> c : collections) {

System.out.println(classify(c)); }

public static String classify(Collection<?> c) { return c instanceOf Set ? "Set" : c instanceOf List ? "List" : "Unknown Collection" }

修正するなら、instanceOfで検査

Page 25: Effective java 勉強会

オーバーロードするときのポイント

1.利用者を困惑させるようなオーバーロードを使用しない

2.同一数の引数を持つオーバーロードを行わない

※例外として引数の型が明らかに違う場合はOK

例)ArrayList

・ArrayList(int initialCapacity)

・ArrayList(Collection<? extends E> c)

3.オーバーロードでなく、別にメソッドとして提供する

例)ObjectOutputStream

writeメソッドがあるが、このメソッドをオーバーロードするのでなく

writeBoolean(boolean), writeInt(int), writeLong(long) を提供

メリットとしては、対応する

readBoolean(boolean), readInt(int), readLong(long) が提供できること

Page 26: Effective java 勉強会

オートボクシングによる問題

public class Sample { public static void main(String args[]) { Set<Integer> set = new TreeSet<Integer>(); List<Integer> list = new ArrayList<Integer>(); for (int i = -3; i < 3; i++) { set.add(i); list.add(i); } for (int i = 0; i < 3; i++) { set.remove(i); list.remove(i); } System.out.println(set + " " + list); } }

一見、問題なく list,set共に[-3,-2,-1,0,1,2]と

setされて それぞれ[0,1,2]がremoveされた

[-3,-2,-1][-3,-2,-1] が表示されそう

でも、実際は

[-3,-2,-1][-2,0,2] が表示される

Page 27: Effective java 勉強会

原因 ・ set.remove(i)はオーバーロードしているremove(E)が選択される (iがintからIntegerへオートボクシングされる) ・list.remove(i)はオーバーロードしているremove(int i)が選択されて

“位置”を削除する

※ちなみにListの中身 E remove(int index) リストの指定された位置にある要素を削除します。

boolean remove(Object o) 指定された要素がこのリストにあれば、その最初のものをリストから 削除します。

オートボクシングできるようになってからは、 さらにオーバーロードに気をつける必要あり。

Page 28: Effective java 勉強会

項目38 可変長引数を注意して使用する

static int sum(int args... ) {

int sum = 0;

for (int arg : args) { sum += arg; } return sum; }

可変長引数メソッドの例

(0個の場合もOK)

Page 29: Effective java 勉強会

static int sum(int args... ) {

if (args.length == 0) { throw new IllegalArgumentException("Too few arguments"); }

int min = args[0];

for (int i = 1; i < args.length; i++) { if (args[i] < min) { min = args[i]; } }

return min; }

可変長引数メソッドの例

(0個の場合はNG、 必ず1つ以上)

問題点

・引数なしてこのメソッドを呼んだ場合、コンパイル時でなく、実行時に失敗する。

・メソッドが見づらい

・argsに対して明示的なチェックをしないと行けない

・minをInteger.MAX_VALUEへ初期化しないと、for-eachが使えない

Page 30: Effective java 勉強会

static int sum(int firstArg, int remainingArgs... ) { int min = firstArg; for (int arg : remainingArgs) { if (arg < min) { min = arg; } } return min; }

解決策

・引数が0の場合はfor分に入らないのでOK

・前より見やすい

Page 31: Effective java 勉強会

項目43 nullでなく、空配列か空コレクションを返す

private final List<Cheese> cheesesInStock = ...; /** * @return 店にある全てのチーズを含む配列、もしくは、 * 変えるチーズがなければnull * * @return */ public Cheese[] getCheeses() { if (cheesesInStock.size() == 0) { return null; } }

よくないコード

チーズがない場合に対してわざわざnullを返却しなくていい

呼び出し側はnullを処理するための余分なコードを書かないといけない

Page 32: Effective java 勉強会

Cheese[] cheeses = shop.getCheeses(); // nullチェックしないとダメ

if (cheeses != null && Arrays.asList(cheeses).contains(Cheese.STILTON)) { System.out.println("Jolly good, just the thing"); }

呼び出し側の面倒な処理 ~nullチェックしないといけなくなる~

~containsでいきなりチェックもできない~

Cheese[] cheeses = shop.getCheeses();

if (Arrays.asList(cheeses).contains(Cheese.STILTON)) { System.out.println("Jolly good, just the thing"); }

Page 33: Effective java 勉強会

// コレクションから配列を返す正しい方法 private final List<Cheese> cheesesInStock = ...; private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0]; /** * @return 店にある全てのチーズを含む配列 */ public Cheese[] getCheeses() { return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY); }

解決策

public List<Cheese> getCheeseList() { if (cheesesInStock.isEmpty()) { return Collections.emptyList(); //常に同一のリストを } else { return nre ArrayList<Cheese>()cheesesInStock; } }

1.配列を返す場合

2.コレクションを返す場合

つまり、配列やコレクションを返すメソッドは空配列、

空コレクションの代わりにnullを返すべきでない

Page 34: Effective java 勉強会

項目44 全ての公開API要素に対して ドキュメントコメントを書く

すべての クラス、インターフェイス、コンストラクタ、

メソッド、フィールド宣言の前に

ドキュメントコメント(javadoc)を書く

Page 35: Effective java 勉強会

メソッド 1.メソッドがどのように処理をしているかでなく、

何をしているかを書くこと

・事前条件と事後条件を列挙するべき

事前条件:呼び出すときに成立していないといけないこと

(@throwsタグにより暗黙的に記述)

事後条件:呼び出しが正常に完了した後に成立していないと

いけないこと

2.副作用も書く

・メソッドがバックグラウンドでスレッドを開始していることとか

3.@param @return @throwsもきちんと書く

Page 36: Effective java 勉強会

HTMLへの変換 1.JavaDocはHTMLに変換されるけど、

HTMLテーブルを作るとかまではしなくてOK

2.1.5より前はHTMLのメタ文字はエスケープ

しないといけなかったけど今は{@code}でOK 例) {@code index < 0 || index >= this.size()}

3.複数行からなる場合は<pre>タグを使わないで

{@code}タグを使うこと。 <pre>{@code で始めて、 文字}</pre>

4.”・<,>,&”とかのHTMLメタ文字を生成するときは{@literal}を使う

{@code}と似ているけど、テキストをコードフォント

で表示しない点が違う

例) The triangle inequality is {@literal |x + y| < |x| + |y|}

Page 37: Effective java 勉強会

コメントの最初の文は概要説明を書く 1.クラス、インターフェイス内の2つのメンバーとか

コンストラクタが同じ概要説明を持ってはダメ。

特にオーバロードは同じ内容になりやすいので、注意。

2.ピリオドに注意 例)A college degree, such as B.S., M.S. or Ph.D.

後に空白、タブがあると、そこで概要説明が終わってしまう

解決策としては{@literal}で囲むことで、ピリオドの後に空白が続かない。

/** * A college degree, such as B.S., {@literal M.S.} or Ph.D. * College is a fountain of knowledge where may go to drink. */ public class Degree {...}

Page 38: Effective java 勉強会

メソッドとクラスとで、書き方を変える 1.クラス、インターフェイスの場合

クラス、インターフェイスのによって表される自柄を説明している

名詞句であるべき

例) TimerTask -- A task that can be scheduled for one-time or repeated execution by a Timer

Math.PI -- The double value that is closer than any other to pi,the ratio of the

circumference of a circle to its diameter

2.メソッド、コンストラクタの場合

メソッドやコンストラクタが行う処理を説明している動詞句であるべき

例) ArrayList(int initialCapacity)-- Constructs an empty list with the specified initial capacity

Collection.size()-- Returns the number of elements in this collection

Page 39: Effective java 勉強会

ジェネリックス、enum、アノテーション

1.ジェネリックス ジェネリック型、ジェネリックメソッドの場合は

全ての型パラメータを文書化すること

/** * .... * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values */ public interface Map<K,V> { ....// 残りは省略 }

Page 40: Effective java 勉強会

2.enum型 enum型の場合は型とすべてのpublicメソッド

だけでなく、定数も文章化すること

/** * .... */ public enum OrchestraSection { /** Woodwinds, such as flute,clarinet, and oboe. */ WOODWIND, /** Brass instruments, such as french horn and trumpet. */ BRASS, /** Percussion instruments, such as timpaani and cymbals. */ PERCUSSION }

Page 41: Effective java 勉強会

3.アノテーション アノテーションを文書化する場合は、その型自信

だけでなく、全てのメンバーを文章化すること

/** * .... */ @Retntion(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Exception { /** * The exception that the annotaed test method must throw * in order to pass. (The test is permitted to throw any) * subtype of the type described by this class object.) * Class<? extends Throwable> value(); }

Page 42: Effective java 勉強会

その他 ・Javadocはメソッドのコメントを{@inheritDoc}で

スーパークラスとかインターフェイスから

継承できる

・コメントの中の間違いを探すのはHTM検証チェッカー

が便利。