Tbpgr Blog

Employee Experience Engineer tbpgr(てぃーびー) のブログ

Ruby | Javaの例外処理チェック用スクリプト〜プロジェクト固有のルールで定義された例外処理の整合性をチェック

概要

Javaの例外処理チェック用スクリプトについて

詳細

前提として該当システムではcatchブロックで補足した例外を
MyExceptionという独自定義の例外に詰めなおしてthrowする規約になっているとする。

サンプルコード

サンプルコード1(sample1.java〜sample3.java)
catchブロック2件正常、2件異常のサンプル

package catchsample;

public class SampleCatch {
  public static void main(String[] args) throws MyException {
    try {
      System.out.println("hoge");
    } catch (IllegalArgumentException e) {
      e.printStackTrace();
    } catch (NullPointerException e) {
      if (e.getMessage() == null) {
        System.out.println("null");
      }
    } catch (IllegalAccessError e) {
      throw new MyException();
    } catch (Exception e) {
      if (e.getMessage() == null) {
        System.out.println("null");
      }
      throw new MyException();
    }
  }
}

サンプルコード2(sample4.java)
catchブロックなしのサンプル

package catchsample;

public class SampleCatch {
  public static void main(String[] args) throws MyException {
    System.out.println("hoge");
  }
}

サンプルディレクトリ構成

C:.
│  sample1.java
│  sample2.java
│  sample3.java
│  sample4.java
│
├─dir1
│  │  sample1.java
│  │  sample2.java
│  │  sample3.java
│  │  sample4.java
│  │
│  └─dir1_1
│          sample1.java
│          sample2.java
│          sample3.java
│          sample4.java
│
└─dir2
    │  sample1.java
    │  sample2.java
    │  sample3.java
    │  sample4.java
    │
    └─dir2_1
            sample1.java
            sample2.java
            sample3.java
            sample4.java

チェック処理本体

exception_checker.rb

# encoding: UTF-8

require 'Set'
require "find"

#
# Javaのcatch句でMyExceptionを投げていない個所を検出する
# 
# author:tbpg
# create:2013/04/14
# 
class ExceptionChecker
  # 調査対象から除外する名称リスト
  # TODO 設定ファイルに逃がすように修正したほうが良い
  EXCLUDES = ["dir2_1","sample3.java"]
  # 出力CSVのヘッダー
  HEADER = "java_file_path,line_no,source"
  # Javaのクラス名
  JAVA_FILE_NAME = /.*\.java/
  # 行区切り文字
  LINE_SEPARATOR = "\n"
  # catch句に必要な処理
  CATCH_REQUIRED_CODE = /\sthrow\snew\sMyException\(\)/
  # 中括弧開き
  OPEN_BRACE = "{"
  # 中括弧閉じ
  CLOSE_BRACE = "}"

  def output_illegal_catch_blocks(target_path)
    output_header
    Find.find(target_path).each do |file|
      next unless FileTest.file? file
      next unless java_sorce_file? file
      next if exclude? file

      src = File.open(file).read
      next unless has_catch? src

      each_lines = src.split(LINE_SEPARATOR)

      illegal_catch_blocks = get_illegal_catch_blocks(each_lines)
      output_illegal_catch(file, illegal_catch_blocks)
    end
  end

  private
  def output_header
    puts HEADER
  end

  def java_sorce_file?(file)
    file =~ JAVA_FILE_NAME
  end

  def exclude?(file_name)
    ExceptionChecker::EXCLUDES.each {|v|return true if file_name.include? v}
    return true if file_name.include? "Test.java"
    return false
  end

  def has_catch?(src)
    src =~ /\}\s*catch\s*\(/
  end

  def get_illegal_catch_blocks(each_lines)
    illegal_catch_lines = []
    each_lines.each_with_index do |line,i|
      next unless line.include? "catch"
      
      catch_line = i + 1
      search_lines = each_lines[catch_line..each_lines.size]
      illegal_catch_lines << ",#{catch_line.to_s},#{line}" unless valid_catch?(search_lines)
    end
    illegal_catch_lines
  end

  def valid_catch?(search_lines)
    start_brace_count = 0
    search_lines.each do |line|
      return true if has_myexcepton?(line)

      if has_close_brace?(line)
        return false if close_catch_block?(start_brace_count)
        start_brace_count -= 1
        next
      end

      if has_open_brace?(line)
        start_brace_count += 1
        next
      end
    end
    false
  end

  def has_myexcepton?(line)
    line =~ CATCH_REQUIRED_CODE
  end

  def has_close_brace?(line)
    line.include? CLOSE_BRACE
  end

  def has_open_brace?(line)
    line.include? OPEN_BRACE
  end

  def close_catch_block?(start_brace_count)
    start_brace_count == 0
  end

  def output_illegal_catch(file, illegal_catch_blocks)
    illegal_catch_blocks.each {|line|puts file + line} if illegal_catch_blocks.size > 0
  end
end

チェック処理呼び出しコード

exception_checker_executor.rb

# encoding: UTF-8

require "./exception_checker"

if ARGV.size != 1
  STDERR.puts "パラメータに調査対象のフルパスを指定してください"
  exit
end
ExceptionChecker.new.output_illegal_catch_blocks ARGV[0]

チェック実行用コマンド

$ruby exception_checker_executor.rb "C:/XXXX/test_project/" > output.txt

結果

CSV形式の出力結果

java_file_path,line_no,source
C:/XXXX/test_project/dir1/dir1_1/sample1.java,7,    } catch (IllegalArgumentException e) {
C:/XXXX/test_project/dir1/dir1_1/sample1.java,9,    } catch (NullPointerException e) {
C:/XXXX/test_project/dir1/dir1_1/sample2.java,7,    } catch (IllegalArgumentException e) {
C:/XXXX/test_project/dir1/dir1_1/sample2.java,9,    } catch (NullPointerException e) {
C:/XXXX/test_project/dir1/sample1.java,7,    } catch (IllegalArgumentException e) {
C:/XXXX/test_project/dir1/sample1.java,9,    } catch (NullPointerException e) {
C:/XXXX/test_project/dir1/sample2.java,7,    } catch (IllegalArgumentException e) {
C:/XXXX/test_project/dir1/sample2.java,9,    } catch (NullPointerException e) {
C:/XXXX/test_project/dir2/sample1.java,7,    } catch (IllegalArgumentException e) {
C:/XXXX/test_project/dir2/sample1.java,9,    } catch (NullPointerException e) {
C:/XXXX/test_project/dir2/sample2.java,7,    } catch (IllegalArgumentException e) {
C:/XXXX/test_project/dir2/sample2.java,9,    } catch (NullPointerException e) {
C:/XXXX/test_project/sample1.java,7,    } catch (IllegalArgumentException e) {
C:/XXXX/test_project/sample1.java,9,    } catch (NullPointerException e) {
C:/XXXX/test_project/sample2.java,7,    } catch (IllegalArgumentException e) {
C:/XXXX/test_project/sample2.java,9,    } catch (NullPointerException e) {