Loading

构之有道,建之有法——软工个人阅读作业#2

项目 内容
这个作业属于哪个课程 2021春季计算机学院软件工程(罗杰 任健)
这个作业的要求在哪里 个人阅读作业#2要求
我在这个课程的目标是 提升工程能力和团队意识,熟悉软件开发的流程
这个作业在哪个具体方面帮助我实现目标 阅读《构建之法》,初窥项目流程;了解基础的代码版控软件和持续集成方法

阅读提问

刚发博客作业的时候,被“快速看完整部教材”吓了一跳。打开书籍,\(600\) 多页,顿感任务重大。

没有读书习惯,但有作业逼迫,迫不得已翻开了此书。没想到书中居然没有各种晦涩难懂的概念词汇,取而代之的是以各种真实案例、以及幽默诙谐的“移山公司”事例,去解释敏捷软工的各个实践步骤。知识解释地非常鲜活易懂,常常在前面提出的问题,再读一读书、查一查资料后面都解决了,基本提不出什么问题来。厚厚一本书,不到两周很快就通读了一遍。仍然有一些问题,聊做记录。

问题一:单元测试一定要作者写吗

单元测试必须由最熟悉代码的人(程序的作者)来写。代码的作者最了解代码的目的、特点和实现的局限性。所以,写单元测试没有比作者更适合的人选了。

——《构建之法》第二章 2.1.2 好的单元测试的标准

书中,作者认为,代码的作者最了解代码,因此单元测试最适合的人选便是作者本人。

单元测试最常见的目标便是验证一段代码是否符合预期设计逻辑。其实这种测试是简单的,待测部分往往不是一个函数就是一块代码,对于与外界的交互较少,没有复杂的依赖。对于这样的测试,代码编写者很容易构造出其上下文进行测试。较小的、耦合度低的的代码块,出现 bug 的几率也相对较小,这一部分的单元测试其实并非单元测试的侧重点。

而重点的测试部分都是偏向整体的、业务的逻辑,经常在一个函数中就会有意无意的牵连到其他部分,包括类之间的相互作用等,如何处理这种依赖关系是一个非常大的问题。比如说,验证一个需要数据库依赖的业务一是否符合设计逻辑,那么如果使用嵌入式数据库进行本地测试,此时就是默认了一种“这段代码的 SQL 中,使用嵌入式数据库和生产环境数据库是等价的”的假设,要验证这一个假设是否成立,还需要更进一步的、更全面的“集成测试”,比如真实的使用生产数据库进行测试。

单元测试的流程主要是评估待测试代码范围、拿到开发代码进行调整,之后进行全面仔细的、边界情况和分支全覆盖的测试,以最小化测试漏洞。也就是说,单元测试的一大重点便是对于边界情况进行全覆盖,覆盖的越全面则测试的效果越好。然而写代码本身的人在编码时就在用其本身的逻辑进行开发,这种本身的逻辑很容易将自身陷入一种思维定式,对于某特定分支、边界情况未必能够考虑到,作者本人写的单元测试也往往覆盖不到这种边界的情况。

这是一种设计上的欠缺,是一种思维上的漏洞,而这种漏洞单靠单元测试是无法发现的。笔者之前在 Ruby on Rails 敏捷开发时编写单元测试总有一种感觉,写一堆自己明显感觉不会出错的测试,像是在“凑字数”、“浪费时间”。写完单元测试总是变绿的,但论及许多复杂的分支边界情况,往往在后续的开发中,或者同学之间的交流才能够发现,甚至再也没能发现

当局者迷,旁观者清。如果让一个并不熟悉这一部分代码的人来编写单元测试,他就有更大的概率突破作者本身的思维定式,而对于一些***钻的边界情况进行处理,这种边界情况不仅仅包含输入输出内容,还包括调用位置、调用时机等等多个方面。

在做 ROR 的时候,也曾使用过测试驱动开发(TDD)的开发方式,写测试“变红”再写代码“变绿”,先考虑边界情况、提出测试,再进行编写,似乎也是一种不错的解决思维定式的方案。是否仅有这种方式,单元测试自己编写才更站得住脚?

问题二:结对编程的应用性如何

驾驶员和领航员不断轮换角色,不要连续工作超过一小时,每工作一小时休息15分钟。

只有水平上的差距,没有级别上的差异。两人结对,尽管可能大家的级别资历不同,但不管在分析、设计或编码上,双方都拥有平等的决策权利。

——《构建之法》第四章 4.5.4 如何结对编程

领航员和驾驶员互相轮换角色、拥有平等的决策权利,是结对编程的重点所在。领航员审阅文档、思考架构,驾驶员进行编码和单元测试,这样的分工固然能够节省整个项目的开发时间,但程序的整体编写时间增加了。公司是否会拿两份工资去鼓励结对编程呢?

要做到高效率、高质量的结对编程,首先两名程序员的水平不能差异太大,否则角色的交换可能会让水平较高的一方难以接受。譬如有一个自己十分钟就能写出的版块,队友却要用半个小时一点一点堆出来,堆出来的代码思路不清、码风混乱、bug 频出,把一架飞机驾驶成拖拉机;当领航员时,驾驶员更像是一种代码的表演,领航员理解不了代码的时候?这样的队怕是很难结起来。

其次两名程序员的性格要互补,思路要一致。无论两人开始的萌芽阶段有多么彬彬有礼、避免冲突,在接触了解后难免会产生大量的矛盾冲突,这种矛盾冲突往往来自不同的教育体系、相异的思维模式,因此也难以站在对方的角度思考问题。

所以固然结对可以互相督促、互相学习、理清思路,但也需要两人的配合与默契,找到这样两个人的难度是否和找对象的难度比较接近呢?而且,管理者也往往想用一份工资去换一份工资的效益。

Pair Programming(give it a rest)》一文中,作者表示:“不要认为这(结对编程)有多么具有革命性,你只要集中注意力,努力思考,深入问题,设计解决方案并进行正确的实现和系统的测试”。

结对的一个主要目的便是让代码出错的概率降低、每一行代码都有其存在的意义,不留浑水摸鱼的机会,但书中提到过的代码复审也能够达到类似的目的,而且能够多人参与,声音来源更广,在当下 github gitlab 等代码交流社区的 pull request 也十分便捷。事实上,了解过的部分周围公司也都因为种种原因没能开展结对编程。结对编程最早也是源自两个程序员赶 DDL 不得不做的一个“创举”,仅仅是契合当时的事件背景的一种解决方案。结对编程在当下的应用性究竟如何?

问题三:课程中例会是否仍要“每日”

冲刺期间,每天要开一个每日例会(Scrum Meeting),团队成员大多站着开会,所以又称每日立会。大家依次报告: 我昨天做了啥,我今天要做啥,我碰到了哪些问题。

每日立会强迫每个人向同伴报告进度,迫使大家把 问题摆在明面上。同时启动每日构建,让大家每天都能看到一个逐渐 完善的版本。

——《构建之法》第六章 6.2 敏捷流程的问题和解法

“例会”是为了报告在一段时间内团队工作进度和任务完成程度,从而进一步安排下一步发展目标的会议。但在当下,同学们不仅要完成计网、计网实验的作业内容,还要完成大家自己选各种各样的一般专业课、通识课的相关部分。在这种无法做到每天时间充足的情况下,我们在例会中难免也会出现报告“我昨天没写代码,我今天仍然没写,我没碰到困难”等“狗熊级程序员”的回答。

当场景、功能都计划好的时候,要给员工足够多的时间,让他们投入到工作中去,而不要经常打断他们。要尽量减少非开发时间,不要动不动就开“全体会议”。

——《构建之法》第十一章 11.5.1 闭门造车

敏捷开发中,频繁的小会是为了及时同步全局信息,让团队清楚个人的进度情况、困难之处,同步信息的主要目的在于增加整个团队的效率,即通过队友的进度、压力、困难来 push 每一个人。我相信我们的组员都不会习惯于浑水摸鱼,但人的精力是有限的。在这种课程压力下,在例会会占掉课余时间一大块蛋糕时,同步信息的例会是否还有必要保持“每日”?如果适当地增大例会之间的间隙,并适当规定每个人的贡献与任务,是否也能够在给组员足够压力的同时允许我们自主安排其他课程与软工项目的时间?

问题四:NABCD 模型是否变为 BD 模型

那我们怎么才能按部就班地分析需求,然后有条理地说服别人?大家可以参考NABCD模型。

  1. N(Need,需求)
  2. A(Approach,做法)
  3. B(Benefit,好处)
  4. C(Competitors,竞争)
  5. D(Delivery,推广):在实际项目中经历多次的NABC之后,许多人意识到这个框架还应该加一个元素D:Delivery。

——《构建之法》第八章 8.4 竞争性需求分析的框架

先举几个例子,看一下这几个当下成功的软件是如何做的:

QQ:抓住了手机刚兴起的用户社交需求,在 ICQ 的基础上敏锐地嗅出用户的满足点,开创了离线发送和隐身登录等功能,是其主要的成功原因。

微信:微信的发展是逐步抓住市场契机。先是抓住手机发展的契机,不断更新满足用户需求的功能,比如“附近的人”,漂流瓶、多种语言、手机号的支持,以及便捷的文件传输,甚至一些细节如翻页不卡的处理;再是抓住二维码的契机,率先添加“扫一扫”功能并且与支付功能挂钩;再是对于打车、外卖、买电影票等的支持(所谓“全家桶”绑定),逐渐发展到数亿用户。

网易云:首先抓住了各种能够满足用户的细节,比如与大量音乐人的合作、干净利索的排版、无关评论的删除、智能推荐,甚至是播放歌曲时旋转的胶碟,都能够给用户一种 Benefit。但是作为一款音乐软件,其迁移成本固没有社交软件那么庞大,但重新进行歌曲的收藏仍是与 QQ 音乐等拉开差距的重大阻碍。网易云通过完美的市场推广营销手段,从 Uber 打车券、音乐明信片,到一些满足用户虚荣需求的“听歌测试”、引发广大舆论的地铁 Touch 行动,以及有格调的公关,成功地打下了音乐的半壁江山。

bilibili:最初的竞争力在于高质量视频内容和弹幕,以及大量因缺乏监管而存在的内容,以及极其重要的一点,无广告。这一个竞争优势贯穿 b 站始终,是其核心竞争力。近年来 b 站通过与央视、共青团等多方的合作,发展成了一个受众范围较大的规范视频软件。

观察这几个软件的做法,扩大用户量的主要因素就在一内因(B)一外因(D)。满足用户基本需求的 N,在 QQ 那时或许是一种开创性的做法(其实当时也不算基本需求了),在现下已经是一种企业生存的必需要求了,单单满足这一需求是无法谈论竞争力的。而这些软件之所以能够取得如此规模用户量,更多的其实是满足用户“兴奋需求”的 B,以及市场推广或者捆绑的手段 D。同样地,许多被腾讯推广的游戏,其设计、内容等都远远落后于同类型其他游戏,但无论是用户量还是知名度往往都能够吊打其他游戏。所以其实 NABCD 模型在当下是否转化成了 BD 模型?软件的成功到底是软件工程还是市场营销?

问题五:设计文档如何平衡严谨性与复杂性

规格说明书(Specification)简称Spec,分为以下两种:

  1. 软件功能说明书(Functional Spec),主要用来说明软件的外部功能和用户的交互情况(把软件当作一个黑盒子)。
  2. 软件技术说明书(Technical Spec),又叫设计文档(Design Doc),主要用来说明软件内部的设计规范(把软件当作一个透明的箱子)。

——《构建之法》第十章 10.3 规格说明书

书中写到,软件技术说明书主要用来说明软件内部的设计规范。一个参考资料中也说到,需要包含每个模块的详细设计(输入,处理,算法,输出)的软件详细说明书。

技术说明书包含各个模块的先验条件(假设)、后验条件(功能)、副作用和异常,容易联想到我们在面向对象设计与构造中,学习过的 JML。JML 是用于对 Java 程序进行规格化设计的一种表示语言,提供了一个比较严谨的描述规格的方法,为程序自动化测试提供了可能。但其逻辑的严谨性是由描述的复杂性保证的,一个简单的方法的规格可能比实现还要长几倍;这不仅增大了阅读量,增加了阅读的复杂度,同时还增加了编写的难度

因此,设计文档应该如何平衡严谨性与复杂性?真实软件工程中,是否用到了类似 JML 的规格语言进行功能描述,如果是,如何保证编写的正确性?如果不是,如何保证规格的严谨性?

问题六:集成测试应不应该在模块构建时进行

问:应该什么时候做集成测试?是不是越早越好?

答:原则上是当一个模块稳定的时候,就可以把它集成到系统中,和整个系统一起进行测试。在模块本身稳定之前就提早做集成测试,可能会报告出很多Bug,但是这些由于提早测试而发现的Bug,有点像汽车司机在等待绿灯时不耐烦而拼命地按喇叭——也就是说,有点像噪音。我们还是要等到适当的时机再开始进行集成测试。

——《构建之法》第十三章 13.2.7 场景/集成/系统测试

书中提到,尽量在一个模块稳定后,才与系统进行集成测试。

但实际应用中,比如开发人员在做用户模块,在一天的冲刺开发中只开发出了用户登入登出、修改密码功能。那么类似“用户登入-修改密码”这样的集成测试放在这里未必没有必要,因为它可能涉及到整体模型的构建问题,如果在整个用户模块都做完之后再进行集成测试、发现模型需要重构时,这或许已经浪费了大量时间,这便是一个潜在的“风险”。

根据自己之前 Ruby on Rails 开发的经验(虽然只是个人开发),在实现一个功能后立即对其进行测试是最保险的办法;推广到项目,我认为集成测试可以在与开发进度沟通的基础上适当进行,而没必要在一个模块彻底稳定后再进行。老师和助教们如何理解这里的差异?

问题七:事后诸葛亮会议为何不能融入每日例会

一个里程碑结束了,接下来怎么办?团队有什么经验教训?产品怎么才能做得更好?我们常说“软件的生命周期”——这个软件开发的周期结束了,生命也结束了。我们能不能像医学的尸体解剖一样,把这个软件开发的流程解剖一下?解剖的过程可以叫:Postmortem,Retrospective,Review,事后诸葛亮会议,等等……

——《构建之法》第十五章 15.2 发布之后——事后诸葛亮会议

观察到其中许多影响进展的问题都是源于信息不同步、不准确,进行交流时不够全面公开造成的。而这样的事后诸葛亮会议的目的无非在于:1. 畅所欲言积极参与,2. 寻找短板并分析原因,3. 根据各种声音分析合适的决策。那么这样的复盘与平常的会议有何不同?为什么不在平时的会议就把这些问题解决掉,做到尽全尽美呢?

问题八:70%的创新者都是跨领域创新吗

这个想法看起来没什么错,我们不就是为了成为某个领域的专家,才来上学,拿学位,希望拿到学位之后成为专家,然后再开始这个领域的创新?但是统计数据表明,70%的创新者说,他们最成功的创新,是在他们的拿手领域之外发现的。

——《构建之法》第十六章 16.1.5 迷思之五:要成为领域的专家,才能创新

统计数据表明?70% 的创新者?有具体的数据来源吗,创新者的来源又有哪一些?

某重点高中 A 百年建校史里突然有个人没考上大学,试问该高中是否就不算重点高中了?我可不可以这么说:统计数据表明,在 A 高中,30% 的学生无法考上大学。哪里来的 30%?统计了一下,原来他们班最后一排一共三个人。

不是说个例不能做规律层面的评论,有些情况是可以的。但是把个例往规律上引导,这是上纲上线的手法,某时期常用,文化人不耻。感情牌再怎么打,也不能借题发挥;个例再夸张,也不能擅自夸大。

扯远了,再回来说跨领域创新。所谓拿手领域之外,其实是对该行业没有那么擅长,但自己的经历与该行业存在着一些关系;没有专业的专家了解程度深,少了一层思维定式,可以从不同的角度看待问题、进行创新,但这并不表明跨领域创新更多更容易,这有点像代码开发者和测试者的感觉。书中提到的 Tim Berners-Lee,便是如此。

另外,书中描述的“随身听”成功案例,专家们不仅仅是认为随身听没有市场,还

做了多次市场调查,来证明大众不会喜欢“只能放音乐,不能录音乐的小玩意”。

但在这样的基础上,盛田用直觉推动研发、辞职要挟,最终取得了成功,其中充满了冒险主义和“赌怪”的气息,书中想通过这样的例子说明什么呢?外行的直觉比内行的调研计算要准确?似乎不见得。

因此,与其做着青天白日梦想着自己有朝一日能随便找个行业搞出个创意来,不如脚踏实地干好分内的事、增加专业的知识储备和市场需求的了解,这才是拿出创新产品的正道。

问题九:如何判定“价值观”

如果业绩很好,但价值观不太对路(不太听话?),则是一条野狗。要坚决清除,不然功高震主……

——《构建之法》第十七章 17.3 绩效管理

价值观固然很重要,关系到个人利益与集体利益的合理分配,但如此主观的一种方面如何进行判定与评估呢?

以阿里为例,它这样评估的:客户第一、团队合作、激情、诚信、敬业、拥抱变化”六脉神剑“,进行案例分析和绩效谈话。举个极端点的例子,对于一些一心闷在代码上的、表达能力较弱但业绩能够非常突出的程序员,他能够做到以上几点,但由于表达能力弱、又想不出价值观对应的案例,那这种人就应该当做“野狗”涮掉吗?把一条潜在的藏獒当做野狗逐出公司,这无论对公司还是员工,都是一个极大的损失。

题外话:其实自己比较怕以后成为这样的人,因为赧于表达,常常在需要表达的时候犯嘀咕、怕笑话,不敢去表达自己的想法。学长因此建议我去当 PM,但这涉及到团体利益,一个 PM 要是没有表达和决断能力,注定在开始的萌芽和磨合阶段就当不好,这影响的是整个团队的利益,而这不是我希望的。

调研源代码版本管理软件

相同之处

都是供用 Git 版控的项目使用的托管服务,有创建、上传、修改、fork、PR、合并项目的功能,支持 markdown,能 follow 膜拜大佬,能发 Issue,而且都有组织 Group,可以以小组为单位进行代码管理、项目集中规划、代码协作、测试和部署。

不同之处

Github 是最早供用 Git 版控的项目使用的托管服务,有一亿多的仓库数量,用户基数大,目前全球最大的开源社交编程及代码托管网站。由于其不仅社区庞大,还“提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能”[1],对于协作项目开发、以及项目的存储,Github 无疑是首选网站。免费区间,单个项目不能超过 1G,单个文件不能超过 100M。就是 Github Desktop 做的有点屎,用起来不如直接 git 好用。

Gitlab 是用 ROR 做的,而且开源。“与轻量级目录访问协议集成,允许在Internet上定位和访问各种资源”。缺点是速度较慢,而且许多 UI 比如 CI 都做的不如 Github 好看(个人吐槽)。私有库免费,这是它的一个重要优势。社区版免费区间有 10G 限制,非常适合自托管,这也许就是 OO 改革对准了 gitlab 的原因之一吧。

BitBucket 功能比较少,比较适合小型团队,小型团队提供无限免费私有仓库;不仅仅支持 Git,还支持 hg 项目。被 Atlassian 收购,所以对于这家公司的服务(SourceTree,Cloud9 等)需求量较大的大型企业适合选择 BitBucket。

调研持续集成/部署工具

Gitlab CI

  1. 使用 oo-2020 pre2task6 的内容,本地用 maven 创建 project,并编写一个简易的 JUnit4 测试:

  1. 配置 Gitlab CI,上传到 Gitlab 触发 CI:

  1. 设置 cobertura,并捕获测试覆盖率:

  1. 增加 readme,查看覆盖率:

Gitlab CI 是笔者之前用的比较多的一个 CI/CD 工具,在同一个镜像上实现多个 stage,不同的 stage 之间可以通过 artifact 进行数据交流。

Github Action

  1. 将上述代码转移至 Github,根据官方文档编写 .github/workflows/maven.yml ,即可触发啦!

  1. 配置 codecov,并相应更改 pom 中 cobertura 的格式,顺便更改 README,就可以实时显示覆盖率啦!

Github Action 则是可以在多个平台上、不同环境下运行的工具,每一个 job 都是一个不同的环境,这个环境需要用 runs-on 说明平台类型,需要时还要使用 actions/setup-java 之类的 action 进行环境的配置。由于在不同虚拟环境上跑,因此需要特殊的 actions/upload-artifact 以及 actions/download-artifact 进行 job 之间的数据传输,与此同时,使用频率较高且不常更改的文件/文件夹还可以使用 actions/cache 进行缓存。

题外话:之前由于思维惯性,认为 Github Action 不同 job 也是在同一镜像上跑的,结果 build 里配置了 java-version: 1.8 ,而 test 中没配置,导致不存在 $JAVA_HOME/lib/tools.jar 而报错。原因是虚拟环境不配置时默认为 jdk11,而从 jdk9 以上,tools.jar 就已经被删掉了,费了好长时间。(╯‵□′)╯︵┻━┻

在应用的过程中,能看到在输出内容较多的时候,Gitlab CI 不会卡顿,而 Github Action 上下拖动时会产生卡顿现象;但 Github Action 的速度较快。

至于技术、产品、需求方面的特性和优劣,笔者只是试用一两天总结不太出来,从相关资料摘要分析了如下几点:

Github Action Gitlab CI
独特的功能 可能实现最佳GitHub集成 AutoDev Ops /允许将代码管理和CI放在同一位置
并发作业 允许并发作业,甚至是多平台。 yml轻松配置要并行运行的作业
分布式构建 没有具体提及,但是鉴于任务可以在多个平台上运行,因此分布式构建很有可能也可用。 支持
容器支持 Linux,mac,Windows,或直接在VM中运行 默认Docker容器注册表已集成到GitLab中
管理支持/自托管/报表 即将推出,尚不可用。 支持
构建管道 工作流 通过YML配置文件定义
生态系统 followers 多,已经拥有各种可用的预制工作流
集成 通过共享的第三方工作流程(AWS,Azure,Zeit,Kubernetes等)使集成成为可能 整个GitLab都有大量第三方集成
可通过API或其他方式使用API 目前尚不清楚,但假设GitHub Actions将与GitHub GraphQL API集成(可用的更成熟的GraphQL API实现之一) 提供REST API和(新的)GraphQL API,并计划仅在以后维护GraphQL API。至少在CI需求方面,允许执行几乎可以通过接口执行的所有操作。

可以看出,Github Action 作为一个新兴的工具,有一定欠缺之处的同时,在生态系统上有着固有的领先优势(Github 上进行 PR、Issues 等团队协作方面更加便捷,因此 Github Action 的使用对象也比较广),也在实现机制上与 Gitlab CI 有着很明显的区别(上文所述),同时前端比 Gitlab 做的好看(符合笔者审美!)。当下 Gitlab CI 支持自托管、管理和报表,而 Github Action 无法支持报表等内容。

感觉 Github Action 最牛的地方在它支持直接使用其他仓库的 Action,并且允许自定义参数。上面提到的使用 codecov 测覆盖率就是用 codecov 这个项目的 Action 进行的,也就相当于把库搬到了 Github 上。

因此笔者认为,Gitlab CI 会在较长的一段时间由于其功能上的方便保持使用量的优势,而且比较适合小规模团队开发;而 Github Action 自从创建到现在一直在高速发展,在用户基数大、功能前景很强、(以及前端做的舒服)的条件下,有机会超越 Gitlab,而且许多大型的项目现在都在 Github 上开源,对于大型项目有着不小的优势。

posted @ 2021-03-13 00:30  Potassium  阅读(1031)  评论(13编辑  收藏  举报