【软工】提问回顾与个人总结
项目 | 内容 |
---|---|
课程:北航2020春软件工程 | 博客园班级博客 |
作业:提问回顾与个人总结 | 提问回顾与个人总结 |
前情提要:博客:个人博客作业——软件与软件工程 | 个人博客作业 |
之前的问题与新知
1. 软件的“生命周期”?
原书第一章中写道:
软件团队要从需求分析开始,……,展开后续工作,如设计(软件架构)、实现(数据结构和算法)、测试,到最后发布软件。……,修复各种各样的问题,这叫软件维护,或者服务运营。这一系列过程就是软件的生命周期。
看起来软件生命周期的定义应该为一个序列,包含需求分析、软件设计、实现、测试、运行和维护等。
然而在第五章关于流程等部分又提到了瀑布模型、瀑布模型的各种变体、统一流程(RUP)等。在瀑布模型中,各步骤是可以进行回溯的;在RUP中,需求分析、设计、实现、测试、部署工作流又渗透到了初始、细化、构造、交付四阶段中。
这看起来有些矛盾——我们当然可以概略地说软件的生命过程中包含了需求分析、设计、实现、测试、部署这些阶段,但将其按第一章讲成是一个线性过程、或按RUP来说各个步骤在时间上有并行重叠,似乎都不是定数。
因此我认为对软件的生命周期的定义是不确定的。首先无论是按哪种模型,似乎这些步骤都不是一个重复的Cycle。其次具体的软件流程是按照需求和产品特点不断调整的,没有一个固定的、通用的流程。
……
综上,我认为软件的生命周期是不固定的,且不能被称为“周期”。如果偏要给定一个整体的流程,我认为按照RUP四阶段的粗划分比较合理。
确实,软件的生命周期可能并不是完整的一个Cycle,也很可能不只包括一个Cycle。但实际上可以认为,在软件构建过程中,充满了大大小小不同粒度的“周期”。
如团队作业中,Alpha和Beta阶段是两个大的生命周期,而我们团队又在Alpha阶段内部分了3个子阶段、在Beta内部分了2个子阶段,在5个子阶段的结束时将新功能部署到服务器上,并开始新的设计与实现,那么这5个子阶段就是小的生命周期。
而开发过程中处理每一个小issue的时候,往往也有着技术选型、实现、测试、交付等步骤,于是完成一个issue事项也可以看出是一个微型的生命周期迭代。
“软件的生命周期”的意思不是严格地说软件的实际流程就是一个按步骤执行的序列和循环,而是在每一个大至新阶段、小至新功能的环节中,从一无所有到符合交付要求的过程。
2. Personal Software Process 与单元测试
在原书第二章介绍个人开发流程(PSP)时写道,Development由以下部分组成:
分析需求、生成设计文档、设计复审、代码规范、具体设计、具体编码、代码复审、测试
这里的测试我想是包含了单元测试和回归测试。就回归测试而言,将其写到流程的最后是有道理的——编码完成后进行测试,测试未通过的进行修复,修复后需要进行回归测试以保证其他部分没有退化。但我对单元测试的最早出现位置有质疑。我认为单元测试不仅在编码完成后进行,而且应该伴随着具体编码甚至具体设计时就应该开展。
书里又写道:
在写技术模块的规格说明书的时候,要越详细越好,最好各项要求都可以表示为一个单元测试用例。
……
单元测试必须由最熟悉代码的人(程序的作者)来写。
这里我们知道,单元测试的测试用例可以依照规格文档来构造。那么其实在完成最初的架构和模块设计、在具体编码之前,大部分单元测试的目标对象和测试用例就已经可以得出。我认为,应当在规格设计后先写好单元测试用例和模块接口,每个模块具体编码完成后尽可能早的进行单元测试,而不应该等到全部编码完成、复审后再测试。到那时候,就算是程序的作者可能也记不得具体模块的情况了。
而对结对编程而言,熟悉程序的人有两个了,就可以在Driver写代码的同时,Navigator对着设计文档和Driver写的代码设计测试用例、帮跑单元测试,同样也是在编码的同时,这样效率更高且与函数/类规格更贴切,在写代码时发现的特殊处理需要测试也可以加在已有的测试上。
理论上当接口指定完备后确实可以立即开始构造单元测试代码了,但在实际操作中还是有不小的困难:
- 开发过程本身可能较为困难,耗时和精力较多、任务重,于是实际操作时往往先照顾了正常开发进度、将单元测试视为可有可无的最后步骤。
- 接口在开发过程中可能产生变化(如实现时发现不合理、对接时发现需要更改等),因此先构造的测试代码和样例也需要进行更改。
因此除非接口定死、保持不变,否则还是先开发、对接、联合调试,最后进行单元测试和回归测试比较好操作。
3. 代码设计规范中的参数检查、assert和规格
书中第四章介绍代码规范时提到,
在Debug版本中,所有的参数都要验证其正确性。在正式版本中,对从外部(用户或别的模块)传递过来的参数,要验证其正确性。
如何验证正确性?那就要用assert。当你觉得某事肯定如何时,就可以用断言。……
“所有的参数都要验证其正确性”是一件难以实现的工作。
……
而参数的正确性也是难以规定的。
……
所谓参数的正确性恐怕有如下几个层级:
nodeList
和edgeList
都是List。前者的元素是int
,后者是(int, int)
。- 在
edgeList
中出现的所有边的顶点,都应属于nodeList
。- 无重点、无重边等其他可能由函数规格说明书制定的规则。
- ……
首先,这些验证若全部使用assert进行验证,可能比函数功能本身更费时(计算上的)和费心(程序员角度)。
其次,如何定义正确?正确性的验证应当到什么层次?哪些应当嵌在代码里做运行时检查(万无一失的),哪些应当放在单元测试里进行测试(抽样检查的)?
在理想情况下,当然应该对参数进行检查,配合单元测试就能很强地保证程序的正确性和稳定性。在形如个人作业和结对作业的简单项目中,逐一进行参数检查也许是可行的,但在形如团队作业的大型项目中,对每个函数进行参数检查是不可能的。
实际操作中,对于对用户可见的函数/方法(如向服务器的GET/POST请求),我们采取了参数检查,对于非法参数返回失败code;然而在众多的内部接口与函数中,我们只会对常见的可能错误(如传入的对象是否为undefined/null
?容器是否为空?容器的实际长度和表明长度的变量值是否一致?)进行检查。对于更广泛的变量是否符合规格,似乎没有精力进行逐一检查。
不过实际上恐怕无需对参数正确性进行逐一验证,只需至少保证当参数错误时不会产生严重的副作用即可。
4. NABCD模型
在竞争环境下,NABCD模型中,NAC对产品是否成功的影响似乎过小,BD对产品的影响似乎过大:
我们要在竞争性的环境中实践软件工程,那就要做实用并且创新的项目。……大家可以参考NABCD模型。
N:需求,你的创意满足了用户的什么需求?
A:做法,看看你有什么独特的招数,不光是技术上的,也可以是商业模式上的。
B:好处,会带来什么好处?要花费什么才能得到好处?得到的好处超过迁移成本?
C:竞争,用户需求 vs 我们产品 vs 别人产品
D:推广,如何把产品交到用户手中?
以我使用过的两个软件产品举例。
- Grammarly:英文写作助手,能提供拼写与语法、流畅性、是否吸引人、达意程度、易读性、词汇选择等方面的评分、分析和建议。这款产品的优胜之处在于,它高度满足了用户的爽点(超过了需求的范畴,可以称为Benefits)。用户只需要输入一段文字就可以在几秒钟内得到词和短语级别的各方面的修改建议。无脑的用户只需要点击每项警告,点击应用建议的写法,就可以目睹着自己的分数chuachua地上涨。另一个方面,Grammarly在YouTube大量投入了质量相当高的广告,且直接点击就可以使用,非常简单,在推广(我认为原词Delivery更达意,不是推广好而是易得)上做到了很高的水平。
- 微信:我认为这款产品的成功原因在于Delivery,但不是由于微信本身,而是由于自己与腾讯全家桶的数十款软件高度绑定,微信正在野心勃勃地发展为“下一代互联网的入口”,微信成为了一个“产品矩阵“中的领头羊。微信本身没有满足用户的许多需求,也没有什么特殊的招数,现在更缺少竞品。如果剥离腾讯系的其他app,微信恐怕难以敌过挑剔的用户和追赶的竞品。
在需求大家都能基本满足、做法的先进性用户难以直接感知、产品趋同化的现在,是否满足用户不计成本也要用到的爽点和优秀的营销与推广仿佛决定了一款软件的前途(而不是质量)。这说明NABCD模型是否过时?且NABCD的核心变成了B与D后,是否不再是一个软件工程概念而变成了一个营销与商业发展概念?
首先,BCD的力度和可行性与产品本身的质量和约束息息相关。以我们的团队产品为例,服务器的资源和性能就决定了不可能放开手脚做大规模的推广,因此不应该认为产品是产品、推广是推广、好处是好处,它们之间是分不开、互相制约的,只有产品质量高才能支撑好的推广。
除此以外,在产品设计阶段就对Delivery进行考虑是十分必要的。在我们的软件开发过程中,我们以提高用户易用性为核心目标,先后设计了草稿纸模式、浏览器扩展等功能和手段,以D的要求直接驱动了产品的开发。
5. 团队的创新能力
创新的迷思之七:成功的团队更能创新?
很多成功的企业进入了“创新者的困境”。当成功的企业步入中年,它们当年发迹的市场成熟了,当年赖以成功的创新技术成了主流的成熟技术,又叫“维持性的技术”,在成熟的市场和维持性的技术环境中,技术的创新并不是影响企业成败的主要因素……那些没有成功包袱的小公司反而能把颠覆性的创新带到市场,挑战成熟企业的霸主地位。
……作为热爱创新的从业者,是应该加入百度式的稳定却缺乏创新渐渐老去的企业,还是应该加入字节跳动式的不断创新正在高速发展的新兴企业?我曾听说不少应届生更愿意去应聘字节跳动,但大多是因为更现代的产品和管理模式,即公司本身的“新”,而并不一定是因为公司的“创新”。那我不禁想问,对程序员的职业价值自我实现而言,企业是否创新(不是个人是否创新)是否是一个重要的因素?
这个问题通过软工课程的个人、结对、团队三个项目的经历并不能做出回答。但在团队项目中,我们的产品是以云IDE这一新兴概念为核心的,产品的使用场景也和传统的APP不同。比起其他略显“传统”的团队,如做社团管理系统或博客园手机APP的,我们的团队可能能被称为“创新”的,而我们的团队成员在开发之初就抱有对新鲜事物的热情和对新产品的期待与野心。也许这就是创新的团队带给团队成员的初始激情与动力,也是创新的团队的优秀基因之一。如果是我在日后的职业选择中,我更倾向于(尤其是产品)创新的企业。
知识点
-
需求分析
引导和获取用户需求对于产品的功能和定位十分重要,最好使用焦点小组、深入面谈、调查问卷等手段进行用户调查,研究用户对同类产品的评价和对新产品的期待,包括功能、易用性、交互方式等。
-
产品设计
可以从最终产品形态和功能的角度(面向用户的角度)进行设计,即产品原型设计;也可以使用思维导图、实体关系图等手段从开发者的角度进行设计。前者可以帮助团队快速梳理需要实现的功能和界面,后者可以帮助开发者确定技术路线和软件的组成部分。
-
编程实现
每新增一个新功能或一段代码,在dev自测后可以尽早将其加入正式版本进行build,即“每日构建(Daily Build)”。当新加入的代码能在产品中正常work时,不仅能证明其正确性,更能提升团队的积极性;当代码存在问题时也可以尽早发现。因此应该尽早进行re-build,最好进行daily build,不能仅限于dev版本。
-
测试
测试包括功能测试和非功能测试,在开发过程中常用单元(回归)测试和功能测试以保障新增内容的正确性,在测试与发布阶段通常使用场景测试、Alpha/Beta测试等方法全面测试整个产品,同时还应进行压力测试、效能测试、易用性测试等手段保障用户体验。
-
发布
对于每一个Bug,需要决定采取修复、设计本来如此、不修复、推迟修复等行动。对于不合理的设计可以进行设计变更以包容或避免出现的bug。
-
维护
发布后应当密切监视软件的、服务器的状态,每隔一段时间对收集到的数据进行分析,及时发现诸如服务器负载过大、服务不正常在线等问题并加以维护。
理解和心得
-
个人项目
个人项目的复杂性和难度都较低,其涉及的软件工程核心要素是由规格驱动的测试和源代码管理等个人流程与规范。
-
结对项目
结对项目开始引入了较为复杂的功能需求,需要和结对伙伴一起进行设计,同时设计文档和代码复审在复杂软件中开始发挥作用、证明其必要性。虽然结对编程能一定程度上保障软件的正确性,但编程者拥有清晰的思路仍然是决定性的。与人合作时对源代码的管理和对不同意见的处理是团队项目的基础。
-
团队项目
在团队项目中,团队合作和项目管理是其中的关键,其中的挑战有:团队项目中的任务分配、多人合作、待办事项(issue)管理、掌控项目进度、源代码管理与版本控制等。使用诸如GitHub和燃尽图等工具能极大地规范工作并提升效率,然而将任务进行细粒度、尽量独立准确地分解才能最大程度地发挥其作用。