前言
文章内容源于《代码大全》一书,感兴趣的同学可以直接阅读书籍。
代码大全一书中从序到正文开始,无不提到一词构建,那么软件构建到底是什么?其实在序中已经解释:从软件生命周期开始,到编码完成。
⭐代码构建 一般占据了小型项目的 65%、中型项目的 50%。
⭐因此构建要为小型项目的75% 中大型项目的 50%-75% 的错误负责
当然,实践也表明: 修正上述由于构建造成的错误付出的代价,往往比修正 由于需求和架构变更带来的错误代价要小很多。
但是,这也造成了开发人员的误导:
如果不修正这些构建造成的错误,往往代价是最直接的。比如一个系统错误或者数据维护异常,往往比 需求变更 对 软件产品的 好坏定义来的更直接。 说白话一点,如果出现系统错误、或者数据维护异常,那么软件产品肯定不是一个好产品。但是如果 软件产品是 可用的,只是缺乏带给用户更好的体验,那么至少是一个可用产品。
往往需求变更要付出的代价,其实在构建阶段,如果采用了更好的实践方式,那么面对需求变更,是有更好的响应变化的能力。因此,做好构建是至关重要的。
软件构建?
从构建的产物看构建?
构建的产物——源代码——也是对软件的精确描述(胜过需求规格书和设计文档,代码总是最新的,不会过期)
什么是构建?构建的核心是什么?
软件开发的核心就是软件构建,而软件编码则是构建的核心。软件构建主要活动有:详细设计、编码、调试(debug)、自测(单元测试和集成测试)、集成(联调对接)。
隐喻?
中文含义: 比喻的一种、不用如、像、似、好像等比喻词汇,而是用是、为、成为、变为等词汇来描述。把某一事物比作和他有相似关系的另一事物。例如:少年是祖国的花朵、荷叶成了一把撑开的伞。
最熟悉的隐喻应用场景?
通过把不太理解的东西和一些你较为理解的东西比较,可以对不太理解的东西产生较好的理解。这种方式就使用了隐喻、也称之为建模。
软件隐喻的理解程度,代表了对软件开发的理解深度
软件工程学中没有标准的隐喻手段,每个人隐喻的手段不同,对软件的理解程度则不同。大到软件需求,小到一个方法,一行代码,不同的人见解是不一样的。即软件隐喻的理解程度、代表了对软件开发的理解程度。这点不可否认,往往更具有经验的开发人员,对隐喻的理解更深。
软件隐喻为了干什么?
软件隐喻并不是告诉你去哪里寻找答案,而是告诉你该如何寻找答案。举个例子:当你遇到一个报错,隐喻并不是告诉你是否该去百度或者书籍中寻找答案,而是告诉你如何从报错中,寻找解决这个报错的答案。
⭐软件隐喻 不是答案、不是算法、而是解决思路
软件隐喻场景有哪些?
举个例子:前段时间身份中心开发一个小需求,生成邮箱名,遇到问题是,多音字复姓的处理。由于Git开发拼音包的处理和各类开源框架的汉字拼音处理包多音字处理都有限 ,为了最大灵活度。采用了项目YML配置,存在一个小问题是:怎么通过yml配置的K对应的Value来替换姓。有两种解决方案:
- 使用Java自带的字符处理类,进行切分替换,但是代码判断很多,可读性很差。
- 使用隐喻,这类问题可以是寻找一个字符串的最大子串问题,使用算法来解决,提升效率。且将算法定义为最小方法,使用封装的特性,方便引用。
算法和软件隐喻的区别?
算法直接给你解决问题的指导,而隐喻告诉你该如何发现这些指导信息,去哪里寻找。
⭐ 算法像是已存在的解决方案,如果可以直接使用,那么编程是最方便的。但是编程的难点在于问题概念化。而且编程的大多数错误都是没有更准确的将问题概念化造成的问题。也就是说:算法有很多,但是为什么不能直接使用? 映射到实际工作中: 算法有很多、排序啊、查找啊、最短路径、最大子串、回文等等。但是实际工作中,需求并不会直接告诉你用什么算法去完成什么?而是什么时候用什么算法?将需求的核心问题概念化,在通过隐喻来使用已有的解决方案。
⭐算法的产生本身也是一种隐喻的手段:现实社会中已经存在的问题,或者理论,想要解决这些问题或者提出更好的理论,本身就是将某种实际问题的解决方案 隐喻成为了通用算法。
常用的软件隐喻场景?
-
写作 —— 编写代码
将编写代码 比喻为 写作,但是实际上写作常常要丢掉草稿。在软件编码中,这是代价很大的操作,最常见的就是写到一半,发现思路不通,或者有更好的解决方案,删掉重写。 所以在编写代码时,关键点是 当第一次尝试时就让代码走在正确的方向上。或者在成本最低的时候做修改。
-
培植耕作 ——编写代码
将编写代码比喻为耕作,意思是一部分一部分的开发施肥,一点点的将成果添加过软件产品中。这样的话开发的方式和过程不能控制。例如开发第一部分和开发第二部分,采用的开发方式和过程(比如分析、设计、实现)第一部分和第二部分之间存在关系,那么就是不可控的,因为只有两部分全部完成后,才能一起集成。
⭐类似瀑布模型。
-
养殖 —— 编写代码
将编写代码比喻为养殖,先搭建好框架和架构、涉及实现的细节可以建造好虚拟的类,在架构形成之后,再把真实的类一点点放进去。
⭐支持上述方法论的有演进式交付,也是敏捷开发的基础,敏捷开发就是迭代,循序渐进的开发方法。
-
建造 —— 编写代码
将建造房屋比喻为编写代码,先要做总体设计即软件架构设计,再出蓝图,即软件详细设计,最后要做建造审检,即软件复查(代码走读)和审查(代码审核)。
⭐其实很多软件学中的术语都是从建筑学中演变而来的:软件架构(建筑学)、支撑性测试(脚手架)、构建(建设),这一点体现在不少开源框架的英文官网词语就是建筑学的常用语。
⭐建筑学中结构一旦出现问题,代价是毁灭性的。因此要进行规划和建设。同理,大型商业软件项目,往往需要做更高规的设计,来保证项目的平稳和发展。
-
工具箱 —— 编写代码
将编写代码比作工具箱,即编写代码,在合适的时候采用合适工具,软件领域中,往往不要依赖于某一种 手段或者方法,更多时候,有更多比较好的手段去解决问题。 -
各种隐喻的组合
隐喻是一种启发式手段,而不是算法,但是隐喻没有标准的规范,因此,适当的引申,结合经验去隐喻。
总结:
前期准备
前期准备的重要性
准备工作的是目标什么?
准备工作的目标是为了降低项目风险,让项目平稳计划进行。而项目前期的主要风险在于:
- 风险1:糟糕的需求分析
- 风险2:糟糕的项目计划
准备工作不足的原因是什么?
准备工作不足的原因主要有:
-
开发人员并不具备前期的准备技能,例:项目规划、创作案例、分析全面准备的需求
-
过早的进行编码工作,一部分原因在于没有经验的开发者 有尽快开始编码的欲望、一部分原因在于管理者对于 花时间进行前期准备的程序员的不支持和不理解。
⭐作者趣称这种现象 为WISCA综合症状或者WIMP综合症状。Why Isn't Sam Coding Anything? (为什么Sam不在写代码?)Why isn't Mary Programing?(为什么Mary没有在编程?)
如何解决准备工作不足?
-
学习更多的技能,提升自己的水平,学会如何做好前期准备工作,如何做好需求分析
-
与管理者沟通,告诉他前期准备工作得重要性,优秀的管理者懂得聆听别人的意见
- 诉诸逻辑:要知道搞清楚需要做一个什么样的产品,往往比先做一个错误的东西,在扔掉重头做的成本低得多这样的道理
- 诉诸类比:程序员是软件食物链的最后一环,产品经理吃掉需求, 架构师吃掉架构设计,而程序员笑话设计。这是一条健康的软件食物链。
- 诉诸数据:IBM、惠普、TRW等公司的研究表明:在构建一开始清除一个错误,返工的成本是在中期甚至后期的十分之一百分之一。
- 提前预留出自己做好前期准备工作的时间,看起来评估工作时间的效率不高,但实际上干活的质量却有了保证。
辨明你所从事的软件类型
商业项目的计划、需求、架构活动与构建、系统测试、质量保证交织在一起,性命攸关的项目往往采用了更序列的方式,同时需求稳定是保证可靠稳定性的必备条件之一
- 前期准备工作对 迭代开发和序列式开发的影响
⭐忽略前期准备工作的 迭代式开发,比关注前期工作的 序列式开发成本更高。
⭐成功构建的关键之一:理解前期准备工作的完成程度,根据此来调整开发方法。即:项目初期/需求初期,预估采用迭代式开发,5人/日工作量,当前期工作准备快完成时,对需求和项目的理解已经更加深入,这时候不能太死板,要变化着调整开发方式,和工作量的安排。
- 迭代式开发和序列式开发
什么时候选择迭代式?
-
- 需求相当稳定
- 设计理解透彻
- 开发对领域很熟悉
- 项目风险小
- 项目长期的可预测性
- 后期改需求,返工成本昂贵
什么时候选择序列式?
-
- 需求理解不透彻
- 设计复杂有挑战
- 开发对领域不熟悉
- 项目有诸多风险不可控
- 后期改需求,成本较低
问题定义
为什么需要问题定义?
在构建开始前,有一项先决条件:对这个系统要解决的问题做出清楚的陈述。
如何判断已经写好了问题定义?是否能成为构建活动 的良好基础
- 问题定义在需求分析之前,而需求分析是对问题的深入刨析
-
问题定义应该站在客户的角度,用客户的语言来描述
⭐如果没有好的问题定义,那么可能解决的是一个错误的问题。
需求
需求是什么?
需求:详细描述软件系统该做什么。需求活动有:需求开发,需求分析,需求定义,软件需求,需求功能规格书。
为什么需要需求?
明确需求,可以很大程度上减少成本。参照3-1表,需求阶段没有发现的缺陷,越往后,修复花费的成本代价越高。因此需求越稳定,那么成本浮动就会越少。
需求的稳定性?
稳定的需求是开发的圣杯,然而实际上,项目越长,客户对项目的理解程度会随着参与度而增加。提出的需求随着项目周期和一开始可能大相径庭。IBM的研究也表明:平均每个项目会有25%的需求变更
在构建中处理需求变更?
- 检测需求的质量:通过核对表核对需求的质量。如果需求质量过差,从新开始进行需求分析,直至稳定。
- 确保所有人都知道需求变更的代价:不仅仅是开发人员需要了解,从客户到项目经理,产品经理,架构师,测试都需要知道需求变更的代价。明白了需求变更的代价,才能更好的做好稳步的软件构建。
- 建立可控制的需求变更规范:如果可以和客户制定一套可控制的需求变更流程。那么在规范内,需求变更就是周期性的
- 使用适应变更的开发方法:例如演进交付,分阶段交付,一次交付一部分,通过反馈结果调整设计,再交付下一部分。主要手段是缩短开发周期,在短时间内,将开发成果做展示来控制需求变更。
- 放弃:在需求极度糟糕的状态下,需要放弃,不做不可回收成本的消耗。
- 从商业价值来评估需求:部分需求可能会很好,但站在商业价值上来看,可有可无。那么就需要考虑需求是否值得考虑。
需求核对表
架构
软件架构是什么?
架构是软件设计的高层部分,用来支撑软件细节实现。架构也成为:系统架构,有一份独立的架构规格书文档描述。
架构对构建的影响?
修复架构中的缺陷所花费的成本如3-1所示,因此越稳定越详细的架构,构建开展的越快。相反,糟糕的架构设计,构建活动会无从下手。
架构的组成部分
- 程序组织(包结构定义)
- 主要类的详细定义(程序入口,核心类定义)
- 数据设计(数据库的设计,数据的交互方式)
- 业务规则(程序的业务规则定义,例如这部分客户需要响应30S完成等)
- 用户界面设计
- 资源管理(数据库链接,线程管理、内存设置)
- 安全性(考虑程序的安全性,提前建立威胁模型,对数据和程序进行保护)
- 性能(在内存、成本上需要考虑性能目标)
- 可伸缩性(描述系统增长之后,架构如何应对用户数量、服务器数量、网络节点数量、数据量的变化,系统的伸缩性)
- 互用性(如果系统和其他软件或者硬件共享数据,那么架构需要描述清楚)
- 国际化/本地化(国际化:I18n,让程序支持多个地域的技术。本地化:L10n,翻译程序支持当地特定语言。架构需要考虑国际化之后,字符集和字符串类型,如何不更改代码就能正常使用,架构应该能够描述清楚。)
- 输入输出(架构应该描述定义读取策略,描述IO错误记录在那一层:字段、记录、流、文件)
- 错误处理(架构应该详细定义如何处理异常和错误)
- 容错性(系统的容错性怎么做的?在遇到错误时,是直接不可用,还是提示。或是部分可用?何时恢复?微服务中Hystrix组件就是为了解决服务不可用,整个系统的容错性)
- 架构的可行性(考虑架构的实现需要的硬件和软件条件,再架构工作开展之前需要解决这些条件)
- 健壮性
- 买/卖决策(即声明架构中的组件是买/还是自造)
- 复用性(架构应该声明是否复用业界中已经存在的软件)
- 变更策略(描述架构对于功能增强 或者 变更的支持)
- 总质量(架构应该明确风险区域,解释哪些地方存在风险,并给出解决方案和规避步骤)
架构质量核对表
花费在前期准备上的时间长度
前期准备需要做哪些?花费时间周期?
前期准备工作包括问题定义、需求分析、架构设计。一般来说,占总工作量的20-30%时间比较合理
前期准备工作核对表
构建决策
当构建的前期准备工作已经完善,接下来需要做的就是明确构建的实现过程,即编程语言,编程约定,构建方法。
- 选择编程语言
这部分可以参考项目类型表,做好了前期准备工作,明确了项目类型。采用何种编程语言,可以借鉴已经有名声的经典案例。决定自己构建的语言。
- 编程约定
变量命名、包结构、类定义、子程序名称和定义、注释规范、格式规约。通常能提供一个标准的编程手册最好。
- 你在技术浪潮中的位置
确定构建语言的稳定性,如果在语言最火的前期使用,那么就需要接受语言的一定缺陷和不稳定。一门好的语言一定是受过时间的磨炼才锻造出来的。
- 选择合适的构建实践方法
通过对比构建实践表,来确定是否有合适的构建实践
构建实践核对表