十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从飞机窗户到代码复用:资源紧张期的工程减法

从飞机窗户到代码复用:资源紧张期的工程减法 最近读到一条行业动态在零部件供应紧张、成本压力加大的市场环境下飞机制造商开始采取措施“节约”飞机窗户。乍看有点反直觉——飞机窗户又不是易耗品怎么“节约”但如果仔细想这件事背后的工程逻辑非常值得关注它不是单纯在玻璃上省材料而是通过压缩选装规格、统一接口、提高部件复用度、稳定供应链来应对市场紧缩带来的交付压力。这个逻辑放在软件开发里几乎完全成立。代码中也有很多“飞机窗户”——一个高复用组件、一套公共依赖、一种配置方式。在业务高速增长期团队往往忙着快速迭代很少回头审视这些基础资产等到预算变紧、排期压缩、线上问题变多时才会发现过去随手复制粘贴积累下来的技术债正在以几何级数吞噬团队的可用产能。这篇文章想从“飞机窗户”这个工程小切口延伸出去聊聊资源紧张环境下软件开发应该怎么做减法组件复用、配置化、存量治理以及一套可以直接落地的优先序。文中会给出可运行的代码示例和排查清单适合正在做系统瘦身、组件库建设、依赖治理和降本增效的团队参考。1. 飞机窗户为什么值得制造商扣成本飞机窗户看起来只是一块特殊形状的玻璃实际上它是一个严格意义上的结构件。它要穿过机身蒙皮承受客舱内外压差载荷提供应急观察功能还要考虑防雾、防鸟撞、密封性能和光学表现。每一种飞机窗户都要配套单独的图纸、专用工装、供应商认证、测试报告和安装手册牵涉的环节远比想象中复杂。在常规量产环境下窗户的定制成本往往被忽略。因为单机采购量不大又不直接决定飞行性能工程师通常会按客户意向添加选装项。可一旦订单量爬升供应链进入紧张状态问题就会集中爆发不同的舷窗规格会打散采购批量增加库存单元数量拉长交付周期。任何一个品类在供应端稍微波动整条生产线都可能停下来等一个看似不起眼的零件。这也是为什么制造商会回过头来做减法。从一些公开行业资料看飞机制造商通常的做法是压缩选装目录把尺寸接近、功能相似的设计合并为少数标准规格同时推动主力机型共用同一套窗户接口。供应商集中排产库存才能共享交付节奏才能稳定下来整条供应链的抗风险能力也会明显提高。这个案例最有价值的地方不是“省了一片玻璃”而是它展示了工程优化的一种典型顺序先盘点存量再统一接口最后提高复用。软件开发中常见的依赖膨胀、组件五花八门、接口各写各的本质上和飞机窗户的定制陷阱同构。我们谈到降本增效时往往一上来就讲“上云”“优化资源”却忽略了最基础的工作把已经有能力沉淀好减少不必要的重复建设。2. 核心概念标准化、模块化与资产化要理解飞机制造商的“窗户节约”思路先要把三个词讲清楚标准化、模块化、资产化。这三个概念同样可以直接映射到软件工程中。标准化是统一尺寸、统一接口、统一测试标准。做单一产品时标准化听起来像约束同时维护多个产品时标准化是在降低认知成本和维护成本。软件领域的对应物是接口规范、目录约定、代码风格、领域模型。一个团队如果没有约定每个人按自己的习惯写代码任何公共能力都很难沉淀下来。模块化是把复杂系统拆成可独立替换的单元。飞机窗户并不是一块玻璃焊死在机身上而是玻璃、窗框、密封件、遮光板共同组成一个可拆装的总成。某一部件需要升级或维修时不需要改动整个机身结构。对应到软件就是单一职责的组件、服务、依赖包模块之间通过稳定接口协作彼此不关心内部实现细节。资产化是把已经生产出来的零件、图纸、测试报告当作长期资产而不是一次性的项目交付物。零件库、物料清单、可复用设计在这个视角下和代码库里的公共组件包、内部工具库一样都要被显式管理并持续投入维护。很多团队做组件库失败不是代码写得不好而是先把“灵活”做成了无标准——每个组件各自为政导致没有人敢复用于是大家继续复制粘贴。三者的关系是标准化是模块化的前提模块化是资产可复用的基础。缺少任何一环工程优化都难以持续。下面这个表可以更直观地看出制造领域和软件领域的对应关系制造领域软件领域核心动作标准件库组件库 / 公共包统一接口、统一文档窗户接口模块化服务接口抽象依赖倒置、面向接口编程库存单元盘点依赖与代码资产盘点建立清单、标记废弃供应商认证第三方库安全评估锁定版本、持续扫描压缩选装目录产品配置开关配置化代替硬编码分支3. 软件开发浪费的典型场景重复造轮子的三个信号先别急着写代码我们要先识别问题。绝大多数软件团队的浪费并不是因为工程师懒而是因为系统性缺乏“复用”意识。你可以对照以下三个信号看看自己的项目中有没有同类情况。第一类信号是新页面复制旧页面。业务方强调“这次很简单跟上一个差不多”开发为了赶工时把整个页面复制一份改两行文案和字段就提交。短期内这确实最快但长期会让两版逻辑逐渐漂移。某个 Bug 修了 A 页面B 页面还留着某个字段加了校验C 页面却没加。等到业务规模扩大维护成本直接翻倍。第二类信号是每套服务自己封装请求层。A 服务用 axios 封装B 服务直接用 fetchC 服务又引入了团队内部的某个 HTTP client。每个封装的错误处理、超时策略、埋点逻辑都不一致。等到要统一做全链路追踪或鉴权时只能对每一套实现单独改造工期成倍增加风险也随之上升。第三类信号是相似组件在不同仓库重复实现。一个“用户选择器”营销后台有运营后台有数据分析平台还有一个三套代码实现各不相同样式不完全一致数据源接口接口也不同。表面上是不同项目的差异需求实际上 90% 的功能是重叠的。重复造轮子最可怕的地方在于它不是一次性成本而是后续每一次需求变更、版本升级、安全漏洞修复都要重复支付的成本。识别这三个信号的目的不是要揪出某个团队不努力而是先建立一种敏感性当代码里出现“又一版”的时候我们是在创造可复用资产还是在制造新的维护负担。飞机窗户的启示是每次新增规格之前先问一句能不能用标准件满足如果能就应该走标准件流程而不是重新设计一个非标部件。4. 组件复用的完整示例一个部门选择器为了让上面的思路真正落地我们用一个最小的前端场景来演示多个表单页面都需要选择部门。很多团队的第一反应是每个页面写一段选择逻辑这里我们把它提炼成一个可独立复用的组件。示例代码基于 React 18 TypeScript理论上 React 16.8 即可运行。如果你用 Vite 快速搭建项目直接把组件放进 src 目录里就能验证。// 文件路径src/components/DepartmentSelect.tsx import { useEffect, useState } from react; interface Department { id: string; name: string; } interface DepartmentSelectProps { multiple?: boolean; placeholder?: string; value?: string[]; onChange?: (value: string[]) void; loadApi?: () PromiseDepartment[]; } export function DepartmentSelect({ multiple false, placeholder 请选择部门, value [], onChange, loadApi, }: DepartmentSelectProps) { const [visible, setVisible] useState(false); const [options, setOptions] useStateDepartment[]([]); const [loading, setLoading] useState(false); useEffect(() { const loader loadApi || (() Promise.resolve([ { id: dev, name: 研发部 }, { id: ops, name: 运维部 }, { id: pm, name: 产品部 }, ])); setLoading(true); loader() .then((data) setOptions(data)) .finally(() setLoading(false)); }, [loadApi]); const toggleOption (id: string) { if (!onChange) return; if (multiple) { onChange(value.includes(id) ? value.filter((v) v ! id) : [...value, id]); } else { onChange([id]); setVisible(false); } }; return ( div classNamedepartment-select button typebutton onClick{() setVisible((v) !v)} {loading ? 加载中... : value.length ? value.join(, ) : placeholder} /button {visible ( ul {options.map((item) ( li key{item.id} label input type{multiple ? checkbox : radio} checked{value.includes(item.id)} onChange{() toggleOption(item.id)} / {item.name} /label /li ))} /ul )} /div ); }调用方使用起来非常简洁// 文件路径src/pages/UserFormPage.tsx import { useState } from react; import { DepartmentSelect } from ../components/DepartmentSelect; export function UserFormPage() { const [department, setDepartment] useStatestring[]([]); return ( form label所属部门/label DepartmentSelect multiple{false} placeholder请选择所属部门 value{department} onChange{setDepartment} / /form ); }这个组件遵循两个关键设计第一value 和 onChange 采用受控模式可以无缝嵌套进 Ant Design、Element Plus 等主流表单库第二loadApi 支持从外部注入生产环境从后端接口读取部门数据演示场景走默认数组组件本身不绑定任何具体业务接口。这样每个新页面只要引入组件再传入当前页面关心的 props 即可不需要再复制一份交互逻辑。验证方式非常简单。把代码放入一个 Vite React 项目后执行npm install npm run dev浏览器打开页面点击按钮会出现部门列表选择结果会显示在按钮上。如果项目原本有很多页面都写了自己的部门选择逻辑替换成这个公共组件后后续修改交互、调整样式、替换数据源都只需要改一个文件。5. 配置化用一份 YAML 渲染整个表单组件复用解决的是“同一个交互在不同页面出现”的问题。下一个问题是业务规则变化频繁每次需求变更都要改代码、发版效率太低。这时候可以考虑配置化。配置化的核心思想是让同一个引擎处理不同配置而不是为新需求写新的页面逻辑。仍然以表单为例。假设多个页面存在相同的字段布局业务方经常调整字段顺序、增加或停用字段。如果每个字段都写死在页面里每次调整都是一次开发迭代。我们可以把表单结构抽成配置用一个通用渲染器读取配置并生成页面。下面是一份简单的 YAML 表单配置# 文件路径config/forms/userForm.yaml form: title: 员工信息录入 submitText: 提交 fields: - name: username type: input label: 姓名 required: true - name: department type: departmentSelect label: 所属部门 multiple: false required: true - name: interests type: departmentSelect label: 感兴趣的方向 multiple: true required: false然后在渲染器里读取配置。下面这段代码以 Vite 项目为例使用js-yaml解析 YAML并通过?raw的方式直接导入 yaml 文件内容npm install js-yaml// 文件路径src/components/ConfigForm/ConfigForm.tsx import { useMemo } from react; import yaml from js-yaml; import { DepartmentSelect } from ../DepartmentSelect; import formConfigText from ../../config/forms/userForm.yaml?raw; interface FieldConfig { name: string; type: string; label: string; multiple?: boolean; required?: boolean; } export function ConfigForm() { const config useMemo(() { return yaml.load(formConfigText) as { form: { title: string; submitText: string; fields: FieldConfig[] }; }; }, []); return ( div h2{config.form.title}/h2 form {config.form.fields.map((field) { if (field.type departmentSelect) { return ( DepartmentSelect key{field.name} multiple{field.multiple} placeholder{请选择${field.label}} / ); } return ( div key{field.name} label{field.label}/label input name{field.name} required{field.required} / /div ); })} /form /div ); }需要说明的是?raw是 Vite 的特殊导入方式不同构建工具写法不同如果你使用 Webpack需要配置 raw-loader 或改用 JSON 配置。关键不是这段代码本身而是“结构描述与渲染逻辑分离”的模式。配置化的收益体现在几个方面新需求如果落在已有字段类型范围内新增一段 YAML 即可字段顺序调整不必发版某个字段要临时停用直接注释配置项即可。配置化的风险也要警惕。如果某个需求字段只出现一次却为了它专门设计一套配置模板那配置化就成了过度设计。更合理的判断标准是一个字段类型被超过 3 个页面使用才值得沉淀为配置化字段类型少于 3 个直接写业务组件更划算。飞机厂商压缩选装目录也是同一个逻辑——只有那些出现频率足够高、标准化收益明显的选型才值得进入标准件目录而不是把每一种客户意愿都做进配置系统。6. 存量治理实战写个脚本盘点依赖在动手做新东西之前更该做的是把旧账翻出来。飞机厂商要节约窗户一定先盘点现有库存、采购单和供应商产能。软件开发侧的“库存盘点”核心是盘点依赖和重复代码。很多项目的依赖已经膨胀到无法维护却没有人知道某个库到底被谁引用。这里提供一个非常小的依赖盘点脚本用来回答两个问题哪些依赖被引入了但代码里几乎没有引用哪些依赖被大量文件引用值得重点维护和升级# 文件路径tools/dependency_audit.py import json import os import re from collections import Counter def load_dependencies(file_pathpackage.json): with open(file_path, r, encodingutf-8) as f: data json.load(f) deps {} deps.update(data.get(dependencies, {})) deps.update(data.get(devDependencies, {})) return deps def scan_import_usage(deps, src_dirsrc): usage Counter() for root, _, files in os.walk(src_dir): for name in files: if not name.endswith((.ts, .tsx, .js, .jsx)): continue path os.path.join(root, name) with open(path, r, encodingutf-8) as f: content f.read() for dep in deps: # 匹配 import xxx from dep 或 require(dep) 或 import dep if re.search(rf[\]({dep})[\\u002f], content): usage[dep] 1 return usage if __name__ __main__: deps load_dependencies() usage scan_import_usage(deps) print(依赖总数:, len(deps)) print(\n0次引用依赖可考虑下线:) for dep in sorted(deps): if usage.get(dep, 0) 0: print( -, dep) print(\n高使用率依赖 Top 5:) for dep, count in usage.most_common(5): print(f - {dep}: 被引用 {count} 次)运行方式python tools/dependency_audit.py预期输出大概如下依赖总数: 42 0次引用依赖可考虑下线: - moment - lodash.omit 高使用率依赖 Top 5: - react: 被引用 86 次 - axios: 被引用 24 次 - dayjs: 被引用 18 次这个脚本的核心价值在于建立“盘点”的意识而不是追求正则完美。真实工程中moment可能被动态 import 调用也可能出现在.vue或.md文件中脚本不一定扫描到位。所以脚本输出只能作为初步筛选不能直接删依赖。比较稳妥的做法是再结合depcheck等专门工具交叉验证再人工确认一次是否真的没有引用。更推荐的做法是把依赖盘点纳入固定节奏而不是等到线上出了安全漏洞才开始查。每隔一个迭代周期运行一次脚本把零引用依赖列入待清理清单与业务迭代一起排期。清理时按照“少量多次”的原则每次只下线一两个依赖然后观察一到两周确认没有编译错误和线上异常再继续下一批。7. 资源紧张期技术团队的降本优先序前面已经讲了组件复用、配置化和存量治理把它们串联起来的是一套优先序。资源紧张时团队不能所有事都做任何增量开发都必须严格论证。如果没有排序很容易陷入“什么都想优化结果哪个都没做透”的窘境。我建议按下面这个顺序执行。第一步盘点。先摸清代码仓库、依赖清单、组件库里有哪些可复用资产。没有清单之前讨论“要不要新建”“要不要重构”都是拍脑袋。盘点不是一次性的工程而是持续更新的地图。第二步立标准。在盘点基础上把接口规范、目录结构、命名约定定下来。不一定需要多复杂的文档两三页的团队约定就够关键是要在代码评审中强制执行让标准真正成为团队习惯。第三步做高价值复用。聚焦出现两次以上的重复场景把相同组件抽成公共组件把重复接口收敛为公共能力。这个阶段要克制一次只做一个做完一个验证一个不要一口气把整个系统都抽一遍。第四步配置化改造。对需求变更频繁、字段结构标准化程度高的模块引入配置化。例如表单、审批流、路由表这类功能非常适合用配置描述。而复杂业务规则、强状态流转的任务不建议强行配置化。第五步淘汰低价值资产。有了依赖清单和组件画像后把零引用依赖、废弃组件逐一清理。这样能腾出维护精力让团队专注于真正被使用的核心系统。这个顺序背后的原则是先确认旧资产的价值再决定新增和改造先做低成本、高确定性的事把重构和治理放在可控范围内。飞机厂商不会在经济紧张时去研发全新增材制造材料而是在成熟材料上做规格合并和复用优化应对逻辑是完全一样的。8. 常见问题与排查思路团队在落地组件复用、配置化、依赖治理时经常会遇到一些绕不开的问题。下面这张表整理了典型的状况、可能原因和排查方向。问题现象可能原因排查方式解决方案抽出的公共组件没人用组件接口复杂、文档缺失查看组件使用量、近三个月提交记录简化 props补充文档和示例在代码评审中强制优先复用组件可复用改造后原有页面样式变化样式耦合或语义化调整导致回归使用视觉回归测试对比改造前后截图引入快照测试先覆盖核心页面再全量切换配置化后配置越来越多、难维护把过多页面都配置化配置层早已僵化统计配置项数量、字段类型命中率只保留使用次数超过 3 的字段类型其余退回硬编码依赖盘点脚本输出可疑正则匹配不到动态引入或文件类型遗漏检查脚本执行目录、文件后缀、动态 import 写法结合 depcheck 或 ESLint import 插件交叉验证团队对“先盘点再开发”方案抵触缺少明确收益和试点案例选取一个页面做试点统计改造前后耗时用数据说话先小范围推行不强制一刀切这里重点提醒一个问题不要把一个已存在的组件直接重构成通用组件。组件抽取是一个“提炼”过程必须在至少两个真实使用场景下进行。如果只有一个页面用到先不要抽公共层因为此时你还不确定稳定的接口边界是什么。等第二个页面出现时再对比两个页面的差异找到公共部分这个提取才会更准确。9. 最佳实践与工程建议最后沉淀几条工程建议。它们不是通行的银弹而是多数中大型项目普遍适用的经验。命名与目录要规范。公共组件统一放在独立目录下按领域分组例如common、business、layout。公共组件的名字必须使用通用名词不要带业务特定命名。一个叫DepartmentSelect的组件可以被多个页面使用一个叫EmployeeTransferApplyDepartmentSelect的组件则很容易变成某条业务线的私有实现。组件接口保持稳定。凡是暴露给外部调用方的 props升级时要按语义化版本管理尽量向后兼容。如果某次重构确实无法兼容先废弃旧接口再提供迁移工具或 codemod而不是直接删除。飞机窗户的接口一旦确定后续的所有升级都要兼容既有机身开口道理一样。配置默认值要合理。配置化字段的默认值应该让页面“零配置可用”。只有默认值无法满足需求时调用方再传额外参数。这样可以避免每个页面都把全部配置项复制一遍配置本身也会更精简。依赖审计要常态化。每个迭代周期安排一次存量依赖审计不要等安全漏洞爆出来才想起来。审计结果要进团队例会让“这个依赖为什么存在”“能不能删”成为日常问题而不是季度运动。代码评审里加入“复用检查”。评审页面或服务时多问一句如果这个需求被另一个业务方使用能直接复用当前实现吗如果答案是不能或很勉强说明模块边界划分有问题。这个问题能倒逼设计者把通用部分和业务部分切割开也是预防重复造轮子的最实际手段。这套思路的起点其实是飞机厂商面对苛刻市场环境时的选择不增加更多非标设计不追求每一处都“定制化”而是在已有能力上做合并、做标准化、做复用。软件开发面对排期压力和成本约束时最欠缺的往往不是技术能力而是克制“重新实现一遍”冲动的那份自觉。下一次新需求排期时先别急着开新组件打开项目里的共享目录找一找已有的资产这个动作比很多宏大思维框架都有效。
返回列表