カテゴリー
投資

サイクロマティック複雑度

import re
import sys

def remove_comments_and_strings(code):
    """
    コメント(ブロックコメント、行コメント)や文字列リテラルを除去し、
    制御構造キーワードの誤検出を防ぎます。
    """
    # ブロックコメントの除去 (DOTALLオプションで改行も含む)
    code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)
    # 行コメントの除去
    code = re.sub(r'//.*', '', code)
    # 文字列リテラルの除去(単純に "" に置換)
    code = re.sub(r'"(?:\\.|[^"\\])*"', '""', code)
    return code

def extract_methods(code):
    """
    シンプルな正規表現と括弧のカウントにより、
    ソース内の各メソッドブロックを抽出します。
    """
    method_pattern = re.compile(
        r'(public|protected|private|static|\s)+[\w\<\>\[\]]+\s+\w+\s*\([^)]*\)\s*\{'
    )
    methods = []
    for match in method_pattern.finditer(code):
        start_index = match.end() - 1  # '{' の位置
        brace_count = 1
        end_index = start_index + 1
        while end_index < len(code) and brace_count > 0:
            if code[end_index] == '{':
                brace_count += 1
            elif code[end_index] == '}':
                brace_count -= 1
            end_index += 1
        method_body = code[match.start():end_index]
        methods.append(method_body)
    return methods

def count_decision_points(method_code):
    """
    以下のキーワードに着目して決定点をカウントします。
      - if
      - for
      - while
      - case
      - catch
      - 三項演算子 '?'
      - 論理演算子 '&&' および '||'
    """
    decisions = 0
    decisions += len(re.findall(r'\bif\s*\(', method_code))
    decisions += len(re.findall(r'\bfor\s*\(', method_code))
    decisions += len(re.findall(r'\bwhile\s*\(', method_code))
    decisions += len(re.findall(r'\bcase\b', method_code))
    decisions += len(re.findall(r'\bcatch\s*\(', method_code))
    decisions += len(re.findall(r'\?', method_code))
    decisions += len(re.findall(r'&&', method_code))
    decisions += len(re.findall(r'\|\|', method_code))
    return decisions

def extract_relevant_lines(method_code):
    """
    巨大なメソッド全体ではなく、
    テストケース作成のために必要な箇所(メソッドシグネチャおよび決定点を含む行)だけを抽出して返します。
    """
    lines = method_code.splitlines()
    relevant_lines = []
    decision_keywords = ['if(', 'if (', 'for(', 'for (', 'while(', 'while (',
                         'case ', 'catch(', 'catch (', '?', '&&', '||']
    signature_included = False
    for line in lines:
        stripped = line.strip()
        if not signature_included and stripped:
            relevant_lines.append(stripped)
            signature_included = True
            continue
        for kw in decision_keywords:
            if kw in stripped:
                relevant_lines.append(stripped)
                break
    return "\n".join(relevant_lines)

def calculate_cyclomatic_complexity(java_filename):
    """
    指定したJavaファイルからサイクロマティック複雑度を算出し、
    以下の項目を出力します。
      ・E: グラフのエッジ数
      ・N: グラフのノード数
      ・P: 連結成分数(ここではメソッド数)
      ・M: サイクロマティック複雑度
    また、算出対象となったロジックの必要な箇所(決定点を含む部分)と、
    各メソッドごとのE, N, P, 複雑度も出力します。
    """
    with open(java_filename, 'r', encoding='utf-8') as f:
        code = f.read()
    code = remove_comments_and_strings(code)
    
    methods = extract_methods(code)
    if not methods:
        methods = [code]
    
    global_decisions = 0
    print("【各メソッドごとの算出対象ロジックおよび統計】")
    for idx, method in enumerate(methods, start=1):
        d = count_decision_points(method)
        global_decisions += d
        
        # 各メソッドの統計計算
        method_P = 1             # 各メソッドは1つの連結成分
        method_M = d + 1         # 複雑度 = d + 1
        method_N = d + 2         # ノード数 = d + 2
        method_E = 2 * d + 1     # エッジ数 = 2*d + 1
        
        relevant = extract_relevant_lines(method)
        print(f"\n---- メソッド {idx} ----")
        print("【対象ロジック】")
        print(relevant)
        print("【統計】")
        print("  E (エッジ数):", method_E)
        print("  N (ノード数):", method_N)
        print("  P (連結成分数):", method_P)
        print("  サイクロマティック複雑度:", method_M)
    
    # グローバル統計(全メソッド合算)
    P = len(methods)
    global_M = global_decisions + P
    global_N = global_decisions + 2 * P
    global_E = 2 * global_decisions + P

    print("\n【全体のサイクロマティック複雑度の結果】")
    print("E (グラフのエッジ数):", global_E)
    print("N (グラフのノード数):", global_N)
    print("P (連結成分数/メソッド数):", P)
    print("サイクロマティック複雑度:", global_M)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python cyclomatic.py <JavaFile>")
        sys.exit(1)
    java_filename = sys.argv[1]
    calculate_cyclomatic_complexity(java_filename)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です