Java コーディング規約
はじめに
一般に利用・参照されている Java コーディング規約やガイドラインを以下に示す。本規約の作成においても、下記規約類を参照・抜粋している。
規約 | 著作者 | URL |
---|---|---|
Code Conventions for the Java Programming Language | Sun Microsystems | http://www.oracle.com/technetwork/java/codeconvtoc-136057.html |
Writing Robust Java Code | Scott W. Ambler | http://www.ambysoft.com/downloads/javaCodingStandards.pdf |
オブジェクト倶楽部版 Java コーディング基準 | オブジェクト倶楽部 | http://objectclub.jp/community/codingstandard/CodingStd.pdf |
電通国際情報サービス版 Java コーディング規約 2004 | 電通国際情報サービス | http://objectclub.jp/community/codingstandard/JavaCodingStandard2004.pdf |
JJGuideline (Java - J2EE Conventions and Guidelines) | Stephan.J & JCS Team | http://www.fedict.belgium.be/sites/default/files/downloads/Java_J2EE_conventions_and_guidelines_EN.pdf |
Google Java Style (非公式和訳) |
https://kazurof.github.io/GoogleJavaStyle-ja/
|
|
Acroquest Technology Java コーディング規約 | Acroquest Technology | https://www.acroquest.co.jp/webworkshop/javacordingrule/Acroquest_JavaCodingStandard_6_7.pdf |
※ Sun Microsystems の規約は Java 草創期から一応の基準という位置づけだったが、オブジェクト指向、及び、その開発環境の普及・発展 によって、設計やコーディングにおいて、直接的に有用な知識や豊富な指針を含むような優れた規約や、ツールなどによる機械的な 基準チェックと連携する規約が普及してきている。
規約の重要性
基準としての規約を定義し、尊寿することの重要性を以下に示す。
- ソフトウェアメンテナンスにおける、可読性、保守性、拡張性の向上
- 問題を起こしやすい実装を未然に回避することによる、品質、生産性の向上
- 標準規約を通して得られる一般的な実装知識やノウハウ(=学習効果)
コーディングの心得
長いプログラムを記述すること(ステップ数)によって生産性が評価されたのは、過去の時代の出来事である。 現在は、クラスやメソッドの役割が明確で、ロジックが読みやすく、保守性に優れたプログラムを記述することが評価される。 コーディング規約は、コードの書き方に関する一種のパターンと考えることもでき、コードの保守性を向上させる 具体的な方法を示している。 したがって、規約の一つ一つの意図を理解し、守ることが重要になる。 しかし、保守性に優れたコードを作成するためには、コーディング規約を守ることに加えて、良いコードを記述するための基本的な 心構えをしっかり心に留めておく必要がある。 以下では、その心得について述べる。
【コーディングの心得5か条】
- 見やすさを重視せよ
- ネーミングはわかりやすく
- サンプルを鵜呑みにしない
- 同じコードを二度書かない
- 役割は一つに
見やすさを重視せよ
「良いコード」の基本は、「他の人が読んでもわかりやすいと感じられるコード」。 コードの見やすさは、フォーマットはもちろん、ロジックの簡潔さやAPIの常識的な使い方などから生まれる。 コーディングにあたっては、常に他の人の視点を意識しながら、見やすさに気を配って記述する必要がある。 例えば、自分で記述したコードであっても、しばらくたってから読み返してみると理解に時間がかかった経験は誰にもあるはず。 「3日前に書いたコードは他人のコードと同じ」ということもよく言われる。 見やすさを重視することは、他の人のためだけでなく自分のためにもなる。 コードを読んでもすぐに理解できないような実装は、再考(リファクタリング)の必要がある。
ネーミングはわかりやすく
コーディングでは、様々な変数やメソッドなどにネーミング(名前付け)する必要がある。 ネーミングとは、本来、その対象の本質を表すような名前を考える作業である。 大変難易度の高い作業だが、一方で適当に行ってもコードの動作は変わらないため、人によっては手を抜きがちとなる。 しかし、ネーミングの良し悪しは、コードの可読性に非常に大きな影響を及ぼす。 例えば、「C0001」というクラス名があるとする。 これでは、何を表すクラスなのかすぐにはわからないだろう。 また、「int p = 500;」という記述があるとする。 プログラマに聞くと、変数名 p は価格(price)の略だと言うのだが、それならば略さずに、「int price = 500;」としたほうが分かりやすいはずである。 「ネーミングはわかりやすく」の背景には、読んで内容が理解できるという意味で、文章のようなプログラミングを行う、という考え方に基づく。
サンプルを鵜呑みにしない
サンプルコードを活用すること自体は、著作権等を侵害しなければ問題ない。 問題なのは、その内容や背景を理解しないまま、サンプルコードだけを鵜呑みにして、「おまじない」として表面的に適用してしまうことである。 コードを「おまじない」ととらえていては、サンプルコードの間違いを気づかないまま適用してしまうこともある。 例えば、ストリームのクローズ処理を行っていないサンプルコードであっても、それに気づかずに 自分のコードに適用してしまい、後で思わぬ障害を引き起こすという可能性がある。 サンプルコードは、そこで説明する内容に絞ったコードが多いため、このような例はよく見られる。 また、サンプルコードをそのまま適用した結果、自分が記述すべきコードには必要のないコードが含まれてしまう場合もある。 その場合、コードの可読性を下げる原因となる。 自分のコードは、自分で深く理解して記述すべきである。
同じコードを二度書かない
コードをコピー・ペーストしていませんか? コピー・ペーストしてしまうと、何らかの修正をする際に、全ての箇所に同じ修正をする羽目になる。 同じコードが現れるようならまとめて一つにし、外に出してコールするような書き方にすべきである。 同じコードをまとめる作業は、どちらかといえば、コーディング時よりリファクタリング(ソフトウェアの外部的振る舞いを変更せずに内部構造を改善する作業) で行われることが多い。 しかし、コーディング時からできるだけ気をつけておきたいことでもある。
役割は一つに
メソッドの役割が明確で、かつ1つであれば単体テストが行いやすくなる。 つまり、コードの「試験性」が高まる。 また、役割が一つであれば、後でコードを変更する際に修正箇所がわかりやすいため、障害修正に要する時間が短くなる。 つまり、コードの「保守性」があることになる。 例えば「チェックをして実行する」機能を実現するために、checkAndDo() メソッドが存在したとする。 この場合、このメソッドは check() メソッドと do() メソッドに分割すべきである。 なぜなら、checkAndDo() メソッドの check() ロジックに誤りがあった場合、do() メソッドに書かれる内容まで把握する必要が生じるためである。 分割してあれば、check() メソッドだけの変更で済む。 このことはクラスの設計にもあてはまる。
ネーミング規約
全般
-
大文字・小文字の違いで名前を区別しない。
良い例:
private int carNumber; private int trainNumber;
1
2悪い例:
private int num; private int Num;
1
2 - 基本は簡単な英単語を利用するが、ローマ字(ヘボン式)を使用する特殊なケースではヘボン式にて命名する
-
英単語の対称性を意識した名称にする
良い例:
//名称が対照的である public void open() public void close() public void read() public void write()
1
2
3
4
5
6悪い例:
//名称が対照的でない public void open() public void dispose() public void read() public void output()
1
2
3
4
5
6 - 長い文字数の名前は避ける。
15文字以内がよい※省略しすぎで意味が分からなくなる場合は15文字を超えてもよい。 - 名称から型を推測しやすいように応じた命名を行う。
※変数やメソッド共に配列なら複数形。
Collectionフレームワークのオブジェクトなら、List/Map/Set/Queue などを末尾につける良い例:
int[] indexies; List numberList; Map clientMap; public Set createdIdSet(); public Queue getResponseQueue();
1
2
3
4
5悪い例:
int[] index; List number; Map client; public Set createdId(); public Queue getResponse();
1
2
3
4
5 - キャメルケースで命名する際に、ID等の省略形ですべて大文字で表される単語の場合は例外的にそのままの使用を許可
ただし、単語間の途中では先頭文字以外は小文字にすること良い例:
private String xxxID; private String xxxIdxxx;
1
2悪い例:
private String xxxIDxxx;
1
パッケージ
- パッケージ名はすべて小文字にする
- パッケージ名は意味のある名前にする
- サブパッケージ名の重複は可能
クラス
クラス名は単語の先頭を大文字にする
良い例:
public class Entry {
1悪い例:
public class entry {
1インターフェースの命名で、名前の接尾辞に'able'、'ible'、'er'を付ける習慣があるが必須ではない。
- 同一プロジェクトにて同名のクラスを命名してはいけない。
- 独自の例外クラスを作成する場合は、"Exception"を接尾辞に付けた命名とする。
- テストコードのクラス名は、ソースコードのクラス名の末尾に"Test"を付与した名前とする
- モッククラスは、ソースコードのクラス名の末尾に"Mock"を付与した名前とする
メソッド
- コンストラクタと同じ名前をメソッドはつくらない
メソッド名は区切りのみ大文字にする
良い例:
public String getName() { //・・・ }
1
2
3悪い例:
public String getname() { //・・・ } public String GETNAME() { //・・・ }
1
2
3
4
5
6変換メソッド名は「” to ” + オブジェクト名」にする
良い例:
public String toString() {
1悪い例:
public String string() {
1- ゲッターメソッド名は「”get” + 属性名」にする
型がbooleanの場合は「”is” + 属性名」にする - セッターメソッド名は「”set” + 属性名」にする
boolean変数を返すメソッド名はtrue/falseの状態がわかるようにする
良い例:
public boolean isAsleep() { } public boolean exists() { } public boolean hasExpired() { }
1
2
3
4
5
6- 基本的に先頭を能動態の動詞で始めること
- ファクトリメソッド(オブジェクトを生成するメソッド)は "create + オブジェクト名"とする
良い例:
public SampleBean createSampleBean()
1悪い例:
public SampleBean makeSampleBean()
1
引数
メソッドのパラメータ名とインスタンス変数名を一緒にしない ただし、アクセサメソッドやコンストラクタなど、統合開発環境の機能により自動生成するものに関しては可とする。 アンダースコア _ をつけての区別は原則禁止とする。
良い例:
public double calc(double rate) { return this.value * rate; }
1
2
3悪い例:
public double calc(double value) { return this.value * value; } public double calc(double _value) { return this.value * _value; }
1
2
3
4
5
6
変数全般
boolean変数はtrue/falseの状態がわかるようにする
良い例:
private boolean isOpen;
1悪い例:
private boolean flag;
1定数は全てstatic final とし、すべて大文字、区切りは”_”
良い例:
private static final String SYSTEM_NAME = "販売管理システム";
1変数名は小文字とし、単語の区切りのみ大文字にする
良い例:
private String thisIsString;
1
ローカル変数
スコープが狭い変数名は省略した名前でもよい
良い例:
if (・・・) { String s = "・・・・" ; //変数sを利用した処理 数行 }
1
2
3
4悪い例:
String s = "・・・・"; if (・・・) { //変数sを利用した処理 数行 } ・・・ if (・・・) { //変数sを利用した処理 数行 }
1
2
3
4
5
6
7
8
変数sの利用範囲が広いので役割が明確になる変数名に変更する。- for 文のループカウンタは、ネストごとに”i”,"j","k"・・・を使う
- try{} catch{} の例外の変数名には'e'の1文字で表現することを許可する
Enum
- Enum 名はクラス名と同じく、単語の先頭を大文字にする
列挙定数は定数と同じく、すべて大文字、区切りは”_”
良い例:
enum Season { WINTER, SPRING, SUMMER, FALL }
1
2
3
4
5
6悪い例:
enum Season { winter, spring, summer, fall }
1
2
3
4
5
6
コーディング規約
全般
原則としてオブジェクトの参照にはインターフェースを利用する
オブジェクトを参照する際は、そのオブジェクトの実装クラスを用いて宣言できるが、実装クラスに適切なインターフェースが存在している場合は、必ずインターフェースを用いて宣言すること。良い例:
List<Entry> list = new ArrayList<>(); Map<String, String> map = new HashMap<>();
1
2悪い例:
ArrayList<Entry> list = new ArrayList<>(); HashMap<String, String> map = new HashMap<>();
1
2- 推奨されないAPIを使用しない
アノテーション@Deprecatedで指定されたメソッドは利用しないこと。 - 使われないコードは書かない
- 宣言は適切な権限で行うこと(public,protected,private)
- finalを適切に利用する
継承されないクラス、オーバーライドされないメソッド、値の変わらない変数(つまらない定数)等、変化のないもの/変化させたくないものについてはfinalで宣言する。良い例:
//継承されないクラス public final class CalculateUtils { //・・・ } //値の変わらない変数(定数) private static final String MESSAGE = "・・・・" ; //オーバーライドされないメソッド public final int sum(/*変化させたくない値*/final int... values) { int sumValue = 0; for (/*変化させたくない値*/final int value : values) { sumValue += value; } return sumValue; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 - 各プロジェクトにてフレームワークや共通にて提供されているクラスと類似した独自実装クラスを作成してはいけない
- 各文字数や行数は下記内容を目安とすること
1行は120文字
1メソッドは150行
1クラスは500行
※JavaDocやコメント、空行は対象外として実行のみ - 同じ内容のハードコーディングを複数記述しないこと
適切な命名で定数化すること
フォーマット
- インデントは空白文字4文字分のTabを使用する
- 長すぎる行は避ける
{の後にステートメントを記述しない
良い例:
if (s == null) { return 0; }
1
2
3悪い例:
if (s == null) {return 0;}
11行に2つ以上のステートメントを記述しない
悪い例:
} catch (Exception e) { log.error("Error",e);return null; }
1
2
3カンマの後には空白文字を
良い例:
process(x, y, z);
1悪い例:
process(x,y,z);
1代入演算子(=,+=,-=, ...)の前後には空白文字を挿入する
良い例:
int a = x; a += 10;
1
2悪い例:
int a=x; a+= 10;
1
2for 文内のセミコロンの後には空白文字を挿入する
良い例:
for (int i = 0; i < array.length; i++) { //・・・ }
1
2
3悪い例:
for (int i = 0;i < array.length ;i++) { //・・・ }
1
2
3++や--とオペランドの間には空白文字を入れない
良い例:
i++;
1
悪い例:i ++;
1- ビット演算子(|、&、^、<<、>>)の前後には空白文字を挿入する
- 論理演算子(||、&&)の前後には空白文字を挿入する
- 関係演算子(<、>、>=、<=、==、!=)の前後には空白文字を挿入する
- 算術演算子(+、−、*、/、%)の前後には空白文字を挿入する
return 文ではカッコを使わない
良い例:
int answer = (a + b + c) * d; return answer;
1
2悪い例:
return ((a + b + c) * d);
1if などの条件式で boolean の変数を比較しない
良い例:
if (hasStock)
1悪い例:
if (hasStock == true)
1不等号の向きは左向き(<、<=)にする
良い例:
if (from <= x && x <= to) {
1悪い例:
if (x >= from && x <= to) {
1
コメント
- ファイルの先頭へのCopyright の表記について
ソースのファイルヘッダにコピーライト表記は法的拘束力がないため、不要とする。
ただし、顧客からの要求があった場合を除く。 - Javadoc コメントには、少なくとも author と version(クラス)、param と return と exception(メソッド)を記述する
- 今後もバージョンアップのリリースが予定されているソースでは、上記に加えて since(バージョン)を記述する
- @Overrideのあるメソッドでは、上記に加えて{@Inherit}を記述する
Javadoc クラスヘッダコメントのフォーマットは以下の通り
良い例:
/** * Action (or Bean) クラス メニュー名所 * * @author 姓 名 * @version バージョン YYYY/MM/DD 説明 */
1
2
3
4
5
6コメントは必要な物だけを簡潔に
悪い例:
/** * 文字列に変換 */ @Override public String toString() { /** * コピー * * @return コピーしたインスタンス */ public Entry copy() {
1
2
3
4
5
6
7
8
9
10
11
12- 不要なコメントは記載しない
- コードからすぐわかること・冗長なコメント
- 名前の説明
コメントではなくわかりやすい名前を付ける - 別システムで管理している内容
ソースコード管理システム、バグトラッキングシステムでかんりしている内容はソースコードにコメントで記載する必要はない。- コメントアウトされたコード
ソースコード管理システムで管理されている
- コメントアウトされたコード
インポート
java.langパッケージはインポートしない
悪い例:
import java.lang.String; //必要のない記述
1- 原則として static インポートしない
JUnit の作成やフレームワークとして static インポートが推奨されるような場合は利用してもよい 原則としてオンデマンドのインポート宣言(type-import-on-demand declaration)(アスタリスク*によるインポート) は行わない
悪い例:
import java.util.*;
1
コンストラクタ
public 宣言していないクラスにはpublic権限のコンストラクタを作らない
良い例:
class Entry { //・・・ Entry(int id) { //・・・ }
1
2
3
4
5悪い例:
class Entry { //・・・ public Entry(int id) { //・・・ }
1
2
3
4
5- インスタンスメンバを持たない(static メンバのみの)クラスは、private権限のコンストラクタを作成する
メソッド
- オーバーライドさせたくないメソッドはfinalを利用する
戻り値が配列のメソッドで、戻る配列のサイズが 0 の場合、メソッドを使用するクライアントの余計な null チェックのロジックを回避するため、null ではなく長さゼロの配列を戻すようにする。
良い例:
public String[] toArray(String s) { if (s == null || s.isEmpty()) { return ArrayUtils.EMPTY_STRING_ARRAY; } return new String[] { s }; } public List<String>toList(String s) { if (s == null || s.isEmpty()) { return Collrctions.emptyList(); } return Arrays.asList(s); }
1
2
3
4
5
6
7
8
9
10
11
12
13悪い例:
public String[] toArray(String s) { if (s == null || s.isEmpty()) { return null; } return new String[] { s }; } public List<String> toList(String s) { if (s == null || s.isEmpty()) { return null; } return Arrays.asList(s); }
1
2
3
4
5
6
7
8
9
10
11
12
13- メソッドは1つの役割にする
- setter は必要に応じて作成する
インスタンス生成時に保有した内部データを参照のみ提供したい時などに、setter による内部データが更新される可能性を排除したいときはコンストラクタにて必要なデータを受け取り getter のみを提供する良い例:
public NameData (String id,String name) public String getId() public String getName()
1
2
3悪い例:
public NameData () public String getId() public void setId(String id) public String getName() public void setName(String name)
1
2
3
4
5 - toString() を必要に応じてオーバーライドする
クラスメソッド
クラスメソッドを利用するときは、クラス名を使って呼び出す
良い例:
int comp = Integer.compare(x, y);
1悪い例:
Integer a = // int comp = a.compare(x, y);
1
2
変数全般
1つのステートメントには1つの変数宣言
良い例:
/** 科目コード */ private String code; /** 科目名 */ private String name; /** 科目略名 */ private String shortName;
1
2
3
4
5
6悪い例:
private String code, name, shortName;
1リテラルは使用しない
リテラルとは、コード中に、表現が定数として直接現れており、記号やリストで表現することができないものを指す(数値、文字列両方含む 通称マジックナンバー)。コードの可読性・保守性の低下を防ぐために、リテラル定数(static finalフィールド)を使用すること。
例外:-1,0,1 等をカウント値としてループ処理等で使用するような場合良い例:
private static final double ONE_MILE_METRE = 1609.344; public double mileToMetre (double mi) { return mi * ONE_MILE_METRE; }
1
2
3
4
5悪い例:
public double mileToMetre (double mi) { return mi * 1609.344; }
1
2
3リテラル定数の名前はその値の意味を正しく表現したものにする
悪い例:
private static final int ZERO = 0;
1
配列宣言は「型名[ ]」にする
良い例:
private int[] sampleArray = new int[10];
1悪い例:
private int sampleArray[] = new int[10];
1- できるだけローカル変数を利用する
ローカル変数で事足りるものをインスタンス変数として利用するなど、必要のないインスタンス変数を定義すると、パフォーマンスや可読性の低下やの大きな要因となる上、 マルチスレッドを意識した際に不整合がおきる可能性があるので、インスタンス変数は必要性を充分に考慮してから使用すること。 - 定数はfinalで宣言する
- ローカル変数とインスタンス変数を使いわける
- ラッパークラスでの型宣言よりプリミティブの型宣言にする
良い例:
private int count; private boolean isUpdate;
1
2悪い例:
private Integer count; private Boolean isUpdate;
1
2
定数
publicで宣言するクラス変数とインスタンス変数は、定数のみとし、static finalで定義する
finalではないstaticな定数は作成しない。良い例:
public static final String PROTOCOL_HTTP = "http";
1- 定数(staticフィールド)に、staticではないメソッドから書き込まない
定数は、プリミティブ型もしくは、不変(Immutable)オブジェクトで参照する
- 不変Listの生成にはCollectionsクラスのunmodifiableList()メソッドを利用する
良い例:
public static final List<Integer> VALUES = Collections.unmodifiableList(Arrays.asList(1, 2, 3, 4));
1悪い例:
public static final List<Integer> VALUES = Arrays.asList(1, 2, 3, 4);
1 - 不変Setの生成にはCollectionsクラスのunmodifiableSet()メソッドを利用する
不変Mapの生成にはCollectionsクラスのunmodifiableMap()メソッドを利用する 良い例:
public static final Map<Integer, String> VALUES_MAP = Collections.unmodifiableMap(new HashMap<>() { { put(1, "A"); put(2, "B"); put(3, "C"); } });
1
2
3
4
5
6
7悪い例:
public static final Map<Integer, String> VALUES_MAP = new HashMap<>() { { put(1, "A"); put(2, "B"); put(3, "C"); } };
1
2
3
4
5
6
7- 不変Listの生成にはCollectionsクラスのunmodifiableList()メソッドを利用する
- 不変な配列インスタンスは長さ 0 の配列以外は生成不可能なため、外部から参照される(public)定数では利用せず、List等への置き換えをすること
良い例:
public static final List<Integer> VALUES = Collections.unmodifiableList(Arrays.asList(1, 2, 3, 4));
1悪い例:
public static final int[] VALUES = { 1, 2, 3, 4 };
1
インスタンス変数
- インスタンス変数はprivateにする
良い例:
public class Employee { private long id; //・・・ //getter/setter }
1
2
3
4
5
6悪い例:
public class Employee { public long id; //・・・ //getter/setter }
1
2
3
4
5
6 - 可変なクラスを保持する場合はカプセル化を意識する
可変なクラスのsetterやgetterを提供するのではなく、可変なクラスへの操作が行えるメソッドを提供する良い例:
private Map<String, String> map; public void put(String key, String value) public String get(String key)
1
2
3悪い例:
private Map<String, String> map; public void setMap(Map<String, String> map) public Map<String,String> getMap()
1
2
3
クラス変数
- public static final宣言した配列を利用しない
※「定数」を参照 - クラス変数にはクラス名を使用してアクセスすること
良い例:
BigDecimal b = BigDecimal.ZERO;
1悪い例:
BigDecimal a = // BigDecimal b = a.ZERO;
1
2 可読性が向上するような表記方法を心掛ける
スリープ時間をミリ秒で求める処理ではTimeUnitを利用する
※16進進数表記などは、内容がわかりにくいため必要な場合を除き利用は控える良い例:
//リトライ時の待機時間(ミリ秒) private static final int RETRY_SLEEP_MS = TimeUnit.SECONS.toMillis(10); private static final int MAX_BYTE_SIZE = 1024 * 128;
1
2
3悪い例:
//リトライ時の待機時間(ミリ秒) private static final int RETRY_SLEEP_MS = 10000; private static final int RETRY_SLEEP_MS = 1000 * 10; private static final int RETRY_SLEEP_MS = 0x20000;
1
2
3
4パターンを表す定数には、タイプセーフとなるようEnum型を積極的に利用する
良い例:
public enum Color { RED, BLUE, YELLOW, }
1
2
3
4
5悪い例:
public static final int RED = 1; public static final int BLUE = 2; public static final int YELLOW = 3;
1
2
3int定数は、enumで管理する
配列やリストのindex等をint定数を作成する際は、enumで定義する方が後々の修正が楽になる。
※enumは列挙した順に先頭から序数に0から採番されます良い例:
//定義 ※enumにすることで配列の順番が変わったり、要素間に新しい要素が追加された時も容易に変更可能(並び順を変えたり、間に追加するだけ) public enum index { ID, NAME, VALUE, } //利用時 String str = strs[index.ID.ordinal()];
1
2
3
4
5
6
7
8悪い例:
//定義 ※int定数の場合、配列の順番が変わったり、要素間に新しい要素が追加された時は、影響を受ける定数の値を全て変える必要がある public static final int INDEX_ID = 0; public static final int INDEX_NAME = 1; public static final int INDEX_VALUE = 2; //利用時 String str = strs[XXX.INDEX_ID];
1
2
3
4
5
6
ローカル変数
- ローカル変数は利用する直前で宣言する
行間の程度にもよるが、ある程度まとめて宣言するのは OK とする。良い例:
for (int i = 0; i < lines.length; i++) { String line = lines[i]; //lineの処理 }
1
2
3
4悪い例:
String line; for (int i = 0; i < lines.length; i++) { line = lines[i]; //lineの処理 }
1
2
3
4
5 - ローカル変数は安易に再利用しない
一度宣言したローカル変数を、複数の目的で安易に使いまわさないこと。ローカル変数は、役割ごとに新しいものを宣言して初期化することにより、コードの可読性・保守性の向上、及びコンパイラの最適化の促進をはかる。
引数
- メソッド引数への代入は行わない
原則としてfinalで宣言する。良い例:
public void add(final int value) { //・・・ }
1
2
3
継承
-
スーパークラスのインスタンス変数をサブクラスでオーバーライドしない
スーパークラスと同じ名前のフィールドをサブクラスで宣言しないこと。 同じ名前のフィールドを宣言すると、スーパークラスのフィールドはサブクラスで宣言されたフィールドによって隠ぺいされてしまうので、他の人の混乱を招くことを防ぐため重複する名前は付けないこと。悪い例:
public class Abs { protected String name; } public class Sub extends Abs { protected String name; //Abs#nameは隠ぺいされる }
1
2
3
4
5
6
7 -
スーパークラスのメソッドをオーバーライドするときは@Override アノテーションを指定する。
良い例:
public class Abs { protected void process() { } } public class Sub extends Abs { @Override protected void process() { } }
1
2
3
4
5
6
7
8
9
10
11
12悪い例:
public class Abs { protected void process() { } } public class Sub extends Abs { //@Overrideアノテーションの指定がない protected void process() { } }
1
2
3
4
5
6
7
8
9
10
11
12 - スーパークラスで private 宣言されているメソッドと同じ名前のメソッドをサブクラスで定義しない
スーパークラスにある private メソッドと同じ名前のメソッドをサブクラスで定義しないこと。private メソッドはオーバーライドされず全く別のメソッドとして扱われ、 他の人の混乱を招き、バグにつながる恐れがある。
インナークラス
- 原則としてインナークラスは利用しない
一つの java ファイルに複数のクラスを記載するのは NG とする。また無名クラスを利用するのも原則として NG とする。
Enum の定数固有メソッド実装(constant-specific method implementation)、Java8 のラムダ式は内部的にインナークラスとされるがこれらは許可する。
メンバー順序
- 以下の順で記述する
- static フィールド
- static イニシャライザー
- static メソッド
- フィールド
- イニシャライザー
- コンストラクター
- メソッド
- 同一カテゴリー内では以下の可視性の順で記述する
- public
- protected
- パッケージ private
- private
インスタンス
- オブジェクト同士はequals()メソッドで比較する
良い例:
String s1 = "text"; String s2 = "text"; if (s1.equals(s2)) { //・・・ }
1
2
3
4
5悪い例:
String s1 = "text"; String s2 = "text"; if (s1 == s2) { //・・・ }
1
2
3
4
5
ただし Enum の場合は==演算子を利用して比較する
equals()メソッドで比較する際、左辺のオブジェクトが null にならないように制御すること。 - Class 名を利用した比較はおこなわない
良い例:
if (o instanceof Foo) { //・・・ }
1
2
3悪い例:
if ("my.Foo".equals(o.getClass().getName())) { //・・・ }
1
2
3
制御構造
- 制御文(if,else,while,for,do while)の{ }は省略しない
良い例:
if (s == null) { return; }
1
2
3悪い例:
if (s == null) return;
1
2 - ステートメントが無い{ }ブロックを利用しない
悪い例:
//{ }内の記述がない if (s == null) { }
1
2
3 - if/whileの条件式で=は利用しない
良い例:
boolean a =// if (!a) { //・・・ }
1
2
3
4悪い例:
boolean a =// if(a = false) {//コーディングミス //・・・ } boolean a =// boolean b =// if(a = b) {//おそらくコーディングミス //・・・ }
1
2
3
4
5
6
7
8
9
10
11 - forとwhileの使い分けを意識する
- for文を利用した繰り返し処理中でループ変数の値を変更しない
悪い例:
String[] array = { /*・・・*/ }; for (int i = 0; i < array.length; i++) { //・・・ i += 2;//NG } for (String s : array) { //・・・ s = "string"; //NG }
1
2
3
4
5
6
7
8
9
10 - for 文のカウンタは特別な事情がない限り、0 から始める
- 配列やリストなどの全要素に対するループ処理は拡張 for 文を使用する。
良い例:
for (int value : array) { //・・・ } for (String value : list) { //・・・ }
1
2
3
4
5
6
7 - 配列をコピーするときはArrays.copyOf()メソッドを利用する
良い例:
int[] newArray = Arrays.copyOf(array, array.length);
1悪い例:
int[] newArray = new int[array.length]; System.arraycopy(array, 0, newArray, 0, array.length);
1
2 - 繰り返し処理中のオブジェクトの生成は最小限にする
- if 文と else 文の繰り返しや switch 文の利用はなるべく避け、オブジェクト指向の手法を利用する
良い例:
CodingKind codingKind = toCodingKind(kind); d = codingKind.encode(s); //--- CodingKind codingKind = toCodingKind(kind); s = codingKind.decode(d);
1
2
3
4
5
6
7悪い例:
switch (kind) { case 1: d = encode1(s); break; case 2: d = encode2(s); break; default: break; } //--- switch (kind) { case 1: s = decode1(d); break; case 2: s = decode2(d); break; default: break; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 - 繰り返し処理の内部でtryブロックを利用しない
特に理由がない場合は繰り返し処理の外にtryブロックを記載する。
ただし、繰り返し処理内部で例外をキャッチし処理を行いたい場合は繰り返し処理の内部でtryブロックを利用してもよい。良い例:
for (String s : array) { BigDecimal num; try { num = new BigDecimal(s); } catch (NumberFormatException e) { num = BigDecimal.ZERO; } //・・・ }
1
2
3
4
5
6
7
8
9 if文で分岐した結果、booleanのみを返す処理は、if文の結果そのものを返すようにする
良い例:
//ケース1 return xxx == 0;
1
2//ケース2 boolean isUpdate = xxx == 0;
1
2悪い例:
//ケース1 if (xxx == 0) { return true; } else { return false; }
1
2
3
4
5
6//ケース2 boolean isUpdate; if (xxx == 0) { isUpdate = true; } else { isUpdate = false; }
1
2
3
4
5
6
7
文字列操作
- 文字列同士が同じ値かを比較するときは、equals()メソッドを利用する
良い例:
String s1 = "text"; String s2 = "text"; if (s1.equals(s2)) { //・・・ }
1
2
3
4
5悪い例:
String s1 = "text"; String s2 = "text"; if (s1 == s2) { //・・・ }
1
2
3
4
5 - 文字列リテラルはnewしない
良い例:
String s = "";
1悪い例:
String s = new String();
1 - 更新される文字列にはStringBuilderクラスを利用する
良い例:
StringBuilder builder = new StringBuilder(); for (String s : array) { builder.append(s); } System.out.println(builder.toString());
1
2
3
4
5悪い例:
String string = ""; for (String s : array) { string += s; } System.out.println(string);
1
2
3
4
5
スレッドセーフ性が保証されていない箇所では`StringBuffer`クラスを利用する
※パフォーマンスについても記載しているので参考にしてください - 更新されない文字列にはStringクラスを利用する
- 文字列リテラルと定数を比較するときは、文字列リテラルのequals()メソッドを利用する
良い例:
private static final String PROTOCOL_HTTP = "http"; if (PROTOCOL_HTTP.equals(url.getProtocol())) { }
1
2
3
4
5悪い例:
private static final String PROTOCOL_HTTP = "http"; if (url.getProtocol().equals(PROTOCOL_HTTP)) { }
1
2
3
4
5 - プリミティブ型とStringオブジェクトの変換には、変換用のメソッドを利用する
良い例:
int i = 1000; String s = String.valueOf(i);// "1000" s = NumberFormat.getNumberInstance().format(i);// 3桁区切り "1,000" boolean b = true; s = String.valueOf(b);// true/false s = BooleanUtils.toString(b);// on/off
1
2
3
4
5
6
7 - 文字列の中に、ある文字が含まれているか調べるには、contains()メソッドを利用する
- システム依存記号(\n 、 \rなど)は使用しない。
悪い例:
String text = Arrays.stream(array) .collect(Collectors.joining("\n"));
1
2 空文字チェックを行うときは、String.isEmpty()を使用すること。
良い例:
String str = "xxx"; if (str.isEmpty()) {
1
2悪い例:
String str = "xxx"; if ("".equals(str)) {
1
2
数値
- 誤差の無い計算をするときは、BigDecimalクラスを使う
浮動小数点演算は科学技術計算に利用するもので、誤差が発生する。これに対して、クラス「BigDecimal」は、文字列で数値の計算を行うので、金額などの正確な計算に適している。BigDecimalではインスタンス生成時に指定された桁数での精度が保証される。 - 数値の比較は精度に気をつける
良い例:
BigDecimal a = new BigDecimal("1"); BigDecimal b = new BigDecimal("1.0"); if (a.compareTo(b) == 0) { System.out.println("一致"); }
1
2
3
4
5悪い例:
BigDecimal a = new BigDecimal("1"); BigDecimal b = new BigDecimal("1.0"); if (a.equals(b)) { System.out.println("精度が違うためこの分岐には入らない"); }
1
2
3
4
5
6 - 低精度なプリミティブ型にキャストしない
- BigDecimalをString変換する際はtoString()ではなくtoPlainString()を利用すること
toString()を利用した場合、指数表記になることがあります。 BigDecimalのインスタンスを生成するときに、「BigDecimal(double val)」のコンストラクタを使用してはいけない
良い例:
BigDecimal number = new BigDecimal("1.0"); BigDecimal number = BigDecimal.valueOf (1.0);
1
2悪い例:
BigDecimal number = new BigDecimal(1.0);
1
日付
日付の文字列のフォーマットには、DateTimeFormatter を使う(ただし、Java のバージョンが8.0以降に限る)
※SimpleDateFormat は使用禁止(パフォーマンス観点からも DateTimeFormatter の方が SimpleDateFormat より40%向上しているとの Oracle から報告あり)
Java のバージョンが8.0に満たない場合は、SimpleDateFormat を使う良い例:
Date date = new Date(); DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");//年はuuuuであることに注意 String str = dateFormat.format(LocalDateTime.now());
1
2
3
三項演算子
- 入れ子の三項演算子の利用は禁止
可読性が悪くなるので三項演算子を入れ子で行うのは禁止。
コレクション
- Java2 以降のコレクションクラスを利用する
Vectorクラス、Hashtableクラス、Enumeration等は、特にこれらを利用する理由がなければ、インターフェースを統一する目的で、 これらの代わりにList(ArrayListクラス)、Map(HashMapクラス)、Iteratorを使用すること。List などのインターフェースを利用することで JDK1.2 で整理されたわかりやすいメソッドを利用でき、また、インターフェースの特性から呼び出し元を変更せずに実装クラスを変更することができる。 - 特定の型のオブジェクトだけを受け入れるコレクションクラスを利用する
良い例:
List<Map<String, String>> list = new ArrayList<>();
1 - ListのソートはList.sort()を利用する
Listクラスの要素をソートする際は Java8 で追加されたList.sort()を利用すること。
Java 7 以前で利用されていたCollections.sort()は利用しないこと。 - Collection.forEach()は利用しない。拡張 for 文の利用を検討する
Java8 で追加されたメソッド。
拡張 for 文を利用したほうが多くの場合でデバッグに有利であり、可読性においてもforEachの優位性は少ないため、forEachは原則利用しない。拡張 for 文を利用する。
具体的には下記のメソッドを利用しないこと。- Collection#forEach
- Set#forEach
- List#forEach
※Map#forEachはこの限りではない良い例:
for (String s : Arrays.asList("A", "B")) { //処理 }
1
2
3悪い例:
Arrays.asList("A", "B").forEach(s -> { //処理 });
1
2
3
ただし、メソッド参照で処理できる場合はforEachを利用する。
(デバッグのデメリットがほとんどなく、他と比較して処理効率が良いため)良い例:
Arrays.asList("A", "B").forEach(this::process);
1悪い例:
for (String s : Arrays.asList("A", "B")) { this.process(s); }
1
2
3 自作したクラスをListやMapに加える際には、equals()やhashCode()を実装する
equals()やhashCode()を実装する際に、java.util.Objectsを使用することで記述がしやすくなる
※Javaのバージョン7.0以降に限る良い例:
return Objects.hash("1", "2", "3");
1悪い例:
return new HashCodeBuilder().append("1").append("2").append("3").hashCode();
1書き換えてほしくないコレクションクラスを外に渡すときは、読み取り専用にしてから渡すようにする
良い例:
Collections.unmodifiableMap(map).entrySet(); Collections.unmodifiableMap(map); Collections.unmodifiableList(list); Collections.unmodifiableSet(set);
1
2
3
4ダイヤモンド演算子を用いて、ジェネリックスの記述を簡素化する
※Java のバージョン7.0以降に限る良い例:
List<String>list = new ArrayList<>();
1悪い例:
List<String>list = new ArrayList<String>();
1List.toArray()で配列を渡すときは必ず必要なサイズを指定する
良い例:
List list = new ArrayList(); String strs = list.toArray(new String[list.size()]);
1
2悪い例:
List list = new ArrayList(); String strs = list.toArray(new String[0]); (String strs = list.toArray(new String[]{});)
1
2
3
ラムダ式・メソッド参照・コンストラクタ参照
- ラムダ式が利用できる箇所はラムダ式を利用してよい
※パフォーマンスについても記載しているので参考にしてください - ただし、メソッド参照・コンストラクタ参照が利用できる場合はメソッド参照・コンストラクタ参照を利用する
良い例:
String::compareToIgnoreCase
1悪い例:
(s1, s2) -> s1.compareToIgnoreCase(s2)
1良い例:
BigDecimal::add
1悪い例:
(b1, b2) -> b1.add(b2)
1 - ラムダ式記述の際、型宣言は省略記法で記述する
良い例:
(s1, s2) -> s1 + "=" + s2
1悪い例:
(String s1, String s2) -> s1 + "=" + s2
1 - 原則ラムダ式内の行数は 1 行とする
複数行で利用したい場合は、privateメソッドを作成しメソッド参照を利用する良い例:
this::getMax private int getMax(int i1, int i2) { if (i1 > i2) { return i1; } else { return i2; } }
1
2
3
4
5
6
7
8
9悪い例:
(i1, i2) -> { if (i1 > i2) { return i1; } else { return i2; } }
1
2
3
4
5
6
7 - 原則ラムダ式は 1 行記述に限定するので、中カッコ、returnは必ず省略する
良い例:
(s1, s2) -> s1 + "=" + s2
1悪い例:
(s1, s2) -> { return s1 + "=" + s2; }
1
2
3
実質的 final(effectively final)
- 実質的 final を利用する
変数宣言にfinalを記載しなくてよい。
Stream API
- 利用してよい
※パフォーマンスについても記載しているので参考にしてください - 並列ストリームは利用しないこと
悪い例:
Stream<?> s = list.parallelStream(); Stream<?> s = list.stream().parallel();
1
2 - StreamAPI 記述の際の改行位置は、各中間処理・末端処理前のピリオドの前で改行する
良い例:
List<Character> alphabetLower = list.stream() .filter(Character::isAlphabetic) .map(Character::toLowerCase) .collect(Collectors.toList());
1
2
3
4悪い例:
List<Character> alphabetLower = list.stream().filter(Character::isAlphabetic) .map(Character::toLowerCase).collect(Collectors.toList()); List<Character> alphabetLower = list .stream() .filter(Character::isAlphabetic) .map(Character::toLowerCase) .collect(Collectors.toList());
1
2
3
4
5
6
7
8 - インデントは統合開発環境の提供するフォーマッタに合わせる
- 中間処理の数は 3 つ(3 行)程度までを推奨する
中間処理の記述が多くなると可読性も悪くなり、デバッグも難しくなるため、3 行程度を目安にロジックを検討すること。 - コメントは、原則として処理中には記載しない
難解になってしまった場合のみ処理中の記載を認める良い例:
// クラスFootのフィールドStrの値で昇順にソートし、フィールドStrの要素を取得して処理する。 footList.stream() .sorted(Comparator.comparing(Foo::getStr)) .map(Foo::getStr) .forEach(this::proc);
1
2
3
4
5悪い例:
footList.stream() .sorted(Comparator.comparing(Foo::getStr))//クラスFooのフィールドStrの値で昇順にソート .map(Foo::getStr)//フィールドStrの要素を取得 .forEach(this::proc);//処理 footList.stream() //クラスFooのフィールドStrの値で昇順にソート .sorted(Comparator.comparing(Foo::getStr)) //フィールドStrの要素を取得 .map (Foo::getStr) //処理 .forEach(this::proc);
1
2
3
4
5
6
7
8
9
10
11
12
13 - Stream は極力変数代入しないこと
Stream は中間処理、末端処理を行うと使用済みとなり、以降同じインスタンスは利用できない。
変数代入はほとんどの場合意味をなさず、むしろミスの元となるため極力変数代入はしないこと。良い例:
List<String> list1 = Stream.of("A", "B", "C") .map(String::toLowerCase) .collect(Collectors.toList()); List<String> list2 = Stream.of("A", "B", "C") .map(s -> s + s) .collect(Collectors.toList());
1
2
3
4
5
6
7悪い例:
Stream<String> stream = Stream.of("A", "B", "C") Stream<String> stream1 = stream.map(String::toLowerCase); List<String> list1 = stream1.collect(Collectors.toList()); Stream<String> stream2 = stream.map(s -> s + s); //コーディングミス streamは使用済のためエラーになる List<String> list2 = stream2.collect(Collectors.toList());
1
2
3
4
5
6
Optional
- Optional は同メソッド内で値を取り出す場合は極力変数代入しないこと
Optional とその値の変数は同じものを示す名前となり、同じ意味の変数名が複数現れることで可読性が下がるため、Optional の変数代入は行わないこととする。良い例:
Employee employee = findEmployee(employeeId) .orElseThrow(IllegalArgumentException::new);
1
2悪い例:
Optional<Employee> employeeOpt = findEmployee(employeeId); Employee employee = employeeOpt.orElseThrow(IllegalArgumentException::new);
1
2
直接、値を取り出すことなくOptionalでのみ扱う場合はOptionalを変数代入してもよい。良い例:
Optional<Employee> employee = findEmployee(employeeId); Dept dept = employee.map(Employee::getDivision) .map(Division::getDept); .orElse(null); Role role = employee.map(Employee::getRole) .orElse(null); //------- Optional<Employee> employee = findEmployee(employeeId); //・・・処理 return employee;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ストリーム(InputStream OutputStream)
- ストリームを扱う API を利用するときは、try-with-resources 文で後処理をする
良い例:
try (InputStream inputStream = Files.newInputStream(Paths.get("foo.txt")) { //inputStreamに対する処理を記載 }
1
2
3 - ObjectOutputStreamではreset()を利用する
リソースの解放
- リソース解放を必要とするクラスを利用するときは、try-with-resources 文で後処理をする
良い例:
try (InputStream inputStream = Files.newInputStream(Paths.get("foo.txt")) { //inputStreamに対する処理を記載 }
1
2
3 - リソース解放を必要とするクラスを作成する場合はAutoCloseableをimplementsする
AutoCloseableをimplementsすることで try-with-resources 文が利用できるようになります。
例外
- catch 文で受け取る例外は、詳細な例外クラスで受け取る
良い例:
try (InputStream inputStream = Files.newInputStream(Paths.get("foo.txt")) { //・・・ } catch (IOException e) { log.error("Error", e); throw e; }
1
2
3
4
5
6悪い例:
try (InputStream inputStream = Files.newInputStream(Paths.get("foo.txt")) { //・・・ } catch (Exception e) {//範囲が広すぎる例外クラスの利用はNG log.error("Error", e); throw e; }
1
2
3
4
5
6 - Exceptionクラスのオブジェクトを生成してスローしない
- catchブロックでは基本、例外処理をする。ただし処理を書いてはいけない部分もあるので、その部分については、"// ignore" というコメントを記述すること。
- 例外クラスは無駄に定義しない
Java バージョン7.0以降に限り、複数のExceptionを1つのcatch文で記述する
※キャッチした例外処理が全く同じ処理の場合に限る良い例:
try { } catch (例外A|例外B e) { }
1
2
3標準API使用時や機能によっては、全ての例外を受け止める必要がある場合において、例外的にExceptionでの受け取りを許容する
例)WebSocket切断に関わるエラーハンドリング等
ガベージコレクション
- 原則finalize()のオーバーライド実装は禁止
- もしfinalize()をオーバーライドした場合はsuper.finalize()を呼び出す
- アプリケーションからfinalize()を呼び出さない
DBアクセス
- 参照しか行わないトランザクションは読み取り専用モードにする
- 参照結果のセッションを保持したまま多様な処理を行う場合は、参照結果をメモリに展開しセッションをクローズする
- 繰り返し処理の中でSQLを発行する場合は、PreparedStatement化する
- DBアクセスにはコネクションプーリングを利用する
テストソース
テストコードのクラスはソースコードのクラスと1対1となるように作成する
良い例:
//テスト対象のクラス src/main/java/jp/co/acroquest/app/Sample.java //テストクラス src/main/java/jp/co/acroquest/app/Sample.java
1
2
3
4悪い例:
//テスト対象のクラス src/main/java/jp/co/acroquest/app/Sample.java //テストクラス src/main/java/jp/co/acroquest/app/Example.java
1
2
3
4- テストパターン1つにつき1メソッドを作成する
- メンテナンスしやすいようにテストデータのファイルは、テストクラスと同じパッケージ位置に配置する
保守のしやすさや手戻りをなくす意味でも、テストケースとテストデータは別ファイルにする
Excelにテストケースとテストデータを作成する
テストソースは、Excelからテストデータと期待値を読み込んでテストを行う
※テストソース上にテストデータと期待値を記述しない
ロギング
ログ出力部分で、例外が発生しないようにする
良い例:
try { String param = obj.getParam(); } catch(IOException ex) { if (obj != null) { logger_.warn(obj.toString(), ex); } else { logger_.warn(obj = null", ex); } }
1
2
3
4
5
6
7
8
9悪い例:
try { String param = obj.getParam(); } catch(IOException ex) { //obj が null の可能性がある logger_.warn(obj.toString(), ex); }
1
2
3
4
5
6log4J2.0を使用する
※log4J2にて「is****Enabled()」に対応している為、開発者が意識して記述する必要はないログ書き込みは、最小限に止める
※ログへの書き込みをしないという意味ではない
for や while など、ループ処理の中では必要以上にログ出力を行わないことUtilityクラスでのログ出力は行わないようにすること
ログ出力は、呼び出し元のクラスで行うようにする- ログファイルから特定のログを抽出しやすいように、一意になる文字列をログに書き込む
- ログを書き込む際に引数の値だったり、ERRORの原因となった変数の値なども一緒に書き込む
状態遷移を行う処理は問題が発生しやすいため、ログを出力すること
他システムや装置などと連携する部分となる外部インターフェースではログを出力する
※本番運用時のパフォーマンスを考慮して、INFO or DEBUG のログレベルを判断する- DEBUGログとして書き込むメッセージ生成など、本番運用時のパフォーマンス劣化に繋がる処理の前にログレベルを判断する条件文を記述する
シングルトンパターン
シングルトンパターンを利用する時は、enumを使用する
従来の方法では、リフレクションやシリアライズからのデシリアライズによる複数のインスタンス生成を回避するのが、面倒かつコーディング量が増えることにより可読性が低下する
enumを用いることで上記の問題を意識せずに解決することができる良い例:
public enum Test { INSTANCE; }
1
2
3悪い例:
public class Test { public static final Test INSTANCE = new Test(); private Test() = {・・・}; }
1
2
3
4
インターフェース
抽象クラスよりインターフェースを選ぶ
抽象クラスで共通メソッドを多数用意してサブクラスがオーバーライドする使い方より、機能単位のインターフェースを作成して必要なインターフェースを実装する使い方の方が保守性や可読性が向上する
コメント規約
よいコメントの鉄則
- コードを明確化するコメントを書く
コードにコメントを書く理由は、自分自身、一緒に仕事をしている人、後に関わる開発者にとってコードをより理解しやすいものにするためである。 - コメント化する価値がないプログラムならば、実行するに値しない
有用な格言。コメントは必須。 - 過剰な装飾は使わない (例:見出し状のコメント)
1960 年代から 1970 年代の典型的な COBOL プログラマにはアスタリスク(*)でコメントを囲った箱を書く習慣があった。 彼らの芸術的な主張を表わしているのかもしれないが、率直に言えばそれは製品に加わるちょっとした価値に比べれば大きな時間の無駄である。 かわいいコードではなくきれいなコードを書くはずである。 さらに、コードを表示するディスプレイや印刷するプリントに使われるフォントはプロポーショナルだったりそうでなかったりして、箱をきれいに整列させることは難しい。 - コメントはシンプルに
かつて見たもっとも最良のコメントは、シンプルな要点をまとめた注釈であった。 なにも本を書く必要はなく、他の人がコードを理解するに十分な情報を提供するだけでよいのである。 - コードを書く前に先にコメントを記述する
コードをコメント化する最良の方法は、コードを書く前にコメントを書くことである。それが、コードを書く前にコードがどのように動作するかについて考えるよい機会となり、コメントの存在を保障することにもつながる。 少なくともコードを書いた時にコメントすべきである。 コメントによってコードが理解しやすくなることで、コードの開発中にアドバンテージを得ることができる。コードにコメントを書く時間を費やせば、それによって得られるものがある。 - コメントには、なぜそうなのかを書く。コードを読めば分かることを書かない
基本的に、コードの一部分を見ればそれが何かを理解することはできる。例えば、以下のコードを見て、$1000 以上の注文については 5%ディスカウントされることは理解できる。 なぜそうなのか?大きな注文ではディスカウントがつきものだというビジネスルールがあるのだろうか?大きな注文に時間限定サービスがあるのか、それともずっとサービスがあるのか?これを書いたプログラマの気前がよかったのか?
どこかソースコード中か別な文書にコメントされていない限り、それがなぜなのかを知ることはできない。if (grandTotal >= 1000.00) { grandTotal = grandTotal * 0.95; }
1
2
3
なお、メソッドコメントには、適切な javadoc コメント(タグ)のほかに、以下の内容も可能な限り明記すること。- 副作用のある処理の場合は、その内容 (→ メソッドの引数オブジェクトがメソッド内で変更されるケースなど)
- 既知のバグ (→ 判明しているが修正しないことにした場合など)
- 影響のある事前条件、事後条件 (→ メソッドが正しく動作するための前提について)
- 並行性 (→ マルチスレッドでアクセスされた場合の動作について)
- 該当メソッドの使用例やサンプルコード
- TODO コメント
設計者確認待ち、共通処理の作成待ちなどの理由により、実装時に TODO がある場合、下記のようにコメントを記述する。
(Eclipse の TODO コメント形式を採用)例)
//TODO:ワークフローの仕様決定待ち 関連チケット#12345
1
Java コメント(3 種類)の使い分け
Java では 3 種類のコメントが使える。javadoc コメントは/**で開始され、*/で終わる。 C 風コメントは/*で開始され*/で終わる。単一行コメントは//で開始され、そのソースコード行が終わるまで続く。 以下の表ではコメントの使い方とその例を示す。 (コメントのスタイルに関しては、前述の「標準規約に準拠したコーディング例」を参照)
コメント種類 | 使用方法 | 例 |
---|---|---|
javadocコメント
/** comment */ |
interface、class、メソッド、フィールドの直前に書く。 コメントは javadoc によって処理され、外部ドキュメント(HTML)として生成される。 (この形式以外のコメントはドキュメントとして出力されないことに注意) | /*_
_ 顧客(Customer)-
_ 顧客はわれわれがサービスまたは製品を売った人物 _ もしくは組織のいずれかである。 _ @author 開発太郎 _/ |
C 風コメント
/* comment */ |
特定のコードを無効化したいが、後で使用するかもしれないので残しておくためにコメント化する時や、デバッグ時に一時的に無効化するときに使用する。 | /_
このコードは J.T.Kirk によって 1997.12.9 に前述のコードと置き換えたためコメント化した。2 年間不要であるならば削除せよ。
... (ソースコード) _/ |
単一行コメント
// comment |
メソッド内にて、ビジネスロジック、コードの概要、一時変数の定義内容などを記述する。 | // 1995 年 2 月に開始された X 氏の寛大なキャンペーンで
// 定められた通り 1000$を超える請求には、全て 5%割引を // 適用する。 |
※ ロジック中に、頻繁に C 風コメントでコメントを書くとまとめてコメントアウトする場合に不便なため、基本的にロジック中では単一行コメントを利用すること。
パフォーマンス
Stream API
Java8 で追加された Stream API での記述は、可読性も高く、簡潔に書けますが、パフォーマンス・性能面で注意が必要な場合があります。
List の処理を行う際、拡張 for 文で処理する場合は Iterator インスタンスが 1 つだけ生成されますが、Stream API で処理する場合、最初の Stream インスタンスに加え、各中間処理ごとにも Stream インスタンスが生成され、その分の性能劣化が懸念されます。
以下に処理例と計測結果を記載します。
- 拡張 for 文
List<String> list = //数値文字列のList List<String> resultList = new ArrayList<>(); for (String string : list) { if (string.endsWith("0")) { resultList.add(string); } } return resultList;
1
2
3
4
5
6
7
8 - Stream API
List<String> list = //数値文字列のList List<String> resultList = list.stream() .filter(s -> s.endsWith("0")) .collect(Collectors.toList()); return resultList;
1
2
3
4
5 - 計測結果
処理する List の件数 | 拡張 for 文 (ms) | StreamAPI (ms) |
---|---|---|
100 万件 | 7 | 9 |
1,000 万件 | 88 | 114 |
1 億件 | 949 | 1,026 |
2 億件 | 1,822 | 2,081 |
小中規模の処理量であれば考慮するほどの性能差はありませんが、大量の処理が見込まれる場合は考慮が必要です。
また、Stream API は並列処理(スレッド処理)の機能をサポートしていますので、利用できる場合は並列処理も含めての検証が必要です。
ラムダ式・メソッド参照・コンストラクタ参照
Java8 で追加されたラムダ式・メソッド参照・コンストラクタ参照は、匿名クラスを利用するよりも効率的です。
積極的な利用を推奨します。
以下に Comparator を生成した場合の計測結果を記載します。
- 匿名クラス
Comparator<String> c = new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareToIgnoreCase(o2); } };
1
2
3
4
5
6 - ラムダ式
Comparator<String> c = (o1, o2) -> o1.compareToIgnoreCase(o2);
1 - メソッド参照
Comparator<String> c = String::compareToIgnoreCase;
1 - 計測結果
- 匿名クラス
new Comparator<String>() { @Override public int compare(String o1, String o2) { return arg.equals("DESC") ? o2.compareToIgnoreCase(o1) : o1.compareToIgnoreCase(o2); } }
1
2
3
4
5
6
7 - ラムダ式
Comparator<String> c = (o1,o2) -> arg.equals("DESC") ? o2.compareToIgnoreCase(o1) : o1.compareToIgnoreCase(o2);
1
2 - 計測結果
処理件数 | 匿名クラス (ms) | ラムダ式 (ms) | メソッド参照 (ms) |
---|---|---|---|
10 億回 | 380 | 0(計測不能) | 0(計測不能) |
100 億回 | 6,374 | 0(計測不能) | 0(計測不能) |
1 京回 | (30 秒以上) | 14 | 10 |
ラムダ式は外部の変数を利用する場合、匿名クラスとほぼ同じ動作をします。
処理件数 | 匿名クラス (ms) | ラムダ式 (ms) |
---|---|---|
10 億回 (パラメータあり) | 571 | 572 |
100 億回 (パラメータあり) | 9,900 | 9,864 |
文字列連結
文字列連結(繰り返し)
文字列連結を繰り返し処理中で行う際、+演算子で処理することはアンチパターンとして知られています。
繰り返し処理中の文字列連結は、 StringBuilder、StringJoiner、StringBufferを利用します。
また、コレクション要素の結合であればString#joinが利用できます。
以下に処理例と計測結果を記載します。
- +演算子
String s = ""; for (int i = 0; i < list.size(); i++) { String string = list.get(i); if (i > 0) { s += ","; } s += string; } return s;
1
2
3
4
5
6
7
8
9 - StringBuilder
StringBuilder sb = new StringBuilder(); for (int i = 0; i < list.size(); i++) { String string = list.get(i); if (i > 0) { sb.append(","); } sb.append(string); } return sb.toString();
1
2
3
4
5
6
7
8
9 - StringBuffer
StringBuffer sb = new StringBuffer(); for (int i = 0; i < list.size(); i++) { String string = list.get(i); if (i > 0) { sb.append(","); } sb.append(string); } return sb.toString();
1
2
3
4
5
6
7
8
9 - String#join
return String.join(",", list);
1 - 計測結果
処理する List の件数 | +演算子 (ms) | StringBuilder (ms) | StringBuffer (ms) | String#join (ms) |
---|---|---|---|---|
1,000 件 | 5 | 0(計測不能) | 0(計測不能) | 0(計測不能) |
1 万件 | 1,016 | 1 | 1 | 1 |
10 万件 | (30 秒以上) | 2 | 5 | 5 |
100 万件 | (30 秒以上) | 29 | 42 | 51 |
文字列連結(定数)
基本的に処理中の文字列連結では+演算子は使わないで処理するほうがパフォーマンスが高くなりますが、 定数の場合は+演算子で定義するほうがパフォーマンスが高いです。
たとえば以下のように、処理したい場合、
private static final String CONST_A = "A";
private static final String CONST_B = "B";
private static final String CONST_AB = CONST_A + CONST_B;
2
3
StringBuilder で処理しようとすると以下のようになります。
private static final String CONST_AB = new StringBuilder(CONST_A).append(CONST_B).toString();
しかし、これらをバイトコード上で確認するとそれぞれ以下のようになります。
- +演算子
private static final java.lang.String CONST_AB = "AB";
1 - StringBuilder
private static final java.lang.String CONST_AB; static {}; 0 new java.lang.StringBuilder [20] 3 dup 4 lbc <String "A"> [8] 6 invokespecial java.lang.StringBuilder(java.lang.String) [22] 9 lbc <String "B"> [11] 11 invokespecial java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [26] 14 invokespecial java.lang.StringBuilder.toString() : java.lang.String [30] 17 putstatic jp.co.packagename.ClassName.CONST_AB : java.lang.String[34] 20 return
1
2
3
4
5
6
7
8
9
10
11
12 - 計測結果
+演算子を利用した場合コンパイル時に最適化され、文字列"A"と"B"をあらかじめ結合して class が作成されます。
StringBuilderを利用した場合は最適化はされず、記述した通りの処理が行われます。
計測した場合、下記のようになります。
処理回数 | StringBuilder (ms) | +演算子 (ms) |
---|---|---|
5,000 万回 | 559 | 0(計測不能) |
1 億回 | 1,059 | 0(計測不能) |
通常、定数処理を大量に処理することは考えられないので性能問題になることはありませんが、 +演算子を利用したほうがパフォーマンスが高いこともあるということを理解してください。
List の種類
ListにはArrayListのようなRandomAccessを implements した、ランダムアクセスをサポートしているクラスと、
LinkedListのようなランダムアクセスをサポートしていない(シーケンシャルアクセス)クラスが存在します。
RandomAccessではないListは、List#getなどインデックスを利用するような操作のパフォーマンスが低いので注意してください。
以下に処理例と計測結果を記載します。
- for 文(List#get(int)によるループ)
int size = list.size(); for (int i = 0; i < size; i++) { String s = list.get(i); //処理 }
1
2
3
4
5 - 拡張 for 文
for (String s : list) { //処理 }
1
2
3 - forEach
list.forEach(this::処理);
1 - 計測結果
処理する List の件数 | ArrayList for 文(List#get(int)によるループ) (ms) | LinkedList for 文(List#get(int)によるループ) (ms) | ArrayList 拡張 for 文 (ms) | LinkedList 拡張 for 文 (ms) | ArrayList forEach (ms) | LinkedList forEach (ms) |
---|---|---|---|---|---|---|
1 万件 | 0(計測不能) | 73 | 0(計測不能) | 0(計測不能) | 0(計測不能) | 0(計測不能) |
10 万件 | 0(計測不能) | 7,576 | 0(計測不能) | 0(計測不能) | 1 | 2 |
20 万件 | 0(計測不能) | 17,740) | 0(計測不能) | 0(計測不能) | 0(計測不能) | 0(計測不能) |
50 万件 | 0(計測不能) | (30 秒以上) | 0(計測不能) | 2 | 0(計測不能) | 2 |
100 万件 | 1 | (30 秒以上) | 0(計測不能) | 4 | 0(計測不能) | 4 |
1,000 万件 | 16 | (30 秒以上) | 8 | 45 | 6 | 44 |
ランダムアクセスをサポートしているListがシーケンシャルアクセス(iterator を利用した処理など)で遅いということはないので、
ループの処理は拡張 for 文等、Iterator によるループで記述するのが無難です。
List#getでの処理をすべて禁止することはできませんが、高いパフォーマンスが求められる場合はListの種類にも注目してみてください。
String から Integer・Long への変換
数値文字列のStringをIntegerに変換するには、Integer#valueOf(String)を利用して下記のように記述します。
String s = "1";
Integer value = Integer.valueOf(s);
2
しかし、下記のようにも記述できます。
String s = "1";
Integer value = new Integer(s);
2
これらの違いは、
new Integer(s)とした場合、必ず Integer インスタンスが生成されますが、
Integer.valueOf(s)とした場合は -128 から 127 の間の数値であればキャッシュから取り出すためインスタンスを生成しません。
このため、前者のInteger#valueOf(String)を利用した記述のほうが効率的です。
Long#valueOf(String)も同様です。
性能差が少ないため、ほとんど問題にはなりませんが、FindBugs 等、静的解析で検出される問題のため、理解が必要です。
また、String からの変換だけでなく、int や long からの変換も#valueOfが効率的ですが、オートボクシングを利用した場合、コンパイルで自動的にこれらの処理に変換されるため、記述することはありません。
String から int・long への変換
数値文字列のStringをintに変換するには、Integer#parseInt(String)を利用して下記のように記述します。
String s = "1";
int value = Integer.parseInt(s);
2
しかし、オートボクシングが利用できるため、意図せず下記のように記述ミスをする場合があります。
String s = "1";
int value = Integer.valueOf(s); //取得したIntegerインスタンスをオートボクシングでintにcastしている
2
String s = "1";
int value = new Integer(s); //生成したIntegerインスタンスをオートボクシングでintにcastしている
2
「オートボクシング」の説明に記載した通り、性能に差が出るだけでなく、 記述から明らかにミスであることが解るため、FindBugs 等、静的解析で検出されるコードです。
longへの変換の場合はLong#parseLong(String)を利用します
以下に計測結果を記載します。
- 計測結果
処理回数 | Integer.valueOf(String) (ms) | Integer#parseInt(String) (ms) |
---|---|---|
1,000 万回 | 396 | 318 |
1 億回 | 4,060 | 3,077 |
BigDecimal の ZERO との比較
BigDecimal の正・負・ZERO の判定はBigDecimal#signumを利用します。
compareToを利用してBigDecimal.ZEROと比較しても同じことができますが、signumを利用したほうが効率的です。
以下に処理例と計測結果を記載します。
- compareTO 利用
BigDecimal value = new BigDecimal("0.0"); if (value.compareTo(BigDecimal.ZERO) == 0) {
1
2 - signum 利用
BigDecimal value = new BigDecimal("0.0"); if (value.signum() == 0) {
1
2 - 計測結果
(単位:マイクロ秒)
処理回数 | compareTo 利用 (マイクロ秒) | signum 利用 (マイクロ秒) |
---|---|---|
1 京回 | 527
max:26,367 min:0 |
424
max:21,213 min:0 |
性能差が少ないので、必ずしも signum を利用する必要はありませんが、大量に処理する場合など、高いパフォーマンスが求められる場合は意識してください。
スレッド
スレッドの生成・起動処理はコストが高いため、スレッドプールを利用する
Threadクラスを用いてのスレッド実行や停止は行わないこと良い例:
ExecutorService mailThread = Executors.newFixedThreadPool(3); mailThread .execute;
1
2
初期化のタイミング
フレームワーク/ライブラリを利用する際に、一度だけ初期化やオブジェクトの取得を行えば良い処理を、複数個所で行わない下記のようなものがある。
JNDI の lookup 、Log4j の初期化、DI コンテナの初期化(Spring, Seasar2 など)
リフレクション
- リフレクションでのインスタンス生成、メソッド実行などは圧倒的に性能劣化につながるため禁止とする
セキュアコーディング
入力値検査とデータの無害化
- SQL インジェクションを防ぐ
- SQL を発行する場合は、PreparedStatement 化する
宣言と初期化
クラスの初期化を循環させない
自クラス内でインスタンス生成する場合など、コーディングの仕方によっては想定外の動作となるため、プログラムの制御フローを意識した上で設計・コーディングを行うこと
複数のクラス同士で静的変数を参照し合う場合に、初期化の順番によって結果が異なる作りになりうる。
このようなクラス間での循環が発生しないためにもクラス間の関連を把握する必要がある。
式
- メソッドの戻り値を無視しない
- null ポインタ参照しない
- 配列の中身を比較するには、Arrays.equals(配列, 配列) を使う
ボクシングされたプリミティブ型の値の比較に等値演算子(== or !=)を使わない
演算子によって、アンボクシングが行われたり行われなかったりするためその違いを理解して使用する
等値演算子は、アンボクシングされないためオブジェクト同士の参照比較となってしまう。
数値型とその操作
- 整数オーバーフローを検出あるいは防止する
- 同一のデータに対してビット演算と算術演算の両方を行わない
- 除算と剰余演算でゼロ除算エラーを起こさない
除数がゼロでないかをチェックする - NaN との比較を行わない
Double.NaN との比較をせずに、Double.isNaN() を使用して NaN 値であるかをチェックする - 浮動小数点の文字列表現を比較したり内容を調べたりしない
数値型の縮小変換時にデータの欠損や誤解釈を引き起こさない
ビット幅の小さな型に変換する場合は、価の範囲チェックを行った上で縮小変換を行うこと
オブジェクト指向
- 不変条件を持つクラスやメソッドの拡張は信頼できるサブクラスのみに許す
基本方針としてサブクラスはスーパークラスの不変条件を守ることが挙げられている
不用意な継承は避け、慎重に検討した上で、設計を行うこと
可能なら継承よりコンポジションを選択する - スーパークラスに変更を加える場合、サブクラスの依存性を保つ
スーパークラスに変更を加える場合には、サブクラスが依存しているプログラムの不変条件を維持しなくてはならない
サブクラスに与える影響を調査した上で、慎重に変更を加えること - 信頼できないコードにインスタンスを安全に渡すため、可変クラスにはコピー機能を実装する
- private かつ可変なクラスメンバへの参照を返す前にそのディフェンシブコピーを作成する
可変なクラスメンバの参照を返すことでカプセル化の破綻を招き、クラスの内部情報を変更する機会を与えてしまう
クラスの可変メンバへの参照を返してはならない - コンストラクタが例外をスローする場合には最新の注意を払う
メソッド
- メソッドの引数を検証する
性能とのトレードオフになるため、設計時に慎重に検討すること - セキュリティチェックを行うメソッドは private もしくは final 宣言する
- equals() メソッドをオーバーライドする時は等価性に関する契約を守る
- equals() メソッドを実装するクラスでは hashCode() メソッドも実装する
- compareTo() メソッドを実装する場合、メソッドの一般契約に従う
- 比較演算に用いるキーは不変にする
例外時の動作
- チェック例外を抑制あるいは無視しない
- メソッドが処理に失敗した場合はオブジェクトの状態を元に戻す
- finally ブロックの処理を途中で終了しない
- チェック例外を finally ブロックの外に伝搬させない
finally ブロックのなかでスローされたチェック例外を適切に処理しなければならない - 宣言されていないチェック例外をスローしない
- NullPointerException およびその親クラスの例外をキャッチしない
null ポインタ参照が発生する根本原因を解決せずに、NullPointerException をキャッチすることで、null ポインタ参照を処理することは好ましくない
ロック
- 信頼できないコードから使用されるクラスを同期するには private final ロックオブジェクトを使用する
- 信頼できないコードによって変更されうる static フィールドへのアクセスは同期する
- デッドロックを回避するためにロックは同一順序で要求および解放する
- 例外発生時には保持しているロックを解放する
- 途中で待機状態になる可能性のある操作をロックを保持したまま実行しない
ダブルチェックロック手法を誤用しない
遅延初期化は必要に応じて注意しながら使用すること
可能なら使用しないこと
Last Updated: 2022/08/23