1701 字
9 分钟
重构机械臂项目:从混乱到清晰

为什么要重构?#

我的 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:

robot/franka_robot.py
gripper_pos = 0.04 if gripper_action > 0 else 0.0 # 正值 = 打开
# envs/franka_env.py
gripper_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/
SACsac.pysac_agent.py
SAC+HERsac_her.py(无)
ReplayBufferreplay_buffer.pyreplay_buffer.py
HER Bufferher_buffer.pyreplay_buffer.py
MLP/Policy内联定义networks.py

修复:

  1. 把 algos/her_buffer.py 迁移到 learning/her_buffer.py
  2. 创建 learning/sac_her_agent.py 继承 SACAgent
  3. 删除整个 algos/ 目录
  4. 更新所有 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

经验教训#

  1. 不要 copy-paste 代码:4 处颜色检测、3 处 move_to、2 处 Trainer,每次修改都要改 4 个地方,迟早出错。

  2. 命名约定要统一:夹爪正=开还是正=关,必须全局一致。

  3. 空文件和死代码要及时清理:它们会让新开发者困惑,以为还有功能没实现。

  4. 先设计 API 再实现:algos/ 和 learning/ 的 SAC 实现功能相同但接口不同,就是因为没有统一 API 设计。

  5. Gymnasium 包装器是好东西:不想改核心类的时候,用包装器模式桥接新旧系统。


重构工具:Claude Code Explore Agent + 手动修复 耗时:约 2 小时 删除文件:9 个 新增文件:3 个 修改文件:12 个 项目地址:asl_ywh(Gitee)

回头看,这次重构最有价值的不是修掉了 28 个问题,而是建立了「发现重复就抽公共模块、发现约定不一致就写进文档」的习惯。技术债不可怕,可怕的是没有人定期还。

重构机械臂项目:从混乱到清晰
https://theywh.me/posts/pybullet-franka-refactor/
作者
鸢尾花
发布于
2026-06-03
许可协议
CC BY-NC-SA 4.0