L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Développement · SKILL.md

code-review

code-review

科研代码审查助手,专注于确保科研代码的正确性、可重复性和可维护性。支持代码质量检查、安全漏洞扫描、可重复性验证、数据完整性检查、最佳实践验证。当用户需要:(1) 审查科研代码质量、(2) 确保实验代码可重复、(3) 检查数据分析脚本、(4) 验证统计分析代码、(5) 准备代码发布或提交论文时触发。关键词:code review、代码审查、代码质量、peer review、科研代码。

Independent GitHub snapshot · Automated classification · Original documentation governs

Hhollis36 / claude-skillApache-2.0

Documentation de la Skill

GitHub

name: code-review description: | 科研代码审查助手,专注于确保科研代码的正确性、可重复性和可维护性。支持代码质量检查、安全漏洞扫描、可重复性验证、数据完整性检查、最佳实践验证。当用户需要:(1) 审查科研代码质量、(2) 确保实验代码可重复、(3) 检查数据分析脚本、(4) 验证统计分析代码、(5) 准备代码发布或提交论文时触发。关键词:code review、代码审查、代码质量、peer review、科研代码。 license: Apache-2.0

科研代码审查助手

帮助科研人员系统化地审查代码,确保研究代码的质量、正确性和可重复性。

核心原则

科研代码审查的四个维度

  1. 正确性 - 代码是否正确实现了研究方法
  2. 可重复性 - 他人能否复现研究结果
  3. 可维护性 - 代码是否清晰、有文档、易于理解
  4. 数据完整性 - 数据处理是否安全、准确

审查流程

第一阶段:预审查准备

在开始代码审查前,收集必要信息:

## 代码审查准备清单

### 基本信息
- [ ] 代码用途和研究目标
- [ ] 预期输入和输出
- [ ] 使用的数据集和数据格式
- [ ] 依赖的软件包和版本
- [ ] 已知的限制和假设

### 审查范围
- [ ] 需要审查的文件列表
- [ ] 关键函数和模块
- [ ] 测试覆盖情况
- [ ] 文档完整性

### 参考材料
- [ ] 相关论文和方法描述
- [ ] 现有测试和验证
- [ ] 以前的审查反馈

第二阶段:代码结构审查

项目组织

# 检查项目结构是否清晰
def check_project_structure():
    """
    理想的科研项目结构:

    project/
    ├── README.md              # 项目说明
    ├── requirements.txt       # 依赖列表
    ├── environment.yml        # Conda环境
    ├── data/                  # 数据目录
    │   ├── raw/              # 原始数据
    │   ├── processed/        # 处理后数据
    │   └── README.md         # 数据说明
    ├── src/                   # 源代码
    │   ├── __init__.py
    │   ├── preprocessing.py
    │   ├── analysis.py
    │   └── visualization.py
    ├── notebooks/             # Jupyter notebooks
    ├── tests/                 # 测试代码
    ├── scripts/               # 运行脚本
    ├── results/               # 结果输出
    └── docs/                  # 文档
    """
    pass

审查要点:

  • 目录结构清晰合理
  • README 文件完整
  • 数据和代码分离
  • 结果和代码分离
  • 有适当的 .gitignore

代码模块化

# 反模式:所有代码在一个文件中
# ❌ BAD
def analysis():
    # 1000+ 行代码在一个函数中
    data = load_data()
    # ... 大量处理逻辑
    plot_results()

# 最佳实践:模块化和单一职责
# ✅ GOOD
from src.data import load_data, preprocess_data
from src.analysis import run_statistical_test
from src.visualization import plot_results

def main():
    """主分析流程"""
    # 步骤1: 加载数据
    raw_data = load_data('data/raw/experiment.csv')

    # 步骤2: 预处理
    clean_data = preprocess_data(raw_data)

    # 步骤3: 统计分析
    results = run_statistical_test(clean_data)

    # 步骤4: 可视化
    plot_results(results, output='results/figures/')

    return results

审查要点:

  • 函数职责单一(每个函数做一件事)
  • 函数长度合理(< 50 行)
  • 避免重复代码(DRY原则)
  • 适当的抽象层次

第三阶段:代码正确性审查

数值计算正确性

import numpy as np
import pandas as pd

# 常见错误:整数除法
# ❌ BAD (Python 2 style)
mean = sum(values) / len(values)  # 可能产生整数除法错误

# ✅ GOOD
mean = sum(values) / float(len(values))
# 或使用 numpy
mean = np.mean(values)

# 常见错误:浮点数比较
# ❌ BAD
if calculated_value == expected_value:
    print("Match!")

# ✅ GOOD
import math
if math.isclose(calculated_value, expected_value, rel_tol=1e-9):
    print("Match!")

# 常见错误:数组索引
# ❌ BAD - 可能索引错误
for i in range(len(data)):
    result[i] = process(data[i])

# ✅ GOOD - 使用enumerate
for i, value in enumerate(data):
    result[i] = process(value)

# 或更好的向量化操作
result = np.array([process(x) for x in data])

统计分析正确性

from scipy import stats

# 检查假设是否满足
def check_statistical_assumptions(data):
    """检查统计检验的假设"""
    issues = []

    # 1. 检查样本量
    if len(data) < 30:
        issues.append(f"样本量较小 (n={len(data)}), 考虑使用非参数检验")

    # 2. 检查正态性
    _, p_value = stats.shapiro(data)
    if p_value < 0.05:
        issues.append(f"数据不满足正态分布 (p={p_value:.4f})")

    # 3. 检查异常值
    Q1 = np.percentile(data, 25)
    Q3 = np.percentile(data, 75)
    IQR = Q3 - Q1
    outliers = np.sum((data < Q1 - 1.5*IQR) | (data > Q3 + 1.5*IQR))
    if outliers > 0:
        issues.append(f"检测到 {outliers} 个异常值")

    return issues

# 使用示例
issues = check_statistical_assumptions(data)
if issues:
    print("警告:")
    for issue in issues:
        print(f"  - {issue}")

审查要点:

  • 统计方法选择正确
  • 假设检验已执行
  • 多重比较已校正
  • 效应量已计算
  • 置信区间已报告

数据处理正确性

import pandas as pd

# 常见错误:链式赋值警告
# ❌ BAD
df[df['age'] > 30]['category'] = 'old'  # SettingWithCopyWarning

# ✅ GOOD
df.loc[df['age'] > 30, 'category'] = 'old'

# 常见错误:缺失值处理不当
# ❌ BAD - 静默删除
df = df.dropna()

# ✅ GOOD - 记录和报告
print(f"原始数据: {len(df)} 行")
missing_counts = df.isnull().sum()
print(f"缺失值统计:\n{missing_counts[missing_counts > 0]}")
df_clean = df.dropna()
print(f"删除缺失值后: {len(df_clean)} 行 ({len(df)-len(df_clean)} 行被删除)")

# 常见错误:未处理重复数据
# ❌ BAD - 未检查重复
data = pd.read_csv('data.csv')

# ✅ GOOD - 检查并处理重复
data = pd.read_csv('data.csv')
duplicates = data.duplicated().sum()
if duplicates > 0:
    print(f"警告: 发现 {duplicates} 个重复行")
    data = data.drop_duplicates()

审查要点:

  • 缺失值处理策略明确
  • 异常值检测和处理
  • 数据类型正确
  • 重复数据已处理
  • 数据转换有记录

第四阶段:可重复性审查

随机种子固定

import numpy as np
import random
import tensorflow as tf

# ❌ BAD - 没有设置随机种子
model = train_model(data)

# ✅ GOOD - 固定所有随机种子
def set_random_seeds(seed=42):
    """设置所有随机数生成器的种子"""
    random.seed(seed)
    np.random.seed(seed)
    tf.random.set_seed(seed)
    # 如果使用PyTorch
    # import torch
    # torch.manual_seed(seed)
    # torch.cuda.manual_seed_all(seed)

    print(f"随机种子已设置为: {seed}")

# 在代码开始时调用
set_random_seeds(42)
model = train_model(data)

依赖版本管理

# requirements.txt (固定版本)
# ✅ GOOD
numpy==1.24.0
pandas==2.0.0
scipy==1.10.0
matplotlib==3.7.0
scikit-learn==1.2.2

# ❌ BAD (不固定版本)
numpy
pandas
scipy
# environment.yml (Conda环境)
name: research-project
channels:
  - conda-forge
  - defaults
dependencies:
  - python=3.11
  - numpy=1.24.0
  - pandas=2.0.0
  - scipy=1.10.0
  - matplotlib=3.7.0
  - pip:
    - scikit-learn==1.2.2

审查要点:

  • 随机种子已固定
  • 依赖版本已锁定
  • 环境可重建(environment.yml 或 requirements.txt)
  • 数据来源可追溯
  • 中间结果可保存

路径处理

import os
from pathlib import Path

# ❌ BAD - 硬编码绝对路径
data = pd.read_csv('/Users/username/project/data/data.csv')

# ❌ BAD - 依赖当前工作目录
data = pd.read_csv('data/data.csv')

# ✅ GOOD - 使用相对于脚本的路径
script_dir = Path(__file__).parent
data_file = script_dir / 'data' / 'data.csv'
data = pd.read_csv(data_file)

# ✅ BETTER - 使用项目根目录
PROJECT_ROOT = Path(__file__).parent.parent
DATA_DIR = PROJECT_ROOT / 'data'
data_file = DATA_DIR / 'raw' / 'data.csv'
data = pd.read_csv(data_file)

审查要点:

  • 无硬编码路径
  • 使用 Path 对象(跨平台)
  • 路径相对于项目根目录
  • 配置文件管理路径

第五阶段:代码质量审查

命名规范

# ❌ BAD - 无意义的名称
def f(x, y):
    z = x + y
    return z

# ✅ GOOD - 描述性名称
def calculate_total_score(baseline_score, bonus_points):
    """计算总分(基准分 + 奖励分)"""
    total_score = baseline_score + bonus_points
    return total_score

# ❌ BAD - 缩写不清晰
df = pd.read_csv('data.csv')
for i in range(len(df)):
    val = df.iloc[i]['col']

# ✅ GOOD - 清晰的命名
experiment_data = pd.read_csv('data.csv')
for index, row in experiment_data.iterrows():
    measurement_value = row['measurement']

命名规范:

  • 变量:小写下划线 variable_name
  • 函数:小写下划线 function_name
  • 类:驼峰命名 ClassName
  • 常量:大写下划线 CONSTANT_NAME

文档字符串

# ❌ BAD - 无文档
def calculate_statistics(data):
    return np.mean(data), np.std(data)

# ✅ GOOD - 完整的文档字符串
def calculate_statistics(data):
    """计算描述性统计量

    Args:
        data (array-like): 数值型数据数组

    Returns:
        tuple: (均值, 标准差)

    Raises:
        ValueError: 如果数据为空或包含非数值

    Example:
        >>> data = [1, 2, 3, 4, 5]
        >>> mean, std = calculate_statistics(data)
        >>> print(f"Mean: {mean:.2f}, Std: {std:.2f}")
        Mean: 3.00, Std: 1.41
    """
    if len(data) == 0:
        raise ValueError("数据不能为空")

    mean = np.mean(data)
    std = np.std(data, ddof=1)  # 样本标准差

    return mean, std

审查要点:

  • 所有函数有文档字符串
  • 参数类型和含义清晰
  • 返回值说明
  • 异常情况说明
  • 包含使用示例

代码注释

# ❌ BAD - 无用的注释
x = x + 1  # 将x加1

# ❌ BAD - 注释代码而不是删除
# old_method(data)
new_method(data)

# ✅ GOOD - 解释为什么这样做
# 使用Welch's t-test因为两组方差不齐(Levene检验 p < 0.05)
t_stat, p_value = stats.ttest_ind(group1, group2, equal_var=False)

# ✅ GOOD - 解释复杂算法
# Smith-Waterman算法:
# 1. 初始化得分矩阵
# 2. 填充矩阵(动态规划)
# 3. 回溯找到最优对齐
alignment = smith_waterman(seq1, seq2)

审查要点:

  • 注释解释"为什么"而不是"是什么"
  • 复杂算法有说明
  • 无注释掉的代码
  • 关键决策有记录

第六阶段:测试和验证审查

单元测试

import pytest
import numpy as np
from src.analysis import calculate_statistics

def test_calculate_statistics_basic():
    """测试基本统计计算"""
    data = [1, 2, 3, 4, 5]
    mean, std = calculate_statistics(data)

    assert np.isclose(mean, 3.0)
    assert np.isclose(std, 1.5811, rtol=1e-4)

def test_calculate_statistics_empty():
    """测试空数据异常处理"""
    with pytest.raises(ValueError, match="数据不能为空"):
        calculate_statistics([])

def test_calculate_statistics_single_value():
    """测试单个值的情况"""
    data = [5.0]
    mean, std = calculate_statistics(data)

    assert mean == 5.0
    assert np.isnan(std)  # 单个值标准差未定义

def test_calculate_statistics_negative():
    """测试负数数据"""
    data = [-5, -4, -3, -2, -1]
    mean, std = calculate_statistics(data)

    assert np.isclose(mean, -3.0)
    assert std > 0  # 标准差总是正数

测试审查要点:

  • 有单元测试覆盖
  • 测试正常情况
  • 测试边界情况
  • 测试异常情况
  • 测试命名清晰

集成测试

import pytest
import pandas as pd
from pathlib import Path

@pytest.fixture
def sample_data():
    """创建测试数据"""
    return pd.DataFrame({
        'subject_id': range(1, 11),
        'treatment': ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'],
        'response': [23, 25, 27, 29, 31, 18, 20, 22, 24, 26]
    })

def test_full_analysis_pipeline(sample_data, tmp_path):
    """测试完整的分析流程"""
    # 保存测试数据
    data_file = tmp_path / "test_data.csv"
    sample_data.to_csv(data_file, index=False)

    # 运行分析
    from src.pipeline import run_analysis
    results = run_analysis(data_file, output_dir=tmp_path)

    # 验证输出
    assert results['p_value'] < 0.05  # 应该有显著差异
    assert (tmp_path / "results.csv").exists()
    assert (tmp_path / "figures" / "plot.png").exists()

审查要点:

  • 测试完整工作流
  • 使用临时目录
  • 清理测试文件
  • 验证输出文件

第七阶段:安全性和数据完整性审查

数据验证

import pandas as pd
import hashlib

def validate_input_data(df, schema):
    """验证输入数据的完整性和格式"""
    errors = []

    # 检查必需列
    required_columns = schema['required_columns']
    missing_cols = set(required_columns) - set(df.columns)
    if missing_cols:
        errors.append(f"缺失必需列: {missing_cols}")

    # 检查数据类型
    for col, expected_type in schema['types'].items():
        if col in df.columns:
            if not df[col].dtype == expected_type:
                errors.append(f"列 '{col}' 类型错误: 期望 {expected_type}, 实际 {df[col].dtype}")

    # 检查值范围
    for col, (min_val, max_val) in schema.get('ranges', {}).items():
        if col in df.columns:
            if df[col].min() < min_val or df[col].max() > max_val:
                errors.append(f"列 '{col}' 值超出范围 [{min_val}, {max_val}]")

    return errors

# 使用示例
schema = {
    'required_columns': ['subject_id', 'age', 'treatment'],
    'types': {'subject_id': 'int64', 'age': 'int64', 'treatment': 'object'},
    'ranges': {'age': (0, 120)}
}

df = pd.read_csv('data.csv')
errors = validate_input_data(df, schema)
if errors:
    print("数据验证失败:")
    for error in errors:
        print(f"  - {error}")
    raise ValueError("数据验证失败")

数据完整性检查

def calculate_checksum(file_path):
    """计算文件的MD5校验和"""
    md5 = hashlib.md5()
    with open(file_path, 'rb') as f:
        for chunk in iter(lambda: f.read(4096), b""):
            md5.update(chunk)
    return md5.hexdigest()

# 保存数据时记录校验和
data.to_csv('data.csv', index=False)
checksum = calculate_checksum('data.csv')
with open('data.csv.md5', 'w') as f:
    f.write(checksum)

# 读取数据时验证校验和
def load_data_with_verification(data_file):
    """加载数据并验证完整性"""
    checksum_file = f"{data_file}.md5"

    if os.path.exists(checksum_file):
        with open(checksum_file, 'r') as f:
            expected_checksum = f.read().strip()

        actual_checksum = calculate_checksum(data_file)

        if actual_checksum != expected_checksum:
            raise ValueError(f"数据文件校验失败: {data_file}")

        print(f"数据完整性验证通过: {data_file}")

    return pd.read_csv(data_file)

审查要点:

  • 输入数据验证
  • 数据完整性检查
  • 敏感数据保护
  • 无硬编码密码或密钥
  • 日志记录适当

第八阶段:性能审查

向量化操作

import numpy as np
import time

# ❌ BAD - 循环计算(慢)
def calculate_distances_loop(points):
    n = len(points)
    distances = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            distances[i, j] = np.sqrt(np.sum((points[i] - points[j])**2))
    return distances

# ✅ GOOD - 向量化计算(快)
def calculate_distances_vectorized(points):
    # 使用广播计算所有点对之间的距离
    diff = points[:, np.newaxis, :] - points[np.newaxis, :, :]
    distances = np.sqrt(np.sum(diff**2, axis=2))
    return distances

# 性能比较
points = np.random.rand(100, 3)

start = time.time()
dist1 = calculate_distances_loop(points)
time1 = time.time() - start

start = time.time()
dist2 = calculate_distances_vectorized(points)
time2 = time.time() - start

print(f"循环方法: {time1:.4f}s")
print(f"向量化方法: {time2:.4f}s")
print(f"加速比: {time1/time2:.1f}x")

审查要点:

  • 使用向量化操作(NumPy/Pandas)
  • 避免不必要的循环
  • 大数据集使用批处理
  • 内存使用合理

代码审查检查清单

📋 完整审查清单

项目组织 (5分)

  • 项目结构清晰合理
  • README文件完整(目的、安装、使用)
  • 依赖管理(requirements.txt/environment.yml)
  • 数据和代码分离
  • 适当的.gitignore

代码质量 (10分)

  • 命名规范(变量、函数、类)
  • 函数长度合理(< 50行)
  • 单一职责原则
  • DRY原则(无重复代码)
  • 文档字符串完整
  • 注释解释"为什么"
  • 代码格式一致(PEP 8)
  • 无注释掉的代码
  • 无调试print语句
  • 无硬编码的魔法数字

正确性 (15分)

  • 数值计算正确
  • 统计方法适当
  • 假设检验已执行
  • 数据处理正确
  • 边界情况处理
  • 异常处理适当
  • 数据类型正确
  • 索引和切片正确
  • 浮点数比较使用容差
  • 缺失值处理明确

可重复性 (10分)

  • 随机种子固定
  • 依赖版本锁定
  • 路径使用相对路径
  • 数据来源可追溯
  • 环境可重建
  • 中间结果可保存
  • 参数可配置
  • 无硬编码路径

测试 (10分)

  • 有单元测试
  • 测试覆盖主要函数
  • 测试边界情况
  • 测试异常处理
  • 集成测试存在
  • 测试可运行
  • 测试命名清晰

文档 (5分)

  • README完整
  • 函数文档字符串
  • 使用示例
  • 安装说明
  • 依赖说明

安全性 (5分)

  • 输入验证
  • 数据完整性检查
  • 无硬编码密码
  • 敏感数据保护
  • 日志记录适当

总分:60分

  • 优秀:≥ 50分
  • 良好:40-49分
  • 合格:30-39分
  • 需改进:< 30分

自动化代码审查工具

Python代码质量工具

# 安装工具
pip install flake8 pylint black mypy pytest pytest-cov

# 1. Flake8 - 代码风格检查
flake8 src/ --max-line-length=100 --ignore=E203,W503

# 2. Black - 代码格式化
black src/ --line-length=100

# 3. Pylint - 代码质量检查
pylint src/ --disable=C0111,R0903

# 4. MyPy - 类型检查
mypy src/ --ignore-missing-imports

# 5. Pytest - 运行测试
pytest tests/ -v --cov=src --cov-report=html

# 6. 生成覆盖率报告
coverage report -m

配置文件示例

# setup.cfg 或 .flake8
[flake8]
max-line-length = 100
ignore = E203, W503
exclude = .git,__pycache__,docs,build,dist

[pylint]
disable = C0111, R0903
max-line-length = 100

[mypy]
ignore_missing_imports = True

R代码质量工具

# 安装工具
install.packages(c("lintr", "styler", "goodpractice"))

# 1. lintr - 代码风格检查
library(lintr)
lint("analysis.R")
lint_dir("R/")

# 2. styler - 代码格式化
library(styler)
style_file("analysis.R")
style_dir("R/")

# 3. goodpractice - 最佳实践检查
library(goodpractice)
gp(".")

审查报告模板

# 代码审查报告

## 基本信息
- **审查日期**: 2026-04-06
- **审查人**: [姓名]
- **代码版本**: [Git commit hash]
- **审查范围**: [文件列表]

## 审查总结
- **总体评分**: 45/60 (良好)
- **主要发现**: 3个关键问题,5个次要问题,10个建议

## 详细发现

### 🔴 关键问题 (必须修复)

#### 1. 随机种子未固定
**位置**: `src/analysis.py:45`
**问题**: 模型训练未设置随机种子,导致结果不可重复
**建议**:
```python
import numpy as np
np.random.seed(42)

2. 缺失值处理不当

位置: src/preprocessing.py:78 问题: 直接删除缺失值without记录 建议: 记录缺失值数量并说明处理策略

🟡 次要问题 (建议修复)

1. 函数过长

位置: src/analysis.py:100-250 问题: run_analysis() 函数超过150行 建议: 拆分为多个子函数

💡 改进建议

1. 添加类型提示

位置: 所有函数 建议: 使用类型提示提高代码可读性

检查清单得分

类别得分满分
项目组织45
代码质量710
正确性1215
可重复性710
测试610
文档45
安全性55
总计4560

后续行动

  1. 修复所有关键问题
  2. 修复次要问题
  3. 考虑改进建议
  4. 重新审查

审查人签名

[姓名], [日期]


## 常见问题与解决方案

### Q1: 如何审查别人的代码?
**A**:
1. 先理解代码目的和背景
2. 运行代码并查看输出
3. 按检查清单逐项检查
4. 提供建设性反馈
5. 区分"必须修复"和"建议改进"

### Q2: 如何接受代码审查反馈?
**A**:
1. 感谢审查人的时间和意见
2. 不要防御性回应
3. 澄清不清楚的反馈
4. 区分技术问题和风格偏好
5. 修复后更新代码

### Q3: 科研代码是否需要像生产代码一样严格?
**A**:
- 发表论文的代码:是!可重复性至关重要
- 探索性分析:可以宽松,但要记录
- 共享给他人的代码:是!
- 个人使用的代码:至少满足基本标准

### Q4: 如何平衡代码质量和研究进度?
**A**:
- 从一开始就养成好习惯
- 使用自动化工具(flake8, black)
- 设定最低标准(测试、文档、可重复性)
- 在关键节点进行审查(论文提交前、代码分享前)

## 参考资源

- [Good Enough Practices in Scientific Computing](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1005510)
- [Ten Simple Rules for Reproducible Computational Research](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1003285)
- [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/)
- [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html)
- [The Tidyverse Style Guide (R)](https://style.tidyverse.org/)
- [Code Review Guidelines](https://google.github.io/eng-practices/review/)
Skills enregistrées