コーディングルール (Java)

Bymosaos
コーディングルール (Java)

Java 開発する中で個人的にまとめたコーディングルール。
※ 企業でのプロダクト開発などでの経験も踏まえてはいる。

はじめに

良いコードかどうかの一つの指標として「他人が読んでもわかりやすいかどうか?」というものがある。
コードがわかりやすいかどうかは、ロジックの判り易さはもちろん、フォーマットやAPIの常識的な利用方法など、様々な要素から決定される。
複数人から構成されるプロジェクトでコーディングを行うにあたっては、他人が見た場合にも判りやすいかどうかという点は意識しなければならない。

見やすさを重視する事は、忘れた頃にメンテナンスする ( 可能性がある ) 自分のためにもなる。

ここではjavaを開発言語にしたアプリケーション開発プロジェクトにおける、コーディングルール、推奨を記す。

単純に「ここに記載されている事だけ気を付ければよい」という事ではなく、判りやすいコードを書くという視点を忘れずに柔軟にコーディングを行って頂きたい。

※ 叩き台として作成したものなので、あくまで参考用。プロジェクトや利用者毎に適宜変更推奨。

ファイル

publicクラスはそのクラス名の1ファイルにする。

  • 複数のpublicクラスを1つのファイルに書いてはいけない。
  • 非publicのクラスで、他クラスから利用されないものに関してはそのクラスが利用されるpublicクラスのファイルに含めてもよい(インナークラス)。ただし、インナークラスは原則利用しない事。

ファイルエンコードはUTF-8とする。

ファイルの階層構造

Mavenの標準ディレクトリレイアウトに従う。

MavenはJavaのビルドツールのデファクトスタンダートとして長期間利用されており、Mavenの標準ディレクトリレイアウトを利用する事で、多くのJava開発者にとって見通しのよいレイアウトになる。

命名規則

大文字小文字

Javaでは大文字と小文字を区別するが、大文字/小文字の違いによって区別をするコードを書いてはいけない。

悪い例)

    private int num;
    private int Num;

良い例)

    private int cartNumber;
    private int productNumber;

パッケージ名

  • "." で区切られた文字列(英子文字)とし、アプリケーション/プロジェクトを表す名前を含める。
  • 外部公開予定であれば基本的にはドメイン名を含める。

例) com.mosaos.projectname

クラス名

アッパーキャメルケースを用いる。

例) UpperCamelCase

例外クラス名

最後をExceptionとしたクラス名とする。

インターフェース名

クラス名と同じ。

実装クラス名

特にインターフェースと区別する必要があるのであれば、最後に Impl を付与する。

抽象クラス名

適当な名前が無い場合、Abstractからはじまりサブクラスを連想させる名前を付ける。

尚、むやみに抽象クラスを作成する事はおすすめしない。Templateパターン等明らかな目的がある場合を除き、本当に抽象クラスが必要なのか考える事。

以下のような理由での抽象クラス作成(継承の利用)は控える事。

  • メソッド共有のための継承
    • 共通メソッドの変更/機能追加が難しくなる。
    • 基底クラスに共通メソッドが追加されまくり、ネ申クラスが誕生する。
  • メンバ変数の共有のための継承
    • 派生クラスへの影響があるため、メンバ変数を変更しにくくなる。

定数(static final)

  • 大文字スネークケースを用いる。

例) UPPER_SNAKE_CASE

メソッド名

ローワーキャメルケースを用いる

例) lowerCamelCase

操作を行うメソッドに関しては 動詞 + 対象 となるような名称にする事。対象 + 動詞ではない。

悪い例) couponGet
良い例) getCoupon

属性取得

属性取得のメソッドには getXxx, isXxx ( xxx は属性名 ) 形式のメソッド名を用いる。

属性設定

属性設定のメソッドには setXxx ( xxx は属性名 ) 形式のメソッド名を用いる。

英語と日本語

すべての識別子の名前は英語を基本とする。例外的に英語化が困難な対象に関してはローマ字表記も認めるが、可能な限り対応用語辞書を作成 / メンテナンスし、同一の用語に対して異なる命名をしないようにすること。

名前の対称性

命名する場合に、以下のような英語の対象性に気を付けること。

  • add / remove
  • insert / delete
  • get / set
  • start / stop
  • begin / end
  • send / receive
  • first / last
  • get / release
  • put / get
  • up / down
  • show / hide
  • source / target
  • open / close
  • source / destination
  • increment / decrement
  • lock / unlock
  • old / new
  • next / previous

ループカウンタ

スコープが狭いループカウンタには、i, j, k を用いる

スコープが狭い変数

スコープが狭い変数名には省略形を用いても構わない。ただし省略形ばかりで可読性が低くなってはならない。

例)

    InputStream in = new BufferedInputStream(...);

意味が判る名前

原則として名前から意味が判るような名前にする事。

悪い例)

    copy(s1, s2);

良い例)

    copy(source, destination);

スタイル

コーディングスタイルは JDKソースのスタイルに準ずる。

K&RのC言語のスタイルと同様だが、クラス/メソッド定義開始の"{"は改行せずに記述する。

スタイルのチェックは checkstyle を利用して、可能な限り自動化する事。

コメント

  • publicなクラス/メソッドには原則Javadocコメントを記載する事。

  • コメントは必要なものを簡潔に記載する。

  • コメントには、なぜそうなのかを書く。

  • コードを書く前に先にコメントを記述する。
    やってみると判るが良いコメントを書くための最上の方法の一つは、コードを書く前に処理内容を端的にコメントとして記載する事。
    複数のステップから構成される処理であれば、ステップ毎に行を分けてコメント記載し、それからコーディングに取り掛かる。
    こうすれば必ずコメントが書かれるし、コメントに記載した通りの処理が実装される事につながります。

  • TODO コメントを活用する事。
    Eclipse等のIDEで TODO はまとめて参照可能なので、(他者の実装待ちや仕様確定待ち等の理由で)残タスクとして残っている部分には TODO コメントとして後から行うべき事を書いておきましょう。
    ※ Eclipseで一覧表示する場合にはタスクビューを利用してください。
    ※ TODO 以外にも以下のようなコメントで利用可能なタスクタグがある。

    • FIXME : 修正が必要
    • XXX : 危険!動くけどなぜうごくかわからない。

    ※ FIXME,XXX以外もあるが、Eclipseで利用する場合、
    [設定]>[Java]>[コンパイラー]>[タスク・タグ]で利用するコメントを追加する必要がある。

import

import文で * は原則使わない。

スコープ

メソッド、フィールドには適切なスコープを設定する事。

インスタンス変数

原則 private スコープとし setter/getter を必要に応じて記述する。
単純に値を設定/取得するだけの setter/getter については適宜 Lombok を利用してコードの可読性や開発効率を高める事。

ローカル変数の利用

エンティティクラスや Bean クラス等を除き、インスタンス変数の利用は可能な限り避ける事。
特に Spring Framework 等の DI コンテナを利用する場合、ある種のクラスはシングルトンとしてインスタンス化される事がデフォルト設定の場合もあるため、インスタンス変数を不用意に利用した場合スレッドアンセーフな実装となる。
この結果、単体や結合試験ではバグを発見できず、実運用で初めて不具合が発生する可能性がある。
場合によっては個人情報の漏洩等の重篤な問題を引き起こす可能性もあるため、利用する際は注意して実装を行う事。

finalの使用

必要に応じて final を適切に利用する。

  • 継承され(たくない)ないクラス
  • オーバーライドされ(たくない)メソッド
  • 値の変わらない(変わってはまずい)変数

比較

オブジェクトの比較には equals メソッドを利用する。
インスタンスが同一かどうかを比較したい場合に == 比較を行うのは構わない。

booleanの比較

条件式でbooleanの変数を比較しない

悪い例)

  if (isEmpty == true)

良い例)

  if (isEmpty)

ループ

拡張forループが利用可能である場合には拡張forループを使う事。

インターフェースでの参照

原則としてオブジェクトの参照にはインターフェースを利用する。特にJava APIの利用等において以下のようなコーディングをたまに見かけるが、良くない実装なので止める事。

悪い例)

    ArrayList<Entry> list = new ArrayList<Entry>();

良い例)

    List<Entry> list = new ArrayList<Entry>();

数値

必要に応じて BigDecimal クラスを適切に利用する事。
プリミティブ型や BigDecimal 以外の型における算術演算では厳密には誤差が発生する場合がある。
金融系や科学技術系等の処理で正確な演算が必要な場合には BigDecimal の利用が適している。

try with resources

ストリーム等リソース解放を必要とするクラスを利用する場合、可能であれば try with resources を利用する事。

文字列連結

特にループ処理等で文字列連結を行う等、文字列連結処理でのパフォーマンスが懸念される場合には StringBuilder (あるいはStringBuffer)を利用する事。
※スレッドセーフでない箇所で利用する場合にはStringBufferを利用する

System.out.print の利用

ログ出力API等に置き換える。
標準出力がどうしても必要である場合や、自身の一時的なデバッグ目的で利用する場合を除いて、プロダクトレベルのアプリケーションでの System.out.print, System.err.print 系の利用は行わないこと。

  • 出力元の特定が困難
  • ログ出力APIであればログフォーマットの設定によって出力箇所の特定は容易であるし、出力先やレベルの変更等一括して行える。

ラムダ

ラムダ式が利用できる箇所はラムダ式を利用してよい。

Stream API

利用してよい。
ただし、Stream APIは中間処理のたびにインスタンスが生成されるため、拡張forを利用する場合に比べてパフォーマンスが劣化する可能性がある。
パフォーマンス重視の処理を実装する場合には各方式でパフォーマンス計測する等してから実装方式を決める事をおすすめする。

var (Local-Variable Type Inference)

var は原則利用しない。
TypeScript などの普及でも判るが、現代のチーム開発では型安全性の確保が強く求められている。型推論による省力化よりも、コードの堅牢性を優先すべき。
ただし、スコープの都合などで var を使わないデメリットが非常に大きい例外的なケースに限り、利用を認める。