1. 关于这个系列
我认为,工程化是前端各类细分技术领域中最为基础而关键,最具有知识广度与深度因而学习曲线较为陡峭,但同时也是对整体开发效率、质量增益最大因而对个体而言最具有学习价值的高阶技能之一。
具体来说,工程化领域向上可以探索学习各种构建工具、静态代码分析工具、CI/CD 与开发工作流等具象工具;横向可认真研判、梳理、落地各类研发规则,提前帮助业务开发者做出技术选择,以实现更高效而规范地业务迭代;向下可以深入挖掘编程、集成、发布、运维等各个研发阶段的目标、最佳实践与核心矛盾,引入或自研各类工具实现研发任务的自动化与高效的质量防劣化措施。综合来看,效率收益明确,技术探索空间比常规的前端功能开发高出许多,是一个非常适合中高阶工程师发展的方向。
但也许正是因为这种纷繁复杂,社区很少见到关于前端工程化兼具体系化、实用性、深度的高质量知识总结,大多数资料还是过度聚焦在具体工具的应用、优化与原理层面,并未触及所谓前端工程化的根本内核,碰巧我个人一直对这个方向抱有浓厚兴趣,加上最近几年工作内容主要聚焦在大规模复杂工程的治理上,沉淀的不少经验与思考,因此计划撰写一系列文章,覆盖前端工程的阶段拆解,各阶段的目标与核心矛盾,相关的工具、实践、管理策略、最佳实践等内容,希望能一次性讲好工程管理这个高级话题,感兴趣的同学欢迎关注我的个人公众号。
2. 为什么需要工程化
谈到工程化,相信有经验的同学脑海中都会自然浮现出一系列技术名词,例如:Webpack、ESLint、Typescript、Jest、Babel、 CI/CD、灰度
等等,这些技术无疑都能解决一部分 工程问题,但也无疑都比较复杂,且不论性能优化、底层原理这些高级课题,单是想把它们用好就已经需要花费比较非常多时间精力不断学习实践,那么我们为什么要花时间去研究它们呢?
答案很简单,一言蔽之 —— 在项目规模增长后,长期保持可持续发展与质量稳定,关键点在于:规模化、持续可发展、稳定性。
首先,规模化是一切的前提,假如工程项目只需要服务于极少数的用户,或者功能非常单一,或者只需要适应有限种类的工作环境,或者只有少量开发人员,或者只有极短的生命周期,那么许多工程化管理举措可能根本没有引入的必要,只会让事情变的 过度复杂。
举个例子,假设需要开发一个面向小团体的简易信息采集工具,受众、功能、执行环境都非常单一,且工具的存活周期只在天级别,可能只需要一人天就能完成,总代码行数控制在数百行内,在这种简单需求上叠加过多工程化工具显然是不合时宜的。例如,没必要为此设计出多么精巧的 CI/CD 规则,因为代码变更与发布频率都非常低;也没必要精心编写单测,因为未来并不会持续迭代,单测收益很低。这种场景本质上是编程问题,而不是工程问题,聚焦于解决 编码问题 就行。
但假如项目规模发生变化,例如受众群体从少量个体扩大成千上万甚至更多时,问题的复杂度就会开始几何式增长,我们需要解决复杂环境的兼容问题,解决高并发带来的性能问题,解决告诉迭代带来的质量下降问题,解决墨菲定律引发的边缘 Case 问题等等,最终解决方案的复杂度也必然会随之发生剧变,我们需要投入的人力、时间也必然随之剧烈增长。此时问题的本质,就会从“如何编码”蜕变跃迁为“如何协作”问题,更具体的说:在开发人数、需求复杂度持续增长的情况下,如何借助工程化技术持续保持高效开发与高质量产出。
总之,工程化与编程是两件完全不同的事情,编码的目标是满足功能需求;而工程化的目标则在于抵抗规模化引发熵增所带来的无序、低效,以及缓解问题进一步劣化。
3. 什么是工程化
那么,什么是前端工程化呢?这个问题很重要,但很多同学对此可能还比较雾里看花,始终着眼于某些具体工具而没有抓住工程化的本质,在介绍具体手段之前有必要花点时间先深入讨论下。前端工程化是“软件工程”学科在前端领域的具体实践,而软件工程需要解决的核心课题是如何在规模化协作中保持更高的开发效率,产出更稳定的工程制品。
我们可以将开发一个项目或者功能点的过程大致可归纳为如下步骤:
从左到右,前期的需求分析、方案设计是一个推导与定义目标的过程,是后续所有工作的一切的起点,决定了项目整体是否顺利是否需要返工等,但内容多数聚焦于人与人之间的沟通,并没有太多自动化空间,因此不作赘述。
进入编码阶段后,我们需要做出许多架构、技术选型、工程化方面的设计与决策,在团队成员水平不齐、项目时间跨度大的情况下,保证最终产出的一致性、可读性、健壮性、正确性,进而确保项目长期可维护。具体来说,我们可能需要制定许多编码规范以约束个体的编码行为,这些规范可能需要细致到类型、函数、代码行,甚至变量命名的粒度,而为了保证这些规范的执行,进而我们需要引入一系列代码静态分析工具保障每一段、每一行代码都是风格统一、正确,且最终组合成全局最优解。
编码完成进入联调测试阶段后,需要开始引入“服务端”、“测试”等角色协同完成功能测试,变更进入“预交付”状态,但通常并不稳定,多数时候还会持续发生迭代微调。为此,需要重点关注工程的“可测试性”,需要在生产环境之外设置一套可独立运行、支持多版本、可持续部署的测试环境,确保测试行为不会对应用状态造成预期之外的副作用。其次,还需要一些技术手段监控调用链路上可能发生的异常,帮助开发者快速识别问题、解决问题。
初步完成测试工作后,相关代码分支就需要开始推进合入仓库主干,这是一件严肃的事情,因为所有对主干施加的变更都会体现为最终产物的质量。为此,需要结合应用的发布策略,考虑在多人、多分支并行开发的情况下,以何种方式将功能分支安全地合入确保应用的稳定性;并且,在这一阶段需要严格地实行许多自动化的非自动化的检查,保证代码质量不会随长年累月迭代发生明显的质量下滑。
代码合入后,接下来就需要考虑如何将其发布到线上供用户消费,小项目可以选择一次性将变更推送到生产环境,这确实有助于提升整体迭代速度,即使出现问题,由于用户体量小,一般也不至于出现过大的负面影响。但对于大型项目则复杂许多,在庞大体量下,任何细微问题都可能影响到大面积用户,因此理所当然的对质量非常敏感,对变更一般会持比较谨慎、稳健的态度,为此通常需要设计一套方法论与工具,渐进式地将变更逐批推送到用户侧,且一旦发现问题能够随时回滚到变更前的状态。
变更发布上线后,开始被分发到用户侧使用,此时期望应用在不同环境、不同用户手上都能保持稳定的功能与性能表现,但我们不可能肉身盯着每一位使用者的操作行为与系统反馈,因此需要使用一些技术手段收集代码的执行情况并汇总上报,以这些数据观测应用在线上的表现,实现一种“后置”的可观测效果,避免潜在问题进一步扩散放大。
综上,如果说工程化的世界观,在于抵抗规模化引发熵增所带来的无序、低效;那么方法论层面,则是通过合理地组合串联各种自动化工具、固化约束、优化流程等手段,形成一种相对统一、标准化、自动化的协作环境,降低个体思维天然的“随机性”所带来的效率损耗与质量风险,它不一定能带来多么惊为天人的结果,但能持续保证过程质量与产品质量的 baseline,规避多数低级或严重问题。
不过,要实现成熟的工程化模型并不容易,不是简单引入几个工具,写几篇规范文档就能宣告完成了,许多细微问题、隐患随机散落在研发过程的各个阶段中,需要被有意识地观测归纳,提炼为清晰的问题定义,进而寻求纵深方向上的全局最优解。这个过程需要有比较强的技术视野与认知,才能正确发现、定义问题,并以优雅的方案解决问题;需要对各阶段使用的技术栈有比较深入的理解,才能设计出 ROI 最优的组合方案;需要有一定的创造性与技术功底,在必要时创造出更有针对性,更有效率的工程化工具;还需要始终保持好奇、精益求精、孜孜不倦的态度,如同维护产品一般自驱地持续观测研发过程,持续迭代工程模型以适应工程本身随时间推进所发生的变化。
在后续的章节中,我会深入介绍研发各个阶段的特点、问题、工具等,帮助读者在上述理论基础上,更深入理解各个维度的方法论。
4. 总结
这是前端工程化系列的先导篇,内容更多聚焦在软性的认知调节上,下一篇文章我会展开讲解开发阶段的核心问题与相应的工程治理策略、工具、高效编码方案等,感兴趣的同学可关注我的个人公众号,持续围观,有问题的同学也欢迎微信沟通: