Yasu777 commited on
Commit
256bf92
·
verified ·
1 Parent(s): 5efcd4a

Create design_implementation_validator.py

Browse files
Files changed (1) hide show
  1. design_implementation_validator.py +431 -0
design_implementation_validator.py ADDED
@@ -0,0 +1,431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import ast
3
+ import difflib
4
+ from typing import Dict, Any, List, Optional, Set, Tuple
5
+
6
+ class DesignImplementationValidator:
7
+ """設計書と実装コードの整合性を検証するクラス"""
8
+
9
+ def __init__(self):
10
+ """設計・実装検証クラスの初期化"""
11
+ pass
12
+
13
+ def validate(self, design_doc: str, implementation_code: str) -> Dict[str, Any]:
14
+ """設計書と実装コードの整合性を検証する"""
15
+ result = {
16
+ "is_compliant": True,
17
+ "issues": [],
18
+ "missing_components": [],
19
+ "missing_methods": [],
20
+ "missing_requirements": [],
21
+ "extra_components": [],
22
+ "compliance_score": 0.0,
23
+ "suggestion": None
24
+ }
25
+
26
+ # 設計書から要素を抽出
27
+ design_components = self._extract_design_components(design_doc)
28
+
29
+ # 実装コードから要素を抽出
30
+ implementation_components = self._extract_implementation_components(implementation_code)
31
+
32
+ # 設計書と実装の整合性を検証
33
+ self._validate_consistency(design_components, implementation_components, result)
34
+
35
+ # 検証結果に基づいてコンプライアンススコアを計算
36
+ self._calculate_compliance_score(result)
37
+
38
+ # 改善のための提案を生成
39
+ self._generate_suggestions(result)
40
+
41
+ return result
42
+
43
+ def _extract_design_components(self, design_doc: str) -> Dict[str, Any]:
44
+ """設計書からコンポーネント、クラス、メソッド、要件などを抽出する"""
45
+ components = {
46
+ "classes": [],
47
+ "interfaces": [],
48
+ "methods": {},
49
+ "attributes": {},
50
+ "requirements": [],
51
+ "dependencies": [],
52
+ "data_structures": []
53
+ }
54
+
55
+ # 1. クラス定義の抽出
56
+ class_pattern = r'(class\s+(\w+)(?:\s*\(\s*\w+\s*\))?\s*:)'
57
+ for match in re.finditer(class_pattern, design_doc):
58
+ class_name = match.group(2)
59
+ components["classes"].append(class_name)
60
+ components["methods"][class_name] = []
61
+ components["attributes"][class_name] = []
62
+
63
+ # クラス定義の範囲を特定
64
+ class_start = match.end()
65
+ next_class_match = re.search(class_pattern, design_doc[class_start:])
66
+ class_end = class_start + next_class_match.start() if next_class_match else len(design_doc)
67
+ class_body = design_doc[class_start:class_end]
68
+
69
+ # メソッド定義の抽出
70
+ method_pattern = r'def\s+(\w+)\s*\((.*?)\)'
71
+ for method_match in re.finditer(method_pattern, class_body):
72
+ method_name = method_match.group(1)
73
+ components["methods"][class_name].append(method_name)
74
+
75
+ # 属性の抽出(単純なパターンマッチング)
76
+ attribute_pattern = r'self\.(\w+)\s*='
77
+ for attr_match in re.finditer(attribute_pattern, class_body):
78
+ attr_name = attr_match.group(1)
79
+ if attr_name not in components["attributes"][class_name]:
80
+ components["attributes"][class_name].append(attr_name)
81
+
82
+ # 2. インターフェース定義の抽出(ABC, Protocolなど)
83
+ interface_pattern = r'(class\s+(\w+)(?:\s*\(\s*(?:ABC|Protocol)\s*\))?\s*:)'
84
+ for match in re.finditer(interface_pattern, design_doc):
85
+ interface_name = match.group(2)
86
+ if interface_name not in components["classes"]: # クラスとして既に登録されていない場合
87
+ components["interfaces"].append(interface_name)
88
+ components["methods"][interface_name] = []
89
+
90
+ # インターフェース定義の範囲を特定
91
+ interface_start = match.end()
92
+ next_interface_match = re.search(interface_pattern, design_doc[interface_start:])
93
+ interface_end = interface_start + next_interface_match.start() if next_interface_match else len(design_doc)
94
+ interface_body = design_doc[interface_start:interface_end]
95
+
96
+ # メソッド定義の抽出
97
+ method_pattern = r'def\s+(\w+)\s*\((.*?)\)'
98
+ for method_match in re.finditer(method_pattern, interface_body):
99
+ method_name = method_match.group(1)
100
+ components["methods"][interface_name].append(method_name)
101
+
102
+ # 3. 要件の抽出
103
+ requirement_sections = [
104
+ "要件", "機能要件", "非機能要件", "制約", "前提条件",
105
+ "Requirements", "Functional Requirements", "Non-functional Requirements"
106
+ ]
107
+
108
+ for section in requirement_sections:
109
+ section_match = re.search(rf'{section}[::]\s*(.*?)(?=\n\n|\n#|\Z)', design_doc, re.DOTALL)
110
+ if section_match:
111
+ section_text = section_match.group(1).strip()
112
+
113
+ # 箇条書きの抽出
114
+ bullet_patterns = [
115
+ r'[-*•]\s*(.*?)(?=\n[-*•]|\n\n|\Z)', # ハイフン、アスタリスク、中黒
116
+ r'\d+\.\s*(.*?)(?=\n\d+\.|\n\n|\Z)' # 番号付きリスト
117
+ ]
118
+
119
+ for pattern in bullet_patterns:
120
+ for req_match in re.finditer(pattern, section_text, re.DOTALL):
121
+ requirement = req_match.group(1).strip()
122
+ if requirement and requirement not in components["requirements"]:
123
+ components["requirements"].append(requirement)
124
+
125
+ # 4. データ構造の抽出
126
+ data_structure_patterns = [
127
+ r'(class|struct)\s+(\w+)', # クラスまたは構造体
128
+ r'(enum)\s+(\w+)', # 列挙型
129
+ r'(type|typedef)\s+(\w+)' # 型定義
130
+ ]
131
+
132
+ for pattern in data_structure_patterns:
133
+ for match in re.finditer(pattern, design_doc):
134
+ structure_type = match.group(1)
135
+ structure_name = match.group(2)
136
+ if structure_name not in components["classes"] and structure_name not in components["interfaces"]:
137
+ components["data_structures"].append(structure_name)
138
+
139
+ # 5. 依存関係の抽出
140
+ dependency_patterns = [
141
+ r'依存[::]\s*(.*?)(?=\n\n|\Z)',
142
+ r'Dependencies[::]\s*(.*?)(?=\n\n|\Z)',
143
+ r'利用[::]\s*(.*?)(?=\n\n|\Z)',
144
+ r'Uses[::]\s*(.*?)(?=\n\n|\Z)',
145
+ r'import\s+(\w+)',
146
+ r'from\s+(\w+)\s+import'
147
+ ]
148
+
149
+ for pattern in dependency_patterns:
150
+ for match in re.finditer(pattern, design_doc):
151
+ dependency = match.group(1).strip()
152
+ if dependency and dependency not in components["dependencies"]:
153
+ components["dependencies"].append(dependency)
154
+
155
+ return components
156
+
157
+ def _extract_implementation_components(self, implementation_code: str) -> Dict[str, Any]:
158
+ """実装コードからコンポーネント、クラス、メソッド、属性などを抽出する"""
159
+ components = {
160
+ "classes": [],
161
+ "interfaces": [],
162
+ "methods": {},
163
+ "attributes": {},
164
+ "imports": [],
165
+ "functions": []
166
+ }
167
+
168
+ try:
169
+ # ASTを使ってPythonコードを解析
170
+ tree = ast.parse(implementation_code)
171
+
172
+ # インポート文の抽出
173
+ for node in ast.walk(tree):
174
+ if isinstance(node, ast.Import):
175
+ for name in node.names:
176
+ components["imports"].append(name.name)
177
+ elif isinstance(node, ast.ImportFrom):
178
+ if node.module:
179
+ components["imports"].append(node.module)
180
+
181
+ # クラスの抽出
182
+ for node in ast.walk(tree):
183
+ if isinstance(node, ast.ClassDef):
184
+ class_name = node.name
185
+
186
+ # インターフェース(抽象クラス)かどうかを判定
187
+ is_interface = False
188
+ for base in node.bases:
189
+ if isinstance(base, ast.Name) and base.id in ["ABC", "Protocol"]:
190
+ is_interface = True
191
+ break
192
+
193
+ if is_interface:
194
+ components["interfaces"].append(class_name)
195
+ else:
196
+ components["classes"].append(class_name)
197
+
198
+ components["methods"][class_name] = []
199
+ components["attributes"][class_name] = []
200
+
201
+ # メソッドと属性の抽出
202
+ for item in node.body:
203
+ if isinstance(item, ast.FunctionDef):
204
+ components["methods"][class_name].append(item.name)
205
+ elif isinstance(item, ast.Assign):
206
+ for target in item.targets:
207
+ if isinstance(target, ast.Name):
208
+ components["attributes"][class_name].append(target.id)
209
+
210
+ # トップレベル関数の抽出
211
+ for node in ast.walk(tree):
212
+ if isinstance(node, ast.FunctionDef) and node.name not in [m for methods in components["methods"].values() for m in methods]:
213
+ if not any(isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(tree)):
214
+ components["functions"].append(node.name)
215
+
216
+ except SyntaxError as e:
217
+ print(f"[Warning] Syntax error during code analysis: {e}")
218
+ # 構文エラーの場合は正規表現ベースのフォールバック解析を使用
219
+ self._fallback_extract_implementation_components(implementation_code, components)
220
+
221
+ return components
222
+
223
+ def _fallback_extract_implementation_components(self, code: str, components: Dict[str, Any]) -> None:
224
+ """構文エラーがある場合のフォールバック抽出メソッド(正規表現ベース)"""
225
+ # クラス定義の抽出
226
+ class_pattern = r'class\s+(\w+)(?:\s*\(.*?\))?\s*:'
227
+ for match in re.finditer(class_pattern, code):
228
+ class_name = match.group(1)
229
+ if class_name not in components["classes"]:
230
+ components["classes"].append(class_name)
231
+ components["methods"][class_name] = []
232
+ components["attributes"][class_name] = []
233
+
234
+ # クラス本体の抽出
235
+ class_start = match.end()
236
+ # 簡易的なブロック終了の検出(インデントベース)
237
+ class_body = ""
238
+ in_class = True
239
+ indent_level = None
240
+
241
+ for line in code[class_start:].split('\n'):
242
+ if not line.strip():
243
+ class_body += line + '\n'
244
+ continue
245
+
246
+ current_indent = len(line) - len(line.lstrip())
247
+
248
+ if indent_level is None:
249
+ if current_indent > 0: # クラス本体の最初の行
250
+ indent_level = current_indent
251
+ class_body += line + '\n'
252
+ continue
253
+
254
+ if current_indent >= indent_level:
255
+ class_body += line + '\n'
256
+ else:
257
+ break # クラスブロックの終了
258
+
259
+ # メソッド定義の抽出
260
+ method_pattern = r'def\s+(\w+)\s*\('
261
+ for method_match in re.finditer(method_pattern, class_body):
262
+ method_name = method_match.group(1)
263
+ if method_name not in components["methods"][class_name]:
264
+ components["methods"][class_name].append(method_name)
265
+
266
+ # 属性の抽出
267
+ attribute_pattern = r'self\.(\w+)\s*='
268
+ for attr_match in re.finditer(attribute_pattern, class_body):
269
+ attr_name = attr_match.group(1)
270
+ if attr_name not in components["attributes"][class_name]:
271
+ components["attributes"][class_name].append(attr_name)
272
+
273
+ # インポート文の抽出
274
+ import_pattern = r'import\s+(\w+)'
275
+ for match in re.finditer(import_pattern, code):
276
+ import_name = match.group(1)
277
+ if import_name not in components["imports"]:
278
+ components["imports"].append(import_name)
279
+
280
+ from_import_pattern = r'from\s+(\w+)\s+import'
281
+ for match in re.finditer(from_import_pattern, code):
282
+ import_name = match.group(1)
283
+ if import_name not in components["imports"]:
284
+ components["imports"].append(import_name)
285
+
286
+ # トップレベル関数の抽出
287
+ function_pattern = r'^def\s+(\w+)\s*\('
288
+ for match in re.finditer(function_pattern, code, re.MULTILINE):
289
+ func_name = match.group(1)
290
+ if func_name not in components["functions"]:
291
+ components["functions"].append(func_name)
292
+
293
+ def _validate_consistency(self, design: Dict[str, Any], implementation: Dict[str, Any], result: Dict[str, Any]) -> None:
294
+ """設計と実装の整合性を検証する"""
295
+ # 1. クラスの整合性チェック
296
+ for class_name in design["classes"]:
297
+ if class_name not in implementation["classes"] and class_name not in implementation["interfaces"]:
298
+ result["is_compliant"] = False
299
+ result["missing_components"].append(f"Class: {class_name}")
300
+ result["issues"].append(f"設計書で定義されているクラス '{class_name}' が実装に存在しません。")
301
+
302
+ # 2. インターフェースの整合性チェック
303
+ for interface_name in design["interfaces"]:
304
+ if interface_name not in implementation["interfaces"] and interface_name not in implementation["classes"]:
305
+ result["is_compliant"] = False
306
+ result["missing_components"].append(f"Interface: {interface_name}")
307
+ result["issues"].append(f"設計書で定義されているインターフェース '{interface_name}' が実装に存在しません。")
308
+
309
+ # 3. メソッドの整合性チェック
310
+ for class_name, methods in design["methods"].items():
311
+ if class_name in implementation["methods"]:
312
+ for method_name in methods:
313
+ if method_name not in implementation["methods"][class_name]:
314
+ result["is_compliant"] = False
315
+ result["missing_methods"].append(f"{class_name}.{method_name}")
316
+ result["issues"].append(f"設計書で定義されているメソッド '{class_name}.{method_name}' が実装に存在しません。")
317
+
318
+ # 4. 属性の整合性チェック(厳密でない - 属性は実装時に追加されることもある)
319
+ for class_name, attrs in design["attributes"].items():
320
+ if class_name in implementation["attributes"]:
321
+ for attr_name in attrs:
322
+ if attr_name not in implementation["attributes"][class_name]:
323
+ # 属性の不一致は警告レベル
324
+ result["issues"].append(f"設計書で定義されている属性 '{class_name}.{attr_name}' が実装に存在しません(警告)。")
325
+
326
+ # 5. 要件の実装検証(キーワードベースの簡易検証)
327
+ code_text = implementation_code if hasattr(self, 'implementation_code') else ""
328
+ for req in design["requirements"]:
329
+ # 要件からキーワードを抽出
330
+ keywords = self._extract_keywords(req)
331
+
332
+ # キーワードが実装に含まれているかチェック
333
+ if keywords and not all(keyword.lower() in code_text.lower() for keyword in keywords if len(keyword) > 3):
334
+ result["is_compliant"] = False
335
+ result["missing_requirements"].append(req)
336
+ result["issues"].append(f"要件 '{req}' が実装に反映されていない可能性があります。")
337
+
338
+ # 6. 実装に存在する余分なコンポーネントを確認
339
+ for class_name in implementation["classes"]:
340
+ if class_name not in design["classes"] and class_name not in design["interfaces"]:
341
+ result["extra_components"].append(f"Class: {class_name}")
342
+ # 余分なコンポーネントは警告レベル
343
+ result["issues"].append(f"実装に存在するクラス '{class_name}' が設計書に定義されていません(警告)。")
344
+
345
+ def _extract_keywords(self, text: str) -> List[str]:
346
+ """テキストから重要なキーワードを抽出する"""
347
+ # ストップワードを定義
348
+ stop_words = {
349
+ "a", "an", "the", "this", "that", "these", "those",
350
+ "is", "are", "was", "were", "be", "been", "being",
351
+ "have", "has", "had", "do", "does", "did", "will", "would", "shall", "should",
352
+ "can", "could", "may", "might", "must", "and", "or", "but", "if", "then", "else",
353
+ "when", "where", "why", "how", "all", "any", "both", "each", "few", "more", "most",
354
+ "other", "some", "such", "no", "nor", "not", "only", "own", "same", "so", "than",
355
+ "too", "very", "を", "に", "は", "が", "で", "と", "から", "まで", "より", "して", "する", "ます", "です"
356
+ }
357
+
358
+ # 単語分割(英語と日本語の両方に対応)
359
+ words = []
360
+ # 英単語の抽出
361
+ english_words = re.findall(r'\b[a-zA-Z]+\b', text.lower())
362
+ words.extend(english_words)
363
+
364
+ # 日本語単語の簡易抽出(文字単位の分割)
365
+ japanese_text = re.sub(r'[a-zA-Z0-9\s]', '', text)
366
+ for char in japanese_text:
367
+ if char not in "、。!?「」『』()[]【】…:;":
368
+ words.append(char)
369
+
370
+ # ストップワードと短すぎる単語を除外
371
+ keywords = [w for w in words if w not in stop_words and len(w) > 1]
372
+
373
+ # 重複を削除
374
+ unique_keywords = list(set(keywords))
375
+
376
+ return unique_keywords
377
+
378
+ def _calculate_compliance_score(self, result: Dict[str, Any]) -> None:
379
+ """コンプライアンススコアを計算する(0.0〜10.0の範囲)"""
380
+ # 初期スコア
381
+ score = 10.0
382
+
383
+ # 不足しているコンポーネントごとに減点
384
+ score -= len(result["missing_components"]) * 2.0
385
+
386
+ # 不足しているメソッドごとに減点
387
+ score -= len(result["missing_methods"]) * 1.0
388
+
389
+ # 不足している要件ごとに減点
390
+ score -= len(result["missing_requirements"]) * 1.5
391
+
392
+ # 余分なコンポーネントごとに軽く減点
393
+ score -= len(result["extra_components"]) * 0.5
394
+
395
+ # スコアの範囲を調整
396
+ result["compliance_score"] = max(0.0, min(10.0, score))
397
+
398
+ # コンプライアンス判定(スコアが7.0以上で合格)
399
+ result["is_compliant"] = result["compliance_score"] >= 7.0
400
+
401
+ def _generate_suggestions(self, result: Dict[str, Any]) -> None:
402
+ """検証結果に基づいて改善提案を生成する"""
403
+ if result["is_compliant"]:
404
+ if result["compliance_score"] == 10.0:
405
+ result["suggestion"] = "設計書と実装は完全に一致しています。素晴らしい実装です。"
406
+ else:
407
+ result["suggestion"] = "設計書と実装は概ね一致していますが、いくつかの軽微な問題があります。詳細は issues リストを確認してください。"
408
+ else:
409
+ # 不足コンポーネントの提案
410
+ if result["missing_components"]:
411
+ result["suggestion"] = f"設計書で定義されている以下のコンポーネントを実装してください: {', '.join(result['missing_components'][:3])}"
412
+ if len(result["missing_components"]) > 3:
413
+ result["suggestion"] += f" ... 他 {len(result['missing_components']) - 3} 件"
414
+
415
+ # 不足メソッドの提案
416
+ elif result["missing_methods"]:
417
+ result["suggestion"] = f"設計書で定義されている以下のメソッドを実装してください: {', '.join(result['missing_methods'][:3])}"
418
+ if len(result["missing_methods"]) > 3:
419
+ result["suggestion"] += f" ... 他 {len(result['missing_methods']) - 3} 件"
420
+
421
+ # 不足要件の提案
422
+ elif result["missing_requirements"]:
423
+ result["suggestion"] = "以下の要件が実装に反映されていない可能性があります。要件を満たす機能を実装してください。"
424
+ for i, req in enumerate(result["missing_requirements"][:3]):
425
+ result["suggestion"] += f"\n{i+1}. {req}"
426
+ if len(result["missing_requirements"]) > 3:
427
+ result["suggestion"] += f"\n... 他 {len(result['missing_requirements']) - 3} 件"
428
+
429
+ # 一般的な提案
430
+ else:
431
+ result["suggestion"] = "設計書と実装の間にいくつかの不一致があります。issues リストを確認して修正してください。"