Home on 246net ] [ Javaプロジェクト規約

Javaコーディング規約の一例

TAKAHASHI, Toru
2010年10月24日着手
はじめに
プロジェクトとして共通に定義しておくとよい規約の一つ、コーディング規約についての規定サンプルです。

 

前提条件

コーディング規約をどこまで細かく規定するか、については、常に論争となる点です。そこで、まず前提条件を明確化し、不要な論争を避け、有意義な議論ができる土台を定義します。

前提とするソフトウェア開発組織

理想的なソフトウェア開発組織は、同じ開発文化を共有する開発者チームが継続していろいろな仕事をこなす形態です。

しかし、ソフトウェア開発組織を継続して保有(常備)するためには、定常的にコストがかかります。そのため、中にはソフトウェア開発組織の質よりも常備コスト削減を優先し、開発案件が発生したときに臨時でソフトウェア開発組織を編成し、案件終了時にソフトウェア開発組織を解散する、というやり方を取る会社があります。

継続するソフトウェア開発組織であれば、ソフトウェア開発能力はメンバー間の暗黙知による共有により十分期待できるのですが、臨時編成するソフトウェア開発組織では、能力はまちまち、暗黙知は共有できない、という状況ですから、形式知によって開発のやり方をそろえる必要があります。

コーディング規約の必要性、内容については、この開発組織の編成方法の違いによって変えていく必要があります。前者は、暗黙知が中心なので、明文化する規約には、細かな規定をつらつら書くよりも、理念、目標、目的、指針を示すのがよいでしょう。一方、後者は理念を掲げてもその場限りの組織ですし、抽象的な内容は解釈がばらつきますから、具体的な規定をしっかり明示するのがよいでしょう。

ここでは、開発案件ごとに臨時編成するソフトウェア開発組織を前提とします。

なお、臨時編成の組織で開発効率が高いかどうかは別な場での議論です。

前提とする開発者の技術・モチベーション

理想的なソフトウェア開発組織では、開発者を新たに採用する場合、その人物の持つ技術、資質、モチベーションが採用担当者だけでなく同僚となる開発者からも十分に吟味され、十分と判断された者のみが新たに加わります。

しかし、プロジェクトごとに人を集めるやり方で、お金(時間当たりの単価)や会社間の付き合いで決まるような場合、開発者の技術・モチベーションはピンきりとなってしまいます。

後者の場合、コーディング規約を渡した場合のこまった反応として

といった対応が往々にして見受けられます。本来、規約は資料を渡すのみではなく、時間をかけて教育をして、最初の段階ではこまめにチェック(レビュー)をするものです。しかし、そのようなコストを認めないプロジェクト管理者のもとでは、そうもいきません。

結果として、コーディング規約の目的・心を理解して適用できる技術・資質・モチベーションのある人は(コーディング規約がなくても)良いコードを書くし、そうでない人は(コーディング規約があっても)悪いコードを書く、ということになります。

ここでは、技術・モチベーションがピンきりの開発者がいることを前提とします。

コーディング規約の方針

コーディング規約を読みもしない人、読んでも分からない人、などが混じったプロジェクトで生成するコードの一貫性をそろえ、品質を(ある程度は)確保するために、以下の方針で規約を準備します。

規約で規定する内容は、極力ツールで検証・設定する

たとえば、コードのインデント・フォーマットといったスタイルについては、統合開発環境(NetBeansなど)のソースコード整形機能を使えば一貫性を保てます。

スタイルを含め、命名やコーディング指針は、静的検証ツールでチェックすることで問題個所を提示し、ツールは可能な限り全員が常に使用し、コンパイルの都度かソースコードを記述するそばからチェックするのがよいやり方です。

ただし、それぞれの規定の作成理由を合わせて記述します。

静的検証ツールのうち、全員が常時使用し、コーディングの都度チェックできるものを以下に列挙します。

ツール名  解析対象  概要  備考 
 Checkstyle  ソースコード  主に命名・フォーマット・コメントの検証  
 FindBugs  バイトコード  既知のバグデータベースに合致する疑わしいコードの検出  
 PMD  ソースコード  命名、設計ルール、バグが疑わしいコード、重複(コピペ)コードの検出  

バグと疑わしきコードの検出は、FindBugsが一日の長があるように思います。そこで、FindBugsを主の静的検証ツールにしたいのですが、ソースコードレベルの検証ができないため、CheckStyleと併用するのが、今の時点での判断です。

なお、PMDの持つ重複(コピー&ペースト)コードの検出は使い方を学んでいないので、今後この成果によってはPMDも併用という可能性もあります。

なお、ルールについては、

規約の規定は、既存のJavaに合わせる

一貫性を重視するため、命名、例外、アクセス修飾子の使用などは、既存のJava(Java標準API)に合わせます。

たとえば、継承を前提として設計していないクラスの宣言に final 修飾子を付けるというプラクティスがあります。継承に絡んだバグを防ぐ意味ではよいプラクティスですが、一貫性の観点では、final を付けていないクラスを見た場合、継承を前提に設計されていると判断してしまいます。しかし、Java標準APIには使用されていないので、困惑が生じます。そこで、このプラクティスは採用を見送ります。

そこで、規約の規定は極力既存のJavaに合わせるものを採用します。

やってはいけないNG集を示す

守らなければならないこと(規約・規定)を作成するのは、法律の文面を作成することに近く、実に困難なことです。また、そのような文面は、可読性が悪く(誤解のないよう念を入れて記述せざるをえないため)、読まれない規約の一因になります。

そこで、逆にやってはいけないことを列挙するという方法が効果的です。やってはいけない例を記述する際は、端的に一例を示せばよく、抜け・漏れ・誤解のないよう万全を期す文章でなくてもかまいません。また、読み手も、自分の習慣がNGに記載されているとぎょっとするので、効果的です。

メトリックスは、規約ではなく品質評価のデータとする

ファイル・メソッドの行数、桁数、引数の数、結合度、サイクロマティック複雑度、などのメトリックス値について、静的検証ツールで検証できます。しかし、これをエラー同様必達事項とするのは、本質を見失った修正に走らせる危険があります。たとえば、
「行数を減らせばいいんだろう!じゃぁhogeメソッドの真ん中でぶった切って、hoge2メソッド呼ぶようにすれば簡単だ。あっ、ローカル変数がメソッド分けたから使えない。そうだ、全部フィールドにしてしまえ。さすがEclipse、リファクタリングメニューにローカル変数をフィールドに変換なんて便利なのがある。」
といった具合です。

メトリックスは、1つの指標だけで判断するものではなく、いろいろな指標を集めて総合的に判断するためのものです。

そこで、メトリックスは、コーディング規約で閾値を決めてそれを越えると指摘として扱うのではなく、計測をして品質指標として出力するものとします。

規約の規定例

フォーマットに関する規約

 項目  検証ツール  ルール名 適用   備考
 ファイルの末尾は改行で終了する  CheckStyle  NewlineAtEndOfFile  A  旧POSIX(IEEE Std 1003.1-2001)規定
改行コードがOSデフォルトと異なるときは要カスタマイズ
 改行コードはLFとする      A NetBeansデフォルト 
 文字コードはUTF-8とする      A NetBeansデフォルト
インデントレベルは4とする  CheckStyle   Indentation  B  NetBeansのソース整形で自動的に適用されるため、検証ツールでのチェックはしない。
 ソースコードリポジトリにコミットする前に必ずソース整形を実施する。
 1行には1ステートメントとする  Checkstyle  OneStatementPerLine  B
 for初期化子が空のときは空白を入れない  CheckStyle   EmptyForInitializerPad  B
 for列挙子が空のときは空白を入れない  CheckStyle   EmptyForIteratorPad  B
 メソッド名と開き丸括弧の間に空白・改行を入れない  CheckStyle   MethodParamPad  B
 トークン(配列初期化子の開き波括弧、排他論理和、前置--、前置++、.、!、単項-、単項+)の後ろに空白を入れない  CheckStyle  NoWhitespaceAfter  B
 トークン(セミコロン、後置--、後置++)の前に空白を入れない  CheckStyle  NoWhitespaceBefore  B
 行の途中で改行時、演算子の直前で改行する  CheckStyle  OperatorWrap  B
 開き括弧の直後、閉じ括弧の直前に空白を入れない  CheckStyle   ParenPad  B
 キャストの開き括弧の直後、閉じ括弧の直前に空白を入れない  CheckStyle  TypecastParenPad  B
 TABコードを含まない。(インデントは空白で行う)  CheckStyle  TabCharacter  B
 カンマ、セミコロン、キャスト演算子の直後に空白(改行)を置く  CheckStyle  WhitespaceAfter  B
 トークンの前後に空白を入れる  CheckStyle  WhitespaceAround  B
 開き波括弧は行末に置く  CheckStyle  LeftCurly  B
else, try, catchの前の閉じ波括弧は同一行に置く CheckStyle RightCurly  B
do, else, if, for, whileのブロックには{}が必要  CheckStyle NeedBraces   B
ジェネリクス記号(<>)の前後は空白を入れない Checkstyle  GenericWhitespace   A  NetBeansのソース整形は不完全
 不要な丸括弧 Checkstyle  UnnecessaryParenthes   A  
       
         

命名規約

 項目  検証ツール  ルール名  備考
意図が明確に表れる名前をつける       Clean Code
エンコーディングを使わない。エンコーディングとはハンガリアン記法、メンバープレフィックス('m_'や'_'など)、インタフェースの'I'など       Clean Code
すべての名前は英語でフルスペルを基本とし、略語*1は使わない。ID、連番はもってのほか。パスカル/キャメル形式において頭文字語*2(例:HTML)は2文字目以降を小文字とする(getHtml)。      型の命名と変数の命名で略語の扱いを変える(型の略語は厳しく制限、変数名はスコープに応じて緩く)
大文字・小文字の違いで名前を区別しない       
定数の命名は大文字英数とアンダースコア、先頭は英字  CheckStyle ConstantName static finalフィールド
ローカル変数(finalを含む)は英数字のキャメル形式、先頭は英字  CheckStyle LocalFinalVariableName
LocalVariableName 
 
ローカル変数のスコープが狭い(10行以内でネストしたブロックを含まない)場合は、略語の使用を許可する       
for文のループカウンタは、ネストごとにi, j, k, …を使う       
イテレータは、itを使う       
引数は英数字のキャメル形式、先頭は英字  CheckStyle ParameterName  
フィールド名は英数字のキャメル形式、先頭は英字  CheckStyle MemberName
StaticVariableName 
非staticなfinal含む
メソッド名は英数字のキャメル形式、先頭は英字  CheckStyle MethodName
インスタンスを生成するメソッド名は、create+クラス名とする(要検証)
→ Java標準APIは、newInstance か? 
     
型変換メソッド名は、to+クラス名とする      JLS 6.8.4
アクセッサメソッド名(ゲッター)は、get+属性名とする。但し戻り値型がbooleanの場合はtrue/falseがわかる名前とする       
ミューテータメソッド名(セッター)は、set+属性名とする。       
パッケージ名は英小文字と数字とアンダースコア、先頭は英字。サブパッケージ名の重複は可  CheckStyle PackageName  デフォルトから変更^[a-z]+(\.[a-z][a-z0-9]*)*$
     
クラス型名は英数字のパスカル形式、先頭は英字。その内容(責務・役割)を表す名詞や名詞句とする。  CheckStyle TypeName JLS 
例外クラスは末尾がExceptionで終わる       
インタフェース型名は英数字のパスカル形式、先頭は英字。抽象的な型として用いるときは、説明的な名詞や名詞句が適しており、振る舞いの場合は形容詞を用いる(Runnable, Cloneableなど)      JLS
インタフェース名に接頭辞Iを付けない       
インタフェース実装クラスの末尾にImplを付けない       
型変数名は、簡潔(可能なら1文字)な大文字英字を使用する。基準例
・コンテナ型の要素型にはE、マップの場合はキーの型にK、値の型にV
・任意の例外型にX
・特に区別しない場合はT、複数使用するときはアルファベット順でTの近傍の文字を使う
    JLS 
       
       
       
*1 略語
単語の一部を省略し短くした表記。例:tmp(temporaly), btn(button)。可読性を損なうので原則略語は使用しない。
*2 頭文字語(かしらもじご)
複数の単語の先頭文字同士を並べた表記。例:HTML(Hyper Text Markup Language)。

よくない命名

コメント規約

 項目  検証ツール  ルール名  備考
パッケージのJavadocをpackage-info.javaに記述する   CheckStyle JavadocPackage  
protectedまたはpublicメソッドはJavadocコメントを記述する  CheckStyle JavadocMethod
デフォルト、protectedまたはpublicクラスはJavadocコメントを記述する  CheckStyle JavadocType   
protectedまたはpublicフィールドはJavadocコメントを記述する  CheckStyle  JavadocVariable 
   CheckStyle  JavadocStyle 
       
       
       
       
       

コードの書き方

 項目  検証ツール  ルール名  備考
import文でワイルドカードを使わない  CheckStyle AvoidStarImport  
sunパッケージをimportしない  CheckStyle IllegalImport
重複したimport、無駄なimportはしない  CheckStyle RedundantImport  
未使用なimportはしない  CheckStyle  UnusedImports
修飾子の指定順序は、public, protected, private, abstract, static, final, transient, volatile, synchronized, native, strictfpの順  CheckStyle  ModifierOrder 
重複した修飾子、無駄な修飾子は記述しない   CheckStyle  RedundantModifier  
メソッド途中にブロック(制御構文ではなく単独の{...})を設けない   CheckStyle AvoidNestedBlocks  
空のブロックを書かない  CheckStyle EmptyBlock  
finalを使い、継承される想定をしていないクラスを明示する   CheckStyle FinalClass  
finalを使い、オーバーライドさせないメソッドを明示する      
finalを使い、再代入しない変数を明示する   CheckStyle FinalParameters   
ダブルチェックロッキングイディオムを使わない   CheckStyle DoubleCheckedLocking   
セミコロンだけの行を書かない   CheckStyle EmptyStatement  
equals()をオーバーライドするときはhashCode()も合わせてオーバーライドする   CheckStyle EqualsHashCode  
ローカル変数の名前は同じクラスのフィールド名とかぶらない   CheckStyle HiddenField  
インスタンス生成は直接newするのではなくファクトリーメソッドを呼ぶ   CheckStyle IllegalInstantiation  
代入はトップレベルで行い、式の途中で行わない(for文を除く)   CheckStyle InnerAssignment  
マジックナンバーは使わない(-1, 0, 1, 2を除外)   CheckStyle MagicNumber  除外
switch文には必ずdefault句を記述する   CheckStyle MissingSwitchDefault  
throws句に冗長な例外の記述はしない(2回記述、継承関係にある型、非チェック例外)   CheckStyle RedundantThrows  
booleanの評価式では、冗長な記述をしない(== true、|| true、!false)    CheckStyle SimplifyBooleanExpression  
booleanをリターンするときは、冗長な記述をしない   CheckStyle SimplifyBooleanReturn  
継承を考慮した設計をしているか   CheckStyle DesignForExtension  
staticメソッドとstaticフィールドだけからなるクラスはコンストラクタを公開しない   CheckStyle HideUtilityClassConstructor  
インタフェースは型を定義するために使う。定数を定義するためには使わない   CheckStyle InterfaceIsType  
フィールドはprivate。static finalのみpublic可   CheckStyle VisibilityModifier  
配列型変数宣言で角括弧は型名に付けて書く   CheckStyle ArrayTypeStyle  
コメントに特定のキーワードを書いた箇所をチェックする。("TODO"、"FIXME")   CheckStyle TodoComment デフォルト"TODO:"から変更 
long型のリテラルは小文字のlではなく大文字のLを付与する   CheckStyle UpperEll  
       
       
    

規約の検討

ファイルの記述

ファイル先頭コメント

よくある慣習では、ファイル先頭コメントには、そのファイル名、システム名やプロジェクト名、サブシステム名、そのファイルで定義される型名(クラス・インタフェース・列挙型)、著作権、適用するライセンスなどを書くことになっている。

こうしたファイル先頭コメントの例を次に示す。

/*
 * TemperatureSensor.java
 * 
 * Created: Sat Oct 06 02:24:16 2001
 *
 * Project: Weather report
 * System:  Weather Monitoring System
 * Subsystem:  Sensor Subsystem
 *
 * Contains: TemperatureSensor class
 *           TemperatureSensorChangeHandler class
 *
 * Copyright 2001 by Example Company, Ltd. All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

しかし、本当に必要なのだろうか?ここで書くとされている項目について検討をしてみる。

上記検討から提案するファイル先頭記述は、コメントではなく適切なpackage文があればいい。

package com.example.wether.monitor.sensor;

予想される反論

作成者・修正者を記載するか否か?
数年後、ソースファイルに記載されている作成者/修正者に問い合わせることができるなら記載してもよいだろうが、会社として開発しているなら、個人に問い合わせることは考えられない。記載の必要はない。誰がいつ何を修正したかを管理するのは、ファイル上の記述ではなく構成管理である。

ファイルの編成(記述順序)

1ファイル1クラス(型)編成を採用する場合の、ファイル内宣言順序を検討する。

ソースファイル編成
package com.example.wether.monitor.sensor;  最初の行はpackage文を記述、ファイル先頭コメントは不要 
import java.util.List;
import java.util.logging.Logger; 
 次はimport文の記述。
単一の型インポート宣言および単一のstaticインポート宣言のみを使用し、オンデマンドの型インポート宣言およびオンデマンドのstaticインポート宣言は使用しない。
staticインポート宣言を乱用しない(最低限に留める)
/**
* 温度を測定し報告するセンサー。 * <p> * 指定された周期で温度を測定し、値を * 指定されたオブジェクトへ通知する。
*/
 クラスのドキュメンテーション・コメント
public class TemperatureSensor implements Sensor {  クラスの定義
    private double temperature;
 フィールドの定義
    public TemperatureSensor() {
        :
    }
 コンストラクタの定義

インデントはTABでなく空白

インデントをTAB文字と空白文字とどちらで行うかは、コーディング規約の論争の火種の1つです。論争になる場合、前提を明確にして整理しないと意見が対立したまま収束しないので、まず、前提を明確にします。

前提
検討

インデントに空白を使うと、どのエディタ、Webブラウザ、コマンドプロンプト/ターミナル上でのファイルを標準出力、diff/patch/mergeツール上の扱い、いずれにおいても問題は生じません。

一方、インデントにTAB文字を使う場合は、以下の問題が生じます。

注記) TAB文字を8空白相当、と記述している場合は、TAB文字が機械的に8空白文字に展開されるのではなく、8の倍数の桁まで右に空白文字を入れることを意味する。4空白相当のときは、4の倍数の桁となる。

予想される反論とその想定問答

コーディング

マジックナンバー

マジックナンバーは避けるべしと思っているが、座標計算での90.0や180.0は、定数にした方が可読性が悪い(そもそもなんて変数名にするんだろう、NINTY_DEGREEかDEGREE_90か、どちらにしても90の方が明瞭簡潔だし保守性も差がない(そもそも変更があるなら物理法則がひっくり返るときだ)。

マジックナンバーを避ける場合を上げると