找了一个使用 Python 编写的车辆动力学仿真项目,可以根据油门、制动、转向等输入,计算车辆动力系统、轮胎以及车身状态。用它来:
找到一个现有项目中的问题,分析原因,修改代码,验证结果,然后使用 Git 保存这次修改。
这是我第一次完整走通这样的 Bug 修复流程。
1. Clone 项目并运行
首先从 GitHub 将仓库 Clone 到本地。
这里我第一次真正理解了Clone和Download ZIP的区别。
Download ZIP基本只是:
GitHub 当前代码 ↓ 下载一份文件 ↓ 本地普通文件夹而 Clone 得到的是:
远程 GitHub 仓库 ↕ 本地 Git 仓库除了代码,还保留了 Git 历史以及远程仓库关系。
项目 Clone 完成后,我用 VS Code 打开项目。
目录大致为:
vehicle-dynamics/ │ ├─ example_data/ ├─ vehicle_dynamics/ │ ├─ modules/ │ ├─ structures/ │ └─ utils/ │ ├─ bmw_m8.yaml ├─ main.py ├─ pyproject.toml ├─ README.md └─ setup.py第一次运行:
python main.py出现:
ModuleNotFoundError: No module named 'yaml'原因是代码虽然已经下载下来了,但 Python 环境中还没有安装这个项目需要的依赖。
于是执行:
python -m pip install -e .这里:
pip install负责安装 Python 包。
-e表示 Editable Install,可编辑安装。
.表示安装当前目录中的 Python 项目。
执行后,项目所需要的:
numpy scipy matplotlib pandas PyYAML tqdm ...都被安装完成。
再次:
python main.py项目成功运行。
2. 项目成功运行,但发现了一个奇怪现象
正常运行以后,项目根目录突然多出了一个:
Braking.pickle但项目本身已经有:
example_data/ └─ Braking.pickle也就是说,运行程序之后出现了两份:
vehicle-dynamics/ │ ├─ example_data/ │ └─ Braking.pickle │ └─ Braking.pickle这显然值得检查。
于是打开根目录的main.py。
发现这里:
file_path = "example_data/Braking.pickle" url = "https://github.com/roadview-project/vehicle-dynamics/raw/refs/heads/main/example_data/Braking.pickle" urllib.request.urlretrieve(url, "Braking.pickle") os.makedirs(os.path.dirname(file_path), exist_ok=True) if not os.path.exists(file_path): print(f"{file_path} not found. Downloading from {url}...") urllib.request.urlretrieve(url, file_path) print("Download complete!") else: print(f"{file_path} already exists. Skipping download.")问题就出在:
urllib.request.urlretrieve(url, "Braking.pickle")这一行。
它位于文件存在判断之前。
因此每次运行程序时,不管:
example_data/Braking.pickle是否已经存在,Python 都会先执行:
urllib.request.urlretrieve(url, "Braking.pickle")于是根目录就会被额外下载一份:
Braking.pickle下面的判断:
if not os.path.exists(file_path):只负责判断:
example_data/Braking.pickle是否存在。
所以正确逻辑其实应该是:
检查 example_data/Braking.pickle ↓ 是否存在? / \ 是 否 ↓ ↓ 跳过 下载而原来的逻辑实际上是:
先下载一份到根目录 ↓ 再检查 example_data 中有没有这就造成了重复文件。
3. 不直接修改 main,而是创建 Bug 修复分支
这次我没有直接在main分支修改。
首先创建一个新的分支:
git switch -c fix/duplicate-braking-download这条命令可以拆开理解:
git switch切换分支。
-cCreate,新建分支。
fix/duplicate-braking-download是分支名称。
执行成功:
Switched to a new branch 'fix/duplicate-braking-download'此时可以理解为:
原来的 main │ ● \ └─ fix/duplicate-braking-download目前两个分支代码还是一样的。
后面我们的 Bug 修复只发生在:
fix/duplicate-braking-download分支。
这样即使修改失败,main仍然保持原样。
4. 修改代码
修复非常简单。
删除这一行:
urllib.request.urlretrieve(url, "Braking.pickle")修改后:
file_path = "example_data/Braking.pickle" url = "https://github.com/roadview-project/vehicle-dynamics/raw/refs/heads/main/example_data/Braking.pickle" os.makedirs(os.path.dirname(file_path), exist_ok=True) if not os.path.exists(file_path): print(f"{file_path} not found. Downloading from {url}...") urllib.request.urlretrieve(url, file_path) print("Download complete!") else: print(f"{file_path} already exists. Skipping download.")现在下载动作只会发生在:
if not os.path.exists(file_path):成立的时候。
也就是说,只有文件真的不存在才下载。
5. 一个很容易忽略的问题:修改后必须保存
这次过程中我还遇到了一个很典型的问题。
我明明在 VS Code 中删除了代码,但是运行:
git statusGit 却没有提示:
modified: main.py执行:
git diff也什么都没有。
后来才发现:
我修改了 VS Code 编辑器中的内容,但是没有按
Ctrl + S。
这时候:
VS Code 编辑器中的内容和:
硬盘上的 main.py其实不是同一个状态。
Python 和 Git 读取的都是硬盘上的文件。
所以:
修改代码 ↓ Ctrl + S ↓ 保存到硬盘 ↓ Python / Git 才能看到变化保存后,VS Code 左侧main.py出现:
M这里:
M = Modified说明 Git 已经发现这个文件发生了修改。
6. 使用 Git 确认到底修改了什么
保存之后运行:
git status得到:
On branch fix/duplicate-braking-download Changes not staged for commit: modified: main.py这说明:
当前分支: fix/duplicate-braking-download 被修改的文件: main.py然后执行:
git diffGit 显示:
- urllib.request.urlretrieve(url, "Braking.pickle")这里的:
-表示这行代码被删除。
这一步非常有用。
因为在真正提交之前,可以先检查:
我究竟改了什么?
这次git diff只有一行删除,说明没有误改其他代码。
7. 重新运行程序验证 Bug
先删除之前旧代码生成的根目录文件:
Braking.pickle但是保留:
example_data/ └─ Braking.pickle然后再次:
python main.py程序输出:
example_data/Braking.pickle already exists. Skipping download.车辆动力学仿真继续正常运行:
100%|████████████████████| 9001/9001运行完成后检查目录:
vehicle-dynamics/ │ ├─ example_data/ │ └─ Braking.pickle │ ├─ main.py ├─ bmw_m8.yaml └─ ...根目录没有再次生成:
Braking.pickle这说明:
Bug 修复成功,同时没有破坏项目原本的运行逻辑。
8. Git Add:准备提交修改
确认修复没有问题后执行:
git add main.pygit add并不等于保存版本。
它更像是在告诉 Git:
下一次 Commit,我准备把
main.py的这些修改包含进去。
再次:
git status可以看到:
Changes to be committed: modified: main.py也就是说:
工作区修改 ↓ git add ↓ 暂存区9. 第一次 Commit
最后执行:
git commit -m "Fix duplicate Braking.pickle download"Git 返回:
[fix/duplicate-braking-download e7aca10] Fix duplicate Braking.pickle download 1 file changed, 1 deletion(-)这里包含很多重要信息。
fix/duplicate-braking-download是当前分支。
e7aca10是这个 Commit 的短 ID。
Fix duplicate Braking.pickle download是 Commit Message。
1 file changed说明修改了一个文件。
1 deletion(-)说明删除了一行代码。
此时 Git 历史大致变成:
原作者代码 │ ● main \ ● e7aca10 Fix duplicate Braking.pickle download ↑ fix/duplicate-braking-download至此,一次完整的 Bug 修复完成。
10. 这次真正学到的,不只是删除一行代码
这次 Bug 本身其实非常简单,只删除了一行:
urllib.request.urlretrieve(url, "Braking.pickle")但完整流程比这行代码本身更重要。
我第一次真正走通了:
Clone 开源项目 ↓ 安装项目依赖 ↓ 运行项目 ↓ 发现异常现象 ↓ 阅读源码定位原因 ↓ 创建 Git Branch ↓ 修改代码 ↓ Ctrl + S 保存 ↓ git status ↓ git diff ↓ 运行程序验证 ↓ git add ↓ git commit以前学习 Git 时:
branch commit diff status这些词很容易变成需要背诵的概念。
真正修一次 Bug 以后,它们的用途就清楚很多:
branch:给修改建立一条独立的开发路线。git status:现在仓库是什么状态,哪些文件发生了变化。git diff:具体改了哪几行。git add:选择哪些修改进入下一次提交。git commit:把这一次完整修改保存成一个版本。
后续
这次运行项目时,还发现了另一个问题:
RuntimeWarning: invalid value encountered in scalar divide位置在:
vehicle_dynamics/utils/plot_function.py与轮胎滑移率计算有关。
初步检查发现,在车辆速度和轮速同时为 0 时,部分绘图代码可能产生:
0 / 0不过这属于另外一个独立问题。
所以我没有在这次 Commit 中一起修改。
下一步准备继续按照:
一个问题 → 一个修复 → 一次测试 → 一个 Commit的方式处理这个 Warning。
这样 Git 历史也会更加清楚。