本コーディング規約は、世の中のシステム開発プロジェクトのために無償で提供致します。
ただし、掲載内容および利用に際して発生した問題、それに伴う損害については、フューチャー株式会社は一切の責務を負わないものとします。
また、掲載している情報は予告なく変更することがございますので、あらかじめご了承下さい。
一般に利用・参照されている 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 = 5000;」という記述があるとする。プログラマに聞くと、変数名 p は価格(Price)の略だと言うのだが、それならば略さずに、「int price = 5000;」としたほうが分かりやすいはずである。「ネーミングはわかりやすく」の背景には、読んで内容が理解できるという意味で、文章のようなプログラミングを行う、という考え方に基づく。
サンプルコードを活用すること自体は、著作権等を侵害しなければ問題ない。問題なのは、その内容や背景を理解しないまま、サンプルコードだけを鵜呑みにして、「おまじない」として表面的に適用してしまうことである。コードを「おまじない」ととらえていては、サンプルコードの間違いを気づかないまま適用してしまうこともある。例えば、ストリームのクローズ処理を行っていないサンプルコードであっても、それに気づかずに自分のコードに適用してしまい、後で思わぬ障害を引き起こすという可能性がある。サンプルコードは、そこで説明する内容に絞ったコードが多いため、このような例はよく見られる。また、サンプルコードをそのまま適用した結果、自分が記述すべきコードには必要のないコードが含まれてしまう場合もある。その場合、コードの可読性を下げる原因となる。自分のコードは、自分で深く理解して記述すべきである。
コードをコピー・ペーストしていませんか?コピー・ペーストしてしまうと、何らかの修正をする際に、全ての個所に同じ修正をする羽目になる。同じコードが現れるようならまとめて一つにし、外に出してコールするような書き方にすべきである。同じコードをまとめる作業は、どちらかといえば、コーディング時よりリファクタリング(ソフトウェアの外部的振る舞いを変更せずに内部構造を改善する作業)で行われることが多い。しかし、コーディング時からできるだけ気をつけておきたいことでもある。
メソッドの役割が明確で、かつ 1 つであれば単体テストが行いやすくなる。つまり、コードの「試験性」が高まる。また、役割が一つであれば、後でコードを変更する際に修正箇所がわかりやすいため、障害修正に要する時間が短くなる。つまり、コードの「保守性」があがることになる。例えば、「チェックをして実行する」機能を実現するために、checkAndDo()メソッドが存在したとする。この場合、このメソッドは check()メソッドと do()メソッドに分割すべきである。なぜなら、checkAndDo()メソッドの check()ロジックに誤りがあった場合、do()メソッドに書かれる内容まで把握する必要が生じるためである。分割してあれば、check()メソッドだけの変更で済む。このことはクラスの設計にもあてはまる。
大文字・小文字の違いで名前を区別しない。
良い例:
private int carNumber;
private int trainNumber;
悪い例:
private int num;
private int Num;
クラス名は単語の先頭を大文字にする
良い例:
public class Entry {
悪い例:
public class entry {
インターフェース名、Enum 名、Record 名はクラス名に準ずる
コンストラクタと同じ名前のメソッドはつくらない
メソッド名は区切りのみ大文字にする
良い例:
public String getName() {
//・・・
}
悪い例:
public String getname() {
//・・・
}
public String GETNAME() {
//・・・
}
変換メソッド名は「“to
”+オブジェクト名」にする
良い例:
public String toString() {
悪い例:
public String string() {
ゲッターメソッド名は「“get
”+属性名」にする
型がboolean
の場合は「“is
”+属性名」にする
セッターメソッド名は「“set
”+属性名」にする
boolean
変数を返すメソッド名はtrue
/false
の状態がわかるようにする
良い例:
public boolean isAsleep() {
}
public boolean exists() {
}
public boolean hasExpired() {
}
メソッドのパラメータ名とインスタンス変数名を一緒にしない
ただし、アクセサメソッドやコンストラクタなど、統合開発環境の機能により自動生成するものに関しては可とする。
アンダースコア _
をつけての区別は原則禁止とする。
良い例:
public double calc(double rate) {
return this.value * rate;
}
悪い例:
public double calc(double value) {
return this.value * value;
}
public double calc(double _value) {
return this.value * _value;
}
boolean
変数はtrue
/false
の状態がわかるようにする
良い例:
private boolean isOpen;
悪い例:
private boolean flag;
定数は全てstatic final
とし、すべて大文字、区切りは”_
”
良い例:
private static final String SYSTEM_NAME = "販売管理システム";
変数名は小文字とし、単語の区切りのみ大文字にする
良い例:
private String thisIsString;
変数名に固有名詞が含まれる場合、先頭をのぞき、単語の区切り以外に大文字を使用してもよい
良い例:
private String thisIsIPAddress;
スコープが狭い変数名は省略した名前でもよい
良い例:
if (・・・) {
String s = "・・・・";
//変数sを利用した処理 数行
}
悪い例:
String s = "・・・・";
if (・・・) {
//変数sを利用した処理
}
・・・if (・・・) {
//変数sを利用した処理
}
変数s
の利用範囲が広いので役割が明確になる変数名に変更する。
for
文のループカウンタは、ネストごとに”i
“,”j
“,”k
“・・・を使う
Enum 名はクラス名と同じく、単語の先頭を大文字にする
列挙定数は定数と同じく、すべて大文字、区切りは”_
”
良い例:
enum Season {
,
WINTER,
SPRING,
SUMMER
FALL}
悪い例:
enum Season {
,
winter,
spring,
summer
fall}
原則としてオブジェクトの参照にはインターフェースを利用する
オブジェクトを参照する際は、そのオブジェクトの実装クラスを用いて宣言できるが、実装クラスに適切なインターフェースが存在している場合は、必ずインターフェースを用いて宣言すること。
良い例:
List<Entry> list = new ArrayList<>();
Map<String, String> map = new HashMap<>();
悪い例:
ArrayList<Entry> list = new ArrayList<>();
HashMap<String, String> map = new HashMap<>();
推奨されない 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) {
+= value;
sumValue }
return sumValue;
}
インデントは空白文字 4 文字分の Tab を使用する
長すぎる行は避ける
{
の後にステートメントを記述しない
良い例:
if (s == null) {
return 0;
}
悪い例:
if (s == null) {return 0;}
1 行に 2 つ以上のステートメントを記述しない
悪い例:
} catch (Exception e) {
.error("Error", e);return null;
log}
カンマの後には空白文字を
良い例:
process(x, y, z);
悪い例:
process(x,y,z);
代入演算子( =
, +=
, -=
,
…)の前後には空白文字を挿入する
良い例:
int a = x;
+= 10; a
悪い例:
int a=x;
+= 10; a
for 文内のセミコロンの後には空白文字を挿入する
良い例:
for (int i = 0; i < array.length; i++) {
//・・・
}
悪い例:
for (int i = 0;i < array.length ;i++) {
//・・・
}
++
や --
とオペランドの間には空白文字を入れない
良い例:
++; i
悪い例:
++; i
ビット演算子( |
、 &
、
^
、 <<
、 >>
)の前後には空白文字を挿入する
論理演算子( ||
、&&
)の前後には空白文字を挿入する
関係演算子( <
、 >
、
>=
、 <=
、==
、
!=
)の前後には空白文字を挿入する
算術演算子( +
、 -
、
*
、 /
、 %
)の前後には空白文字を挿入する
return 文ではカッコを使わない
良い例:
int answer = (a + b + c) * d;
return answer;
悪い例:
return ((a + b + c) * d);
if などの条件式で boolean の変数を比較しない
良い例:
if (hasStock)
悪い例:
if (hasStock == true)
不等号の向きは左向き( <
、 <=
)にする
良い例:
if (from <= x && x <= to) {
悪い例:
if (x >= from && x <= to) {
ファイルの先頭への Copyright の表記について
ソースのファイルヘッダにコピーライト標記は法的拘束力がないため、不要とする。
ただし、顧客からの要求があった場合を除く。
Javadoc コメントには、少なくとも author と version(クラス)、param と return と exception(メソッド)を記述する
@Override
のあるメソッドでは、上記に加えて{@Inherit}
を記述するJavadoc クラスヘッダコメントのフォーマットは以下の通り
良い例:
/**
* Action(or Bean)クラス メニュー名称
*
* @author 姓 名
* @version バージョン YYYY/MM/DD 説明
*/
コメントは必要なものだけを簡潔に
悪い例:
/**
* 文字列に変換
*/
@Override
public String toString() {
/**
* コピー
*
* @return コピーしたインスタンス
*/
public Entry copy() {
不要なコメントは記載しない
java.lang
パッケージはインポートしない
悪い例:
import java.lang.String;//必要のない記述
原則として static インポートしない
JUnit の作成やフレームワークとして static
インポートが推奨されるような場合は利用してもよい
原則としてオンデマンドのインポート宣言(type-import-on-demand
declaration)(アスタリスク*
によるインポート)
は行わない
悪い例:
import java.util.*;
public
宣言していないクラスにはpublic
権限のコンストラクタを作らない
良い例:
class Entry {
//・・・
Entry(int id) {
//・・・
}
悪い例:
class Entry {
//・・・
public Entry(int id) {
//・・・
}
インスタンスメンバを持たない(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 Collections.emptyList();
}
return List.of(s);
}
悪い例:
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 List.of(s);
}
メソッドは 1 つの役割にする
クラスメソッドを利用するときは、クラス名を使って呼び出す
良い例:
int comp = Integer.compare(x, y);
悪い例:
Integer a = //
int comp = a.compare(x, y);
1 つのステートメントには 1 つの変数宣言
良い例:
/** 科目コード */
private String code;
/** 科目名 */
private String name;
/** 科目略名 */
private String shortName;
悪い例:
private String code, name, shortName;
リテラルは使用しない
リテラルとは、コード中に、表現が定数として直接現れており、記号やリストで表現することができないものを指す(数値、文字列両方含む 通称マジックナンバー)。コードの可読性・保守性の低下を防ぐために、リテラル定数(static final
フィールド)を使用すること。
例外:-1
,0
,1
等をカウント値としてループ処理等で使用するような場合
良い例:
private static final double ONE_MILE_METRE = 1609.344;
public double mileToMetre(double mi) {
return mi * ONE_MILE_METRE;
}
悪い例:
public double mileToMetre(double mi) {
return mi * 1609.344;
}
リテラル定数の名前はその値の意味を正しく表現したものにする
悪い例:
private static final int ZERO = 0;
配列宣言は「型名[]
」にする
良い例:
private int[] sampleArray = new int[10];
悪い例:
private int sampleArray[] = new int[10];
できるだけローカル変数を利用する
ローカル変数で事足りるものをインスタンス変数として利用するなど、必要のないインスタンス変数を定義すると、パフォーマンスや可読性の低下やの大きな要因となる上、マルチスレッドを意識した際に不整合がおきる可能性があるので、インスタンス変数は必要性を充分に考慮してから使用すること。
定数はfinal
で宣言する
ローカル変数とインスタンス変数を使いわける
public
で宣言するクラス変数とインスタンス変数は、定数のみとし、
static final
で定義する
final
ではない static
な定数は作成しない。
良い例:
public static final String PROTOCOL_HTTP = "http";
定数( static
フィールド)に、 static
ではないメソッドから書き込まない
定数は、プリミティブ型もしくは、不変(Immutable)オブジェクトで参照する
不変List
の生成にはList.of()
を利用する
良い例:
public static final List<Integer> VALUES = List.of(1, 2, 3, 4, 5);
悪い例:
public static final List<Integer> VALUES = Arrays.asList(1, 2, 3, 4, 5);
不変Set
の生成にはSet.of()
を利用する
不変Map
の生成にはMap.of()
を利用する
良い例:
public static final Map<Integer, String> VALUES_MAP = Map.of(1, "A", 2, "B", 3, "C");
悪い例:
public static final Map<Integer, String> VALUES_MAP = new HashMap<>() {
{
put(1, "A");
put(2, "B");
put(3, "C");
}
};
不変な配列インスタンスは長さ 0
の配列以外は生成不可能なため、外部から参照される(public
)定数では利用せず、List
等への置き換えをすること
良い例:
public static final List<Integer> VALUES = List.of(1, 2, 3, 4, 5);
悪い例:
public static final int[] VALUES = { 1, 2, 3, 4, 5 };
インスタンス変数はprivate
にする
良い例:
public class Employee {
private long id;
//・・・
//getter/setter
}
悪い例:
public class Employee {
public long id;
//・・・
//getter/setter
}
public static final
宣言した配列を利用しない
※「定数」を参照
クラス変数にはクラス名を使用してアクセスすること
良い例:
BigDecimal b = BigDecimal.ZERO;
悪い例:
BigDecimal a = //
BigDecimal b = a.ZERO;
ローカル変数は利用する直前で宣言する
行間の程度にもよるが、ある程度まとめて宣言するのは OK とする。
良い例:
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
//lineの処理
}
悪い例:
String line;
for (int i = 0; i < lines.length; i++) {
= lines[i];
line //lineの処理
}
ローカル変数は安易に再利用しない
一度宣言したローカル変数を、複数の目的で安易に使いまわさないこと。ローカル変数は、役割ごとに新しいものを宣言して初期化することにより、コードの可読性・保守性の向上、及びコンパイラの最適化の促進をはかる。
メソッド引数への代入は行わない
原則としてfinal
で宣言する。
良い例:
public void add(final int value) {
//・・・
}
スーパークラスのインスタンス変数をサブクラスでオーバーライドしない
スーパークラスと同じ名前のフィールドをサブクラスで宣言しないこと。
同じ名前のフィールドを宣言すると、スーパークラスのフィールドはサブクラスで宣言されたフィールドによって隠ぺいされてしまうので、他の人の混乱を招くことを防ぐため重複する名前は付けないこと。
悪い例:
public class Abs {
protected String name;
}
public class Sub extends Abs {
protected String name;//Abs#nameは隠ぺいされる
}
スーパークラスのメソッドをオーバーライドするときは@Override アノテーションを指定する。
良い例:
public class Abs {
protected void process() {
}
}
public class Sub extends Abs {
@Override
protected void process() {
}
}
悪い例:
public class Abs {
protected void process() {
}
}
public class Sub extends Abs {
//@Overrideアノテーションの指定がない
protected void process() {
}
}
スーパークラスで private
宣言されているメソッドと同じ名前のメソッドをサブクラスで定義しない
スーパークラスにある private
メソッドと同じ名前のメソッドをサブクラスで定義しないこと。private
メソッドはオーバーライドされず全く別のメソッドとして扱われ、他の人の混乱を招き、バグにつながる恐れがある。
以下の順で記述する
同一カテゴリー内では以下の可視性の順で記述する
オブジェクト同士はequals()
メソッドで比較する
良い例:
String s1 = "text";
String s2 = "text";
if (s1.equals(s2)) {
//・・・
}
悪い例:
String s1 = "text";
String s2 = "text";
if (s1 == s2) {
//・・・
}
ただし Enum の場合は==
演算子を利用して比較する
equals()
メソッドで比較する際、左辺のオブジェクトが null
にならないように制御すること。
Class 名を利用した比較をおこなわない
良い例:
if (o instanceof Foo f) {
// ...
}
悪い例:
if ("my.Foo".equals(o.getClass().getName())) {
= (Foo)o;
Foo f // ...
}
インスタンスの型キャスト(Class キャスト)が必要な場合はパターンマッチングを使用する
良い例:
if (o instanceof String s) {
// ...
}
var str = (o instanceof BigDecimal b) ? b.toPlainString() : String.valueOf(o);
var empty = o == null ||
(o instanceof String s && s.isEmpty()) ||
(o instanceof Collection c && c.isEmpty());
悪い例:
if (o instanceof String) {
String s = (String)o;
// ...
}
var str = (o instanceof BigDecimal) ? ((BigDecimal)o).toPlainString() : String.valueOf(o);
var empty = o == null ||
(o instanceof String && ((String)o).isEmpty()) ||
(o instanceof Collection && ((Collection)o).isEmpty());
制御文( if
, else
, while
, for
, do while
)の { }
は省略しない
良い例:
if (s == null) {
return;
}
悪い例:
if (s == null)
return;
ステートメントが無い {}
ブロックを利用しない
悪い例:
//{}内の記述が無い
if (s == null) {
}
if
/ while
の条件式で =
は利用しない
良い例:
boolean a =//
if (!a) {
//・・・
}
悪い例:
boolean a =//
if (a = false) {//コーディングミス
//・・・
}
boolean a =//
boolean b =//
if (a = b) {//おそらくコーディングミス
//・・・
}
for
と while
の使い分けを意識する
for 文を利用した繰り返し処理中でループ変数の値を変更しない
悪い例:
String[] array = { /*・・・*/ };
for (int i = 0; i < array.length; i++) {
//・・・
+= 2;//NG
i }
for (String s : array) {
//・・・
= "string";//NG
s }
for 文のカウンタは特別な事情がない限り、0 から始める
配列やリストなどの全要素に対するループ処理は拡張 for
文を使用する。
良い例:
for (int value : array) {
//・・・
}
for (String value : list) {
//・・・
}
配列をコピーするときはArrays.copyOf()
メソッドを利用する
良い例:
int[] newArray = Arrays.copyOf(array, array.length);
悪い例:
int[] newArray = new int[array.length];
System.arraycopy(array, 0, newArray, 0, array.length);
繰り返し処理中のオブジェクトの生成は最小限にする
if 文と else 文の繰り返しや switch
文の利用はなるべく避け、オブジェクト指向の手法を利用する
良い例:
= toCodingKind(kind);
CodingKind codingKind = codingKind.encode(s);
d
//---
= toCodingKind(kind);
CodingKind codingKind = codingKind.decode(d); s
悪い例:
switch (kind) {
case 1 ->
= encode1(s);
d case 2 ->
= encode2(s);
d }
//---
switch (kind) {
case 1 ->
= decode1(d);
s case 2 ->
= decode2(d);
s }
繰り返し処理の内部で try
ブロックを利用しない
特に理由がない場合は繰り返し処理の外にtry
ブロックを記載する。
ただし、繰り返し処理内部で例外をキャッチし処理を行いたい場合は繰り返し処理の内部でtry
ブロックを利用してもよい。
良い例:
for (String s : array) {
BigDecimal num;
try {
= new BigDecimal(s);
num } catch (NumberFormatException e) {
= BigDecimal.ZERO;
num }
//・・・
}
文字列同士が同じ値かを比較するときは、equals()
メソッドを利用する
良い例:
String s1 = "text";
String s2 = "text";
if (s1.equals(s2)) {
//・・・
}
悪い例:
String s1 = "text";
String s2 = "text";
if (s1 == s2) {
//・・・
}
文字列リテラルはnew
しない
良い例:
String s = "";
悪い例:
String s = new String();
更新される文字列にはStringBuilder
クラスを利用する
良い例:
StringBuilder builder = new StringBuilder();
for (String s : array) {
.append(s);
builder}
System.out.println(builder.toString());
悪い例:
String string = "";
for (String s : array) {
+= s;
string }
System.out.println(string);
スレッドセーフ性が保証されていない箇所ではStringBuffer
クラスを利用する
1ステートメントのみで行われる文字列の連結には+
演算子を利用する
良い例:
String s = s1 + s2;
return s1 + s2 + s3 + s4 + s5;
悪い例:
String s = new StringBuilder(s1).append(s2).toString();
return new StringBuilder(s1).append(s2).append(s3).append(s4).append(s5).toString();
更新されない文字列にはString
クラスを利用する
文字列リテラルと定数を比較するときは、文字列リテラルのequals()
メソッドを利用する
良い例:
private static final String PROTOCOL_HTTP = "http";
if (PROTOCOL_HTTP.equals(url.getProtocol())) {
}
悪い例:
private static final String PROTOCOL_HTTP = "http";
if (url.getProtocol().equals(PROTOCOL_HTTP)) {
}
プリミティブ型とString
オブジェクトの変換には、変換用のメソッドを利用する
良い例:
int i = 1000;
String s = String.valueOf(i);// "1000"
= NumberFormat.getNumberInstance().format(i);// 3桁区切り "1,000"
s
boolean b = true;
= String.valueOf(b);// true/false
s = BooleanUtils.toStringOnOff(b);// on/off s
文字列の中に、ある文字が含まれているか調べるには、contains()
メソッドを利用する
システム依存記号( \n
、 \r
など)は使用しない。
悪い例:
String text = Arrays.stream(array)
.collect(Collectors.joining("\n"));
誤差の無い計算をするときは、BigDecimal
クラスを使う
浮動小数点演算は科学技術計算に利用するもので、誤差が発生する。これに対して、クラス「BigDecimal
」は、文字列で数値の計算を行うので、金額などの正確な計算に適している。BigDecimal
ではインスタンス生成時に指定された桁数での精度が保証される。
数値の比較は精度に気をつける
良い例:
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("1.0");
if (a.compareTo(b) == 0) {
System.out.println("一致");
}
悪い例:
BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("1.0");
if (a.equals(b)) {
System.out.println("精度が違うためこの分岐には入らない");
}
低精度なプリミティブ型にキャストしない
BigDecimal
をString
変換する際はtoString()
ではなくtoPlainString()
を利用すること
toString()
を利用した場合、指数表記になることがあります。
日付の文字列のフォーマットには、SimpleDateFormat
またはDateTimeFormatter
を使う
良い例:
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
String s = dateFormat.format(date);
一つの値を変数に代入するための if-else 文は代わりに switch
式の使用を推奨する
switch 式の値を使用することで変数を不変(実質的
final)にでき、代入箇所が分散することによる可読性の低下を防げます。
良い例:
var value = switch (op) {
case "add" -> a + b;
default -> a - b;
};
悪い例:
int value;
if (op.equals("add")) {
= a + b;
value } else {
= a - b;
value }
case 句はなるべく一つの式での記述を推奨する
複雑な式や複雑なステートメントを記述しなければならない場合は、メソッドに分割することを検討してください。
switch
式は、コーディングミスによるフォールスルーを避けるため、常にアロー構文を使用する
https://docs.oracle.com/javase/jp/16/language/switch-expressions.htmlからの引用:
ノート:
case L ->
ラベルの使用をお薦めします。case L:
ラベルの使用時は、break
文またはyield
文の挿入を忘れがちです。これを忘れると、コード内で思いがけないフォール・スルーが発生する場合があります。case L ->
ラベルで、複数の文または式でないコード、あるいはthrow
文を指定するには、それらをブロック内に囲みます。case
ラベルが生成する値をyield
文で指定します。
良い例:
var date = LocalDate.now();
var off = switch (date.getDayOfWeek()) {
case MONDAY -> {
if (myCalendar.isOff(date) || localCalendar.isHoliday(date)) {
true;
yield }
.isHoliday(date.minusDays(1));
yield localCalendar}
case TUESDAY, WEDNESDAY, THURSDAY, FRIDAY ->
.isOff(date) || localCalendar.isHoliday(date);
myCalendarcase SUNDAY, SATURDAY -> true;
};
悪い例:
var date = LocalDate.now();
var off = switch (date.getDayOfWeek()) {
case MONDAY:
if (myCalendar.isOff(date) || localCalendar.isHoliday(date)) {
true;
yield }
.isHoliday(date.minusDays(1));
yield localCalendarcase TUESDAY, WEDNESDAY, THURSDAY, FRIDAY:
.isOff(date) || localCalendar.isHoliday(date);
yield myCalendarcase SUNDAY, SATURDAY:
true;
yield };
アロー構文の、中カッコ、yield
を省略できる場合は必ず省略する
良い例:
var day = DayOfWeek.SUNDAY;
var shortDay = switch (day) {
case MONDAY -> "M";
case WEDNESDAY -> "W";
case FRIDAY -> "F";
case TUESDAY, THURSDAY -> "T";
case SUNDAY, SATURDAY -> "S";
};
悪い例:
var day = DayOfWeek.SUNDAY;
var shortDay = switch (day) {
case MONDAY -> {
"M";
yield }
case WEDNESDAY -> {
"W";
yield }
case FRIDAY -> {
"F";
yield }
case TUESDAY, THURSDAY -> {
"T";
yield }
case SUNDAY, SATURDAY -> {
"S";
yield }
};
Enum 値の switch 式で case 句が全ての Enum 値をカバーする場合は
default 句はデッドコードとなるため記述しない
良い例:
var day = DayOfWeek.SUNDAY;
var off = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
case SUNDAY, SATURDAY -> true;
};
var day = DayOfWeek.SUNDAY;
var off = switch (day) {
case SUNDAY, SATURDAY -> true;
default -> false;
};
悪い例:
var day = DayOfWeek.SUNDAY;
var off = switch (day) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
case SUNDAY, SATURDAY -> true;
default -> false;
};
代わりに switch 式が使用できる箇所は switch 式を使用する
return
を記述する場合は switch
文を使用して良いcase 句はなるべく 1 行のステートメントでの記述を推奨する
複雑なステートメントを記述しなければならない場合は、メソッドに分割することを検討してください。
switch
文は、コーディングミスによるフォールスルーを避けるため、なるべくアロー構文を使用することを推奨する
https://docs.oracle.com/javase/jp/16/language/switch-expressions.htmlからの引用:
ノート:
case L ->
ラベルの使用をお薦めします。case L:
ラベルの使用時は、break
文またはyield
文の挿入を忘れがちです。これを忘れると、コード内で思いがけないフォール・スルーが発生する場合があります。case L ->
ラベルで、複数の文または式でないコード、あるいはthrow
文を指定するには、それらをブロック内に囲みます。case
ラベルが生成する値をyield
文で指定します。
良い例:
var date = LocalDate.now();
switch (date.getDayOfWeek()) {
case MONDAY -> {
if (
!myCalendar.isOff(date) && !localCalendar.isHoliday(date) &&
!localCalendar.isHoliday(date.minusDays(1))
) {
work();
}
}
case TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> {
if (!myCalendar.isOff(date) && !localCalendar.isHoliday(date)) {
work();
}
}
}
悪い例:
var date = LocalDate.now();
switch (date.getDayOfWeek()) {
case MONDAY:
if (
!myCalendar.isOff(date) && !localCalendar.isHoliday(date) &&
!localCalendar.isHoliday(date.minusDays(1))
) {
work();
}
break;
case TUESDAY, WEDNESDAY, THURSDAY, FRIDAY:
if (!myCalendar.isOff(date) && !localCalendar.isHoliday(date)) {
work();
}
break;
}
アロー構文を使用しない(コロンを使用する)場合、複数の値をマッチさせるときの
case 句はカンマを使用して列挙する
良い例:
var day = DayOfWeek.SUNDAY;
boolean off = false;
switch (day) {
case SUNDAY, SATURDAY:
= true;
off break;
};
悪い例:
var day = DayOfWeek.SUNDAY;
boolean off = false;
switch (day) {
case SUNDAY:
case SATURDAY:
= true;
off break;
};
Java2 以降のコレクションクラスを利用する
Vector
クラス、Hashtable
クラス、Enumeration
等は、特にこれらを利用する理由がなければ、インターフェースを統一する目的で、これらの代わりにList
(ArrayList
クラス)、Map
(HashMap
クラス)、Iterator
を使用すること。List
などのインターフェースを利用することで JDK1.2
で整理されたわかりやすいメソッドを利用でき、また、インターフェースの特性から呼び出し元を変更せずに実装クラスを変更することができる。
特定の型のオブジェクトだけを受け入れるコレクションクラスを利用する
良い例:
List<Map<String, String>> list = new ArrayList<>();
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 : List.of("A", "B")) {
//処理
}
悪い例:
List.of("A", "B").forEach(s -> {
//処理
});
ただし、メソッド参照で処理できる場合はforEach
を利用する。
(デバッグのデメリットがほとんどなく、他と比較して処理効率が良いため)
良い例:
List.of("A", "B").forEach(this::process);
悪い例:
for (String s : List.of("A", "B")) {
this.process(s);
}
Arrays.asList()
は利用せず、List.of()
を利用する
Java9 で追加されたメソッド。
配列をList
に置き換える場合や、単純な固定のList
を生成する際にはList.of()
を利用する。
Arrays.asList()
とList.of()
の違いList.of()
で生成したList
は、完全に不変(Immutable)なList
で、Arrays.asList()
で生成したList
は、サイズのみ不変で、set
等による値の操作が可能なList
です。set
を行った場合、Arrays.asList()
に与えられた配列インスタンスにも影響します。ラムダ式が利用できる箇所はラムダ式を利用してよい
※パフォーマンスについても記載しているので参考にしてください
ただし、メソッド参照・コンストラクタ参照が利用できる場合はメソッド参照・コンストラクタ参照を利用する
良い例:
String::compareToIgnoreCase
悪い例:
(s1, s2) -> s1.compareToIgnoreCase(s2)
良い例:
BigDecimal::add
悪い例:
(b1, b2) -> b1.add(b2)
ラムダ式記述の際、型宣言は省略記法で記述する
良い例:
(s1, s2) -> s1 + "=" + s2
悪い例:
(String s1, String s2) -> s1 + "=" + s2
原則ラムダ式内の行数は 1 行とする
複数行で利用したい場合は、private
メソッドを作成しメソッド参照を利用する
良い例:
this::getMax
private int getMax(int i1, int i2) {
if (i1 > i2) {
return i1;
} else {
return i2;
}
}
悪い例:
(i1, i2) -> {
if (i1 > i2) {
return i1;
} else {
return i2;
}
}
原則ラムダ式は 1
行記述に限定するので、中カッコ、return
は必ず省略する
良い例:
(s1, s2) -> s1 + "=" + s2
悪い例:
(s1, s2) -> {
return s1 + "=" + s2;
}
final
を記載しなくてよい。並列ストリームは利用しないこと
悪い例:
<?> s = list.parallelStream();
Stream<?> s = list.stream().parallel(); Stream
StreamAPI 記述の際の改行位置は、各中間処理・末端処理前のピリオドの前で改行する
良い例:
List<Character> alphabetLower = list.stream()
.filter(Character::isAlphabetic)
.map(Character::toLowerCase)
.toList();
悪い例:
List<Character> alphabetLower = list.stream().filter(Character::isAlphabetic)
.map(Character::toLowerCase).toList();
List<Character> alphabetLower = list
.stream()
.filter(Character::isAlphabetic)
.map(Character::toLowerCase)
.toList();
インデントは統合開発環境の提供するフォーマッタに合わせる
中間処理の数は 3 つ(3 行)程度までを推奨する
中間処理の記述が多くなると可読性も悪くなり、デバッグも難しくなるため、3
行程度を目安にロジックを検討すること。
コメントは、原則として処理中には記載しない
難解になってしまった場合のみ処理中の記載を認める
良い例:
// クラスFooのフィールドStrの値で昇順にソートし、フィールドStrの要素を取得して処理する。
.stream()
fooList.sorted(Comparator.comparing(Foo::getStr))
.map(Foo::getStr)
.forEach(this::proc);
悪い例:
.stream()
fooList.sorted(Comparator.comparing(Foo::getStr)) //クラスFooのフィールドStrの値で昇順にソート
.map (Foo::getStr) //フィールドStrの要素を取得
.forEach(this::proc); //処理
.stream()
fooList//クラスFooのフィールドStrの値で昇順にソート
.sorted(Comparator.comparing(Foo::getStr))
//フィールドStrの要素を取得
.map (Foo::getStr)
//処理
.forEach(this::proc);
Stream は極力変数代入しないこと
Stream
は中間処理、末端処理を行うと使用済みとなり、以降同じインスタンスは利用できない。
変数代入はほとんどの場合意味をなさず、むしろミスの元となるため極力変数代入はしないこと。
良い例:
List<String> list1 = Stream.of("A", "B", "C")
.map(String::toLowerCase)
.toList();
List<String> list2 = Stream.of("A", "B", "C")
.map(s -> s + s)
.toList();
悪い例:
<String> stream = Stream.of("A", "B", "C");
Stream<String> stream1 = stream.map(String::toLowerCase);
StreamList<String> list1 = stream1.toList();
<String> stream2 = stream.map(s -> s + s);//コーディングミス streamは使用済のためエラーになる
StreamList<String> list2 = stream2.toList();
Optional
は同メソッド内で値を取り出す場合は極力変数代入しないこと
Optional
とその値の変数は同じものを示す名前となり、同じ意味の変数名が複数現れることで可読性が下がるため、Optional
の変数代入は行わないこととする。
良い例:
= findEmployee(employeeId)
Employee employee .orElseThrow(IllegalArgumentException::new);
悪い例:
<Employee> employeeOpt = findEmployee(employeeId);
Optional= employeeOpt.orElseThrow(IllegalArgumentException::new); Employee employee
直接、値を取り出すことなくOptionalでのみ扱う場合はOptionalを変数代入してもよい。
良い例:
<Employee> employee = findEmployee(employeeId);
Optional
= employee.map(Employee::getDivision)
Dept dept .map(Division::getDept)
.orElse(null);
Role role = employee.map(Employee::getRole)
.orElse(null);
//-----
<Employee> employee = findEmployee(employeeId);
Optional//・・・処理
return employee;
次のリンクも参考にしてください。
Style
Guidelines for Local Variable Type Inference in Java
明確な方針で、利用する・利用しないを統一すること
方針無く、var
を混在させるとソースコードの見通しと保守性が悪くなります。
各プロジェクトで、例えば以下ののような方針で統一してください。
var
を利用しないvar
を利用するvar
を利用する以下で2
、3
について例を示します。
原則var
を利用する
利用できる箇所は全てvar
を利用します。
良い例:
var a = "";
var b = 123;
var c = new ArrayList<String>();
悪い例:
var a = "";
int b = 123;
List<String> c = new ArrayList<>();
void methodA() {
var a = "";
}
void methodB() {
String a = "";
}
右辺で、明確に型がわかる場合はvar
を利用する
右辺をみて型がわかる場合は、全てvar
を利用します。
それ以外はvar
を利用してはいけません。
良い例:
var s = ""; // リテラルによって型が明確に判断できます
var list1 = new ArrayList<String>(); // newによって型が明確に判断できます
var list2 = (List<String>) map.get("p"); // キャストによって型が明確に判断できます
var list3 = List.of("A", "B", "C"); // ファクトリーによって型が明確に判断できます
プロジェクトで観点を決めるべき例:
var b1 = s.isEmpty(); // `is`で始まるメソッドは通例としてbooleanを返します
var b2 = Objects.equals(s1, s2); // `equals`メソッドは通例としてbooleanを返します
var i1 = Objects.hash(s); // `hash`、`hashCode`メソッドは通例としてintを返します
var i2 = Objects.compare(s1, s2); // `compare`、`compareTo`メソッドは通例としてintを返します
悪い例:
var a = e.getData(); // `e`の型と、メソッド定義がわからなければ型が判断できません
明確な方針で、利用する・利用しないを統一すること
方針無く、record
とクラスと JavaBeans 形式のクラスや Lombok
の @Data
の使用を混在させるとソースコードの見通しと保守性が悪くなります。
各プロジェクトで、record
を利用しないか、record
の使用しても良い箇所について方針を決めた上で使用するようにしてください。
また、record
は JavaBeans
とは互換性がないため使用している各種ライブラリの対応状況にも注意する必要があります。
方針例:
クラス内で処理する一時的なデータを表現するためだけにrecord
を使用しても良い。
// parentId と id をキーとして、重複を排除した uniqueItems を作成します。
Key(int parentId, int id) {
record }
var map = new HashMap<Key, Item>();
for (Item item : items) {
var key = new Key(item.getParenId(), item.getId());
.putIfAbsent(key, item);
map}
var uniqueItems = map.values();
次の記述スタイルを標準とする
ただし、フォーマッタを導入している場合はフォーマッタに合わせます。
良い例:
/**
* 矩形を表すクラス
*
* @param x 矩形の左上隅の x 座標
* @param y 矩形の左上隅の y 座標
* @param width 矩形の幅
* @param height 矩形の高さ
*/
public record Rect(
/* 矩形の左上隅の x 座標 */
double x,
/* 矩形の左上隅の y 座標 */
double y,
/* 矩形の幅 */
double width,
/* 矩形の高さ */
double height) {
}
次にポイントを説明します。
{
の後、}
の前に改行する
レコードコンポーネント(パラメータ)のカンマの後に改行することを推奨する
レコードコンポーネントが少なく、レコードコンポーネント名からでも意味が理解でき、改行がなくても可読性が低下しない場合は、改行を必要としません。
改行を推奨する理由は以下です。
レコードコンポーネントが多い場合、レコードコンポーネントへ直接コメントをつけることを検討する
レコードコンポーネントの JavaDoc
としては@param
形式でレコード名の上部に記述しますが、このソースコードをテキストとしてみた場合、レコードコンポーネントの定義と@param
の説明とで距離が空いてしまう場合があり、型と説明を読むのに時間がかかってしまう可能性があります。
また、使用する IDE
によっては、アクセサから宣言へのジャンプを使用すると、レコードコンポーネント(パラメータ)の定義へジャンプするものがあります。レコードコンポーネントにコメントがあればすぐに説明を読むことができますが、JavaDoc
しか記述しない場合は、ファイル上部へ移動して対応するレコードコンポーネントの説明を探さなければなりません。
public record Rect(
/* 矩形の左上隅の x 座標 */
double x,
/* 矩形の左上隅の y 座標 */
double y,
/* 矩形の幅 */
double width,
/* 矩形の高さ */
double height) {
}
レコードのアクセサを上書きしない
悪い例:
public record Rect(
double x,
double y,
double width,
double height) {
public double x() {
return x;
}
}
次のリンクも参考にしてください。
Programmer’s
Guide To Text Blocks > Style Guidelines For Text Blocks
複数行の文字列を定義する際、文字列連結よりもテキストブロックを使用する
良い例:
String message = """
複数行の文字列はテキストブロックを使用しましょう。
文字列連結と違い、プラス記号や改行コードのエスケープシーケンスのような無駄を排除でき、
より読みやすいソースコードで書くことができます。""";
悪い例:
String message =
"複数行の文字列はテキストブロックを使用しましょう。\n" +
"文字列連結と違い、プラス記号や改行コードのエスケープシーケンスのような無駄を排除でき、\n" +
"より読みやすいソースコードで書くことができます。\n";
単一行の文字列を定義する際、テキストブロックは使用せず文字列リテラルを使用する
ただし、二重引用符("
)のエスケープを避ける目的ではテキストブロックを使用しても良い。
良い例:
String singleLine = "単一行の文字列です。";
String message = """
"」にエスケープを使用する必要がありません。"""; テキストブロックでは単一の二重引用符「
悪い例:
String singleLine = """
"""; 単一行の文字列です。
テキストブロック内では基本的に改行コードのエスケープシーケンス(\n
)を使用しないが、読みやすさ向上の目的で改行コードのエスケープシーケンス(\n
)を使用しても良い
良い例:
String multiLine = """
複数行の、
文字列です。""";
String csv = """
,説明,MIMEタイプ
名前,"Comma-Separated Valuesの略\nCharacter-Separated Valuesの意味で使用されることもある","text/csv"
CSV,"Tab-Separated Valuesの略","text/tab-separated-values"
TSV""";
悪い例:
String multiLine = """
複数行の、\n文字列です。""";
テキストブロックで定義した文字列を処理する場合は、テキストブロックをローカル変数やフィールドへ代入してから使用することを推奨する
良い例:
String selectX = """
SELECT,
ID
NAME
FROM
TABLE_X""";
String selectY = """
SELECT,
ID
NAME
FROM
TABLE_Y""";
processValues(fetch(selectX, Entity1.class), fetch(selectY, Entity2.class));
悪い例:
processValues(fetch("""
SELECT,
ID
NAME
FROM
TABLE_X""", Entity1.class), fetch("""
SELECT,
ID
NAME
FROM
TABLE_Y""", Entity2.class));
複雑な処理に直接テキストブロックを使用すると可読性を下げる可能性があります。
3
つ以上続く二重引用符("
)をエスケープする際は、最初の二重引用符にエスケープシーケンスを使用する
良い例:
String javaCode = """
String message = \"""
テキストブロックです。""";
\System.out.println(message);
""";
悪い例:
String javaCode = """
String message = \"\"\"
テキストブロックです。"\"\";
\System.out.println(message);
""";
String javaCode = """
String message = ""\"
テキストブロックです。""\";
System.out.println(message);
""";
テキストブロックの開始引用符("""
)は前の行の右端に記述する
良い例:
String message = """
テキストブロックです。""";
悪い例:
String message =
"""
テキストブロックです。""";
テキストブロックのインデントは開始引用符("""
)に合わせる必要はない
良い例:
String message = """
テキストブロックです。""";
悪い例:
String message = """
テキストブロックです。""";
一見すると、読みやすく見えるかもしれませんが、変数名の変更によって簡単に崩れてしまい、修正するために多くの行の変更を強制することになるため、メンテナンス性が低下します。
テキストブロックで定義する文字列のインデントは基本的に周辺の Java
コードに合わせてインデントする
ただし、横に長い文字列などの可読性向上の目的で左端に揃えるのは良い。
良い例:
public class Foo {
public void process() {
String message = """
テキストブロックです。""";
}
}
悪い例:
public class Foo {
public void process() {
String message = """
テキストブロックです。""";
}
}
良い例:
public class Foo {
public void process() {
if (foo) {
String message = """
それはもう長い長いテキストブロックのためインデントするとエディタ上でテキストを見るためには横スクロールが必要になるかもしれません。""";
}
}
}
悪い例:
public class Foo {
public void process() {
if (foo) {
String message = """
それはもう長い長いテキストブロックのためインデントするとエディタ上でテキストを見るためには横スクロールが必要になるかもしれません。""";
}
}
}
テキストブロックのインデントにスペース文字とタブ文字を混在させない
文字列の最後に改行コードを入れずに、意図的にインデントした文字列を定義するとき終了引用符("""
)の前の行の右端に\
を使用する
良い例:
String text = """
ABC
DEF
GHI\""";
悪い例:
String text = """
ABC
DEF""".indent(4); GHI
ストリームを扱う API を利用するときは、try-with-resources 文で後処理をする
良い例:
try (InputStream inputStream = Files.newInputStream(Paths.get("foo.txt")) {
//inputStreamに対する処理を記載
}
ObjectOutputStream
ではreset()
を利用する
リソース解放を必要とするクラスを利用するときは、try-with-resources 文で後処理をする
良い例:
try (InputStream inputStream = Files.newInputStream(Paths.get("foo.txt")) {
//inputStreamに対する処理を記載
}
リソース解放を必要とするクラスを作成する場合はAutoCloseable
をimplements
する
AutoCloseable
をimplements
することで
try-with-resources 文が利用できるようになります。
catch 文で受け取る例外は、詳細な例外クラスで受け取る
良い例:
try (InputStream inputStream = Files.newInputStream(Paths.get("foo.txt")) {
//・・・
} catch (IOException e) {
.error("Error", e);
logthrow e;
}
悪い例:
try (InputStream inputStream = Files.newInputStream(Paths.get("foo.txt")) {
//・・・
} catch (Exception e) {//範囲が広すぎる例外クラスの利用はNG
.error("Error", e);
logthrow e;
}
Exception
クラスのオブジェクトを生成してスローしない
catch
ブロックでは基本、例外処理をする。ただし処理を書いてはいけない部分もあるので、その部分については、“// ignore
”
というコメントを記述すること。
例外クラスは無駄に定義しない
finalize()
のオーバーライド実装は禁止finalize()
をオーバーライドした場合はsuper.finalize()
を呼び出すfinalize()
を呼び出さないコードを明確化するコメントを書く
コードにコメントを書く理由は、自分自身、一緒に仕事をしている人、後に関わる開発者にとってコードをより理解しやすいものにするためである。
コメント化する価値がないプログラムならば、実行するに値しない
有用な格言。コメントは必須。
過剰な装飾は使わない (例:見出し状のコメント)
1960 年代から 1970 年代の典型的な COBOL プログラマにはアスタリスク(
*
)でコメントを囲った箱を書く習慣があった。彼らの芸術的な主張を表わしているのかもしれないが、率直に言えばそれは製品に加わるちょっとした価値に比べれば大きな時間の無駄である。かわいいコードではなくきれいなコードを書くはずである。さらに、コードを表示するディスプレイや印刷するプリントに使われるフォントはプロポーショナルだったりそうでなかったりして、箱をきれいに整列させることは難しい。
コメントはシンプルに
かつて見たもっとも最良のコメントは、シンプルな要点をまとめた注釈であった。なにも本を書く必要はなく、他の人がコードを理解するに十分な情報を提供するだけでよいのである。
コードを書く前に先にコメントを記述する
コードをコメント化する最良の方法は、コードを書く前にコメントを書くことである。それが、コードを書く前にコードがどのように動作するかについて考えるよい機会となり、コメントの存在を保障することにもつながる。少なくともコードを書いた時にコメントすべきである。コメントによってコードが理解しやすくなることで、コードの開発中にアドバンテージを得ることができる。コードにコメントを書く時間を費やせば、それによって得られるものがある。
コメントには、なぜそうなのかを書く。コードを読めば分かることを書かない
基本的に、コードの一部分を見ればそれが何かを理解することはできる。例えば、以下のコードを見て、$1000
以上の注文については
5%ディスカウントされることは理解できる。なぜそうなのか?大きな注文ではディスカウントがつきものだというビジネスルールがあるのだろうか?大きな注文に時間限定サービスがあるのか、それともずっとサービスがあるのか?これを書いたプログラマの気前がよかったのか?
どこかソースコード中か別な文書にコメントされていない限り、それがなぜなのかを知ることはできない。
if (grandTotal >= 1000.00) {
= grandTotal * 0.95;
grandTotal }
なお、メソッドコメントには、適切な javadoc コメント(タグ)のほかに、以下の内容も可能な限り明記すること。
TODO コメント
設計者確認待ち、共通処理の作成待ちなどの理由により、実装時に TODO
がある場合、下記のようにコメントを記述する。
(Eclipse の TODO コメント形式を採用)
例)
//TODO:ワークフローの仕様決定待ち 関連チケット#12345
Java では 3 種類のコメントが使える。javadoc
コメントは/**
で開始され、*/
で終わる。C
風コメントは/*
で開始され*/
で終わる。単一行コメントは//
で開始され、そのソースコード行が終わるまで続く。以下の表ではコメントの使い方とその例を示す。(コメントのスタイルに関しては、前述の「標準規約に準拠したコーディング例」を参照)
コメント種類 | 使用方法 | 例 |
---|---|---|
javadoc コメント/** comment */ |
interface、class、メソッド、フィールドの直前に書く。コメントは javadoc によって処理され、外部ドキュメント(HTML)として生成される。(この形式以外のコメントはドキュメントとして出力されないことに注意) | /* |
C 風コメント/* comment */ |
特定のコードを無効化したいが、後で使用するかもしれないので残しておくためにコメント化する時や、デバッグ時に一時的に無効化するときに使用する。 | /_ |
単一行コメント// comment |
メソッド内にて、ビジネスロジック、コードの概要、一時変数の定義内容などを記述する。 | // 1995 年 2 月に開始された X
氏の寛大なキャンペーンで |
※ ロジック中に、頻繁に C 風コメントでコメントを書くとまとめてコメントアウトする場合に不便なため、基本的にロジック中では単一行コメントを利用すること。
パフォーマンスを考慮した Java のコーディングについて以下に示す。
※ パフォーマンスは jre のバージョンやスペックによって変化します。本内容は jre1.8.0_74 での検証結果を元にした内容です。
※ 性能計測結果についての記載がありますが、あくまでも参考値です。性能を保証するものではありません。
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")) {
.add(string);
resultList}
}
return resultList;
Stream API
List<String> list = //数値文字列のList
List<String> resultList = list.stream()
.filter(s -> s.endsWith("0"))
.toList();
return resultList;
計測結果
処理する 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);
}
};
ラムダ式
Comparator<String> c = (o1, o2) -> o1.compareToIgnoreCase(o2);
メソッド参照
Comparator<String> c = String::compareToIgnoreCase;
計測結果
処理件数 | 匿名クラス (ms) | ラムダ式 (ms) | メソッド参照 (ms) |
---|---|---|---|
10 億回 | 380 | 0(計測不能) | 0(計測不能) |
100 億回 | 6,374 | 0(計測不能) | 0(計測不能) |
1 京回 | (30 秒以上) | 14 | 10 |
ラムダ式は外部の変数を利用する場合、匿名クラスとほぼ同じ動作をします。
匿名クラス
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return arg.equals("DESC") ? o2.compareToIgnoreCase(o1)
: o1.compareToIgnoreCase(o2);
}
}
ラムダ式
Comparator<String> c = (o1, o2) -> arg.equals("DESC") ? o2.compareToIgnoreCase(o1)
: o1.compareToIgnoreCase(o2);
計測結果
処理件数 | 匿名クラス (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 }
+= string;
s }
return s;
StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
String string = list.get(i);
if (i > 0) {
.append(",");
sb}
.append(string);
sb}
return sb.toString();
StringBuffer
StringBuffer sb = new StringBuffer();
for (int i = 0; i < list.size(); i++) {
String string = list.get(i);
if (i > 0) {
.append(",");
sb}
.append(string);
sb}
return sb.toString();
String#join
return String.join(",", list);
計測結果
処理する 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;
StringBuilder で処理しようとすると以下のようになります。
private static final String CONST_AB = new StringBuilder(CONST_A).append(CONST_B).toString();
しかし、これらをバイトコード上で確認するとそれぞれ以下のようになります。
+
演算子
private static final java.lang.String CONST_AB = "AB";
StringBuilder
private static final java.lang.String CONST_AB;
static {};
0 new java.lang.StringBuilder [20]
3 dup
4 ldc <String "A"> [8]
6 invokespecial java.lang.StringBuilder(java.lang.String) [22]
9 ldc <String "B"> [11]
11 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [26]
14 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [30]
17 putstatic jp.co.packagename.ClassName.CONST_AB : java.lang.String [34]
20 return
+
演算子を利用した場合コンパイル時に最適化され、文字列"A"
と"B"
をあらかじめ結合して
class が作成されます。
StringBuilder
を利用した場合は最適化はされず、記述した通りの処理が行われます。
計測した場合、下記のようになります。
計測結果
処理回数 | StringBuilder (ms) | + 演算子 (ms) |
---|---|---|
5,000 万回 | 559 | 0(計測不能) |
1 億回 | 1,059 | 0(計測不能) |
通常、定数処理を大量に処理することは考えられないので性能問題になることはありませんが、+
演算子を利用したほうがパフォーマンスが高いこともあるということを理解してください。
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);
//処理
}
拡張 for 文
for (String s : list) {
//処理
}
forEach
.forEach(this::処理); list
計測結果
処理する 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
の種類にも注目してみてください。
BigDecimal の正・負・ZERO
の判定はBigDecimal#signum
を利用します。
compareTo
を利用してBigDecimal.ZERO
と比較しても同じことができますが、signum
を利用したほうが効率的です。
以下に処理例と計測結果を記載します。
compareTo 利用
BigDecimal value = new BigDecimal("0.0");
if (value.compareTo(BigDecimal.ZERO) == 0) {
signum 利用
BigDecimal value = new BigDecimal("0.0");
if (value.signum() == 0) {
計測結果
(単位:マイクロ秒)
処理回数 | compareTo 利用 (マイクロ秒) | signum 利用 (マイクロ秒) |
---|---|---|
1 京回 | 527 max:26,367 min:0 |
424 max:21,213 min:0 |
性能差が少ないので、必ずしも signum を利用する必要はありませんが、大量に処理する場合など、高いパフォーマンスが求められる場合は意識してください。