不適切なコードデザインに関するルールセットです。
staticメソッドしか持たないクラスで、Singleton化されていないものを検出します。
単純化できるboolean値のreturn文を検出します。
if (bar == x) {
return true;
} else {
return false;
}
// 上のコードは return bar = x; と単純化できる
単純化できるboolean式を検出します。
if (b == true) { // これは if (b) と単純化できる
...
}
default節の無いswitch文を検出します。
深過ぎるif文のネストを検出します。
検出するネスト数の最小値を指定します。
デフォルトは 3 です。
メソッドパラメータへの代入箇所を検出します。
public void foo(String bar) {
bar = "reassign"; // パラメータへの代入はNG
}
caseブロックに存在する長いステートメントを検出します。
switch (x) {
case 1:
// ここに大量のコードを記述するのは好ましくない
break;
}
検出するステートメントの最小数。
デフォルトは 10 です。
コンストラクタ内で、オーバーライドされたメソッド呼び出しを検出します。
例を挙げて説明します。
public class SeniorClass {
public SeniorClass(){
toString();
}
public String toString(){
return "IAmSeniorClass";
}
}
public class JuniorClass extends SeniorClass {
private String name;
public JuniorClass(){
super(); // NullPointerExceptionの発生
name = "JuniorClass";
}
public String toString(){
return name.toUpperCase();
}
}
このとき、
new SeniorClass();
の呼び出しは成功しますが
new JuniorClass();
の呼び出しをすると NullPointerException が発生します。
JuniorClass のコンストラクタ内で super() がコールされると
SeniorClass() に処理が移り toString() がコールされます。
このとき、呼び出されるメソッドは SeniorClass.toString() ではなくJuniorClass.toString() になります。
ここで参照している name フィールドはこの段階ではまだ初期化されていない為NullPointerException が発生してしまうという訳です。
こういったバグはなかなか発見が難しいので、
コンストラクタ内で呼び出すメソッドには充分注意する必要があります。
privateコンストラクタの呼び出し箇所を検出します。
具体的には、以下のような場面です。
public class Outer {
void method(){
Inner ic = new Inner(); // この箇所
}
public class Inner {
private Inner(){}
}
}
Inner クラスのコンストラクタは private ですが、Outer クラスのメソッド内から呼び出すことが出来ます。
このとき、理由はよくわかりませんがアクセッサクラスというものが生成されて
それを検出するのが目的のようです。
finalフィールドなのに static 宣言されていないフィールドを検出します。
閉じられていないDBリソース(接続やステートメント等)を検出します。
検出対象となるクラスを定義します。
デフォルトは Connection,Statement,ResultSet です。
staticでないイニシャライザを検出します。
public class Foo {
static {
// これは、staticイニシャライザ
// クラスがロードされるときに一度だけ実行される
}
{
// これが、staticでないイニシャライザ
// インスタンスが生成される度に実行される
}
}
これは正しい構文ですが、あまり使われない上に紛らわしいので
使わない方が良いでしょう。
インスタンス単位の初期化処理はコンストラクタ内に記述すれば良いのです。
default節がswicthブロックの最後以外に出現することを検出します。
default節はブロックの最後に記述した方が見やすいです。
switchブロック内に、caseではないラベル文が出現することを検出します。
switch (x) {
case 1:
break;
mylabel: // これは単なるラベルであり、紛らわしい
...
}
最適化されていない toArray メソッドの呼び出しを検出します。
x.toArray(new Foo[0]); // これでもいいが、後者の方が効率が良い x.toArray(new Foo[x.size()]);
Double.NaN との比較箇所を検出します。
if (x == Double.NaN) {
// この式は常にfalseを返す。正しくはDouble.isNaN(x)
}
equalsメソッドの引数にnullを指定している箇所を検出します。
if (x.equals(null)) {
// nullと同値になるxは存在しない。よってこのロジックは意味が無い
}
紛らわしい if-else 文を検出します。
if (x != y) {
diff();
} else {
same();
}
これは以下のように記述した方が読みやすいです。
if (x == y) {
same();
} else {
diff();
}
不必要な getClass() 呼び出しを検出します。
new Object().getClass(); // これは、次のようにシンプルに出来る Object.class;
意味の無いオペレーションを検出します。
x = x;
SimpleDateFormatのインスタンス生成時にロケールが指定されていない箇所を検出します。
new SimpleDateFormat("pattern"); // ロケールが指定されていない
new SimpleDateFormat("pattern", Locale.US);
変更されないフィールドを検出します。
通常これはfinalで宣言できます。
private int x; // このフィールドは final 宣言すべき
public Foo() {
x = 7;
}
public void foo() {
int a = x + 2;
}
toUpperCase / toLowerCase 呼び出しに
ロケールが指定されていない箇所を検出します。
finalクラスに定義された protected フィールドを検出します。
static フィールドへの代入を検出します。
private static int v;
public Bar(int v) {
this.v = v; // この代入は安全ではない
}
staticメソッドおよびpublicコンストラクタが存在しないクラスを検出します。
このクラスは外部から利用できません。
public class Foo {
private Foo() {}
void foo() {}
}
メソッドレベルでの synchronized 構文の使用を検出します。
このようにすると、そのメソッドに処理が追加された場合に
それら全ても同期化されてしまいます。
出来る限り同期化の範囲を限定することを望むならば、これはあまり良くない事です。
synchronized void foo() { // メソッドレベルでの同期はNG
}
void bar() { // 以下のように、ブロックレベルでの同期はOK
synchronized(this) {
}
}
caseブロックの中でbreak文が記述されていない箇所を検出します。
notify() メソッドが使用されている箇所を検出します。
代わりに notifyAll() メソッドを使用しましょう。
catchブロックの中でinstanceof演算子を使用している箇所を検出します。
try {
...
} catch (Exception e) {
if (e instanceof IOException) {
// このような振り分けは良くない
}
}
try {
...
} catch (IOException e) {
// このように振り分けた方が良い
}
abstractメソッドが存在しないabstractクラスを検出します。
このようなクラスはabstractにする必要がありません。
instanceof演算子と同時にnullチェックを行っている箇所を検出します。
if (x != null && x instanceof String) {
// instanceof演算子はxがnullでも正常に働くので、明示的なnullチェックは必要無い
}
オブジェクトを == 演算子で比較しています。
オブジェクトの比較には equals メソッドを使用します。
オブジェクトとリテラル(定数など)の比較時に、リテラルが左辺にない箇所を検出します。
if (str.equals("abc")) {
// strがnullの場合に例外が発生してしまう
}
if ("abc".equals(str)) {
// このようにすれば、strがnullでもOK
}
不必要なローカル変数宣言を検出します。
int x = getX(); return x; // これは return getX(); で良い
スレッドセーフになっていないSingletonクラスを検出します。
private static Foo foo = null;
public static Foo getFoo() {
if (foo == null) {
foo = new Foo();
}
return foo;
}
このようにすると、同時に複数の Foo.getFoo() 呼び出しがあった場合に
それらは別々のインスタンスを返す可能性があります。
つまり、Singleton ではなくなってしまいます。
これを解決するには、上記ロジックを同期化するか foo を予め初期化しておきます。
Singleton クラスに存在するstaticメソッドを検出します。
Singleton クラスに存在するstaticフィールドを検出します。
コメントが付いていない空メソッドを検出します。
コメントが付いていない空コンストラクタを検出します。
いわゆる「定数インターフェイス」を検出します。
public interface IConstants {
public static final int CONSTANT1 = 0;
// このようなインターフェイスは使用しない方が良い
}
static で定義された SimpleDateFormat を同期化して使用していない箇所を検出します。
このクラスはスレッドセーフではない為、本来はstaticで定義せずに
スレッド毎にインスタンスを作成するべきです。
複数のスレッドから同一のインスタンスを使用する場合は、
同期化する必要があります。
catchした例外のスタックトレースを捨てている箇所を検出します。
try {
...
} catch (IOException e) {
throw new MyException(e.getMessage());
}
このようにすると、例外が発生した箇所がわからなくなってしまい
デバッグ作業の効率が非常に悪くなります。
コレクションが空かどうかを判定するのに size() == 0 を使っている箇所を検出します。
Collectionには isEmpty() というメソッドがあるので、これを使いましょう。
if (list.size() == 0) ... // これはNG if (list.isEmpty()) ... // これがOK
privateのコンストラクタしか持たないクラスがfinalで定義されていない箇所を検出します。
通常こういったクラスは final で宣言します。
abstractクラス内にある空実装のメソッドを検出します。
通常こういったメソッドは abstract で宣言します。
一箇所からしか使われていないフィールドを検出します。
そういう場合はローカル変数として定義しましょう。
インナークラス内のフィールドをチェックします。
不明です。
戻り値が配列のメソッドでnullを返している箇所を検出します。
これはサイズ0の配列を返すようにします。
abstractメソッドの無い abstract クラスを検出します。
少なすぎるケース数のswitchを検出します。
検出しない最小ケース数を定義します。
デフォルト値は3です。この場合、2以下のケース数のswitchを検出します。
ケース数が2なら、ifで代用しましょう。