この記事は?
自社開発をするにあたり、作成したコーディング規約です。
自分自身、過去にいくつか他社の作成した意図不明なコーディング規約によって苦しめられた経験があり、かなり緩めにしています。
この規約は守るべき?
規約なんだから当然守るべき、という考えもあるでしょう。
でも私はそういう安直な考えは嫌いです。
コーディング規約を守ることが目的になってしまっては本末転倒です。
なので、極論このコーディング規約は守らなくてよいです。
ではなぜ書くか?
それは、ある程度ルールがあった方が迷わなくて済むからです。
確信があるのならば、このコーディング規約は無視してください(もしくはコーディング規約を修正してください)。
どうすべきか迷ったときに、このコーディング規約を参考にしてください。
凡例
★:重要。この規約は違反すべきではありません。違反するべき合理性がある場合は、コーディング規約の方を変更すべきです。ただし、コーディング規約の変更に合わせ全ソースのリファクタリングが発生する覚悟はした方が良いでしょう。
◆:大事。「違反しているが、この方が良い!」という確信がある場合は違反してもよいでしょう。コーディング規約の変更を検討する良い機会です。
●:おすすめ。特にこだわりがない場合はとりあえず従いましょう。
命名規約
共通
●基本的な考え方
基本的にはGoogle Java Style Guide (非公式和訳)に従います。
★よく考えること。
適切な命名を行うということは、「それ」が何であるかを簡潔・明確に定義するということです。これはすなわちプログラム設計です。
命名に悩む場合、
- プログラム設計が簡潔・明確でない
- 仕様が理解できていない
- レガシーコストが積み上がりすぎている
といった問題がある可能性があります。(=問題を検知できます)
命名をテキトーにするとこのアラートが働かず、プログラム設計はどんどん崩壊していきます。
過不足のない、「これしかいない!」と胸を張れる命名を行いましょう。
★原則英語であること。日本語、ローマ字、謎のコードはNG。
謎のコード(画面ID等)はそれが何を指すのか一見してわからないためNG。
ローマ字は単純に読みにくいのでNG。
日本語は毎度全角/半角キーを押すのが面倒なのでNG。
public class Employee {
public String name;
private Decimal monthlyIncome;
}
public class Class002 {
public String namae;
private Decimal 月収;
}
◆ごく一般的なもの以外、略語を用いないこと。
略語の解読に時間がかかるため。また逆に、略語を作るのにも時間がかかるため。
Object menuLg; // NG:'Lg'はLanGuage? LenGth?
String employeeNm; // NG:'Nm'はName? NuMber?
SystemParameterDAO dao; // OK:'DAO'がDataAccessObjectを指すことは一般的
DBConnection dbConnection; // OK:'DB'がDataBaseを指すことは一般的
一般的でない略語を作りたいほど名前が長くなる場合、プログラム設計が適切でないことを疑った方がよいです。
また、スコープがとても狭い変数は、略語である方が見やすい場合があるのでその場合はOKです。
public class Corporation {
private List<Employee> employees;
public List<String> getEmployeesNameList() {
List<String> result = new ArrayList();
employees.forEach(e -> result.add(e.getName());
return result;
}
}
public class Org {
private List<String> corpEmpNmLst;
public String getcorpEmpNmLst() {
return corpEmpNmLst;
}
}
余談ですが、昔、ローマ字+母音略(GakuseiBango -> GKSIBNG)という謎規約があるプロジェクトで死にかけたことがあります。
パッケージ・クラス名
●基底クラス名
基本的にはクラス名に準じます。
ただし、派生クラスありきで安直に<SubClassName>Base
とすることは避けてほしいです。
Developer
のスーパークラスはDeveloperBase
ではなくEmployee
であるべきですし、そのスーパークラスはPerson
であるべきです。
変数・定数・メソッド名
◆変数名
真偽値の場合、is-
、has-
といったようにtrue/falseの意味が明確であるよう命名してください。真偽値で-Flag
は断固拒否します。
◆メソッド名
真偽値を返すメソッドについては、前述の変数名も参考に。
コーディングスタイル
★体裁
インデントや{}の書き方や1行に複数ステートメント書くなとか、そのあたりのことは、コードフォーマッタに委ねます。
コーディング中にイチイチそんなことに気を取られるのはリソースのムダです。
◆ネスト
ネストが深くなるということは、だいたい何か間違っています。
目安として3層を超える場合は考え直した方が良いと思います。
public List<String> fizzBuzzList(List<Integer> themes) {
List<String> result = new ArrayList<>();
for(Integer theme: themes) {
result.add(fizzBuzz(theme));
}
return result;
}
private String fizzBuzz(Integer theme) {
Map<Integer, String> rule = new LinkedHashMap<>();
rule.put(30, "FizzBuzzFizz");
rule.put(15, "FizzBuzz");
rule.put(5, "Buzz");
rule.put(3, "Fizz");
for(Integer ruleKey: rule.keySet()) {
if(0 == theme % ruleKey) return rule.get(ruleKey);
}
return theme.toString();
}
public List<String> fizzBuzzList(List<Integer> themes) {
List<String> result = new ArrayList<>();
for(Integer theme: themes) {
if(0 == theme % 3) {
if(0 == theme % 5) {
if(0 == theme % 2) {
result.add("FizzBuzzFizz");
} else {
result.add("FizzBuzz");
} else {
result.add("Fizz");
}
} else if(0 == theme % 5) {
result.add("Buzz");
} else {
result.add(theme.toString());
}
}
return result;
}
◆二重否定は理解できない
二重否定を理解できるほど人間は賢くありません。
否定は人間にとっては非常にコストの高い処理です。
public Boolean isActive(Boolean visible, Boolean enabled) {
return visible && enabled;
}
public Boolean isNotInactive(Boolean invisible, Boolean notDisabled) {
return !invisible && notDisabled;
}
###◆論理演算子乱舞はしない
1ステートメント当たり、論理演算子は可能であれば1個、多くても3個程度に収めましょう。
人間にはそれ以上の論理演算は無理です。
Boolean isTooBad = (!(arg1.equals('1') && arg2 == 0) || arg0 > 30) ^ arg3.canRead();
◆マジックナンバーは駆逐する
ここでいうマジックナンバーは広い意味で、文字列も含みます。
意味が分からない・改修できない・影響範囲が分からないの三重苦となるので見つけ次第駆逐してください。
もちろん、0 != list.size()
のように意味が明確である場合は除きます。
public Decimal getPrice(Item selectedItem, Campaign campaign) {
return selectedItem.price() * campaign.discountRate() * Constants.TAX_RATE;
}
public Decimal getPrice(Item selectedItem, Boolean campaign) {
if(compaign) {
return selectedItem.price() * 0.8 * 1.08;
} else {
return selectedItem.price() * 1.0 * 1.08;
}
}
コメント
◆Javadoc
クラスおよびメンバにはできるだけつけましょう。
その際、Javadocタグは適切に利用しましょう。
特に@param
, @return
, @throws
, @deprecated
あたりは必要に応じて必須。
逆に、@author
, @since
あたりは別に要らないです。どうせ適切にメンテされません。バージョン管理システムのなかった頃の遺物です。
◆不要なコメントは滅する
世の中には不要なコメントが多すぎます。
以下のコメントは発見次第滅してください。
- 改修前のソースを念のためコメントアウトして残しておいたもの
- 改修範囲(開始/終了位置)、改修日、改修者等を示すコメント
- ごく簡単な処理の内容説明
// 変数a に 10 を代入する
とか - 仕様書に紐づけた処理説明
// この処理は仕様書xxxのnnページの表yyを表現している
とか
特に「念のためコメントアウト」「改修位置コメント」が存在する場合、存在しない場合に比べ影響範囲調査工数・修正工数・バグ発生率が1.5~4.0倍増加する気がします。
●歓迎すべきコメント
「何を目的としてこの処理を書いたか(Why)」もしくは「ほかの方法でも同様の処理ができるが、なぜそうしなかったか(Why not)」を表現するコメントは歓迎すべきです。
このコメントからは期待結果を予想することができますし、もし実行結果が期待結果通りでない場合バグであることが検知できるためです。
void createCurry(RetortCurry retortCurry) {
// カレーを熱めに温める (解説:"熱め"にすることが重要であり、レンジのパワーやカレーの容量によっては秒数を変更する必要があることが想像できる)
Microwave.execute(retortCurry, 180);
// 見栄えを考慮し、皿に盛る (解説:皿に盛る順は"見栄え"のためにこの順にしていることがわかる。見栄えを無視すれば順番は崩してもよい)
return new Dish().add(rice).add(retortCurry).add(fukujinzuke);
}
void createCurry(RetortCurry retortCurry) {
// カレーをレンジに入れ、180秒セットし、実行する (解説:目的が分からないため、レンジのパワーやカレーの容量が変わっても秒数を変更する必要があるかどうかわからない)
Microwave.execute(retortCurry, 180);
// 皿に米、カレー、福神漬けを乗せる (解説:何を目的としているかわからないため、順番を変えることができない)
return new Dish().add(rice).add(retortCurry).add(fukujinzuke);
}