groxy / design_implementation_validator.py
Yasu777's picture
Create design_implementation_validator.py
256bf92 verified
Raw History Blame Contribute Delete
22.1 kB
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 リストを確認して修正してください。"