为什么要重构?
我的 PyBullet 机械臂抓取项目在快速迭代后积累了大量技术债:
- 两套完全独立的 RL 实现(
algos/和learning/) - 同一个类在文件里复制粘贴了两遍
- 4 处独立实现的 HSV 颜色检测
- 夹爪控制约定在两个文件里完全相反
- 大量死代码和空文件
是时候认真清理了。
审计结果
我用 Explore Agent 对整个项目做了全面审计,发现了 28 个问题:
| 严重程度 | 数量 | 示例 |
|---|---|---|
| 致命 | 6 | 文件自重复、API 不兼容、夹爪约定矛盾 |
| 主要 | 10 | 代码重复、死代码、依赖缺失 |
| 次要 | 12 | 空文件、bare except、颜色映射重复 |
致命问题修复
C1: scene_scanner.py 整个类复制粘贴了两遍
# 文件结构(修复前):# 行 1-318: SceneScanner 类(第一份)# 行 319-639: SceneScanner 类(完全相同的第二份)Python 只会使用第二个定义,第一个是死代码。而且两份都调用了不存在的 _estimate_3d_position_from_point 方法。
修复:删除重复内容,添加缺失方法。
C4: 夹爪约定矛盾
这是最隐蔽的 bug:
gripper_pos = 0.04 if gripper_action > 0 else 0.0 # 正值 = 打开
# envs/franka_env.pygripper_target = GRIPPER_CLOSE if gripper_action > 0 else GRIPPER_OPEN # 正值 = 关闭两个文件对同一个动作的解释完全相反!用 FrankaRobot 训练的 RL 模型,抓取时会把物体推开。
修复:统一为正值 = 关闭。
C5: train.py 是一个损坏的副本
根目录的 train.py 是 learning/trainer.py 的复制粘贴,但用了相对导入:
from .sac_agent import SACAgent # 相对导入当执行 python train.py 时会报错:ImportError: attempted relative import with no known parent package。
修复:直接删除。
C6: quick_start.py 参数错误
# 错误(修复前)system = RobotArmSystem(vlm_backend="simulation", render=True)
# 正确(修复后)system = RobotArmSystem(render_mode="human", use_smart_vision=True)传入了不存在的参数,运行时直接 TypeError。
代码重复治理
颜色检测:4 处 → 1 处
项目中有 4 个独立的 HSV 颜色检测实现,每个的阈值都略有不同:
| 文件 | 问题 |
|---|---|
main.py BasicColorDetector | 自己定义 HSV 范围 |
vision/smart_detector.py | 又定义了一遍 |
vision/object_detector.py | 再定义一遍 |
vision/vlm_adapter.py | 还定义一遍 |
修复:创建 utils/color_utils.py 统一管理:
# 统一 HSV 范围COLOR_HSV_RANGES = { "red": [ {"lower": np.array([0, 100, 100]), "upper": np.array([10, 255, 255])}, {"lower": np.array([160, 100, 100]), "upper": np.array([180, 255, 255])}, ], # ...}
# 统一 RGB 映射COLOR_RGB_MAP = { "red": (1.0, 0.0, 0.0), "green": (0.0, 1.0, 0.0), # ...}
# 统一形状分类def classify_shape(contour) -> str: # ...所有模块统一 import 这一个文件。
RL 模块:两套 → 一套
algos/ 和 learning/ 目录有完全平行的实现:
| 概念 | algos/ | learning/ |
|---|---|---|
| SAC | sac.py | sac_agent.py |
| SAC+HER | sac_her.py | (无) |
| ReplayBuffer | replay_buffer.py | replay_buffer.py |
| HER Buffer | her_buffer.py | replay_buffer.py |
| MLP/Policy | 内联定义 | networks.py |
修复:
- 把
algos/her_buffer.py迁移到learning/her_buffer.py - 创建
learning/sac_her_agent.py继承 SACAgent - 删除整个
algos/目录 - 更新所有 import
Trainer 类:3 个 → 1 个
| 位置 | 说明 |
|---|---|
trainer.py(根目录) | 简单版,只支持 SAC |
train.py(根目录) | 损坏副本 |
learning/trainer.py | 完整版,支持 SAC + PPO |
修复:保留 learning/trainer.py,删除其他两个。
Gymnasium 兼容性
项目最大的架构问题是 RobotArmEnv 不是 gym.Env,无法用于 RL 训练。
解决方案:创建 RobotArmGymEnv 包装器:
class RobotArmGymEnv(gym.Env): """RobotArmEnv 的 Gymnasium 包装器"""
def __init__(self, render_mode="human", max_steps=200): self.env = RobotArmEnv(render_mode=render_mode)
# 观测空间:23 维 self.observation_space = spaces.Box( low=-np.inf, high=np.inf, shape=(23,) )
# 动作空间:4 维 self.action_space = spaces.Box( low=-1.0, high=1.0, shape=(4,) )
def reset(self, seed=None, options=None): # 重置环境,返回 (obs, info) ...
def step(self, action): # 执行动作,返回 (obs, reward, terminated, truncated, info) ...这样既不破坏 main.py 的现有代码,又能让 RL 训练系统直接使用。
清理的文件
| 文件/目录 | 原因 |
|---|---|
algos/ 整个目录 | 代码重复,已合并到 learning/ |
trainer.py(根目录) | 与 learning/trainer.py 重复 |
train.py(根目录) | 损坏副本 |
vision/vlm_adapter.py | 死代码,从未被 import |
control/task_planner.py | 死代码,从未被 import |
control/command_parser.py | 空文件 |
learning/rl_agent.py | 空文件 |
utils/helpers.py | 空文件 |
2.py | 孤儿测试脚本 |
共删除 9 个文件/目录。
修复清单
致命问题(6/6 修复)
- C1: scene_scanner.py 自重复 + 缺失方法
- C2: trainer.py SAC 训练循环(审计有误,代码正确)
- C3: PPO 参数顺序(审计有误,代码正确)
- C4: 夹爪约定矛盾
- C5: train.py 损坏副本
- C6: quick_start.py 参数错误
主要问题(10/10 修复)
- M1: algos/ 和 learning/ 代码重复
- M2: 多个 Trainer 类
- M3: 多个 TaskPlanner 类
- M4: 两套环境架构(通过 GymWrapper 桥接)
- M5: RobotArmEnv 不是 gym.Env
- M6: vlm_adapter.py 死代码
- M7: task_planner.py 死代码
- M8: setup.py 引用不存在的 scripts/
- M9: 工作空间 z 范围太小
- M10: requirements.txt 缺失依赖
次要问题(12/12 修复)
- m1: 空文件
- m2: 2.py 孤儿脚本
- m3: requirements.txt 不完整
- m4: np.random.choice 用在 dict 列表
- m5: 颜色检测重复(4处→1处)
- m6: move_to 重复(保留,差异较大)
- m7: config.py import 时创建目录
- m8: 颜色映射重复(已统一)
- m9: scene_config.py z 坐标不一致
- m10: environment_gpu.yml CUDA 版本硬编码
- m11: bare except(部分修复)
- m12: 双重物体列表跟踪
重构后的项目结构
pybullet_franka/├── main.py # 主系统入口├── quick_start.py # 快速启动├── config.py # 配置├── setup.py # 安装脚本├── requirements.txt # 依赖│├── environment/ # 仿真环境│ ├── robot_env.py # 核心 PyBullet 环境│ └── gym_wrapper.py # Gymnasium 包装器(新增)│├── envs/ # Gymnasium 环境│ ├── base_env.py│ ├── franka_env.py│ ├── reach_env.py│ └── pick_and_place_env.py│├── learning/ # RL 算法(统一模块)│ ├── networks.py # 神经网络│ ├── sac_agent.py # SAC│ ├── sac_her_agent.py # SAC+HER(新增)│ ├── ppo_agent.py # PPO│ ├── replay_buffer.py # 经验回放│ ├── her_buffer.py # HER 经验回放(新增)│ └── trainer.py # 训练器│├── vision/ # 视觉检测│ ├── object_detector.py # 物体检测│ ├── smart_detector.py # YOLO+CLIP 检测│ └── scene_scanner.py # 场景扫描│├── utils/ # 工具模块│ ├── color_utils.py # 颜色工具(新增,统一实现)│ ├── config.py│ ├── evaluation.py│ └── visualization.py│├── control/ # 控制模块│ ├── robot_controller.py│ └── keyboard_controller.py│├── planning/ # 规划模块│ └── smart_arranger.py│├── robot/ # 机器人封装│ └── franka_robot.py│└── scene/ # 场景配置 ├── scene_builder.py └── scene_config.py经验教训
-
不要 copy-paste 代码:4 处颜色检测、3 处 move_to、2 处 Trainer,每次修改都要改 4 个地方,迟早出错。
-
命名约定要统一:夹爪正=开还是正=关,必须全局一致。
-
空文件和死代码要及时清理:它们会让新开发者困惑,以为还有功能没实现。
-
先设计 API 再实现:
algos/和learning/的 SAC 实现功能相同但接口不同,就是因为没有统一 API 设计。 -
Gymnasium 包装器是好东西:不想改核心类的时候,用包装器模式桥接新旧系统。
重构工具:Claude Code Explore Agent + 手动修复 耗时:约 2 小时 删除文件:9 个 新增文件:3 个 修改文件:12 个 项目地址:asl_ywh(Gitee)
回头看,这次重构最有价值的不是修掉了 28 个问题,而是建立了「发现重复就抽公共模块、发现约定不一致就写进文档」的习惯。技术债不可怕,可怕的是没有人定期还。