Javaのアンチパターン
- 標準出力(System.out.println)を使わない
- Debug出力は if 文で囲む
- 「値格納」メソッドを使用しない
- 例外から例外を投げない(二重例外)
- 共通定義を複数個所に持たせない
- 二重チェックイデオムは使用しない(遅延初期化)
- 変数の初期化は使用する直前に行う
- [Swing] 画面更新はEDT上で行う
標準出力(System.out.println)を使わない
-
標準出力が必要ではない場合、ログ出力API等に置き換えること。
消し忘れが多く、出力元の特定が困難。 -
アンチパターン
// 見るだけで殺意を覚えるコード System.out.println("DEBUG TEST!!");
リファクタリング// Log4jで置き換えた例 if (LOG.isDebugEnabled()) { LOG.debug("DEBUG TEST!!"); }
Debug出力は if 文で囲む
-
ログ出力レベルを「Info」とした際に、「Debug」箇所のコードが評価されてしまう。
性能に影響有り。 -
アンチパターン
// 文字列結合部分が評価されてしまう! LOG.debug( "SampleValue1=" + arg[0] + ", SampleValue2=" + arg[1] + ", SampleValue3=" + arg[2] );
リファクタリング// Log4j DEBUG出力が有効かどうか if (LOG.isDebugEnabled()) { // たとえ文字列結合でも丁寧に! LOG.debug( new StringBuilder() .append("SampleValue1=").append(arg[0]) .append(", SampleValue2=").append(arg[1]) .append(", SampleValue3=").append(arg[2]) .toString() ); }
「値格納」メソッドを使用しない
-
実際多いが、やりすぎると可読性が悪くなる。
特に設定される対象が、MapとかList、StringBuilderとかになると余計に可読性が悪くなる。 -
アンチパターン
// サンプルオブジェクト final ExampleObject sampleObj = new ExampleObject(); // 値を格納している(このメソッド名がややこしい場合は、特にダメ!) this.setValues(sampleObj); // 値の取得 System.out.println(sampleObj.getTitle());
リファクタリング// サンプルオブジェクト final ExampleObject sampleObj = this.createExample(); // 値の取得 System.out.println(sampleObj.getTitle());
// サンプルオブジェクト final ExampleObject sampleObj = new ExampleObject(); // 値格納メソッドを使用しない sampleObj.setTitle(args); sampleObj.setText(args); // 値の取得 System.out.println(sampleObj.getTitle());
例外から例外を投げない(二重例外)
-
例外から例外はスローしないようにする。
例外処理に漏れが発生する。 -
アンチパターン
// 実装元サンプル public class SampleExcetpion extends IOException { public SampleExcetpion(String[] args) { if (args == null) { // 例外の中で例外を投げている throw new NullPointerException(); } } } // 呼び元 public static void main(final String[] args) { ・・・処理 // try-catchが漏れていた場合はバグとなる throw new SampleExcetpion(args); }
リファクタリング// 実装元サンプル public class SampleExcetpion extends IOException { public SampleExcetpion(String[] args) { // 例外クラス内では例外を発生させない } } // 呼び元 public static void main(final String[] args) { if (args == null) { // 例外処理をする } ・・・処理 // Exception生成処理はほとんどが正常終了するべき throw new SampleExcetpion(args); }
共通定義を複数個所に持たせない
-
共通のシステム定義情報を複数個所に持たせない。
共通定義は、必ず1か所に集約して呼び出させる(メモリの節約にもなる)。
例えば、文字コードやロケール情報といったプロパティ情報。 -
アンチパターン
// 例として一番手っ取り早いString // 「UTF-8」→「SJIS」に変わった時、死にたくなる final String str1 = new String(strBytes, "UTF-8"); // 他の機能とか final String str2 = new String(strBytes, "UTF-8"); // 他の機能とか final String str3 = new String(strBytes, "UTF-8");
リファクタリング// 実装サンプル private static final String DEF_ENCODE = System.getProperty("file.encoding"); public static String getDefaultEncode() { // ・メモリキャッシュした内容を返却するとか // ・定義ファイルから取得するとか return MySystemConst.DEF_ENCODE; } // 呼び元 final String str1 = new String(strBytes, MySystemConst.getDefaultEncode()); // 他の機能とか final String str2 = new String(strBytes, MySystemConst.getDefaultEncode()); // 他の機能とか final String str3 = new String(strBytes, MySystemConst.getDefaultEncode());
二重チェックイデオムは使用しない(遅延初期化)
-
二重チェックイデオムは使用しない。
コンストラクタ生成タイミングによってバグとなる(JVM依存)。
回避策として、シングルトン パターンまたはstaticイニシャライザを検討する。
JVMによっては、volatile修飾子でも良い。 -
アンチパターン
// JVMによっては、「volatile」修飾子を付ければ動作は保障される private static ExampleProperties prop = null; // propの読み込み public static String getExampleProperties(final String str) { // 1回目のチェック if (prop == null) { // 排他 synchronized (MySample.class) { // 2回目のチェック if (prop == null) { // prop初期化 prop = initProperties(); } } } // ほぼ正常に動作するが、 // 初期化のタイミングでnullが返却される可能性がある(JVM依存) return prop; }
リファクタリング// シングルトン パターンの例 private static final MySample INSTANCE = new MySample(); private final ExampleProperties prop; // シングルトンのコンストラクタで初期化させる private MySample() { // 初期化 prop = initProperties(); } // propの読み込み public static ExampleProperties getExampleProperties() { // 返却 return INSTANCE.prop; }
// static イニシャライザの例 private static ExampleProperties prop = null; static { // 初期化 prop = initProperties(); } // propの読み込み public static ExampleProperties getExampleProperties() { // 返却 return prop; }
// 「volatile」修飾子で動作保障がされる private static volatile ExampleProperties prop = null; // propの読み込み public static ExampleProperties getExampleProperties() { // 1回目のチェック if (prop == null) { // 排他 synchronized (MySample.class) { // 2回目のチェック if (prop == null) { // prop初期化 prop = initProperties(); } } } // 「volatile」とすることで、一貫性が保たれている。 return prop; }
変数の初期化は使用する直前に行う
-
変数の初期化位置によって対象スコープが決まるため、適切な位置での初期化をする。
ソースの可読性向上とバグ防止。 -
アンチパターン
// 初期化位置が悪い(スコープが変) final String fileName = "SampleFileName"; if (isCheck()) { // 変数 fileName を参照していない・・ } ・・・処理 // この内部でやっと参照する if (isCheckXXX()) { // 参照! final File file = new File(fileName); }
リファクタリングif (isCheckXXX()) { // 正しいスコープ範囲にすることで // 可読性やメモリ効率も上がる final String fileName = "SampleFileName"; // 参照! final File file = new File(fileName); }
[Swing] 画面更新はEDT上で行う
-
SwingやAwtの更新が発生する場合は、EDTと呼ばれるイベントディスパッチスレッド上で画面描画を行う。
画面のフリーズやハングアップを引き起こす場合がある。 -
アンチパターン
// 画面の表示(これがEDT上で実行されるなら問題ないが・・) frame.setVisible(true);
リファクタリング// SwingUtilitiesを使用して明示的にEDT上で実行させる SwingUtilities.invokeLater(new Runnable() { @Override public void run() { frame.setVisible(true); } }); // 特に時間がかかる場合は、SwingWorkerの実装を検討する new SwingWorker<Object, Object>(){ @Override protected Object doInBackground() throws Exception { // 時間がかかる処理 // この処理はEDT上では実行されないことに注意 return null; } @Override protected void done() { // EDT上で実行したい処理 // doInBackgroundで処理した結果を画面に反映する等 frame.setVisible(true); } }.execute();