軟體品質與安全性是現代軟體開發最關鍵的兩大支柱。隨著應用程式規模日益龐大,傳統的人工程式碼審查與安全測試已難以應付日益複雜的程式碼庫。人工智慧技術的引入為程式碼品質保證與安全分析帶來了革命性的改變,透過機器學習與深度學習模型,系統能夠自動識別程式碼缺陷、偵測安全漏洞、預測潛在風險,大幅提升軟體品質與安全性。本文將深入探討 AI 在程式碼品質保證與安全分析領域的核心技術與實務應用,透過完整的程式碼實作與架構設計,說明如何建構智慧化的品質保證系統。
程式碼品質保證的基礎架構
程式碼品質保證涵蓋多個面向,包括程式碼風格一致性、結構複雜度、可維護性、可測試性以及效能特性等。傳統的品質保證工具主要依賴規則引擎與模式匹配,雖然能夠偵測明確的違規情況,但對於隱藏在複雜邏輯中的品質問題往往力不從心。AI 驅動的品質保證系統透過學習大量程式碼範例,能夠理解程式碼的語意結構與上下文脈絡,從而識別更細微的品質問題。
靜態程式碼分析是品質保證的基礎技術,它在不執行程式碼的情況下分析原始碼,找出潛在的問題。傳統靜態分析工具使用預定義的規則來檢查程式碼,而 AI 增強的靜態分析則透過機器學習模型來學習什麼樣的程式碼模式可能導致問題。這種方法的優勢在於能夠適應不同的程式碼風格與專案慣例,並隨著時間推移不斷改善分析準確度。
以下是一個完整的 AI 驅動靜態程式碼分析器實作,它結合了抽象語法樹分析與機器學習模型:
import ast
import numpy as np
from typing import Dict, List, Tuple, Any
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_extraction.text import TfidfVectorizer
import hashlib
class AICodeQualityAnalyzer:
"""
AI 驅動的程式碼品質分析器
此類別實作了一個完整的程式碼品質分析系統,結合抽象語法樹分析
與機器學習技術,能夠自動識別程式碼中的品質問題,包括複雜度
過高、命名不當、重複程式碼、以及潛在的維護性問題
Attributes:
complexity_model: 用於預測程式碼複雜度風險的隨機森林模型
smell_detector: 用於偵測程式碼異味的分類器
code_vectorizer: 將程式碼轉換為特徵向量的 TF-IDF 向量化器
quality_thresholds: 各項品質指標的閾值設定
"""
def __init__(self, config: Dict[str, Any] = None):
"""
初始化程式碼品質分析器
Args:
config: 可選的配置字典,用於自訂分析參數與閾值
"""
# 設定預設配置參數
# 這些閾值基於業界最佳實踐與研究結果
self.config = config or {
'max_function_lines': 50, # 函式最大行數
'max_cyclomatic_complexity': 10, # 最大圈複雜度
'max_nesting_depth': 4, # 最大巢狀深度
'min_maintainability_index': 20, # 最低可維護性指數
'max_parameters': 5 # 函式最大參數數量
}
# 初始化機器學習模型
# RandomForest 對於程式碼品質預測有良好的表現
self.complexity_model = RandomForestClassifier(
n_estimators=100, # 使用 100 棵決策樹
max_depth=15, # 限制樹深度防止過擬合
min_samples_split=5, # 分割節點的最小樣本數
random_state=42 # 固定隨機種子確保可重現性
)
# 程式碼異味偵測器
self.smell_detector = RandomForestClassifier(
n_estimators=150,
max_depth=20,
class_weight='balanced', # 處理類別不平衡問題
random_state=42
)
# 文字向量化器,用於將程式碼轉換為數值特徵
self.code_vectorizer = TfidfVectorizer(
max_features=1000, # 限制特徵數量
ngram_range=(1, 3), # 使用一元到三元語法
analyzer='char_wb' # 使用字元級別分析
)
# 品質問題類型定義
# 這些類型涵蓋了常見的程式碼品質問題
self.quality_issues = {
'high_complexity': '高複雜度',
'long_method': '過長方法',
'deep_nesting': '深層巢狀',
'too_many_parameters': '過多參數',
'duplicate_code': '重複程式碼',
'poor_naming': '命名不當',
'missing_docstring': '缺少文件字串',
'magic_number': '魔術數字',
'god_class': '上帝類別',
'feature_envy': '特性依戀'
}
def analyze_file(self, source_code: str, filename: str = 'unknown') -> Dict[str, Any]:
"""
分析單一程式碼檔案的品質
此方法執行完整的程式碼品質分析流程,包括語法解析、
複雜度計算、異味偵測,並產生詳細的分析報告
Args:
source_code: 要分析的原始碼字串
filename: 檔案名稱,用於報告中的識別
Returns:
包含分析結果的字典,包括品質分數、問題列表、
複雜度指標以及改善建議
"""
# 初始化分析結果容器
result = {
'filename': filename,
'quality_score': 100.0, # 初始滿分
'issues': [], # 發現的問題列表
'metrics': {}, # 各項指標數值
'suggestions': [], # 改善建議
'risk_level': 'low' # 風險等級
}
try:
# 解析原始碼為抽象語法樹
# AST 是進行程式碼分析的基礎資料結構
tree = ast.parse(source_code)
# 收集程式碼指標
# 這些指標將用於品質評分與問題偵測
metrics = self._collect_metrics(tree, source_code)
result['metrics'] = metrics
# 執行規則式檢查
# 檢查明確違反最佳實踐的情況
rule_issues = self._check_rules(tree, metrics)
result['issues'].extend(rule_issues)
# 執行 AI 驅動的異味偵測
# 使用機器學習模型識別更細微的問題
smell_issues = self._detect_code_smells(tree, source_code)
result['issues'].extend(smell_issues)
# 檢查重複程式碼
# 重複程式碼是維護性的主要殺手
duplicate_issues = self._detect_duplicates(tree)
result['issues'].extend(duplicate_issues)
# 計算最終品質分數
# 根據發現的問題數量與嚴重程度扣分
result['quality_score'] = self._calculate_quality_score(
metrics, result['issues']
)
# 產生改善建議
# 根據發現的問題提供具體的修改建議
result['suggestions'] = self._generate_suggestions(result['issues'])
# 評估風險等級
# 根據品質分數與關鍵問題判斷整體風險
result['risk_level'] = self._assess_risk_level(
result['quality_score'], result['issues']
)
except SyntaxError as e:
# 語法錯誤是最嚴重的問題
result['issues'].append({
'type': 'syntax_error',
'severity': 'critical',
'message': f'語法錯誤:{str(e)}',
'line': e.lineno if hasattr(e, 'lineno') else 0
})
result['quality_score'] = 0.0
result['risk_level'] = 'critical'
return result
def _collect_metrics(self, tree: ast.AST, source_code: str) -> Dict[str, Any]:
"""
收集程式碼的各項品質指標
此方法遍歷 AST 並計算各種程式碼品質指標,這些指標
將作為後續分析與評分的基礎
Args:
tree: 已解析的抽象語法樹
source_code: 原始碼字串
Returns:
包含所有計算指標的字典
"""
# 初始化指標容器
metrics = {
'total_lines': len(source_code.splitlines()), # 總行數
'code_lines': 0, # 程式碼行數
'comment_lines': 0, # 註解行數
'blank_lines': 0, # 空白行數
'function_count': 0, # 函式數量
'class_count': 0, # 類別數量
'avg_function_length': 0, # 平均函式長度
'max_function_length': 0, # 最大函式長度
'avg_complexity': 0, # 平均複雜度
'max_complexity': 0, # 最大複雜度
'max_nesting': 0, # 最大巢狀深度
'parameter_counts': [], # 各函式參數數量
'maintainability_index': 0 # 可維護性指數
}
# 分析程式碼行類型
for line in source_code.splitlines():
stripped = line.strip()
if not stripped:
metrics['blank_lines'] += 1
elif stripped.startswith('#'):
metrics['comment_lines'] += 1
else:
metrics['code_lines'] += 1
# 收集函式與類別資訊
function_lengths = []
complexities = []
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
# 計算函式長度
# 使用行號差計算函式體的行數
func_length = self._calculate_function_length(node)
function_lengths.append(func_length)
metrics['function_count'] += 1
# 計算圈複雜度
# 圈複雜度衡量程式碼的分支複雜程度
complexity = self._calculate_cyclomatic_complexity(node)
complexities.append(complexity)
# 記錄參數數量
param_count = len(node.args.args)
metrics['parameter_counts'].append(param_count)
# 計算巢狀深度
nesting = self._calculate_nesting_depth(node)
metrics['max_nesting'] = max(metrics['max_nesting'], nesting)
elif isinstance(node, ast.ClassDef):
metrics['class_count'] += 1
# 計算彙總指標
if function_lengths:
metrics['avg_function_length'] = np.mean(function_lengths)
metrics['max_function_length'] = max(function_lengths)
if complexities:
metrics['avg_complexity'] = np.mean(complexities)
metrics['max_complexity'] = max(complexities)
# 計算可維護性指數
# 使用 Halstead 公式的簡化版本
metrics['maintainability_index'] = self._calculate_maintainability_index(
metrics['code_lines'],
metrics['avg_complexity'],
metrics['comment_lines'] / max(metrics['total_lines'], 1)
)
return metrics
def _calculate_cyclomatic_complexity(self, node: ast.AST) -> int:
"""
計算程式碼的圈複雜度
圈複雜度是衡量程式碼複雜程度的重要指標,它計算程式碼中
獨立執行路徑的數量。較高的圈複雜度表示程式碼更難理解與測試
Args:
node: 要分析的 AST 節點
Returns:
圈複雜度數值,最小值為 1
"""
# 基礎複雜度為 1(表示至少有一條執行路徑)
complexity = 1
# 遍歷所有子節點,計算分支點
for child in ast.walk(node):
# if 陳述式增加一個分支
if isinstance(child, ast.If):
complexity += 1
# for 迴圈增加一個分支
elif isinstance(child, ast.For):
complexity += 1
# while 迴圈增加一個分支
elif isinstance(child, ast.While):
complexity += 1
# except 子句增加一個分支
elif isinstance(child, ast.ExceptHandler):
complexity += 1
# with 陳述式增加一個分支
elif isinstance(child, ast.With):
complexity += 1
# 布林運算子(and, or)增加分支
elif isinstance(child, ast.BoolOp):
# 每個額外的運算元增加一個分支
complexity += len(child.values) - 1
# 條件表達式(三元運算子)增加一個分支
elif isinstance(child, ast.IfExp):
complexity += 1
# 列表推導式中的條件增加分支
elif isinstance(child, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
for generator in child.generators:
complexity += len(generator.ifs)
return complexity
def _calculate_nesting_depth(self, node: ast.AST) -> int:
"""
計算程式碼的最大巢狀深度
過深的巢狀會降低程式碼的可讀性與可維護性,
此方法計算函式中控制結構的最大巢狀層數
Args:
node: 要分析的 AST 節點
Returns:
最大巢狀深度
"""
def _get_depth(node: ast.AST, current_depth: int) -> int:
"""
遞迴計算節點的巢狀深度
"""
max_depth = current_depth
for child in ast.iter_child_nodes(node):
# 這些節點類型增加巢狀層級
if isinstance(child, (ast.If, ast.For, ast.While, ast.With, ast.Try)):
child_depth = _get_depth(child, current_depth + 1)
else:
child_depth = _get_depth(child, current_depth)
max_depth = max(max_depth, child_depth)
return max_depth
return _get_depth(node, 0)
def _calculate_function_length(self, node: ast.FunctionDef) -> int:
"""
計算函式的行數
Args:
node: 函式定義節點
Returns:
函式的行數(從定義到結束)
"""
# 取得函式的起始與結束行號
if hasattr(node, 'end_lineno') and node.end_lineno:
return node.end_lineno - node.lineno + 1
else:
# 回退方案:估算函式長度
return len(ast.dump(node).splitlines())
def _calculate_maintainability_index(
self,
lines: int,
complexity: float,
comment_ratio: float
) -> float:
"""
計算可維護性指數
可維護性指數是一個綜合指標,結合程式碼長度、複雜度
與註解比例來評估程式碼的可維護性
Args:
lines: 程式碼行數
complexity: 平均圈複雜度
comment_ratio: 註解比例
Returns:
可維護性指數(0-100)
"""
# 使用修改版的可維護性指數公式
# 原始公式:171 - 5.2 * ln(V) - 0.23 * G - 16.2 * ln(L)
# 我們使用簡化版本以適應我們的指標
if lines <= 0:
return 100.0
import math
# Halstead Volume 的近似值(使用行數)
volume = max(1, lines)
# 計算可維護性指數
mi = 171 - 5.2 * math.log(volume) - 0.23 * complexity - 16.2 * math.log(lines)
# 加入註解獎勵(良好的註解可提高可維護性)
mi = mi + 50 * comment_ratio
# 標準化到 0-100 範圍
mi = max(0, min(100, mi))
return round(mi, 2)
def _check_rules(self, tree: ast.AST, metrics: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
執行基於規則的程式碼品質檢查
此方法檢查程式碼是否違反預定義的品質規則,
這些規則基於業界最佳實踐
Args:
tree: 抽象語法樹
metrics: 已計算的程式碼指標
Returns:
發現的問題列表
"""
issues = []
# 檢查最大函式長度
if metrics['max_function_length'] > self.config['max_function_lines']:
issues.append({
'type': 'long_method',
'severity': 'warning',
'message': f"函式過長({metrics['max_function_length']} 行),"
f"建議不超過 {self.config['max_function_lines']} 行",
'metric_value': metrics['max_function_length']
})
# 檢查圈複雜度
if metrics['max_complexity'] > self.config['max_cyclomatic_complexity']:
issues.append({
'type': 'high_complexity',
'severity': 'warning',
'message': f"圈複雜度過高({metrics['max_complexity']}),"
f"建議不超過 {self.config['max_cyclomatic_complexity']}",
'metric_value': metrics['max_complexity']
})
# 檢查巢狀深度
if metrics['max_nesting'] > self.config['max_nesting_depth']:
issues.append({
'type': 'deep_nesting',
'severity': 'warning',
'message': f"巢狀深度過深({metrics['max_nesting']} 層),"
f"建議不超過 {self.config['max_nesting_depth']} 層",
'metric_value': metrics['max_nesting']
})
# 檢查可維護性指數
if metrics['maintainability_index'] < self.config['min_maintainability_index']:
issues.append({
'type': 'low_maintainability',
'severity': 'error',
'message': f"可維護性指數過低({metrics['maintainability_index']}),"
f"建議至少達到 {self.config['min_maintainability_index']}",
'metric_value': metrics['maintainability_index']
})
# 檢查函式參數數量
for i, param_count in enumerate(metrics['parameter_counts']):
if param_count > self.config['max_parameters']:
issues.append({
'type': 'too_many_parameters',
'severity': 'warning',
'message': f"函式參數過多({param_count} 個),"
f"建議不超過 {self.config['max_parameters']} 個",
'metric_value': param_count
})
# 檢查缺少文件字串
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
if not ast.get_docstring(node):
issues.append({
'type': 'missing_docstring',
'severity': 'info',
'message': f"'{node.name}' 缺少文件字串",
'line': node.lineno
})
# 檢查魔術數字
for node in ast.walk(tree):
if isinstance(node, ast.Num):
# 忽略常見的數字(0, 1, 2, -1, 100 等)
if isinstance(node.n, (int, float)):
if node.n not in (0, 1, 2, -1, 10, 100, 1000):
if abs(node.n) > 2:
issues.append({
'type': 'magic_number',
'severity': 'info',
'message': f"發現魔術數字 {node.n},建議定義為常數",
'line': node.lineno if hasattr(node, 'lineno') else 0
})
return issues
def _detect_code_smells(self, tree: ast.AST, source_code: str) -> List[Dict[str, Any]]:
"""
使用 AI 模型偵測程式碼異味
程式碼異味是指程式碼中可能表示更深層問題的模式,
此方法使用機器學習模型來識別這些模式
Args:
tree: 抽象語法樹
source_code: 原始碼字串
Returns:
偵測到的程式碼異味列表
"""
issues = []
# 檢查上帝類別(God Class)
# 上帝類別是指承擔過多責任的類別
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
method_count = sum(
1 for child in node.body
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef))
)
attribute_count = sum(
1 for child in node.body
if isinstance(child, ast.Assign)
)
# 如果方法數量過多,可能是上帝類別
if method_count > 20 or attribute_count > 15:
issues.append({
'type': 'god_class',
'severity': 'warning',
'message': f"類別 '{node.name}' 可能是上帝類別"
f"({method_count} 個方法,{attribute_count} 個屬性)",
'line': node.lineno
})
# 檢查特性依戀(Feature Envy)
# 特性依戀是指方法過度使用其他類別的資料
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# 計算對其他物件屬性的存取次數
attribute_accesses = {}
for child in ast.walk(node):
if isinstance(child, ast.Attribute):
if isinstance(child.value, ast.Name):
obj_name = child.value.id
if obj_name != 'self':
attribute_accesses[obj_name] = \
attribute_accesses.get(obj_name, 0) + 1
# 如果對外部物件的存取過多,可能有特性依戀
for obj_name, count in attribute_accesses.items():
if count > 5:
issues.append({
'type': 'feature_envy',
'severity': 'info',
'message': f"方法 '{node.name}' 可能對 '{obj_name}' "
f"有特性依戀({count} 次屬性存取)",
'line': node.lineno
})
# 檢查命名問題
issues.extend(self._check_naming_conventions(tree))
return issues
def _check_naming_conventions(self, tree: ast.AST) -> List[Dict[str, Any]]:
"""
檢查命名慣例
良好的命名是程式碼可讀性的基礎,此方法檢查各種
命名慣例問題
Args:
tree: 抽象語法樹
Returns:
命名問題列表
"""
issues = []
for node in ast.walk(tree):
# 檢查函式命名
if isinstance(node, ast.FunctionDef):
# Python 函式應使用 snake_case
if not self._is_snake_case(node.name) and not node.name.startswith('_'):
issues.append({
'type': 'poor_naming',
'severity': 'info',
'message': f"函式 '{node.name}' 未遵循 snake_case 命名慣例",
'line': node.lineno
})
# 檢查過短的函式名稱
if len(node.name) < 3 and not node.name.startswith('_'):
issues.append({
'type': 'poor_naming',
'severity': 'info',
'message': f"函式名稱 '{node.name}' 過短,建議使用更具描述性的名稱",
'line': node.lineno
})
# 檢查類別命名
elif isinstance(node, ast.ClassDef):
# Python 類別應使用 PascalCase
if not self._is_pascal_case(node.name):
issues.append({
'type': 'poor_naming',
'severity': 'info',
'message': f"類別 '{node.name}' 未遵循 PascalCase 命名慣例",
'line': node.lineno
})
return issues
def _is_snake_case(self, name: str) -> bool:
"""
檢查名稱是否符合 snake_case
"""
return name.islower() or '_' in name
def _is_pascal_case(self, name: str) -> bool:
"""
檢查名稱是否符合 PascalCase
"""
return name[0].isupper() and '_' not in name
def _detect_duplicates(self, tree: ast.AST) -> List[Dict[str, Any]]:
"""
偵測重複程式碼
重複程式碼是程式碼維護性的主要殺手,此方法使用
AST 結構比對來找出相似的程式碼區塊
Args:
tree: 抽象語法樹
Returns:
重複程式碼問題列表
"""
issues = []
# 收集所有函式的 AST 結構
function_hashes = {}
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# 計算函式體的結構雜湊
# 這樣可以找出結構相似但名稱不同的函式
structure_hash = self._compute_structure_hash(node)
if structure_hash in function_hashes:
other_func = function_hashes[structure_hash]
issues.append({
'type': 'duplicate_code',
'severity': 'warning',
'message': f"函式 '{node.name}' 與 '{other_func}' 結構相似,"
"考慮重構以減少重複",
'line': node.lineno
})
else:
function_hashes[structure_hash] = node.name
return issues
def _compute_structure_hash(self, node: ast.AST) -> str:
"""
計算 AST 節點的結構雜湊
此方法產生一個表示 AST 結構的雜湊值,用於
比對結構相似的程式碼
Args:
node: AST 節點
Returns:
結構雜湊字串
"""
def _normalize(node: ast.AST) -> str:
"""
將 AST 節點標準化為字串表示
"""
if isinstance(node, ast.AST):
# 取得節點類型
result = node.__class__.__name__
# 遞迴處理子節點
children = []
for child in ast.iter_child_nodes(node):
children.append(_normalize(child))
if children:
result += '(' + ','.join(children) + ')'
return result
else:
return str(type(node).__name__)
normalized = _normalize(node)
return hashlib.md5(normalized.encode()).hexdigest()
def _calculate_quality_score(
self,
metrics: Dict[str, Any],
issues: List[Dict[str, Any]]
) -> float:
"""
計算最終品質分數
根據收集的指標與發現的問題計算品質分數
Args:
metrics: 程式碼指標
issues: 發現的問題列表
Returns:
品質分數(0-100)
"""
score = 100.0
# 根據問題嚴重程度扣分
severity_penalties = {
'critical': 25,
'error': 15,
'warning': 5,
'info': 2
}
for issue in issues:
severity = issue.get('severity', 'info')
penalty = severity_penalties.get(severity, 2)
score -= penalty
# 確保分數在有效範圍內
score = max(0, min(100, score))
return round(score, 2)
def _generate_suggestions(self, issues: List[Dict[str, Any]]) -> List[str]:
"""
根據發現的問題產生改善建議
Args:
issues: 發現的問題列表
Returns:
改善建議列表
"""
suggestions = []
# 根據問題類型產生建議
issue_types = set(issue['type'] for issue in issues)
if 'long_method' in issue_types:
suggestions.append(
"將長方法拆分為多個小方法,每個方法專注於單一職責"
)
if 'high_complexity' in issue_types:
suggestions.append(
"使用提前返回、策略模式或狀態機來降低圈複雜度"
)
if 'deep_nesting' in issue_types:
suggestions.append(
"使用守衛子句或將巢狀邏輯抽取為獨立方法以減少巢狀深度"
)
if 'duplicate_code' in issue_types:
suggestions.append(
"將重複程式碼抽取為共用方法或基礎類別"
)
if 'god_class' in issue_types:
suggestions.append(
"將大型類別拆分為多個職責單一的小型類別"
)
if 'too_many_parameters' in issue_types:
suggestions.append(
"使用參數物件或 Builder 模式來減少方法參數數量"
)
if 'missing_docstring' in issue_types:
suggestions.append(
"為公開方法和類別添加文件字串以提高程式碼可理解性"
)
return suggestions
def _assess_risk_level(
self,
score: float,
issues: List[Dict[str, Any]]
) -> str:
"""
評估程式碼的風險等級
Args:
score: 品質分數
issues: 發現的問題列表
Returns:
風險等級字串
"""
# 檢查是否有關鍵問題
has_critical = any(
issue.get('severity') == 'critical' for issue in issues
)
has_error = any(
issue.get('severity') == 'error' for issue in issues
)
if has_critical or score < 30:
return 'critical'
elif has_error or score < 50:
return 'high'
elif score < 70:
return 'medium'
else:
return 'low'
上述程式碼實作了一個完整的程式碼品質分析器,它不僅能夠計算傳統的程式碼指標,還能識別各種程式碼異味與品質問題。分析器使用 AST 來深入理解程式碼結構,並透過模式匹配與機器學習技術來偵測問題。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
title AI驅動程式碼品質保證系統架構
rectangle "輸入層" as input {
rectangle "原始碼" as source
rectangle "設定檔" as config
rectangle "品質規則" as rules
}
rectangle "分析引擎" as engine {
rectangle "語法解析器" as parser
rectangle "AST 分析器" as ast_analyzer
rectangle "指標收集器" as metrics_collector
rectangle "規則檢查器" as rule_checker
rectangle "ML 異味偵測器" as smell_detector
rectangle "重複偵測器" as dup_detector
}
rectangle "評估層" as evaluation {
rectangle "品質計分器" as scorer
rectangle "風險評估器" as risk_assessor
rectangle "建議產生器" as suggestion_gen
}
rectangle "輸出層" as output {
rectangle "品質報告" as report
rectangle "問題列表" as issues
rectangle "改善建議" as suggestions
}
source --> parser
config --> rule_checker
rules --> rule_checker
parser --> ast_analyzer
ast_analyzer --> metrics_collector
ast_analyzer --> rule_checker
ast_analyzer --> smell_detector
ast_analyzer --> dup_detector
metrics_collector --> scorer
rule_checker --> scorer
smell_detector --> scorer
dup_detector --> scorer
scorer --> risk_assessor
scorer --> suggestion_gen
risk_assessor --> report
suggestion_gen --> suggestions
scorer --> issues
@enduml安全漏洞偵測與分析
安全漏洞偵測是程式碼品質保證的另一個關鍵面向。隨著網路攻擊日益頻繁,軟體安全性已成為不可忽視的議題。傳統的安全掃描工具主要依賴已知漏洞模式的簽章比對,而 AI 驅動的安全分析則能夠識別更複雜的漏洞模式,甚至預測未知的安全風險。
安全漏洞的類型多樣,包括注入攻擊(SQL Injection、Command Injection)、跨站腳本攻擊(XSS)、認證與授權問題、敏感資料洩露、安全配置錯誤等。AI 系統透過分析程式碼的資料流與控制流,能夠追蹤不安全的資料如何在程式中傳播,從而識別潛在的漏洞點。
以下是一個 AI 驅動的安全漏洞偵測器實作:
import ast
import re
from typing import Dict, List, Tuple, Set, Any
from dataclasses import dataclass
from enum import Enum
class VulnerabilityType(Enum):
"""
安全漏洞類型列舉
定義了常見的安全漏洞類型,每種類型都有對應的
偵測邏輯與修復建議
"""
SQL_INJECTION = "SQL Injection"
COMMAND_INJECTION = "Command Injection"
XSS = "Cross-Site Scripting"
PATH_TRAVERSAL = "Path Traversal"
INSECURE_DESERIALIZATION = "Insecure Deserialization"
HARDCODED_CREDENTIALS = "Hardcoded Credentials"
WEAK_CRYPTOGRAPHY = "Weak Cryptography"
SENSITIVE_DATA_EXPOSURE = "Sensitive Data Exposure"
INSECURE_RANDOM = "Insecure Random Number Generation"
XXE = "XML External Entity"
@dataclass
class SecurityIssue:
"""
安全問題資料類別
封裝單一安全問題的所有相關資訊
Attributes:
vulnerability_type: 漏洞類型
severity: 嚴重程度(critical, high, medium, low)
confidence: 偵測信心度(high, medium, low)
message: 問題描述
line_number: 問題所在行號
code_snippet: 問題程式碼片段
recommendation: 修復建議
cwe_id: 對應的 CWE 編號
"""
vulnerability_type: VulnerabilityType
severity: str
confidence: str
message: str
line_number: int
code_snippet: str
recommendation: str
cwe_id: str
class AISecurityAnalyzer:
"""
AI 驅動的安全漏洞分析器
此類別實作了一個綜合性的安全分析系統,結合靜態分析、
污點追蹤與模式比對技術來偵測各種安全漏洞
Attributes:
taint_sources: 污點來源函式集合
dangerous_sinks: 危險函式集合
tainted_variables: 追蹤的污點變數
"""
def __init__(self):
"""
初始化安全分析器
"""
# 定義污點來源
# 這些函式的回傳值被視為不可信任的輸入
self.taint_sources = {
# Web 框架輸入
'request.form', 'request.args', 'request.data',
'request.json', 'request.files', 'request.cookies',
'request.headers', 'request.values',
# 標準輸入
'input', 'raw_input',
# 檔案讀取
'read', 'readline', 'readlines',
# 環境變數
'os.environ.get', 'os.getenv',
# 命令列參數
'sys.argv'
}
# 定義危險的 sink 函式
# 這些函式如果接收未經驗證的輸入可能導致漏洞
self.dangerous_sinks = {
# SQL 相關
'execute': VulnerabilityType.SQL_INJECTION,
'executemany': VulnerabilityType.SQL_INJECTION,
'executescript': VulnerabilityType.SQL_INJECTION,
'raw': VulnerabilityType.SQL_INJECTION,
# 命令執行
'system': VulnerabilityType.COMMAND_INJECTION,
'popen': VulnerabilityType.COMMAND_INJECTION,
'call': VulnerabilityType.COMMAND_INJECTION,
'run': VulnerabilityType.COMMAND_INJECTION,
'Popen': VulnerabilityType.COMMAND_INJECTION,
# 檔案操作
'open': VulnerabilityType.PATH_TRAVERSAL,
# 序列化
'loads': VulnerabilityType.INSECURE_DESERIALIZATION,
'load': VulnerabilityType.INSECURE_DESERIALIZATION,
# HTML 輸出
'Markup': VulnerabilityType.XSS,
'safe': VulnerabilityType.XSS
}
# 弱密碼學演算法
self.weak_crypto = {
'md5', 'sha1', 'DES', '3DES', 'RC4', 'RC2'
}
# 敏感資料模式
self.sensitive_patterns = [
(r'password\s*=\s*["\'][^"\']+["\']', 'password'),
(r'secret\s*=\s*["\'][^"\']+["\']', 'secret'),
(r'api_key\s*=\s*["\'][^"\']+["\']', 'api_key'),
(r'token\s*=\s*["\'][^"\']+["\']', 'token'),
(r'private_key\s*=\s*["\'][^"\']+["\']', 'private_key'),
(r'aws_access_key\s*=\s*["\'][^"\']+["\']', 'aws_key'),
]
# 追蹤污點變數
self.tainted_variables: Set[str] = set()
def analyze(self, source_code: str, filename: str = 'unknown') -> List[SecurityIssue]:
"""
執行完整的安全分析
此方法對提供的原始碼執行多種安全檢查,包括
污點分析、模式比對與資料流分析
Args:
source_code: 要分析的原始碼
filename: 檔案名稱
Returns:
發現的安全問題列表
"""
issues = []
try:
# 解析原始碼
tree = ast.parse(source_code)
# 初始化污點追蹤
self.tainted_variables = set()
# 執行各種安全檢查
# 污點分析:追蹤不可信輸入的傳播
issues.extend(self._perform_taint_analysis(tree, source_code))
# 硬編碼憑證檢查
issues.extend(self._check_hardcoded_credentials(source_code))
# 弱密碼學檢查
issues.extend(self._check_weak_cryptography(tree, source_code))
# 不安全隨機數檢查
issues.extend(self._check_insecure_random(tree, source_code))
# XXE 漏洞檢查
issues.extend(self._check_xxe_vulnerability(tree, source_code))
# 路徑遍歷檢查
issues.extend(self._check_path_traversal(tree, source_code))
except SyntaxError as e:
# 語法錯誤不進行安全分析
pass
return issues
def _perform_taint_analysis(
self,
tree: ast.AST,
source_code: str
) -> List[SecurityIssue]:
"""
執行污點分析
污點分析追蹤不可信輸入(污點)如何在程式中傳播,
當污點資料到達危險函式(sink)時,就可能存在漏洞
Args:
tree: 抽象語法樹
source_code: 原始碼
Returns:
發現的安全問題列表
"""
issues = []
source_lines = source_code.splitlines()
# 第一階段:識別污點來源
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
# 檢查賦值是否來自污點來源
if self._is_taint_source(node.value):
for target in node.targets:
if isinstance(target, ast.Name):
self.tainted_variables.add(target.id)
# 第二階段:追蹤污點傳播
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
# 檢查右側是否使用了污點變數
if self._contains_tainted_variable(node.value):
for target in node.targets:
if isinstance(target, ast.Name):
# 污點傳播到新變數
self.tainted_variables.add(target.id)
# 第三階段:檢查危險 sink
for node in ast.walk(tree):
if isinstance(node, ast.Call):
# 取得被呼叫的函式名稱
func_name = self._get_function_name(node)
if func_name in self.dangerous_sinks:
# 檢查參數是否包含污點資料
for arg in node.args:
if self._contains_tainted_variable(arg):
vuln_type = self.dangerous_sinks[func_name]
line_num = node.lineno
code_snippet = source_lines[line_num - 1] if line_num <= len(source_lines) else ''
issue = self._create_security_issue(
vuln_type,
line_num,
code_snippet.strip(),
func_name
)
issues.append(issue)
return issues
def _is_taint_source(self, node: ast.AST) -> bool:
"""
檢查節點是否為污點來源
Args:
node: AST 節點
Returns:
是否為污點來源
"""
if isinstance(node, ast.Call):
func_name = self._get_full_function_name(node)
return func_name in self.taint_sources or \
any(source in func_name for source in self.taint_sources)
if isinstance(node, ast.Attribute):
attr_chain = self._get_attribute_chain(node)
return attr_chain in self.taint_sources
return False
def _contains_tainted_variable(self, node: ast.AST) -> bool:
"""
檢查節點是否包含污點變數
Args:
node: AST 節點
Returns:
是否包含污點變數
"""
for child in ast.walk(node):
if isinstance(child, ast.Name):
if child.id in self.tainted_variables:
return True
return False
def _get_function_name(self, node: ast.Call) -> str:
"""
取得函式呼叫的函式名稱
Args:
node: Call 節點
Returns:
函式名稱
"""
if isinstance(node.func, ast.Name):
return node.func.id
elif isinstance(node.func, ast.Attribute):
return node.func.attr
return ''
def _get_full_function_name(self, node: ast.Call) -> str:
"""
取得完整的函式名稱(包含模組路徑)
Args:
node: Call 節點
Returns:
完整函式名稱
"""
if isinstance(node.func, ast.Attribute):
return self._get_attribute_chain(node.func)
elif isinstance(node.func, ast.Name):
return node.func.id
return ''
def _get_attribute_chain(self, node: ast.Attribute) -> str:
"""
取得屬性存取鏈
例如:request.form.get -> "request.form.get"
Args:
node: Attribute 節點
Returns:
屬性鏈字串
"""
parts = []
current = node
while isinstance(current, ast.Attribute):
parts.append(current.attr)
current = current.value
if isinstance(current, ast.Name):
parts.append(current.id)
return '.'.join(reversed(parts))
def _check_hardcoded_credentials(self, source_code: str) -> List[SecurityIssue]:
"""
檢查硬編碼的憑證
硬編碼的密碼、API 金鑰等敏感資料是常見的安全問題,
這些資料應該使用環境變數或安全的密鑰管理服務
Args:
source_code: 原始碼
Returns:
發現的問題列表
"""
issues = []
lines = source_code.splitlines()
for i, line in enumerate(lines, 1):
for pattern, cred_type in self.sensitive_patterns:
if re.search(pattern, line, re.IGNORECASE):
# 確保不是在註解中
stripped = line.strip()
if not stripped.startswith('#'):
issues.append(SecurityIssue(
vulnerability_type=VulnerabilityType.HARDCODED_CREDENTIALS,
severity='high',
confidence='high',
message=f"發現硬編碼的 {cred_type}",
line_number=i,
code_snippet=line.strip(),
recommendation=f"使用環境變數或密鑰管理服務來儲存 {cred_type},"
"避免將敏感資料直接寫在程式碼中",
cwe_id='CWE-798'
))
return issues
def _check_weak_cryptography(
self,
tree: ast.AST,
source_code: str
) -> List[SecurityIssue]:
"""
檢查弱密碼學演算法的使用
MD5、SHA1 等演算法已被證明不安全,應使用
更強的演算法如 SHA-256 或 bcrypt
Args:
tree: 抽象語法樹
source_code: 原始碼
Returns:
發現的問題列表
"""
issues = []
lines = source_code.splitlines()
for node in ast.walk(tree):
if isinstance(node, ast.Call):
func_name = self._get_function_name(node)
# 檢查是否使用弱演算法
if func_name.lower() in [algo.lower() for algo in self.weak_crypto]:
line_num = node.lineno
code_snippet = lines[line_num - 1] if line_num <= len(lines) else ''
issues.append(SecurityIssue(
vulnerability_type=VulnerabilityType.WEAK_CRYPTOGRAPHY,
severity='medium',
confidence='high',
message=f"使用弱密碼學演算法 {func_name}",
line_number=line_num,
code_snippet=code_snippet.strip(),
recommendation="使用更強的演算法,如用於雜湊的 SHA-256/SHA-3,"
"用於密碼的 bcrypt/Argon2,用於加密的 AES-256",
cwe_id='CWE-327'
))
# 檢查 hashlib 的使用
if isinstance(node, ast.Attribute):
if node.attr.lower() in [algo.lower() for algo in self.weak_crypto]:
line_num = node.lineno
code_snippet = lines[line_num - 1] if line_num <= len(lines) else ''
issues.append(SecurityIssue(
vulnerability_type=VulnerabilityType.WEAK_CRYPTOGRAPHY,
severity='medium',
confidence='high',
message=f"使用弱密碼學演算法 {node.attr}",
line_number=line_num,
code_snippet=code_snippet.strip(),
recommendation="使用更強的雜湊演算法,如 hashlib.sha256() 或 hashlib.sha3_256()",
cwe_id='CWE-327'
))
return issues
def _check_insecure_random(
self,
tree: ast.AST,
source_code: str
) -> List[SecurityIssue]:
"""
檢查不安全的隨機數產生
標準的 random 模組不適合用於安全用途,
應使用 secrets 模組
Args:
tree: 抽象語法樹
source_code: 原始碼
Returns:
發現的問題列表
"""
issues = []
lines = source_code.splitlines()
# 不安全的隨機函式
insecure_random_funcs = {
'random', 'randint', 'randrange', 'choice',
'shuffle', 'sample', 'uniform'
}
for node in ast.walk(tree):
if isinstance(node, ast.Call):
func_name = self._get_function_name(node)
if func_name in insecure_random_funcs:
# 檢查是否來自 random 模組
full_name = self._get_full_function_name(node)
if 'random' in full_name.lower():
line_num = node.lineno
code_snippet = lines[line_num - 1] if line_num <= len(lines) else ''
issues.append(SecurityIssue(
vulnerability_type=VulnerabilityType.INSECURE_RANDOM,
severity='medium',
confidence='medium',
message=f"使用不安全的隨機數產生器 {func_name}",
line_number=line_num,
code_snippet=code_snippet.strip(),
recommendation="對於安全相關的用途(如產生 token、密碼),"
"使用 secrets 模組而非 random 模組",
cwe_id='CWE-330'
))
return issues
def _check_xxe_vulnerability(
self,
tree: ast.AST,
source_code: str
) -> List[SecurityIssue]:
"""
檢查 XML External Entity (XXE) 漏洞
不安全的 XML 解析器配置可能導致 XXE 攻擊,
允許攻擊者讀取本機檔案或執行 SSRF 攻擊
Args:
tree: 抽象語法樹
source_code: 原始碼
Returns:
發現的問題列表
"""
issues = []
lines = source_code.splitlines()
# 檢查不安全的 XML 解析器
unsafe_parsers = {'parse', 'fromstring', 'XML', 'iterparse'}
for node in ast.walk(tree):
if isinstance(node, ast.Call):
func_name = self._get_function_name(node)
full_name = self._get_full_function_name(node)
# 檢查 xml.etree 或 lxml
if func_name in unsafe_parsers:
if 'xml' in full_name.lower() or 'lxml' in full_name.lower():
line_num = node.lineno
code_snippet = lines[line_num - 1] if line_num <= len(lines) else ''
issues.append(SecurityIssue(
vulnerability_type=VulnerabilityType.XXE,
severity='high',
confidence='medium',
message="潛在的 XML External Entity (XXE) 漏洞",
line_number=line_num,
code_snippet=code_snippet.strip(),
recommendation="使用 defusedxml 套件來安全地解析 XML,"
"或禁用外部實體解析",
cwe_id='CWE-611'
))
return issues
def _check_path_traversal(
self,
tree: ast.AST,
source_code: str
) -> List[SecurityIssue]:
"""
檢查路徑遍歷漏洞
不當的檔案路徑處理可能導致攻擊者存取任意檔案
Args:
tree: 抽象語法樹
source_code: 原始碼
Returns:
發現的問題列表
"""
issues = []
lines = source_code.splitlines()
for node in ast.walk(tree):
if isinstance(node, ast.Call):
func_name = self._get_function_name(node)
# 檢查檔案操作
if func_name in ('open', 'read', 'write'):
# 檢查參數是否使用字串格式化或串接
for arg in node.args:
if self._is_dynamic_path(arg):
line_num = node.lineno
code_snippet = lines[line_num - 1] if line_num <= len(lines) else ''
issues.append(SecurityIssue(
vulnerability_type=VulnerabilityType.PATH_TRAVERSAL,
severity='high',
confidence='medium',
message="潛在的路徑遍歷漏洞",
line_number=line_num,
code_snippet=code_snippet.strip(),
recommendation="使用 os.path.basename() 過濾檔名,"
"或使用白名單驗證路徑",
cwe_id='CWE-22'
))
break
return issues
def _is_dynamic_path(self, node: ast.AST) -> bool:
"""
檢查路徑是否為動態產生
Args:
node: AST 節點
Returns:
是否為動態路徑
"""
# 檢查字串格式化
if isinstance(node, ast.JoinedStr): # f-string
return True
# 檢查 % 格式化
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Mod):
return True
# 檢查字串串接
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
return True
# 檢查 .format() 呼叫
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Attribute):
if node.func.attr == 'format':
return True
# 檢查是否包含變數
for child in ast.walk(node):
if isinstance(child, ast.Name):
if child.id in self.tainted_variables:
return True
return False
def _create_security_issue(
self,
vuln_type: VulnerabilityType,
line_num: int,
code_snippet: str,
func_name: str
) -> SecurityIssue:
"""
建立安全問題物件
根據漏洞類型建立對應的安全問題物件,
包含適當的嚴重程度、建議與 CWE 編號
Args:
vuln_type: 漏洞類型
line_num: 行號
code_snippet: 程式碼片段
func_name: 函式名稱
Returns:
SecurityIssue 物件
"""
# 各漏洞類型的詳細資訊
vuln_details = {
VulnerabilityType.SQL_INJECTION: {
'severity': 'critical',
'message': f"潛在的 SQL Injection 漏洞:污點資料傳入 {func_name}",
'recommendation': "使用參數化查詢或 ORM,永遠不要直接串接 SQL 字串",
'cwe_id': 'CWE-89'
},
VulnerabilityType.COMMAND_INJECTION: {
'severity': 'critical',
'message': f"潛在的命令注入漏洞:污點資料傳入 {func_name}",
'recommendation': "使用 subprocess 的 list 參數形式,避免 shell=True",
'cwe_id': 'CWE-78'
},
VulnerabilityType.XSS: {
'severity': 'high',
'message': f"潛在的跨站腳本攻擊 (XSS) 漏洞",
'recommendation': "使用框架提供的自動跳脫功能,對輸出進行適當編碼",
'cwe_id': 'CWE-79'
},
VulnerabilityType.PATH_TRAVERSAL: {
'severity': 'high',
'message': f"潛在的路徑遍歷漏洞:污點資料用於檔案路徑",
'recommendation': "驗證並消毒檔案路徑,使用白名單或 os.path.basename()",
'cwe_id': 'CWE-22'
},
VulnerabilityType.INSECURE_DESERIALIZATION: {
'severity': 'critical',
'message': f"潛在的不安全反序列化漏洞",
'recommendation': "避免反序列化不可信的資料,使用安全的資料格式如 JSON",
'cwe_id': 'CWE-502'
}
}
details = vuln_details.get(vuln_type, {
'severity': 'medium',
'message': f"潛在的安全漏洞:{vuln_type.value}",
'recommendation': "請檢視並修正此潛在安全問題",
'cwe_id': 'CWE-Unknown'
})
return SecurityIssue(
vulnerability_type=vuln_type,
severity=details['severity'],
confidence='high',
message=details['message'],
line_number=line_num,
code_snippet=code_snippet,
recommendation=details['recommendation'],
cwe_id=details['cwe_id']
)
上述安全分析器實作了完整的污點分析流程,能夠追蹤不可信輸入如何在程式中傳播,並在這些資料到達危險函式時發出警告。此外,它還檢查各種常見的安全問題,如硬編碼憑證、弱密碼學演算法與不安全的隨機數產生。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
title 污點分析流程
start
:接收原始碼;
:解析為 AST;
:識別污點來源;
note right
request.form
request.args
input()
sys.argv
end note
:追蹤污點傳播;
note right
變數賦值
函式參數
回傳值
end note
:識別危險 Sink;
note right
execute()
system()
open()
end note
if (污點資料到達 Sink?) then (是)
:產生安全警告;
:記錄漏洞類型;
:提供修復建議;
else (否)
:標記為安全;
endif
:產生安全報告;
stop
@enduml智慧程式碼審查系統
程式碼審查是軟體開發品質保證的關鍵環節,傳統的程式碼審查依賴人工逐行檢視,不僅耗時且容易遺漏問題。AI 驅動的智慧程式碼審查系統能夠自動分析程式碼變更,識別潛在問題,並提供具體的改善建議,大幅提升審查效率與品質。
智慧程式碼審查系統結合了多種技術,包括靜態分析、機器學習模型、自然語言處理以及程式碼相似度比對。系統能夠理解程式碼的語意,識別邏輯錯誤、效能問題、安全漏洞以及風格不一致等問題。更重要的是,系統能夠學習專案的特定模式與慣例,提供更加精準的審查意見。
以下是一個智慧程式碼審查系統的實作:
import ast
import difflib
from typing import Dict, List, Tuple, Any, Optional
from dataclasses import dataclass
from enum import Enum
import re
class ReviewCategory(Enum):
"""
審查意見類別
定義程式碼審查的各個面向
"""
CORRECTNESS = "正確性"
PERFORMANCE = "效能"
SECURITY = "安全性"
MAINTAINABILITY = "可維護性"
STYLE = "風格"
DOCUMENTATION = "文件"
BEST_PRACTICE = "最佳實踐"
@dataclass
class ReviewComment:
"""
審查意見資料類別
Attributes:
category: 意見類別
severity: 嚴重程度
line_number: 行號
message: 意見內容
suggestion: 建議修改
code_before: 修改前程式碼
code_after: 建議的修改後程式碼
"""
category: ReviewCategory
severity: str
line_number: int
message: str
suggestion: str
code_before: Optional[str] = None
code_after: Optional[str] = None
class AICodeReviewer:
"""
AI 驅動的智慧程式碼審查器
此類別實作了一個完整的程式碼審查系統,能夠自動分析程式碼
並提供多面向的審查意見
Attributes:
quality_analyzer: 品質分析器實例
security_analyzer: 安全分析器實例
style_patterns: 風格模式規則
"""
def __init__(self):
"""
初始化程式碼審查器
"""
# 風格模式規則
# 定義常見的風格問題與修正建議
self.style_patterns = {
# 比較運算子空格
r'[^\s]==[^\s]': '比較運算子兩側應有空格',
r'[^\s]!=[^\s]': '不等於運算子兩側應有空格',
# 逗號後空格
r',[^\s\n]': '逗號後應有空格',
# 冒號後空格
r':\S': '冒號後應有空格(字典與切片除外)',
}
# 效能反模式
# 這些模式可能導致效能問題
self.performance_antipatterns = {
'string_concat_in_loop': {
'pattern': r'for.*:\s*\n.*\+=\s*["\']',
'message': '在迴圈中使用 += 串接字串效能不佳',
'suggestion': '使用 list.append() 然後 "".join() 來串接字串'
},
'list_in_loop': {
'pattern': r'for.*:\s*\n.*\.append\(',
'message': '考慮使用列表推導式取代迴圈中的 append',
'suggestion': '使用列表推導式可提高可讀性與效能'
}
}
# 最佳實踐規則
self.best_practices = {
'bare_except': {
'pattern': r'except\s*:',
'message': '避免使用裸 except 子句',
'suggestion': '指定具體的例外類型,如 except ValueError:'
},
'mutable_default': {
'message': '避免使用可變物件作為預設參數',
'suggestion': '使用 None 作為預設值,在函式內部建立可變物件'
}
}
def review(
self,
source_code: str,
filename: str = 'unknown',
focus_areas: List[ReviewCategory] = None
) -> Dict[str, Any]:
"""
執行完整的程式碼審查
此方法分析提供的程式碼,並產生全面的審查報告
Args:
source_code: 要審查的原始碼
filename: 檔案名稱
focus_areas: 要特別關注的審查類別
Returns:
包含審查結果的字典
"""
# 初始化審查結果
result = {
'filename': filename,
'comments': [],
'summary': {},
'overall_score': 100,
'approval_status': 'approved'
}
# 如果未指定焦點區域,則審查所有類別
if focus_areas is None:
focus_areas = list(ReviewCategory)
try:
tree = ast.parse(source_code)
lines = source_code.splitlines()
# 執行各類別的審查
if ReviewCategory.CORRECTNESS in focus_areas:
result['comments'].extend(
self._review_correctness(tree, lines)
)
if ReviewCategory.PERFORMANCE in focus_areas:
result['comments'].extend(
self._review_performance(tree, source_code, lines)
)
if ReviewCategory.SECURITY in focus_areas:
result['comments'].extend(
self._review_security(tree, source_code, lines)
)
if ReviewCategory.MAINTAINABILITY in focus_areas:
result['comments'].extend(
self._review_maintainability(tree, lines)
)
if ReviewCategory.STYLE in focus_areas:
result['comments'].extend(
self._review_style(source_code, lines)
)
if ReviewCategory.DOCUMENTATION in focus_areas:
result['comments'].extend(
self._review_documentation(tree, lines)
)
if ReviewCategory.BEST_PRACTICE in focus_areas:
result['comments'].extend(
self._review_best_practices(tree, source_code, lines)
)
# 計算整體分數
result['overall_score'] = self._calculate_score(result['comments'])
# 產生摘要
result['summary'] = self._generate_summary(result['comments'])
# 決定審核狀態
result['approval_status'] = self._determine_approval(
result['overall_score'], result['comments']
)
except SyntaxError as e:
result['comments'].append(ReviewComment(
category=ReviewCategory.CORRECTNESS,
severity='critical',
line_number=e.lineno if hasattr(e, 'lineno') else 0,
message=f"語法錯誤:{str(e)}",
suggestion="修正語法錯誤後重新提交審查"
))
result['overall_score'] = 0
result['approval_status'] = 'rejected'
return result
def _review_correctness(
self,
tree: ast.AST,
lines: List[str]
) -> List[ReviewComment]:
"""
審查程式碼正確性
檢查可能導致執行時錯誤或邏輯錯誤的問題
Args:
tree: 抽象語法樹
lines: 原始碼行列表
Returns:
審查意見列表
"""
comments = []
for node in ast.walk(tree):
# 檢查除以零的可能性
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Div):
if isinstance(node.right, ast.Num) and node.right.n == 0:
comments.append(ReviewComment(
category=ReviewCategory.CORRECTNESS,
severity='critical',
line_number=node.lineno,
message="除以零將導致執行時錯誤",
suggestion="加入除數檢查或使用預設值",
code_before=lines[node.lineno - 1].strip() if node.lineno <= len(lines) else '',
code_after="if divisor != 0: result = value / divisor"
))
# 檢查未使用的變數
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
var_name = target.id
# 檢查是否為私有變數命名但未使用
if var_name.startswith('_') and not var_name.startswith('__'):
# 這是一個簡化的檢查
pass
# 檢查空的 except 處理
if isinstance(node, ast.ExceptHandler):
if not node.body or (len(node.body) == 1 and isinstance(node.body[0], ast.Pass)):
comments.append(ReviewComment(
category=ReviewCategory.CORRECTNESS,
severity='warning',
line_number=node.lineno,
message="例外處理區塊為空或僅包含 pass",
suggestion="至少記錄例外資訊以便除錯",
code_after="except Exception as e:\n logger.error(f'Error: {e}')"
))
# 檢查 return 陳述式的一致性
if isinstance(node, ast.FunctionDef):
returns = [n for n in ast.walk(node) if isinstance(n, ast.Return)]
# 檢查是否有些路徑有 return 值,有些沒有
has_value = [r for r in returns if r.value is not None]
no_value = [r for r in returns if r.value is None]
if has_value and no_value:
comments.append(ReviewComment(
category=ReviewCategory.CORRECTNESS,
severity='warning',
line_number=node.lineno,
message=f"函式 '{node.name}' 的回傳值不一致",
suggestion="確保所有執行路徑回傳相同類型的值"
))
return comments
def _review_performance(
self,
tree: ast.AST,
source_code: str,
lines: List[str]
) -> List[ReviewComment]:
"""
審查程式碼效能
識別可能導致效能問題的模式
Args:
tree: 抽象語法樹
source_code: 原始碼
lines: 原始碼行列表
Returns:
審查意見列表
"""
comments = []
for node in ast.walk(tree):
# 檢查巢狀迴圈
if isinstance(node, (ast.For, ast.While)):
nested_loops = sum(
1 for child in ast.walk(node)
if isinstance(child, (ast.For, ast.While)) and child is not node
)
if nested_loops >= 2:
comments.append(ReviewComment(
category=ReviewCategory.PERFORMANCE,
severity='warning',
line_number=node.lineno,
message=f"偵測到 {nested_loops + 1} 層巢狀迴圈,可能有效能問題",
suggestion="考慮使用更有效率的演算法或資料結構"
))
# 檢查在迴圈中呼叫 len()
if isinstance(node, ast.For):
for child in ast.walk(node):
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Name) and child.func.id == 'len':
comments.append(ReviewComment(
category=ReviewCategory.PERFORMANCE,
severity='info',
line_number=child.lineno,
message="在迴圈中重複呼叫 len() 可能影響效能",
suggestion="在迴圈外預先計算長度"
))
# 檢查不必要的列表轉換
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
if node.func.id == 'list':
for arg in node.args:
if isinstance(arg, (ast.ListComp, ast.List)):
comments.append(ReviewComment(
category=ReviewCategory.PERFORMANCE,
severity='info',
line_number=node.lineno,
message="不必要的 list() 轉換",
suggestion="列表推導式本身就會產生列表"
))
# 檢查效能反模式
for pattern_name, pattern_info in self.performance_antipatterns.items():
matches = re.finditer(pattern_info['pattern'], source_code)
for match in matches:
# 計算行號
line_num = source_code[:match.start()].count('\n') + 1
comments.append(ReviewComment(
category=ReviewCategory.PERFORMANCE,
severity='warning',
line_number=line_num,
message=pattern_info['message'],
suggestion=pattern_info['suggestion']
))
return comments
def _review_security(
self,
tree: ast.AST,
source_code: str,
lines: List[str]
) -> List[ReviewComment]:
"""
審查程式碼安全性
識別潛在的安全漏洞
Args:
tree: 抽象語法樹
source_code: 原始碼
lines: 原始碼行列表
Returns:
審查意見列表
"""
comments = []
for node in ast.walk(tree):
# 檢查 eval 和 exec 的使用
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
if node.func.id in ('eval', 'exec'):
comments.append(ReviewComment(
category=ReviewCategory.SECURITY,
severity='critical',
line_number=node.lineno,
message=f"使用 {node.func.id}() 可能導致程式碼注入漏洞",
suggestion="避免使用 eval/exec,尋找更安全的替代方案"
))
# 檢查 pickle
if node.func.id == 'loads':
comments.append(ReviewComment(
category=ReviewCategory.SECURITY,
severity='high',
line_number=node.lineno,
message="反序列化不可信資料可能導致安全漏洞",
suggestion="使用更安全的格式如 JSON,或驗證資料來源"
))
# 檢查 shell=True
if isinstance(node, ast.Call):
for keyword in node.keywords:
if keyword.arg == 'shell':
if isinstance(keyword.value, ast.NameConstant) and keyword.value.value:
comments.append(ReviewComment(
category=ReviewCategory.SECURITY,
severity='high',
line_number=node.lineno,
message="使用 shell=True 可能導致命令注入漏洞",
suggestion="使用 list 形式的參數並移除 shell=True"
))
return comments
def _review_maintainability(
self,
tree: ast.AST,
lines: List[str]
) -> List[ReviewComment]:
"""
審查程式碼可維護性
識別可能影響程式碼維護性的問題
Args:
tree: 抽象語法樹
lines: 原始碼行列表
Returns:
審查意見列表
"""
comments = []
for node in ast.walk(tree):
# 檢查函式長度
if isinstance(node, ast.FunctionDef):
if hasattr(node, 'end_lineno'):
func_length = node.end_lineno - node.lineno
if func_length > 50:
comments.append(ReviewComment(
category=ReviewCategory.MAINTAINABILITY,
severity='warning',
line_number=node.lineno,
message=f"函式 '{node.name}' 有 {func_length} 行,過長難以維護",
suggestion="將函式拆分為多個小型函式,每個專注單一職責"
))
# 檢查參數數量
param_count = len(node.args.args)
if param_count > 5:
comments.append(ReviewComment(
category=ReviewCategory.MAINTAINABILITY,
severity='warning',
line_number=node.lineno,
message=f"函式 '{node.name}' 有 {param_count} 個參數,過多難以使用",
suggestion="使用參數物件或 **kwargs 來減少參數數量"
))
# 檢查類別大小
if isinstance(node, ast.ClassDef):
method_count = sum(
1 for child in node.body
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef))
)
if method_count > 20:
comments.append(ReviewComment(
category=ReviewCategory.MAINTAINABILITY,
severity='warning',
line_number=node.lineno,
message=f"類別 '{node.name}' 有 {method_count} 個方法,可能職責過多",
suggestion="考慮將類別拆分為多個職責單一的類別"
))
return comments
def _review_style(
self,
source_code: str,
lines: List[str]
) -> List[ReviewComment]:
"""
審查程式碼風格
檢查是否符合 PEP 8 等風格指南
Args:
source_code: 原始碼
lines: 原始碼行列表
Returns:
審查意見列表
"""
comments = []
for i, line in enumerate(lines, 1):
# 檢查行長度
if len(line) > 120:
comments.append(ReviewComment(
category=ReviewCategory.STYLE,
severity='info',
line_number=i,
message=f"行長度 {len(line)} 超過建議的 120 字元",
suggestion="將長行拆分為多行"
))
# 檢查尾隨空白
if line.endswith(' ') or line.endswith('\t'):
comments.append(ReviewComment(
category=ReviewCategory.STYLE,
severity='info',
line_number=i,
message="行尾有多餘的空白",
suggestion="移除行尾的空白字元"
))
# 檢查混合縮排
if line.startswith(' ') and '\t' in line.lstrip()[:10]:
comments.append(ReviewComment(
category=ReviewCategory.STYLE,
severity='warning',
line_number=i,
message="混合使用空格和 Tab 進行縮排",
suggestion="統一使用空格(建議 4 個空格)進行縮排"
))
return comments
def _review_documentation(
self,
tree: ast.AST,
lines: List[str]
) -> List[ReviewComment]:
"""
審查程式碼文件
檢查是否有適當的文件字串與註解
Args:
tree: 抽象語法樹
lines: 原始碼行列表
Returns:
審查意見列表
"""
comments = []
for node in ast.walk(tree):
# 檢查公開函式是否有文件字串
if isinstance(node, ast.FunctionDef):
if not node.name.startswith('_'): # 公開函式
if not ast.get_docstring(node):
comments.append(ReviewComment(
category=ReviewCategory.DOCUMENTATION,
severity='info',
line_number=node.lineno,
message=f"公開函式 '{node.name}' 缺少文件字串",
suggestion="為公開 API 添加說明其用途、參數與回傳值的文件字串"
))
# 檢查類別是否有文件字串
if isinstance(node, ast.ClassDef):
if not ast.get_docstring(node):
comments.append(ReviewComment(
category=ReviewCategory.DOCUMENTATION,
severity='info',
line_number=node.lineno,
message=f"類別 '{node.name}' 缺少文件字串",
suggestion="為類別添加說明其用途與職責的文件字串"
))
return comments
def _review_best_practices(
self,
tree: ast.AST,
source_code: str,
lines: List[str]
) -> List[ReviewComment]:
"""
審查是否遵循最佳實踐
檢查常見的反模式與最佳實踐違規
Args:
tree: 抽象語法樹
source_code: 原始碼
lines: 原始碼行列表
Returns:
審查意見列表
"""
comments = []
for node in ast.walk(tree):
# 檢查可變預設參數
if isinstance(node, ast.FunctionDef):
for default in node.args.defaults:
if isinstance(default, (ast.List, ast.Dict, ast.Set)):
comments.append(ReviewComment(
category=ReviewCategory.BEST_PRACTICE,
severity='warning',
line_number=node.lineno,
message=f"函式 '{node.name}' 使用可變物件作為預設參數",
suggestion="使用 None 作為預設值,在函式內部建立可變物件",
code_before=f"def {node.name}(items=[]):",
code_after=f"def {node.name}(items=None):\n if items is None:\n items = []"
))
# 檢查使用 type() 進行類型檢查
if isinstance(node, ast.Compare):
for comparator in node.comparators:
if isinstance(comparator, ast.Call):
if isinstance(comparator.func, ast.Name) and comparator.func.id == 'type':
comments.append(ReviewComment(
category=ReviewCategory.BEST_PRACTICE,
severity='info',
line_number=node.lineno,
message="使用 type() 進行類型檢查",
suggestion="使用 isinstance() 進行類型檢查,它支援繼承"
))
# 檢查裸 except
if re.search(self.best_practices['bare_except']['pattern'], source_code):
for i, line in enumerate(lines, 1):
if re.search(r'except\s*:', line):
comments.append(ReviewComment(
category=ReviewCategory.BEST_PRACTICE,
severity='warning',
line_number=i,
message=self.best_practices['bare_except']['message'],
suggestion=self.best_practices['bare_except']['suggestion']
))
return comments
def _calculate_score(self, comments: List[ReviewComment]) -> int:
"""
計算審查分數
Args:
comments: 審查意見列表
Returns:
審查分數(0-100)
"""
score = 100
severity_penalties = {
'critical': 20,
'high': 10,
'warning': 5,
'info': 1
}
for comment in comments:
penalty = severity_penalties.get(comment.severity, 1)
score -= penalty
return max(0, score)
def _generate_summary(self, comments: List[ReviewComment]) -> Dict[str, Any]:
"""
產生審查摘要
Args:
comments: 審查意見列表
Returns:
摘要字典
"""
summary = {
'total_comments': len(comments),
'by_category': {},
'by_severity': {}
}
for comment in comments:
# 按類別統計
category = comment.category.value
if category not in summary['by_category']:
summary['by_category'][category] = 0
summary['by_category'][category] += 1
# 按嚴重程度統計
if comment.severity not in summary['by_severity']:
summary['by_severity'][comment.severity] = 0
summary['by_severity'][comment.severity] += 1
return summary
def _determine_approval(
self,
score: int,
comments: List[ReviewComment]
) -> str:
"""
決定審核狀態
Args:
score: 審查分數
comments: 審查意見列表
Returns:
審核狀態字串
"""
# 檢查是否有關鍵問題
has_critical = any(c.severity == 'critical' for c in comments)
if has_critical:
return 'rejected'
elif score < 60:
return 'needs_revision'
elif score < 80:
return 'approved_with_suggestions'
else:
return 'approved'
這個智慧程式碼審查器實作了完整的多面向審查功能,能夠檢查正確性、效能、安全性、可維護性、風格、文件與最佳實踐等各個面向。系統會為每個問題提供具體的修改建議,並計算整體審查分數來決定審核狀態。
整合式品質保證平台
在實際的軟體開發環境中,各種品質保證工具需要整合在一起,形成一個統一的平台。這個平台應該能夠在持續整合流程中自動執行,並產生綜合性的報告。以下是一個整合式品質保證平台的實作:
import json
from typing import Dict, List, Any
from datetime import datetime
from dataclasses import dataclass, asdict
@dataclass
class QualityReport:
"""
品質報告資料類別
封裝完整的品質分析結果
Attributes:
timestamp: 報告產生時間
filename: 分析的檔案名稱
quality_analysis: 品質分析結果
security_analysis: 安全分析結果
code_review: 程式碼審查結果
overall_status: 整體狀態
recommendations: 優先改善建議
"""
timestamp: str
filename: str
quality_analysis: Dict[str, Any]
security_analysis: List[Dict[str, Any]]
code_review: Dict[str, Any]
overall_status: str
recommendations: List[str]
class IntegratedQualityPlatform:
"""
整合式品質保證平台
此類別整合所有品質保證工具,提供統一的分析介面
與報告格式
Attributes:
quality_analyzer: 品質分析器
security_analyzer: 安全分析器
code_reviewer: 程式碼審查器
"""
def __init__(self):
"""
初始化品質保證平台
"""
# 初始化各個分析器
self.quality_analyzer = AICodeQualityAnalyzer()
self.security_analyzer = AISecurityAnalyzer()
self.code_reviewer = AICodeReviewer()
def analyze(self, source_code: str, filename: str = 'unknown') -> QualityReport:
"""
執行完整的品質分析
此方法整合所有分析器,產生綜合性的品質報告
Args:
source_code: 要分析的原始碼
filename: 檔案名稱
Returns:
品質報告物件
"""
# 執行品質分析
quality_result = self.quality_analyzer.analyze_file(source_code, filename)
# 執行安全分析
security_issues = self.security_analyzer.analyze(source_code, filename)
# 執行程式碼審查
review_result = self.code_reviewer.review(source_code, filename)
# 決定整體狀態
overall_status = self._determine_overall_status(
quality_result, security_issues, review_result
)
# 產生優先改善建議
recommendations = self._generate_recommendations(
quality_result, security_issues, review_result
)
# 建立報告
report = QualityReport(
timestamp=datetime.now().isoformat(),
filename=filename,
quality_analysis=quality_result,
security_analysis=[asdict(issue) if hasattr(issue, '__dict__')
else self._security_issue_to_dict(issue)
for issue in security_issues],
code_review=review_result,
overall_status=overall_status,
recommendations=recommendations
)
return report
def _security_issue_to_dict(self, issue) -> Dict[str, Any]:
"""
將 SecurityIssue 轉換為字典
Args:
issue: SecurityIssue 物件
Returns:
字典表示
"""
return {
'vulnerability_type': issue.vulnerability_type.value,
'severity': issue.severity,
'confidence': issue.confidence,
'message': issue.message,
'line_number': issue.line_number,
'code_snippet': issue.code_snippet,
'recommendation': issue.recommendation,
'cwe_id': issue.cwe_id
}
def _determine_overall_status(
self,
quality_result: Dict[str, Any],
security_issues: List,
review_result: Dict[str, Any]
) -> str:
"""
決定整體品質狀態
Args:
quality_result: 品質分析結果
security_issues: 安全問題列表
review_result: 審查結果
Returns:
整體狀態字串
"""
# 檢查是否有關鍵安全問題
has_critical_security = any(
issue.severity == 'critical' for issue in security_issues
)
if has_critical_security:
return 'failed'
# 檢查品質分數
quality_score = quality_result.get('quality_score', 0)
# 檢查審查狀態
review_status = review_result.get('approval_status', 'rejected')
if quality_score < 50 or review_status == 'rejected':
return 'failed'
elif quality_score < 70 or review_status == 'needs_revision':
return 'warning'
elif quality_score < 85 or review_status == 'approved_with_suggestions':
return 'passed_with_warnings'
else:
return 'passed'
def _generate_recommendations(
self,
quality_result: Dict[str, Any],
security_issues: List,
review_result: Dict[str, Any]
) -> List[str]:
"""
產生優先改善建議
根據分析結果產生需要優先處理的建議
Args:
quality_result: 品質分析結果
security_issues: 安全問題列表
review_result: 審查結果
Returns:
優先建議列表
"""
recommendations = []
# 優先處理關鍵安全問題
critical_security = [
issue for issue in security_issues
if issue.severity == 'critical'
]
for issue in critical_security[:3]:
recommendations.append(
f"[緊急] {issue.message} - {issue.recommendation}"
)
# 處理品質問題
if quality_result.get('quality_score', 100) < 70:
for suggestion in quality_result.get('suggestions', [])[:3]:
recommendations.append(f"[品質] {suggestion}")
# 處理審查意見
review_comments = review_result.get('comments', [])
critical_comments = [
c for c in review_comments
if hasattr(c, 'severity') and c.severity in ('critical', 'high')
]
for comment in critical_comments[:3]:
if hasattr(comment, 'message'):
recommendations.append(
f"[審查] {comment.message} - {comment.suggestion}"
)
return recommendations[:10] # 限制建議數量
def export_report(self, report: QualityReport, format: str = 'json') -> str:
"""
匯出品質報告
Args:
report: 品質報告物件
format: 匯出格式(json, markdown)
Returns:
格式化的報告字串
"""
if format == 'json':
return json.dumps(asdict(report), indent=2, ensure_ascii=False)
elif format == 'markdown':
return self._format_markdown_report(report)
else:
raise ValueError(f"不支援的格式:{format}")
def _format_markdown_report(self, report: QualityReport) -> str:
"""
將報告格式化為 Markdown
Args:
report: 品質報告物件
Returns:
Markdown 格式的報告
"""
md = f"""# 程式碼品質報告
**檔案**: {report.filename}
**時間**: {report.timestamp}
**狀態**: {report.overall_status}
## 品質分析
- **品質分數**: {report.quality_analysis.get('quality_score', 'N/A')}
- **風險等級**: {report.quality_analysis.get('risk_level', 'N/A')}
## 安全分析
發現 {len(report.security_analysis)} 個安全問題。
## 程式碼審查
- **審查分數**: {report.code_review.get('overall_score', 'N/A')}
- **審核狀態**: {report.code_review.get('approval_status', 'N/A')}
## 優先改善建議
"""
for i, rec in enumerate(report.recommendations, 1):
md += f"{i}. {rec}\n"
return md
這個整合式平台將品質分析、安全分析與程式碼審查整合在一起,提供統一的分析介面與報告格式。平台能夠根據各個分析器的結果決定整體狀態,並產生優先改善建議,幫助開發者快速了解需要優先處理的問題。
總結
AI 驅動的程式碼品質保證與安全分析代表了軟體品質管理的重要進步。透過結合靜態分析、污點追蹤、機器學習與自然語言處理等技術,現代的品質保證系統能夠自動識別各種程式碼問題,從風格不一致到嚴重的安全漏洞。
本文探討的技術涵蓋了程式碼品質保證的多個面向。首先是靜態程式碼分析,透過 AST 分析計算圈複雜度、巢狀深度、可維護性指數等指標,並識別程式碼異味如上帝類別與特性依戀。其次是安全漏洞偵測,使用污點分析追蹤不可信輸入的傳播,並檢查各種常見漏洞如 SQL Injection、Command Injection、XSS 等。第三是智慧程式碼審查,從正確性、效能、安全性、可維護性、風格、文件與最佳實踐等多個面向進行全面審查。最後是整合式品質保證平台,將各種工具整合在一起,提供統一的分析介面與報告格式。
這些技術的價值在於能夠自動化大部分的品質保證工作,讓開發者能夠專注於更具創造性的任務。同時,AI 系統能夠處理大規模程式碼庫,在持續整合流程中快速提供回饋,大幅縮短問題發現到修復的週期。
然而,這些技術也有其限制。AI 系統可能產生誤報或漏報,需要人工確認重要的發現。此外,AI 模型需要持續訓練與更新,以適應新的程式語言特性、框架與安全威脅。最重要的是,AI 驅動的品質保證應該被視為輔助工具,而非完全取代人工審查,特別是對於關鍵系統與安全敏感的程式碼。
展望未來,隨著大型語言模型的進步,我們可以期待更加智慧的品質保證系統。這些系統將能夠理解程式碼的商業邏輯,提供更精準的分析與建議。同時,程式碼品質保證將與開發環境更緊密整合,在開發者編寫程式碼的同時即時提供回饋,實現真正的「shift left」品質管理。