import re import ast import difflib from typing import Dict, Any, List, Optional, Set, Tuple class DesignImplementationValidator: """設計書と実装コードの整合性を検証するクラス""" def __init__(self): """設計・実装検証クラスの初期化""" pass def validate(self, design_doc: str, implementation_code: str) -> Dict[str, Any]: """設計書と実装コードの整合性を検証する""" result = { "is_compliant": True, "issues": [], "missing_components": [], "missing_methods": [], "missing_requirements": [], "extra_components": [], "compliance_score": 0.0, "suggestion": None } # 設計書から要素を抽出 design_components = self._extract_design_components(design_doc) # 実装コードから要素を抽出 implementation_components = self._extract_implementation_components(implementation_code) # 設計書と実装の整合性を検証 self._validate_consistency(design_components, implementation_components, result) # 検証結果に基づいてコンプライアンススコアを計算 self._calculate_compliance_score(result) # 改善のための提案を生成 self._generate_suggestions(result) return result def _extract_design_components(self, design_doc: str) -> Dict[str, Any]: """設計書からコンポーネント、クラス、メソッド、要件などを抽出する""" components = { "classes": [], "interfaces": [], "methods": {}, "attributes": {}, "requirements": [], "dependencies": [], "data_structures": [] } # 1. クラス定義の抽出 class_pattern = r'(class\s+(\w+)(?:\s*\(\s*\w+\s*\))?\s*:)' for match in re.finditer(class_pattern, design_doc): class_name = match.group(2) components["classes"].append(class_name) components["methods"][class_name] = [] components["attributes"][class_name] = [] # クラス定義の範囲を特定 class_start = match.end() next_class_match = re.search(class_pattern, design_doc[class_start:]) class_end = class_start + next_class_match.start() if next_class_match else len(design_doc) class_body = design_doc[class_start:class_end] # メソッド定義の抽出 method_pattern = r'def\s+(\w+)\s*\((.*?)\)' for method_match in re.finditer(method_pattern, class_body): method_name = method_match.group(1) components["methods"][class_name].append(method_name) # 属性の抽出(単純なパターンマッチング) attribute_pattern = r'self\.(\w+)\s*=' for attr_match in re.finditer(attribute_pattern, class_body): attr_name = attr_match.group(1) if attr_name not in components["attributes"][class_name]: components["attributes"][class_name].append(attr_name) # 2. インターフェース定義の抽出(ABC, Protocolなど) interface_pattern = r'(class\s+(\w+)(?:\s*\(\s*(?:ABC|Protocol)\s*\))?\s*:)' for match in re.finditer(interface_pattern, design_doc): interface_name = match.group(2) if interface_name not in components["classes"]: # クラスとして既に登録されていない場合 components["interfaces"].append(interface_name) components["methods"][interface_name] = [] # インターフェース定義の範囲を特定 interface_start = match.end() next_interface_match = re.search(interface_pattern, design_doc[interface_start:]) interface_end = interface_start + next_interface_match.start() if next_interface_match else len(design_doc) interface_body = design_doc[interface_start:interface_end] # メソッド定義の抽出 method_pattern = r'def\s+(\w+)\s*\((.*?)\)' for method_match in re.finditer(method_pattern, interface_body): method_name = method_match.group(1) components["methods"][interface_name].append(method_name) # 3. 要件の抽出 requirement_sections = [ "要件", "機能要件", "非機能要件", "制約", "前提条件", "Requirements", "Functional Requirements", "Non-functional Requirements" ] for section in requirement_sections: section_match = re.search(rf'{section}[::]\s*(.*?)(?=\n\n|\n#|\Z)', design_doc, re.DOTALL) if section_match: section_text = section_match.group(1).strip() # 箇条書きの抽出 bullet_patterns = [ r'[-*•]\s*(.*?)(?=\n[-*•]|\n\n|\Z)', # ハイフン、アスタリスク、中黒 r'\d+\.\s*(.*?)(?=\n\d+\.|\n\n|\Z)' # 番号付きリスト ] for pattern in bullet_patterns: for req_match in re.finditer(pattern, section_text, re.DOTALL): requirement = req_match.group(1).strip() if requirement and requirement not in components["requirements"]: components["requirements"].append(requirement) # 4. データ構造の抽出 data_structure_patterns = [ r'(class|struct)\s+(\w+)', # クラスまたは構造体 r'(enum)\s+(\w+)', # 列挙型 r'(type|typedef)\s+(\w+)' # 型定義 ] for pattern in data_structure_patterns: for match in re.finditer(pattern, design_doc): structure_type = match.group(1) structure_name = match.group(2) if structure_name not in components["classes"] and structure_name not in components["interfaces"]: components["data_structures"].append(structure_name) # 5. 依存関係の抽出 dependency_patterns = [ r'依存[::]\s*(.*?)(?=\n\n|\Z)', r'Dependencies[::]\s*(.*?)(?=\n\n|\Z)', r'利用[::]\s*(.*?)(?=\n\n|\Z)', r'Uses[::]\s*(.*?)(?=\n\n|\Z)', r'import\s+(\w+)', r'from\s+(\w+)\s+import' ] for pattern in dependency_patterns: for match in re.finditer(pattern, design_doc): dependency = match.group(1).strip() if dependency and dependency not in components["dependencies"]: components["dependencies"].append(dependency) return components def _extract_implementation_components(self, implementation_code: str) -> Dict[str, Any]: """実装コードからコンポーネント、クラス、メソッド、属性などを抽出する""" components = { "classes": [], "interfaces": [], "methods": {}, "attributes": {}, "imports": [], "functions": [] } try: # ASTを使ってPythonコードを解析 tree = ast.parse(implementation_code) # インポート文の抽出 for node in ast.walk(tree): if isinstance(node, ast.Import): for name in node.names: components["imports"].append(name.name) elif isinstance(node, ast.ImportFrom): if node.module: components["imports"].append(node.module) # クラスの抽出 for node in ast.walk(tree): if isinstance(node, ast.ClassDef): class_name = node.name # インターフェース(抽象クラス)かどうかを判定 is_interface = False for base in node.bases: if isinstance(base, ast.Name) and base.id in ["ABC", "Protocol"]: is_interface = True break if is_interface: components["interfaces"].append(class_name) else: components["classes"].append(class_name) components["methods"][class_name] = [] components["attributes"][class_name] = [] # メソッドと属性の抽出 for item in node.body: if isinstance(item, ast.FunctionDef): components["methods"][class_name].append(item.name) elif isinstance(item, ast.Assign): for target in item.targets: if isinstance(target, ast.Name): components["attributes"][class_name].append(target.id) # トップレベル関数の抽出 for node in ast.walk(tree): if isinstance(node, ast.FunctionDef) and node.name not in [m for methods in components["methods"].values() for m in methods]: if not any(isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(tree)): components["functions"].append(node.name) except SyntaxError as e: print(f"[Warning] Syntax error during code analysis: {e}") # 構文エラーの場合は正規表現ベースのフォールバック解析を使用 self._fallback_extract_implementation_components(implementation_code, components) return components def _fallback_extract_implementation_components(self, code: str, components: Dict[str, Any]) -> None: """構文エラーがある場合のフォールバック抽出メソッド(正規表現ベース)""" # クラス定義の抽出 class_pattern = r'class\s+(\w+)(?:\s*\(.*?\))?\s*:' for match in re.finditer(class_pattern, code): class_name = match.group(1) if class_name not in components["classes"]: components["classes"].append(class_name) components["methods"][class_name] = [] components["attributes"][class_name] = [] # クラス本体の抽出 class_start = match.end() # 簡易的なブロック終了の検出(インデントベース) class_body = "" in_class = True indent_level = None for line in code[class_start:].split('\n'): if not line.strip(): class_body += line + '\n' continue current_indent = len(line) - len(line.lstrip()) if indent_level is None: if current_indent > 0: # クラス本体の最初の行 indent_level = current_indent class_body += line + '\n' continue if current_indent >= indent_level: class_body += line + '\n' else: break # クラスブロックの終了 # メソッド定義の抽出 method_pattern = r'def\s+(\w+)\s*\(' for method_match in re.finditer(method_pattern, class_body): method_name = method_match.group(1) if method_name not in components["methods"][class_name]: components["methods"][class_name].append(method_name) # 属性の抽出 attribute_pattern = r'self\.(\w+)\s*=' for attr_match in re.finditer(attribute_pattern, class_body): attr_name = attr_match.group(1) if attr_name not in components["attributes"][class_name]: components["attributes"][class_name].append(attr_name) # インポート文の抽出 import_pattern = r'import\s+(\w+)' for match in re.finditer(import_pattern, code): import_name = match.group(1) if import_name not in components["imports"]: components["imports"].append(import_name) from_import_pattern = r'from\s+(\w+)\s+import' for match in re.finditer(from_import_pattern, code): import_name = match.group(1) if import_name not in components["imports"]: components["imports"].append(import_name) # トップレベル関数の抽出 function_pattern = r'^def\s+(\w+)\s*\(' for match in re.finditer(function_pattern, code, re.MULTILINE): func_name = match.group(1) if func_name not in components["functions"]: components["functions"].append(func_name) def _validate_consistency(self, design: Dict[str, Any], implementation: Dict[str, Any], result: Dict[str, Any]) -> None: """設計と実装の整合性を検証する""" # 1. クラスの整合性チェック for class_name in design["classes"]: if class_name not in implementation["classes"] and class_name not in implementation["interfaces"]: result["is_compliant"] = False result["missing_components"].append(f"Class: {class_name}") result["issues"].append(f"設計書で定義されているクラス '{class_name}' が実装に存在しません。") # 2. インターフェースの整合性チェック for interface_name in design["interfaces"]: if interface_name not in implementation["interfaces"] and interface_name not in implementation["classes"]: result["is_compliant"] = False result["missing_components"].append(f"Interface: {interface_name}") result["issues"].append(f"設計書で定義されているインターフェース '{interface_name}' が実装に存在しません。") # 3. メソッドの整合性チェック for class_name, methods in design["methods"].items(): if class_name in implementation["methods"]: for method_name in methods: if method_name not in implementation["methods"][class_name]: result["is_compliant"] = False result["missing_methods"].append(f"{class_name}.{method_name}") result["issues"].append(f"設計書で定義されているメソッド '{class_name}.{method_name}' が実装に存在しません。") # 4. 属性の整合性チェック(厳密でない - 属性は実装時に追加されることもある) for class_name, attrs in design["attributes"].items(): if class_name in implementation["attributes"]: for attr_name in attrs: if attr_name not in implementation["attributes"][class_name]: # 属性の不一致は警告レベル result["issues"].append(f"設計書で定義されている属性 '{class_name}.{attr_name}' が実装に存在しません(警告)。") # 5. 要件の実装検証(キーワードベースの簡易検証) code_text = implementation_code if hasattr(self, 'implementation_code') else "" for req in design["requirements"]: # 要件からキーワードを抽出 keywords = self._extract_keywords(req) # キーワードが実装に含まれているかチェック if keywords and not all(keyword.lower() in code_text.lower() for keyword in keywords if len(keyword) > 3): result["is_compliant"] = False result["missing_requirements"].append(req) result["issues"].append(f"要件 '{req}' が実装に反映されていない可能性があります。") # 6. 実装に存在する余分なコンポーネントを確認 for class_name in implementation["classes"]: if class_name not in design["classes"] and class_name not in design["interfaces"]: result["extra_components"].append(f"Class: {class_name}") # 余分なコンポーネントは警告レベル result["issues"].append(f"実装に存在するクラス '{class_name}' が設計書に定義されていません(警告)。") def _extract_keywords(self, text: str) -> List[str]: """テキストから重要なキーワードを抽出する""" # ストップワードを定義 stop_words = { "a", "an", "the", "this", "that", "these", "those", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "shall", "should", "can", "could", "may", "might", "must", "and", "or", "but", "if", "then", "else", "when", "where", "why", "how", "all", "any", "both", "each", "few", "more", "most", "other", "some", "such", "no", "nor", "not", "only", "own", "same", "so", "than", "too", "very", "を", "に", "は", "が", "で", "と", "から", "まで", "より", "して", "する", "ます", "です" } # 単語分割(英語と日本語の両方に対応) words = [] # 英単語の抽出 english_words = re.findall(r'\b[a-zA-Z]+\b', text.lower()) words.extend(english_words) # 日本語単語の簡易抽出(文字単位の分割) japanese_text = re.sub(r'[a-zA-Z0-9\s]', '', text) for char in japanese_text: if char not in "、。!?「」『』()[]【】…:;": words.append(char) # ストップワードと短すぎる単語を除外 keywords = [w for w in words if w not in stop_words and len(w) > 1] # 重複を削除 unique_keywords = list(set(keywords)) return unique_keywords def _calculate_compliance_score(self, result: Dict[str, Any]) -> None: """コンプライアンススコアを計算する(0.0〜10.0の範囲)""" # 初期スコア score = 10.0 # 不足しているコンポーネントごとに減点 score -= len(result["missing_components"]) * 2.0 # 不足しているメソッドごとに減点 score -= len(result["missing_methods"]) * 1.0 # 不足している要件ごとに減点 score -= len(result["missing_requirements"]) * 1.5 # 余分なコンポーネントごとに軽く減点 score -= len(result["extra_components"]) * 0.5 # スコアの範囲を調整 result["compliance_score"] = max(0.0, min(10.0, score)) # コンプライアンス判定(スコアが7.0以上で合格) result["is_compliant"] = result["compliance_score"] >= 7.0 def _generate_suggestions(self, result: Dict[str, Any]) -> None: """検証結果に基づいて改善提案を生成する""" if result["is_compliant"]: if result["compliance_score"] == 10.0: result["suggestion"] = "設計書と実装は完全に一致しています。素晴らしい実装です。" else: result["suggestion"] = "設計書と実装は概ね一致していますが、いくつかの軽微な問題があります。詳細は issues リストを確認してください。" else: # 不足コンポーネントの提案 if result["missing_components"]: result["suggestion"] = f"設計書で定義されている以下のコンポーネントを実装してください: {', '.join(result['missing_components'][:3])}" if len(result["missing_components"]) > 3: result["suggestion"] += f" ... 他 {len(result['missing_components']) - 3} 件" # 不足メソッドの提案 elif result["missing_methods"]: result["suggestion"] = f"設計書で定義されている以下のメソッドを実装してください: {', '.join(result['missing_methods'][:3])}" if len(result["missing_methods"]) > 3: result["suggestion"] += f" ... 他 {len(result['missing_methods']) - 3} 件" # 不足要件の提案 elif result["missing_requirements"]: result["suggestion"] = "以下の要件が実装に反映されていない可能性があります。要件を満たす機能を実装してください。" for i, req in enumerate(result["missing_requirements"][:3]): result["suggestion"] += f"\n{i+1}. {req}" if len(result["missing_requirements"]) > 3: result["suggestion"] += f"\n... 他 {len(result['missing_requirements']) - 3} 件" # 一般的な提案 else: result["suggestion"] = "設計書と実装の間にいくつかの不一致があります。issues リストを確認して修正してください。"