# 代码复用思考指南 > 目的:在新增 helper、常量、配置字段或枚举表之前,先判断这个项目里"应该复用"和 > "刻意重复"的边界。这里的关键不是追求抽象,而是避免事实漂移。 --- ## 先搜索,再决定 改任何值或新增类似逻辑前先跑: ```bash grep -rn "关键字或现有值" scripts blender config ``` 本项目的重复有两类: - **危险重复**:同一事实被多处维护,漏改会静默错产物 - **可接受重复**:运行时边界不同或独立入口需要保留,抽象会扩大耦合 判断之前不要凭直觉抽取。 --- ## 必须复用的事实源 ### 九个 osm2streets 图层 JS 侧只认 `scripts/lib/scene-layers.js:15` 的 `SCENE_LAYERS`。 需要文件名、合并场景、style JSON、QGIS 颜色时,使用同文件导出的派生函数: - `layerFile(layer)`(`scene-layers.js:103`) - `mergeScene(getCollection)`(`scene-layers.js:109`) - `sceneStyle()`(`scene-layers.js:126`) - `qgisRgba(hex, alpha)`(`scene-layers.js:143`) 不要在 `build-osm2streets-qgis.js`、`reimport-gpkg.js` 或 QGIS 项目生成代码里再枚举 九个图层。旧问题正是同一顺序复制到四处,漏一处不报错,只让 Blender/Cesium 场景错栈。 Python 侧必须有 `blender/osmassets/catalog.py:28` 的 `ROAD_LAYERS`,因为它还声明 Blender 高度与线性颜色。两侧靠 `catalog.check_layers()` 对账集合和顺序;颜色故意不同步。 ### 区域输出路径 输出路径只在 `scripts/lib/area-config.js` 的 `normalizeAreaConfig()` 推导。 `scripts/build-area.js` 和 `scripts/diagnose-area.js` 都必须通过 `readAreaConfig()` 读取 区域配置。低层脚本读取 `_pipeline/osm2streets-qgis.config.json`,不要重新读取 `config/areas/*.json` 或在阶段函数里现场拼路径。 新增产物时,在 `area-config.js` 的 `outputs` 里加一项,再按需写入 `writeDerivedConfig()`。这样 `intermediates`、`reimport`、`blender`、`cesium`、 `preview` 和读-only 诊断仍然只通过磁盘产物耦合。 ### 材质声明 Blender 内材质声明集中在 `catalog.MATERIALS`(`catalog.py:56`)。 真实 `bpy.types.Material` 由 `materials.from_spec()`(`materials.py:198`)构建。 Cesium 导出调色也属于同一个材质声明:新场景把 `catalog.MATERIALS[*]["cesium"]` 序列化到 `material["cesium_export"]`,`export_cesium.py` 优先读这个属性。四张 `export_cesium.py` 材质名表仍存在,但只作为旧 `.blend` 的兼容回退。 改材质名时不能只改 `catalog`;必须全仓 grep 材质名,确认新契约和旧回退路径都合理。 --- ## 可接受的重复 ### 多份 `parseArgs` `parseArgs` 现在重复在多个独立入口: - `scripts/build-area.js:54` - `scripts/build-osm2streets-qgis.js:153` - `scripts/reimport-gpkg.js:93` - `scripts/compress-glb.js:16` - `scripts/diagnose-area.js:17` 语义一致:`--kebab-case value` 变 `kebabCase: "value"`,无值 flag 变字符串 `"true"`。 这份重复目前是可接受技术债,因为这些脚本都能独立运行。改其中一处解析语义时,不要顺手 只改一份;要么保持全部入口一致,要么把"抽公共模块"作为独立重构并跑对应入口检查。 ### JS 与 Python 的图层颜色 `scene-layers.js` 的颜色是 QGIS 2D 调试 sRGB hex;`catalog.py` 的颜色是 Blender 线性 RGB。`catalog.py:11-15` 明确说颜色不是同步目标。 把两边颜色抽成同一个表不是复用,是破坏两个运行时各自调过的视觉结果。 --- ## 重复模式检查 ### 看到第二份枚举表 问: - 这份表是否已经能从 `SCENE_LAYERS`、`ROAD_LAYERS`、`MATERIALS` 或配置派生? - 如果必须跨语言重复,是否已有对账机制? - 追加顺序是否影响 GLB 材质索引? 没有对账机制的重复表必须特别谨慎。材质名覆盖就是当前已知风险: `generate_scene.py` 创建材质,`export_cesium.py` 靠字符串覆盖,没有校验。 ### 看到多个模块同样预处理 `water.py:9`、`grass.py:9`、`scrub.py:8` 都调用 `clip_polygon`,这是对要素模块签名的 统一要求:模块接收边界、自己裁剪、退化输入返回 0。 新增第四个要素模块时先照这个形状写,不要把裁剪逻辑上移到调用方。否则旧模块和新模块 的边界会不同,真实 OSM 的越界几何会按要素类型表现不一致。 ### 看到多个地方解析同一格式 优先找已有解析器: - OSM XML → `osmassets/osm.py:parse_osm()` - 米制几何 → `osmassets/geom.py` - GeoJSON 场景合并 → `scene-layers.js:mergeScene(getCollection)` - 区域配置 → `build-area.js:normalizeAreaConfig()` 如果确实需要新解析器,把输入格式、容错语义和调用者写清楚,并给纯 Python 逻辑补测试。 --- ## 什么时候抽象 抽象只在满足至少一条时做: - 同一事实会被三处以上消费,且有真实漏改风险 - 同一段校验逻辑跨多个入口影响产物安全 - 抽出来后能保留运行时边界,比如纯 Python 逻辑进入 `geom.py` 后可被 `python3 -m unittest discover blender/tests` 覆盖 不要因为代码相似就抽象: - 多份 `parseArgs` 当前保持独立入口价值 - `ROAD_LAYERS` 与 `SCENE_LAYERS` 跨语言且承载不同字段 - 每个要素模块各自调用 `clip_polygon` 是模块边界,不是可消除重复 --- ## 提交前自检 - [ ] 已 grep 关键值或新字段 - [ ] 没有新增第二份九图层枚举 - [ ] 没有在阶段函数里重新拼输出路径 - [ ] 改材质名时已检查 `catalog.py`、`generate_scene.py`、`export_cesium.py` - [ ] 新纯几何逻辑放进 `geom.py` 并补 `test_pure.py` - [ ] 声称产物不变的重构已按[产物一致性指南](./artifact-parity-guide.md)校验 --- ## 反模式 | 反模式 | 后果 | |---|---| | 新增一份图层名列表 | 回到旧的四份同步,漏改静默错栈 | | 把两套颜色表统一 | 破坏 QGIS 与 Blender 各自调过的视觉结果 | | 低层脚本直接读 `config/areas/*.json` | 两层配置边界失效 | | 只改一份 `parseArgs` 的语义 | 独立入口行为分裂 | | 把要素模块裁剪逻辑挪到调用方 | 不同要素的越界处理开始漂移 | | 只改 `export_cesium.py` 的旧回退表,不写 `MATERIALS[*]["cesium"]` | 新 `.blend` 不会携带 Cesium 导出契约 |