Effective Java 3rd [Chapter 8] - メソッド
メソッド
Effective Java 第 3 版の個人的メモ
- 項目 49 パラメータの正当性を検査する
- 項目 50 必要な場合、防御的にコピーする
- 項目 51 メソッドのシグネチャを注意深く設計する
- 項目 52 オーバーロードを注意して使う
- 項目 53 可変長引数を注意して使う
- 項目 54 null ではなく、空コレクションか空配列を返す
- 項目 55 オプショナルを注意して返す
- 項目 56 すべての公開 API 要素に対してドキュメントコメントを書く
- 参考URL
1. 項目 49 パラメータの正当性を検査する
1.1. 結論
- 入力値のチェックをしましょう
- Javadocに入力値の説明と例外の説明は記入しましょう
1.2. 良い例
|
|
最初に入力値チェックをすることで、以下を防げる。
- メソッドの処理部で例外が発生してしまうこと(後から解析が必要になる。)
- 処理で例外が出ずに、変な値が返却されてしまうこと(これは最悪の場合)
余談:
@throws ArithmetricException m <= 0の場合.
の@throws
はメソッドにthrows
がついていないので忘れがちかもしれないなと思いました。(IDEで自動的に付加されないので。)
1.3. アサーションの紹介
private
メソッド(外部には公開しない)の場合は、そのパッケージの作成者として、どのような状況でメソッドが呼び出されるのかを
アサーションを用いて入力値の検査をすべき。
|
|
|
|
-ea
(-enableassertions
)をつけた場合のみassert
は有効になる。
-ea
を付けなければ、assert
は有効にならないので、実運用時にコストは発生しない。
2. 項目 50 必要な場合、防御的にコピーする
2.1. 結論
- クライアントが信頼できない場合は、防御的にコピーしましょう
- クライアントが信頼できて、コピーのコストが非常に高い場合は、Javadocに記載しても良いです。
2.2. 防御的コピー
クライアントが、クラスの不等式を破壊するために徹底した努力をすると想定しましょう。
2.2.1. 悪い例
Date
はmutable(フィールドを変更可能なオブジェクト)です。
|
|
|
|
2.2.2. 良い例
|
|
もし
Date
のコピーが非常にコストの高い行為であり、クライアントが信頼できる場合は、
“影響を受ける要素を変更しないこと"をクライアントの責任であると、Javadocに示すことで対応しても良い。
3. 項目 51 メソッドのシグネチャを注意深く設計する
3.1. 結論
- メソッド名を注意深く選ぶべし
- 便利なメソッドを提供しすぎないようにするべし
- 長いパラメータのリストを避けるべし
- パラメータ型に関しては、クラスよりインタフェースを選ぶべし
- booleanパラメータよりは2つの要素を持つenum型を使用するべし
3.2. メソッド名を注意深く選ぶべし
APIを使う人が誤解なく使えるようにするために、以下をすること
- 標準命名規約に従う
- パッケージ内のほかの名前と矛盾のない名前にする
- 広範囲のコンセンサスと矛盾がない名前を選ぶ
- getHogeならhogeを返すなど
3.3. 便利なメソッドを提供しすぎないようにするべし
- 提供するか迷ったらやめるべき
- 【理由】多くのメソッドを用意すると、学習、使用、文書化、テスト、保守を困難にするから
3.4. 長いパラメータのリストを避けるべし
- 特に同じ型のパラメータが続くことは避けるべき
- 【理由1】利用者は多くのパラメータを覚えることができないので、ドキュメンテーションを常に参照しなければならなくなるから
- 【理由2】同じ型のパラメータに対して順序を間違うと、コンパイルエラーにならず、利用者の期待した動作をしないから
長いパラメータリストを避けるテクニックは3つある
- 必要最低限の(細かな)単位でメソッドを作成する
- 例:サブリストから指定された要素が最初に検出された位置のインデックスを返す
indexOfSubList(Object o, int fromIndex, int toIndex)
がある場合、
次のように、複数のメソッドに分割する。
list.subList(fromIndex, toIndex).indexOf(o)
- パラメータ間に繋がりがある場合はまとめる
- 例:トランプの数字と種類をパラメータに取るメソッド
hogemethod(int num, TrampSuit suit)
があった場合、
数字と種類をまとめたヘルパークラスを作成し、パラメータにヘルパークラスをとるようにする
hogeMethod(Tramp tramp)
- 引数が多いコンストラクタの引数が多い場合は、ビルダーパターンを使う。
3.5. パラメータ型に関しては、クラスよりインタフェースを選ぶべし
- パラメータの型は実装クラスよりもインタフェースを使用するべき
- 例えば、HashMapではなくMapにする
- 【理由】制約をゆるくするため。
3.6. booleanパラメータよりは2つの要素を持つenum型を使用するべし
enum型を使用したほうが利用者にとってわかりやすいから。また、新たな状態を追加しやすいから。
- 例:Thermometer型があったとして、
Thermometer.newInstance(true)
より、Thermometer.newInstance(TemperatureScale.CELSIUS)
の方が、分かりやすい。
また、簡単にTemperatureScale.KELVIN
を追加できる。
4. 項目 52 オーバーロードを注意して使う
4.1. 結論
- パラメータの数が同一のオーバーロードされたメソッドを提供するべきでない
4.2. オーバーロード
4.2.1. コードと実行結果_その1
|
|
4.2.2. コードと実行結果_その2
|
|
オーバーロードされたどのメソッドが呼び出されるかの選択はコンパイル時に行われるため。
上の例では、3回のループで、パラメータのコンパイル時の型はCollection<?>
であるので、classify(Collection<?> s)
が呼び出される。
4.3. オーバーライド
4.3.1. コードと実行結果_その3
|
|
オーバーライドは実行時に呼び出されるメソッドが決められるため。
4.4. オーバーロードの利用
- 最も安全な方針は、 パラメータの数が同一のオーバーロードされたメソッドを提供しないこと
- 回避策の例:ObjectOutputStreamは、
write()
をオーバーロードするのではなく、writeBoolean(boolean)
、writeInt(int)
、writeLong(long)
としている。
- 回避策の例:ObjectOutputStreamは、
- 上記の方針を破らなければならない時は、限定されたオーバーロードをより一般的なオーバーロードに転送させるべき
- 例えば、
String#contentEquals()
1 2 3
public boolean contentEquals(StringBuffer sb) { return contentEquals((CharSequence)sb); }
- 例えば、
- コンストラクタの場合は、異なる名前を使用できないが、staticファクトリーメソッドを提供するという選択肢がある
- そもそもオーバーロードを使いまくらないことが多いけど、パラメータの数が同一のオーバーロードされたメソッドを作ることもある
StringBuilder#append()
はオーバーロードを使っている良い例
5. 項目 53 可変長引数を注意して使う
5.1. 結論
すべての引数の型が同じでも、全部可変長ではなく、一部のみにしたほうがいいことがある。
ex) 必須パラメータ と オプションパラメータ で分けたいとき。
|
|
6. 項目 54 null ではなく、空コレクションか空配列を返す
6.1. 結論
nullよりも、 空オブジェクト を常に使うこと。
6.2. 理由
- nullが返ってくる -> 使う側は常にnullチェックを強制される。
- 場合によっては、コレクションが空or要素があるのどちらかを考慮せずコードがかける。
|
|
なお、空オブジェクトを返す挙動を 必ず javadocに記述すること。
- null と 空オブジェクト で意味が異なる場合は、もちろん、両方の意味を定義して使い分けること
- 古いAPIだと 空オブジェクトで良い場面でnullを使っているケースがある
7. 項目 55 オプショナルを注意して返す
7.1. 結論
java8 から Optional<T>
が入ってきた。定義をjavadocから引用すると…
|
|
基本これを使うと、NULLセーフできる。
また、Optionalを使うと、以下が改善できるシーンがでてくる
- if文のブロックを可読性の高いシンプルなコードに置き換えることができる
- lambdaと相性がよいので、stream処理のmap、filterに自然に組み込むことができる
7.2. 補足
- Optionalが戻り値であるメソッドからnullを返してはならない。これを行うと、Optionalを導入した意味がなくなる。
- Intellij は警告してくれる
8. 項目 56 すべての公開 API 要素に対してドキュメントコメントを書く
8.1. 結論
(状況に応じて、できる範囲で)必ず書きなさい。
- 利用者の範囲
- OSS
- 会社内共通
- プロジェクト共通
- 個別業務ロジック
- ライフサイクル
- 長く維持する
- 一発
- 使う場所
- 製品コード
- テストコード
- ツール etc…
必ず、記述粒度は書き始める前に関係者で話しておくこと。
いかのタイミングで重要なタグが追加された。
- Java9で@index
- Java8では@implSpec
- 継承元となる設計をされたクラスは@implSpecタグでメソッドとサブクラスとの決まり事を記述する。
- Java5では@literalと@code
8.2. 書き方
How to Write Doc Comments https://www.amazon.co.jp/エンジニアのためのJavadoc再入門講座-現場で使えるAPI仕様書の作り方-佐藤-竜一/dp/4798119482
結局、一言でいうと、「制約を順序立てて記述する」ことを念頭に置くと、だいたい大丈夫です。
なにか迷ったら、本家のjavadocをパクる。
- public, protected は絶対書く
- 重複している役割のものがある(
@throws
,@exception
/{@code}
,<code>
)ので、 どれを使うかは、チームで軽く決めるとよい。
意外とわすれがちなのは、スレッドセーフかどうか、シリアライズ関連。 特別な考慮事項があれば記述すること。