关于软件工程的一些小总结
伙伴们经常用码农“自嘲”——Are you a Coder or a Programmer or an Engineer ?
明亮老师的主持下做“琢·磨”系列的分享,本来打算做一个前端工程化相关的主题,仔细思考后还是决定先从更宏观/普世的软件工程视角分享一次。
我是一个专业的前端工程师,为什么不直接分享的前端工程化?关于前端工程化的现状,周爱民老师在《前端工程化》一书的推荐序《技术之外》摘录如下:
前端的工程化,事实上还处于一个原始阶段。我们如今之所视,可以一言以蔽之:或在对语言内在功能特性的补充,或在对其外在组织能力的补充。这些种种补充,仅是在工程体系的“工具”这一隅上做功夫。可以预见的是,在前端工程这个体系上前行,必然面临的问题是过程的优化和方法论的建立。
——周爱民老师
回忆一些前端工程化中的细节:
- 复杂的webpack.config.json;
- 功能特性—链式的translate脚本;
- 组织能力—模块的import统一引用;
- 组织能力—图片的base64内嵌;
- 组织能力—镜像构建等等。
前端工程化确实需要从更宏大的软件工程层次进行实践。软件工程从诞生到现在,60年左右的历史,可能依然算不上一个特别严谨的工程学科,在未来还有很长的路要走。这篇分享也是这两年自己的一些反思和思考。本文分如下四部分:
- 软件工程的概述;
- 软件工程的过程;
- 软件工程的重要方法论;
- 结语。
一、软件工程的概述
1.1 软件工程定义
软件工程是一门应用计算机科学理论和技术以及工程管理原则和方法,按预算和进度,实现满足用户要求的软件产品的定义、开发、和维护的工程或进行研究的学科
——GB/T11457-2006
从定义中挖掘一些细节:
- 软件工程同时包含科学和工程的特点,前者偏研究和原理,后者偏管理和实践;
- 完整生命周期包含定义、开发和维护3个阶段;
- 预算和进度,前者指的是经济因素,后者所指的是时间因素,这也是我们做任何项目最难/严格的一件事儿——排期。进度是工程的强相关因素和度量。
软件工程是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。工程管理原则和方法,即将系统化的、规范的、可度量的方法用于软件的开发、运行和维护的过程。
工程管理的3个关键要素如图所示:
- 系统化,即系统化的方法论,以及系统化思维,需要拆解系统循环图,区分增强回路和调节回路;
- 规范化,比如敏捷的流程规范,和需求规范、编程规范、上线规范等等。“规”是科学原理的表达,比如一致性;“范”是范例,比如编码规范中会用严格的缩进示例来表达一致性的原则,可丁可卯,分毫不差(ESLint等工具是保证、简化规范的严格执行,工具为规范服务)。
- 可度量,这是软件工程中面临的最大一个挑战。至少在时间排期上没有严格的需求和研发的计算公式;再回忆我们自己最近参与的几个项目,延期是很难避免的。
1.2 软件工程框架
工业革命以来,各个领域发生了天翻地覆的变化,各个学科后面都可以加一个工程的后缀,比如:
- 建筑工程
- 航海工程
- 航空工程
- 环境工程
- 能源工程
- 机械工程
- 电子工程
- 生物工程
- 基因工程
- ……
在软件工程原则的指导下,诞生了一大批支撑咱们工作和生活的软件:Windows、Mac OS、Linux,Office、VSCode和王者荣耀等等。
软件工程的框架可概括为:目标、过程和原则。
软件工程目标,生产具有正确性、可用性以及开销合宜的产品。
- 正确性指软件产品达到预期功能的程度。
- 可用性指软件基本结构、实现及文档为用户可用的程度。
- 开销合宜是指软件开发、运行的整个开销满足用户要求的程度。
软件工程过程,生产一个最终能满足需求且达到工程目标的软件产品所需要的步骤。
软件工程原则,指围绕工程设计、工程支持以及工程管理在软件开发过程中必须遵循的原则:
- 选取适宜开发范型。
- 采用合适的设计方法。
- 提供高质量的工程支持——“工欲善其事,必先利其器”
- 重视开发的过程管理。
根据软件工程这一框架,软件工程学科的研究内容主要包括:软件开发范型、软件开发方法、软件过程、软件工具、软件开发环境、计算机辅助软件工程(CASE)及软件经济学等。
1.3 软件工程原理
自从1968年提出“软件工程”这一术语以来,研究软件工程的专家学者们陆续提出了100多条关于软件工程的准则或信条。 美国著名的软件工程专家巴利·玻姆(Barry Boehm)综合这些专家的意见,并总结了美国天合公司(TRW)多年的开发软件的经验,于1983年提出了软件工程的七条基本原理(之前已经提出的100多条软件工程准则都可以有这七条原理的任意组合蕴含或派生)。
- 用分阶段的生命周期计划严格管理(注:比如敏捷中严格的阶段)
- 坚持进行阶段评审(注:比如每一次代码提交的代码评审,第二个迭代中对第一个迭代产出的验证)
- 实行严格的产品控制(注:比如严格的测试流程)
- 采纳现代程序设计技术(注:前端很多,18个月就要升级一次技术)
- 结果应能清楚地审查(注:单元测试、验收测试、数据验证等)
- 开发小组的人员应少而精
- 承认不断改进软件工程实践的必要性
工程重在实践,每个迭代之后一般会安排一个复盘,有错改错,持续进步,不断改进积小成大。
1.4 软件工程的难点
熙熙攘攘的软件产业就像时装行业一样,不断被各种流行时尚裹挟着前行,却始终无法找出解决软件工程中存在的最根本问题的解决办法
—— PeterR.Hill(ISBSG CEO)
在按时按预测成本顺利交付高质量软件方面,仍然没有任何重大突破——软件危机依然存在。
一幢建筑自它完成之后,所有的变化便主要集中在一些软装的细节上,很少会再发生剧烈的变动,更不会持续地发生变动,而软件工程特点:
- 不确定性,参与人力多、产出不重复、持续时间长、业务变数大、创造性工作天然的试错属性
- 快速变化,持续迭代变化,运维生命周期长。
管理学本身的目的之一就是要抑制不确定性,产生确定性。但是软件项目的延迟交付率多达75%,大型应用程序的取消率多达35%,高度劳动密集性,这完全说不上工程——软件工程依然任重而道远。
当下,超过50%的资金在软件的缺陷修复、安全缺陷或者灾难上;仅有10%用于创新和新型软件;
期望,创新和新型软件的占比增加到40%;使灾难、错误修复以及安全修复的费用降低到15%以下。
从理论走向实践,就需要在质量控制上做出重要的转变,并且也要从手工编码迁移到基于零缺陷标准组件的构建上来。 从个性化的开发转变成注册组件的构建有非常广阔的前景,同时这样做也会使软件工程学科和软件成本结构有空前的巨变。在真正的工程领域,我们也应该能够使用比目前数量多得多的零缺陷可重用组件。这也是当下流行的中后台可视化搭建平台的基础。
二、软件工程的过程
软件工程的完整过程和生命周期如下图所示,按许式伟老师的话说:架构师(团队)的职责,规划和引导整个系统的演变过程,掌控整个工程全局,为整个软件工程的执行结果和生命周期负责。
软件工程是一项团体活动,大家有分工更有协同。不同的伙伴因为能力差别,可以形成十倍以上的生产力差距。而不同团体更是如此,有时候是天壤之别。团队共识是其中的基础:
- 团队是不是有共同的目标
- 团队是不是有共同的行事做人的准则
- 对产品与市场的要与不要,以及为什么要或为什么不要,是否已达成一致
- 对执行路径有没有共同的认知
- 有没有团队默契,是否日常沟通交流很多地方不必赘述,沟通上一点即透
架构过程就是一次团队共识确认的过程,从项目的混沌之初,到团队形成越来越清晰且一致的视图(Picture),也可以说整个软件的研发过程就是一次共识传递正向确认和反向验证的过程,如下图环环双扣所示:
2.1 设计文档
从软件工程角度来说,产品设计和架构设计是团队最大的共识。
产品经理与架构师是一体两面,对人的能力要求的确会比较像,但是分工不同,关注的维度不同。
产品经理关注维度的关键词是:用户需求、技术赋能、商业成功。
而架构师关注维度的关键词是:用户需求、技术实现、业务迭代。
文档中至少要交代清楚下面五个点:
- 现状 :我们在哪里,现状是什么样的?
- 需求:我们的问题或诉求是什么,要做何改进?
- 目标:要做成什么样,交付物规格,或者说使用界面(接口)是什么?
- 实现:怎么做到?交付物的实现原理。
- 对比:多个设计方案的对比,概要地描述清楚两个设计方案的本质差别,并且从如下这些维度进行对比:
2.2 版本管理
保持版本的只读语义,严格遵循版本语义化规范。线上出问题之后,最小化线上损失的办法是按版本快速回滚。
2.3 质量管理
有效的质量控制能缩短工期,而粗糙的质量控制则延长工期。
应该快速地将质量提升到一个比生产力更高的级别,接着再来改善生产力。这样做的原因是,总体而言,发现和修复bug是软件开发中花销最大的活动。质量主导生产力,没有上乘的质量,想要提高生产力,简直是白日做梦。
- 自动化运行单元测试案例(unit test);
- 单元测试覆盖率检查(code coverage);
- 静态代码质量检查(lint);
- 人工的代码互审(code review);
- 灰度发布(gray release);
- A/B 测试(A/B testing)。
三、软件工程方法论
3.1 只读设计
只读设计提升了软件工程的确定性,所以只读思想被广泛运用。前面我们说开闭原则背后的架构治理哲学,也是模块,或者说软件实体,其业务范畴只读。在业务只读,接口稳定的预期下,模块与模块之间就可以自由组合,构建越来越复杂的系统。
3.2 持续优化/构建/发布
如上图山地骑行,在持续运动中保持平衡。只读设计的思想是软件工程确定性的前提,进一步降低软件工程不确定性的方法:持续优化,持续构建,持续发布。
- 交付的功能越少,因为错误而发生回滚的代价越低,影响面越小
- 交付频率越高,我们对交付过程的训练越频繁,过程的熟练度越高,执行效率也越高(交付时功能开发的一部分)。我们会鼓励更多把研发的绩效与功能线上的表现关联起来,面向客户价值,而非仅仅面向功能开发。
常见的持续集成服务如下图所示:
- 开发人员在本地运行一部分自动化测试(秒级别)
- 然后把本地代码提交到代码版本控制系统中去
- 新提交的代码改动会使持续集成服务器开始一轮新的构建(分钟级别,5-15分钟,甚至可能是小时级别)
- 在新代码构建完成后,持续集成服务器会发送电子邮件把构建报告发送给开发人员
在提交代码前常常只运行与所作修改相关的部分测试,让持续集成服务器在后台运行所有的测试(包括单元测试、集成测试和功能测试,这3种测试类型可以分级处理)。
3.3 双稳态
双稳态定律(Law of the Two Plateaus)
- 质量工具:提高程序员的生产力,保证开发速度
- 设计工具:持续优化设计——编写测试的最大价值不在于结果,而在于编写过程中的学习。
BDUF(先做大设计)可能是有害的,心理上阻碍改进,沉没成本,抵制丢弃既成之事。这也是单元测试的价值,为了找回丢掉的潜力,你需要从编写测试中进一步拓展价值——价值来自于创新及设计导向,而非防止回归缺陷的保护及验证导向。
为了能同时达到两个稳态,从而完全发挥测试的潜力,你需要:
- 像生产代码一样对待你的测试代码——大胆地重构、创建和维护高质量测试,你自己要对它们有信心。
- 开始将测试作为一种设计工具,指导代码针对实际用途进行设计。
3.4 Scrum
四、结语
软件工程之路漫漫其修远兮,躬身入局 & 有容乃大 & 系统思考。