プログラミング一般

Effective Java 第 3 版の個人的メモ

  1. 項目 57 ローカル変数のスコープを最小限にする
  2. 項目 58 従来の for ループより for-each ループを選ぶ
  3. 項目 59 ライブラリを知り、ライブラリを使う
  4. 項目 60 正確な答えが必要ならば、float と double を避ける
  5. 項目 61 ボクシングされたプリミティブ型(boxed primitive)より、プリミティブ型(primitive)を選択すべし
  6. 項目 62 他の型が適切な場所では、文字列を避ける
  7. 項目 63 文字列結合のパフォーマンスに用心する
  8. 項目 64 インタフェースでオブジェクトを参照する
  9. 項目 65 リフレクションよりもインタフェースを選ぶ
  10. 項目 66 ネイティブメソッドを注意して使う
  11. 項目 67 注意して最適化する
  12. 項目 68 一般的に受け入れられている命名規約を守る
  13. 参考URL

1. 項目 57 ローカル変数のスコープを最小限にする

1.1. 結論

  • ローカル変数は初めて使用される時に宣言しましょう
    • かなり前段階で宣言されていると、その時点から変更されているかを確認する必要があるから
  • (ほとんど)全てのローカル変数の宣言時には初期化しましょう
    • 初期化するための情報がない段階で、宣言すべきではないから
  • メソッドを小さくして焦点をはっきりさせましょう
    • 1つのメソッド内で2つの処理があれば、1つ目の処理用のローカル変数が2つ目の処理にも使われているかもしれないので、これを防ぐために 1メソッド:1処理 としましょう。

1.2. サンプル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 良い例
for (Element e : c) {
  doSomething(e);
}

// 悪い例
Iterator<Element> i = c.iterator();
While (i.hasNext()) {
  doSomething(i.next());
}
...
Iterator<Element> i2 = c2.iterator();
While (i.hasNext()) { // バグ(iではなく、i2が正しい。)
  doSomething(i2.next());
}

1.3. ポイント

  • 使用される直前に、ローカル変数を初期化して宣言しましょう
  • 1メソッド:1処理としましょう

備考:while文ではなくfor文を好んで使いましょう。

2. 項目 58 従来の for ループより for-each ループを選ぶ

2.1. 結論

「簡潔」&「バグ予防」にもなるので、
以下の場合を除いて、for文ではなくfor-each文を使いましょう

  • フィルタリング(要素の削除)
    • ある要素だけ削除したい場合は、明示的なイテレータを使用する必要がある。(Iterator#remove()を使用するため。)
  • 変換(値の置換)
    • ある要素の値を変更するために、リストイテレータや配列インデックスが必要であるため。
  • 並列イテレーション
    • 複数のコレクションを並列にイテレートするためには、イテレータやインデックス変数を明示的に(自分で)制御する必要があるため。

Collectionに対して変更できるが、フェイルファストには気を付けましょう。
Collectionによっては、iteratorおよびlistIteratorメソッドによって返されるイテレータは、フェイルファストです。イテレータの作成後に、イテレータ自体のremoveまたはaddメソッド以外の方法でリストが構造的に変更されると、イテレータはConcurrentModificationExceptionをスローします。 https://docs.oracle.com/javase/jp/8/docs/api/java/util/ArrayList.html

2.2. サンプル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 良くも悪くもない例 1
// イテレータ変数が散らかっている
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
  doSomething((Element) i.next());
}

// 良くも悪くもない例 2
// インデックス変数が散らかっている
for (int i = 0; i < a.length; i++> ) {
  doSomething(a[i]);
}

// 良い例
for (Element e : c) {
  doSomething(e);
}

ネストしたイテレータを使った問題コード

BadIterator.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
 NINE, TEN, JACK, QUEEN, KING }
...
Collection suits = Arrays.asList(Suit.values());
Collection ranks = Arrays.asList(Rank.values());
List deck = new ArrayList();
for (Iterator i = suits.iterator(); i.hasNext(); ){
	for (Iterator j = ranks.iterator(); j.hasNext(); ){
		deck.add(new Card(i.next(), j.next()));
	}
}

外側のコレクション(suits)のイテレータに対してnext()が過剰に呼び出されて、NoSuchElementExceptionがスローされるため。

GoodIterator.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
 NINE, TEN, JACK, QUEEN, KING }
...
Collection suits = Arrays.asList(Suit.values());
Collection ranks = Arrays.asList(Rank.values());
List deck = new ArrayList();
for (Suit suit : suits ) {
  for (Rank rank : ranks ){
    deck.add(new Card(suit, rank));
  }
}

2.3. ポイント

以下の場合を除いて、for文ではなくfor-each文を使いましょう

  • フィルタリング(要素の削除)
  • 変換(値の置換)
  • 並列イテレーション

for-each文を使えるようにするため、Iterableを実装しましょう。

2.4. 付録 ( Iterableの実装 )

Main.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.example.demo;

public class Main {
    public static void main(String[] args) {
        var optPrimeNumbers = PrimeNumbers.createPrimeNumbers(500);
        if(optPrimeNumbers.isEmpty()){
            return;
        }
        var primeNumbers = optPrimeNumbers.get();
        for(var primeNumber : primeNumbers){
            System.out.println(primeNumber);
        }
    }
}

以下の3つのメソッドをオーバーライドすれば良い。

  • java.lang.Iterable#iterator()
  • java.util.Iterator#hasNext()
  • java.util.Iterator#next()

PrimeNumbers.java

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
package com.example.demo;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Optional;

/**
 * 素数を計算するクラス
 */
public class PrimeNumbers implements Iterable<Integer> {

    /**
     * 与えられた整数
     */
    private int myNumber;

    /**
     * 現在の算出素数
     */
    private int curPrimeNumber;

    /**
     * {@link PrimeNumbers}作成メソッド
     *
     * @param myNumber 与えられた整数
     */
    public static Optional<PrimeNumbers> createPrimeNumbers(int myNumber) {
        if (myNumber <= 1) {
            return Optional.empty();
        }
        var primeNumbers = new PrimeNumbers();
        primeNumbers.myNumber = myNumber;
        primeNumbers.curPrimeNumber = 1;
        return Optional.of(primeNumbers);
    }

    /**
     * 要素のイテレータを返す。
     *
     * @see java.lang.Iterable#iterator()
     */
    @Override
    public Iterator<Integer> iterator() {
        // Iteratorインターフェースの実装
        return new Iterator<>() {
            /**
             * 反復処理でさらに要素がある場合にtrueを返す。
             *
             * @see java.util.Iterator#hasNext()
             *
             * return 次の要素があるか否か
             **/
            @Override
            public boolean hasNext() {
                // 現在の算出素数が与えられた整数に達していれば終了
                if (myNumber <= curPrimeNumber) {
                    return false;
                }

                // 開始値から順に素数であるかをチェックする
                for (var i = curPrimeNumber + 1; i <= myNumber; i++) {
                    // 割り切れる値がチェック対象の整数しかなければ素数
                    if (isPrime(i)) {
                        curPrimeNumber = i;
                        return true;
                    }
                }
                return false;
            }

            /**
             * 反復処理で次の要素を返す。
             *
             * @see java.util.Iterator#next()
             * @return 現在の算出素数
             */
            @Override
            public Integer next() {
                // 現在の算出素数が与えられた整数に達していれば終了
                if (myNumber <= curPrimeNumber) {
                    throw new NoSuchElementException();
                }
                return curPrimeNumber;
            }
        };
    }

    /**
     * 素数であるかどうかを判定す
     *
     * @param number 判定対象の数値
     * @return 素数判定結果
     */
    private boolean isPrime(int number) {
        // 入力チェック
        if (number <= 1) {
            return false;
        }
        // 素数判定
        for (var i = 2; i < number; i++) {
            // 2から順番に割っていって、割り切れれば抜ける
            if ((number % i) == 0) {
                return false;
            }
        }
        return true;
    }
}

3. 項目 59 ライブラリを知り、ライブラリを使う

3.1. 結論

  • 標準ライブラリを使いましょう
    • 時間を無駄にすることなく、他の有意義な箇所(実装)に時間を割り当てられる
    • 専門家の知識が活かせる
    • 他に同じライブラリを使った人達の経験が活かせる
      • 詰まった際の解決方法がネット上にある
      • パフォーマンスが良い
  • 標準ライブラリ(java.lang, java.util, java.io)の内容は知っておきましょう

3.2. サンプル

便利なライブラリとして以下の2つは知っておきましょう

  • Collections Framework
    • 関連のないAPI間での相互運用ができる
  • Concurrency Utilities
    • マルチスレッドプログラムの処理を単純化するためのユーティリティがある
    • java.util.concurrent.atomic
    • java.util.concurrent.locks

3.3. ポイント

  • 無駄な努力はしないでください
  • ライブラリが存在するのかを知らなかったら調べてください
    • apache, spring, guavaあたりはよく利用しますね。
  • あなたのコードよりもライブラリの方が優れています

4. 項目 60 正確な答えが必要ならば、float と double を避ける

4.1. 結論

  • 金銭計算など正確な計算が必要な場合は、floatdouble を使わないでおきましょう
    • 9桁以内であれば、int
    • 18桁以内であれば、long
    • 正確な計算が必要もしくは、18桁以上であれば、BigDecimal

4.2. 理由

丸めの誤差が発生するから。

1
2
3
System.out.println(1.00 - 9*.10);
// 実行結果
// 0.09999999999999998

BigDecimalには2つの欠点があるので、最後の手段。

  • 計算式が不便(+, -, *, /, %が使えない。)
    1
    2
    
    BigDecimal price = new BigDecimal();
    price.add(100);
    
  • 処理が遅い
    • BigDecimal を使わない方が速いですね。
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
      StopWatch stopWatch;
    
      // long を利用した場合[1135(ms)]
      stopWatch = new StopWatch();
      stopWatch.start();
      long sumLong = 0;
      for(var i = 0; i < Integer.MAX_VALUE; i++){
          sumLong += i;
      }
      stopWatch.stop();
      System.out.println(stopWatch.prettyPrint());
    
      // BigDecimal を利用した場合[3969(ms)]
      stopWatch = new StopWatch();
      BigDecimal sumBigDecimal = new BigDecimal("0");
      stopWatch.start();
      for(var i = 0; i < Integer.MAX_VALUE; i++){
          sumBigDecimal.add(new BigDecimal(i));
      }
      stopWatch.stop();
      System.out.println(stopWatch.prettyPrint());
    
      // 実行結果
      /**
      * StopWatch '': running time (millis) = 1135
      * -----------------------------------------
      * ms     %     Task name
      * -----------------------------------------
      * 01135  100%
      *
      * StopWatch '': running time (millis) = 3969
      * -----------------------------------------
      * ms     %     Task name
      * -----------------------------------------
      * 03969  100%
      */
    

4.3. 備考

正確に計算できなければ、ArithmeticExceptionがスローされる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static void main(String[] args) {
    BigDecimal bigDecimal = new BigDecimal(1);
    bigDecimal.divide(new BigDecimal(3));
    System.out.println(bigDecimal);
    // 実行結果
    /**
        * Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
        * 	at java.base/java.math.BigDecimal.divide(BigDecimal.java:1722)
        * 	at effectivejava.EJ60.main(EJ60.java:8)
        */
}

5. 項目 61 ボクシングされたプリミティブ型(boxed primitive)より、プリミティブ型(primitive)を選択すべし

5.1. 結論

基本データ型の方が演算が速く、処理の危険性がないため、
以下の場合を除いて、ボクシングされたプリミティブ型(IntegerDouble, Booleanなど)よりもプリミティブ型(intdouble, booleanなど)を使いましょう。

  • Collectionの要素、キー、値として使う場合
  • ThreadLocal<T>を利用する場合
  • リフレクションによりメソッドを呼び出す必要がある場合

5.2. サンプル

5.2.1. 演算速度

プリミティブ型の方が10倍ほど速いですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
StopWatch stopWatch;

// ボクシングされたプリミティブ型(Long) の場合[11092(ms)]
Long sumLong = 0L;
stopWatch = new StopWatch();

stopWatch.start();
for (long i =0; i < Integer.MAX_VALUE; i++){
    sumLong += 1;
}
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());


// プリミティブ型(long) の場合[1171(ms)]
long sum = 0L;
stopWatch = new StopWatch();

stopWatch.start();
for (long i =0; i < Integer.MAX_VALUE; i++){
    sum += 1;
}
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());

// 実行結果
/**
* StopWatch '': running time (millis) = 11092
* -----------------------------------------
* ms     %     Task name
* -----------------------------------------
* 11092  100%
*
* StopWatch '': running time (millis) = 1171
* -----------------------------------------
* ms     %     Task name
* -----------------------------------------
* 01171  100%
*/

5.2.2. 処理の危険性

1
2
3
4
5
6
7
8
9
// 以下を実行すると期待した結果が得られない
naturalOrder.compare(new Integer(1), new Integer(1));

// 不完全なコンパレータ
Comparator<Integer> naturalOrder = new Comparator<Integer>() {
    public int compare(Integer first, Integer second) {
        return first < second ? -1 : (first == second ? 0 : 1);
    }
}
  1. first < secondの際は、自動アンボクシングが実行されて、ちゃんと比較される。
  2. first == secondの際は、同一性比較が行われ、異なるインスタンスであるため、falseが返却される。

備考:naturalOrder.compare(new Integer(1), null);とすると、NullPointerExceptionがスローされる。
ボクシングされたプリミティブ型では、nullが入る可能性があることにも注意。

5.3. ポイント

ボクシングされたプリミティブ型の使用を強いられない限り、プリミティブ型を使いましょう。

6. 項目 62 他の型が適切な場所では、文字列を避ける

6.1. 結論

以下の場合、文字列を使うべきではないです。

  • 他の値型に対する代替
    • はい/いいえ」よりも、booleanでの「true/false」の方が優れている。
  • 列挙型に対する代替
    • 文字列型よりも、Enum型の方が優れている。(比較などもEnum型の方が速かったですよね。)
  • 集合型に対する代替
    • 集合型として「String compoundKey = className + "#" + methodName」とするよりも、単に集合を表すクラスを作成しましょう。
      • 文字列で集合を表すと、文字列解析が必要になるので。
  • 偽造不可能なキー(capability)に対する代替
    • あるAPIへのアクセスを与えるために文字列を使う(set(String key, Object value))よりも、
      java.lang.ThreadLocal<T>を使いましょう。
      • 文字列に基づくAPIが抱える問題を解決でき、かつ、速いので。

6.2. サンプル

偽造不可能なキー(capability)に対する代替 について例を示します。

以下は、2つの独立したクライアントが、それぞれのスレッドローカル変数に同じ名前のキーを使用すると、共有することになるため不完全。

1
2
3
4
5
6
7
8
9
// 不完全
public class ThreadLocal {
    // インスタンス化不可能
    private ThreadLocal() {}

    public static void set(String key, Object value);

    public static Object get(String key);
}

キーとして、文字列を使うのはやめましょう。

1
2
3
4
5
6
7
8
// 完全
public final class ThreadLocal<T> {
    public ThreadLocal();

    public void set(T value);

    public T get();
}

実際のThreadLocalも同じような構成となっています。

1
ThreadLocalインスタンスは、状態をスレッドに関連付けてくれるので、スレッドセーフとなる。

備考:ThreadLocalは副作用が強いので、使用する際は気を付けた方が良いですね。

7. 項目 63 文字列結合のパフォーマンスに用心する

7.1. 結論

簡単な文字列結合でなく、
n個の文字列を結合する場合はStringBuilderを使いましょう。
Stringは不変(immutable)なので、2つの文字列が結合される場合、両方の内容がコピーされるので。

7.2. サンプル

80文字の固定長の文字列連結の場合、
Fast.javaSlow.java と比べて、85倍速い。
StringBuilder b = new StringBuilder();とした場合は、50倍速い。by Joshua Bloch

Slow.java

1
2
3
4
5
6
7
// 遅い
public String statement() {
    String result = "";
    for (int i = 0; i < numItems(); i++)
        result += lineForItem(i);  // 文字列結合
    return result;
}

Fast.java

1
2
3
4
5
6
7
// 速い
public String statement() {
    StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
    for (int i = 0; i < numItems(); i++)
        b.append(lineForItem(i));  // StringBuilderを使った結合
    return b.toString();
}

備考:定数の文字列連結の場合は、+による連結だとコンパイル時に行われるので、実行時のコストは0になる。

8. 項目 64 インタフェースでオブジェクトを参照する

8.1. 結論

実装を切り替える際に、宣言の1行を変更するだけで済むので、
オブジェクト参照には、

  • 実装クラスではなく、インタフェースを使いましょう。
  • インタフェースがなければ、必要な機能を提供しているクラス階層中の最も上位のクラスを使用しましょう。

ただし、以下の場合を除く。

  • 適切なインタフェースが存在しない場合
    • 値クラス
      • 例:String, BigIntegerなど
    • オブジェクトがクラスに基づくフレームワークに属している。
  • インタフェースはあるが、そのインタフェースにない特別なメソッドをクラスが提供している場合

8.2. ポイント

プログラムを柔軟にするために、APIの場合はインタフェースを使うようにしましょう。

9. 項目 65 リフレクションよりもインタフェースを選ぶ

9.1. 結論

リフレクションは、ある種の洗練されたシステムプログラミング処理では強力ですが、
以下の欠点があるので、インタフェース(もしくは、スーパークラス)を使いましょう

  • コンパイル時の型検査の恩恵を全て失う
    • 実行時に、呼び出すクラス名、メソッド名、フィールド名を確認するため。
  • コードが冗長になり読みづらくなる
    • サンプル参照
  • パフォーマンスが悪くなる
    • 2-50倍遅くなる。by Joshua Bloch
    • 1ns が 50nsになったところで、、なことが多い。機械学習とか計算量が多いと問題になるかもしれないですね。

9.2. サンプル

基本的にリフレクションは使わないのですが、強力な機構であるので紹介します。

以下のようにSetインタフェースにアクセスすることができる。
使用例としては以下。

  • Set契約に従っているのかを調べる汎用的な検査プログラム
  • 汎用的なSetのパフォーマンス解析ツール

某プロジェクトでは、Athenaからの取得結果をvarchar/integer/doubleのいずれかに変換するために利用している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 想定引数
// "java.util.HashSet a c b" / "java.util.TreeSet a c b"
public static void main(String[] args) {
    // クラス名をClassオブジェクトへ変換する
    Class<?> cl = null;
    try {
        cl = Class.forName(args[0]);
    } catch (ClassNotFoundException e) {
        System.err.println("Class not found: " + args[0]);
        // 以下はJVM全体を終了させるので、一般的には使われない。
        // コマンドラインユーティリティの異常終了の場合は、適切。
        System.exit(1);
    }

    // インスタンスの生成はリフレクションで行う
    Set<String> set = null;
    try {
        set = (Set<String>) cl.getDeclaredConstructor().newInstance();
        // java 9から、java.lang.Class#newInstance は deprecated。
    } catch (IllegalAccessException e) {
        System.err.println("Class not accessible");
        System.exit(1);
    } catch (InstantiationException e) {
        System.err.println("Class not instantiable");
        System.exit(1);
    } catch (NoSuchMethodException e) {
        System.err.println("Class does not have such method");
        System.exit(1);
    } catch (InvocationTargetException e) {
        System.err.println("Class not invocatable");
        System.exit(1);
    }

    // インスタンスへのアクセスはインタフェース経由で行う
    set.addAll(Arrays.asList(args).subList(1, args.length));
    System.out.println(set);
}

9.3. ポイント

どの型が来ても良い場合にリフレクションは使えるが、
そうではない限りインタフェースを使いましょう。

10. 項目 66 ネイティブメソッドを注意して使う

10.1. 結論

  • 低レベルのリソースや古いライブラリへアクセスするためのネイティブメソッドの使用は正当であるが、できるだけ局所的にし、徹底的にテストするべき
  • パフォーマンス改善のためにネイティブメソッドを使用するべきでない

10.2. ネイティブメソッドとは

  • ネイティブメソッドとは、CやC++などのネイティブのプログラミング言語で書かれた特別なメソッド

  • ネイティブメソッドはJava Native Interface(JNI)を利用することで、Javaアプリケーションから呼び出せる

  • ネイティブメソッドの用途

    • プラットフォーム特有の機能へアクセスのため
      • ただし、たいていの機能はライブラリとしてある
    • 古いデータへのアクセスを提供している古いコードのライブラリへのアクセスのため
    • (パフォーマンス向上のため)
      • 現在のJVM実装はかなり速く、ネイティブメソッドと遜色ないので使用するべきでない
  • ネイティブメソッドの欠点

    • ネイティブメソッドを使用しているアプリケーションは、Javaアプリケーションであってもメモリ破壊エラーの影響を受けるようになる
      • ネイティブメソッドは安全ではないから
    • 移植性がかなり低くなる
      • ネイティブメソッドはプラットフォームに依存するから
    • 性能劣化することもある
      • ネイティブコードへの出入りでコストがかかるから
    • 可読性が低くなる
      • グルーコードが必要となるから

グルーコード:もともと互換性がない部分同士を結合するためだけに働くコード

10.3. まとめ

以下のネイティブメソッドの使い方は正当

  • 低レベルのリソースや古いライブラリへアクセスするための使用は正当である

ただし、多くの欠点があるため、できるだけ局所的にし、徹底的にテストするようにする。

10.4. 余談

現在(2020年1月)、Project PanamaでJNR(Java Native Runtime)がJEP(JDK Enhancement Proposals)として要望されている

11. 項目 67 注意して最適化する

11.1. 結論

  • 実装時には、速いプログラムよりも良いプログラムを目指すべし

    • 健全なアーキテクチャを破壊すると、疎結合を保てなくなるから
  • 性能を制限してしまう設計は避けるべし

    • APIや通信レベルのプロトコル、永続データ形式を後から変更するのは困難だから
  • API設計において性能について考えるべし

    • API設計によって性能に影響が出るから
  • 最適化前後で性能測定をするべし

    • 性能測定なしで、プログラムがどの部分で時間を費やしているかを推測するのは困難だから
    • 最適化前後で性能測定をしないと、最適化によってどれくらいの効果があったか分からないから
  • アーキテクチャの設計の段階で性能について考えるべき

    • 実装上の問題はのちの最適化で修正可能だが、終盤でアーキテクチャの欠陥をシステムを書き直すことなく修正することはほとんど不可能だから
  • API設計において性能について考えるべし

    • publicの型を可変にすると不必要に多くの防御的コピーが必要になるかもしれない(項目50)
    • APIにおいて、インターフェースではなく実装型を使用することで、後でより速い実装が書かれたとしても、特定の実装に拘束されてしまう(項目64)

11.2. 余談

性能試験はどうなったらゴールなのかはやる前に決めておく必要がある

  • 性能試験をするということは性能目標があるはずなのでそれを満たしたら終わり
  • 良いプログラムの計測方法は?

12. 項目 68 一般的に受け入れられている命名規約を守る

12.1. 結論

  • 一般的に受け入れられている命名規約を守るべき
    • API利用者やそのコードで作業する他の開発者が、誤解なくAPIやコードを理解できるようにするため

12.2. 命名規約の種類

命名規約の種類は以下の2つに分けられる

  • 活字的命名規約
  • 文法的命名規約

12.2.1. 活字的命名規約

  • ピリオドで区切られた要素を持ち、階層的であるべき

  • 区切られた要素はアルファベットの小文字とまれに数字から構成されるべき

  • 組織外で使用されるパッケージには、組織のインターネットドメイン名を逆さにしたものから始める

    • (例えば、edu.cmu、com.sun)
  • パッケージ名の残りは、1つ以上の要素から構成され、8文字以下であるべき。

  • 意味を持った省略形も使うべき(例えば、util、awt)

  • クラス名、インターフェース名、アノテーション名

    • 各単語の先頭一文字を大文字に
  • メソッド名、フィールド名

    • 最初の一文字を小文字に
  • 定数フィールドでは大文字の単語をアンダースコアで区切る(例えばNEGATIVE_INFINITY)

  • ローカル変数名

  • フィールド名と同じだが、省略形も認められる(例えばi,xref)

  • 型パラメータ名

    • Tは任意の型、Eはコレクションの要素の型、KとVはmapのキーと値の型、Xは例外の型など

12.2.2. 文法的命名規約

文法的命名規約は活字的命名規約よりも柔軟で議論の余地がある

  • パッケージ名
    • 特になし
  • クラス名
    • インスタンス化可能なクラスは、単数名詞、名詞句で命名される(Timer、BufferedWriter)
    • インスタンス化しないクラスは、名詞の複数形で命名されることが多い(Collectors、Collections)
  • インターフェース名
    • クラスと同じであったり、形容詞で命名される(Iterable、Runnable)
  • アノテーション名
    • 名詞、動詞、前置詞、形容詞すべてよく使われる
    • 様々な意味でアノテーションが使われるから
  • メソッド名
    • 処理をするメソッドは動詞や目的語を含む動詞句で命名される(append、drawImage)
    • booleanを返すメソッドはisまたはhasで始まり、名詞、名詞句が続いて、形容詞として機能するように命名される(isEmpty、hasSibiling)
    • booleanでない機能や属性を返すメソッドは、名詞、名詞句、getで始まる動詞句で命名される
      • getで始まる動詞句だけを認める声もあるが、下の例のように名詞、名詞句を使ったほうがたいていは可読性が上がる
1
2
if (car.speed() > 2 * SPEED_LIMIT)
    generateAudibleAlert("Watch out for cops!");
  • getで始まる動詞句はBeanクラスならば必須。また、クラスが同じ属性を設定するメソッドを持っているならば使う(getAttributeとsetAttribueなど)

いくつかのメソッドは特別な命名がされている

  • toTypeメソッド
    • インスタンスの型を変換し、独立したオブジェクトとして返すメソッド(toString)
  • asTypeメソッド
    • 受け取ったオブジェクトと異なる型のオブジェクトを返すメソッド(asList)
  • typeValueメソッド
    • 呼び出されるオブジェクトと同じ値のプリミティブな値を返すメソッド(Integer#intValue)
  • staticファクトリーメソッド
    • valueOf, of, getInstance, newInstance, getType, newTypeなど
  • フィールド名、ローカル変数名
    • フィールド名の文法規則はそあまり確立されていないし、それほど重要ではない
  • うまく設計されたAPIではフィールドはほとんど公開されないから 名詞や名詞句が多い
    • boolean型のフィールドはアクセサメソッドの最初の文字列だけ消えたものが採用されることが多い
    • ローカル変数名はもっと緩い

12.3. 余談

  • Readable Code を読むと良さそう
  • フィールド名も他の人が見るから気を付けた方が良さそう。

13. 参考URL