2021年软工-第三次结对作业
2021年软工-第三次结对作业
1.教学班级和GitLab项目地址
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2021春季软件工程(罗杰 任健) |
这个作业的要求在哪里 | 结对项目-第三阶段 |
我在这个课程的目标是 | 了解软件开发工程的具体流程,学习项目实践开发的方法与步骤。 |
这个作业在哪个具体方面帮助我实现目标 | 第三次进行结对编程实践,结合结对项目几个阶段的实践经历,记录解决项目的心路历程与收获。 |
结对项目Gitlab地址 | 2021_Qixiang_He-Mingxin_Li_pair_work |
学号后四位 | 6161,3339 |
2. 结对项目实践反思
(1)针对前面两个阶段中出现的问题,分析问题的特征、产生的根源和对质量的影响程度;
前两个阶段的结对项目是递进的,同样地,我们在这两个阶段中的合作模式也是递进的。
-
在第一阶段,我们遇到的问题主要是如何结合两个人的资源来完成项目?
第一阶段是探索阶段,我们需要将《构建之法》中纸面上描述的结对编程转化为可以在现实中实践的结对编程。我们本来是希望按照书中的描述,全程维持一人驾驶,一人领航的模式,但由于各种因素的限制很难真正实践:
- 首先是注意力因素,这是一种主观因素。在驾驶员奋力编程时,会由于过度专注而忽视了领航员的提醒,而此时领航员如果冒然打断有时又会影响驾驶员的思路。也就是说,在编程过程中要做到领航员与驾驶员之间的充分交流是一种十分理想的情况,很多情况下都无法做到。
- 其次是时间因素,这是一种客观因素。我们经历的结对编程并不能独占我们的所有时间,这就意味着我们留给结对编程的时间是受到其他课程上课时间,其他课程作业安排的综合影响,而同时这种影响对我们两个人也是不同的,这就导致我们有时候很难找到一个时间两个人共同进行结对编程。
所幸第一阶段的作业比较简单,经过一段时间的交流与实践,我们决定采用先碰面商量程序整体框架,同时对实现进行分工,再在分工实现过程进行线上交流的模式。使用这种模式虽然也潜藏着各种问题,但总体上将我们最终实现的质量维持在了一个风险可控的范围内。
-
在第二阶段,我们基本上已经熟悉了自己的结对编程模式,但又走入了对指导书细节过于吹毛求疵的死胡同。
第二阶段是真正的实践阶段。但在我们很快构建好了整体框架,准备开始大干一场的时候,却被困在细节的泥潭中寸步难行。很难说这个是如何产生的,又应该由什么东西来负责。但现在想来应该与思考的角度有很大关系:
- 对于指导书的编写者。在编写指导书的时候,其可能是站在一个已经编写好的基准程序的角度来安排指导书内容的,这样做的好处是指导书能很容易的梳理出主干逻辑,但坏处就是容易忽视细节。(这部分基于个人揣测,如果与现实不符请不要放在心上。)
- 而我们反过来作为指导书的实现者,从指导书中梳理出主干逻辑并不困难,构思一个架构去适应这个主干逻辑也不困难,但在具体实现时,不同的架构可能就会具有不同的细节,而我们又担心不同的细节最终是否会导致无法通过评测,这就导致了我们是实现上的困难。
在经过几次的规划与调整后,我们也算是完成了一个遵循issue区细节的实现版本,当然,整个过程的修修改改难免会增加质量的不可控性,但最终也算是能够完成一个符合规范的版本。
(2)总结结对项目中的需求分析实践体会,并分析哪些bug是因为需求分析不足而带来的;
考虑到我们的实际情况是按照指导书的规划来完成一个程序,而指导书本身就能算的上是一种需求分析的结果,所以我们能做的应该算是对需求分析的细化。在细化的过程中,遇到过两个bug:
- 第一个bug发生在第一阶段,是在单元测试时期发现的。具体表现为对空目录使用list函数会产生错误。导致这个bug的原因就是在细化需求分析时,忽略了目录可能处于的各种情况而导致的。
- 第二个bug发生在第一阶段,是在弱测过程中发现的。具体表现为modifyTime与期望不符。这个bug的产生的原因是对需求分析细化的过程中产生了疏忽以及误解导致的,具体包括两方面原因:第一个原因是忽视了
目录修改 当且仅当该目录中的子目录或文件数量发生变化时,该目录被视作修改一次。
,这导致我们一开始认为只要子文件/目录发生变化,目录的modifyTime就会更新。第二个原因是对上面那句话的意思有所误解,我们一开始认为modifyTime需要递归地向上更新,但真正理解了才发现每次只会影响其父目录。 - 第三个bug发生在第一阶段,并且中测结束才发现错误。具体表现为没有判断路径长度是否超过4096字符。产生这个bug的原因是对指导书中的需求还不够敏感,对这种明显的数字约束没有足够的重视导致的。但这种bug的忽视也有一定的偶然性,比如同样具有长度限制的文件/目录名,在我们的程序中就有正常的检查,
当然,需求的细化也并不是一个简单的工作,比如说第二阶段作业的ln, ln -s, mv, cp指令都含有大量的分支,并且这些分支的顺序与划分可能还与自己的实现有一定差别,这时候就需要对分支进行整合与再划分,在保证前后一致性的情况下使其能在自己的架构中比较简单的实现。同时,在对第二阶段作业进行需求分析时,有时还需自己对细节额外进行需求分析,比如在Ubuntu 18.04上验证相关指令行为,比如:mv指令在使用文件/目录覆盖目标文件/目录时,统一会修改父目录的modifyTime,而cp指令在覆盖时都不会修改父目录的modifyTime。
(3)总结结对中的架构设计实践体会,描述通过改进设计来提高程序的性能改进的思路和方法,并分析哪些bug是因为架构设计不足(特别的,需求变化)而触发的bug;
在对架构的设计中,大致在以下五方面进行了抉择与改进:
-
对于.以及..的处理,是采用特殊判断方法还是采用额外创建隐藏的.以及..目录?
两种方法分别有其好处。
对于额外进行特殊判断好处是只需要在与路径有关的地方进行特判,但坏处就是路径不是由单一函数处理的,而是在多个场合下都会被使用到,这就需要确保每种场合都进行了特殊判断,这可能会引起疏忽,特别是在结对编程的时候,这样的疏忽尤其容易出现。
额外创建隐藏的目录的好处是对路径的处理可以变得更加的简单,坏处就是在涉及list,info等需要获取目录内容的指令时就需要进行特殊处理。
我们当时是直接选择了前者,但现在来看当时并没有做更进一步的对比。
-
size的处理方式
对于size,我们在第一阶段作业与第二阶段作业采用了不同的处理方式。在第一阶段,目录有一个size字段,每次文件修改size都会递归地向上更新。但到了第二阶段,我们对size采用实时获取的办法,也就是说每次获取size都自顶向下获取每个文件的size。这两种处理方法分别有其各自的考虑:
- 使用size字段,并主动更新。这样做的好处是在有大量size查询的情况下能在时间复杂度上有较好的表现。这样做在第一阶段是很合适的,因为整体文件系统的结构还比较简单,size的更新也只有少数几个函数会涉及,处理起来的难度比较小。
- 不适用size字段,动态查询自身的size。这样做的好处是不用在程序中安排很多的size更新语句,每次访问的时候通过简单的查询即可,不容易因为疏忽而导致错误。之所以会在第二阶段更改成这种实现也是因为第二阶段新引入了很多特性,而这些特性都导致size的更新变得棘手了起来,并且在编码的时候需要不断将这个问题考虑进去会带了较大的思考负担。
所以针对这个问题,我们考虑了不同情况下的利弊而选择了不同实现方式。
-
modifyTime更新归类
对于modifyTime的更新,我们在第一阶段是枚举了所有需要更新modifyTime的情况并在每一处及进行手动编码更新的。但到了第二阶段,同样是由于系统复杂度的提升,使得这样的一味枚举容易导致bug,所以将modifyTime的更新时机进行了归类:
- 第一类是在内部更新modifyTime。对于这类更新,可以在File类以及Document类内部实现,而不需要在控制类中显式实现,这就降低了维护的复杂度。对于目录,其内部更新指的是其子文件/目录发生改变的时候会触发内部更新,对于文件,是其文件所包含内容的发生改变时会触发内部更新。
- 第二类是外部更新modifyTime。对于这类更新,需要在控制类中显示编码,但由于第一类情况已经涵盖了大部分情形,所以这一类的覆盖就可以比较容易的实现,包括从外部移动文件,指导书中一些额外的更新操作等都属于外部更新。
-
hardLInk实现方式
在实现hardLink时,主要改进了两个方面:
- 首先在最开始实现时,忽视了hardLink与文件之间的高度一致性,导致对hardLink进行了很多复杂且冗余的实现。当意识到这一问题后,就将hardLink的实现完全转为了对文件类的包装,即只有路径相关的方法使用自身的属性,其他方法一律间接调用其指向的文件。
- 其次是hardLink链接模式的改变。最开始在处理硬链接链接硬链接问题的时候,采用的模型是树形模型,即如果链接硬链接,则其指向的文件对象就是那一个硬链接文件对象。这样的实现模型有两个问题:一个是调用方法时可能会经过多级调用效果不高,另一个是对当时的size的处理方式还是主动更新,这样的树形结构会导致类是这样的更新问题有很大的麻烦。所以针对这两个问题将链接模式改变为了单层树结构,即每个硬链接都直接链接到具体的文件对象。此外值得一提的是第二个问题也是当时改变size处理方式的直接原因。
-
条件分支的整合与再划分
对于ln -s, ln, mv, cp指令在指导书中其分支行为或多或少存在一些差别,但经过对这些分支的整合与再划分大致可以分为三个大类
- 不存在目标目录/文件
- 存在目标目录
- 存在目标文件
上面的四个指令划分成这三个大类的过程十分接近,差别只在三个大类内部再具体划分。
这种划分方式再给我带来便利的同时也意外引入了一次bug,具体是当时没有更深入的分析就认为cp可以直接套用mv的分支结构,导致在cp中也检查了要移动的目录是否是当前目录的上层目录,造成了bug。
这几个架构的修改基本上都遵循从模糊到具体的规律,而这也是为了适应程序逐渐增加的复杂度。在程序十分复杂的情况下,粗糙的处理方式会导致很多疏忽以至于很多bug,所以将问题具体化,并进行分类,分层才能使得程序在正确性上有所提升。
(4)总结结对过程中的进度、质量和沟通管理实践体会,并分析哪些bug是因为两个人的理解不一致而导致;
正如前面所说,我们的结对过程大致可以描述为先确定程序整体框架并设计好程序接口,然后分工进行实现。跟具体点说就是:
- 首先我们会找到一个两人都有空的时间碰面探讨指导书的细节,然后先根据指导书的整体逻辑设计架构并初步商讨好程序的各个接口。
- 在大体接口完成后,我们会分配各自要实现的部分。分配的依据是工作量以及在各项工作的连续性,比如第一阶段我们分别完成了文件部分以及目录部分,第二阶段我们分别完成了用户系统部分以及文件系统部分。
- 在分配完任务后,我们会计划各个阶段的DDL,比如在什么时间点之间完成程序部分,在什么时间点之前完成测试部分,在什么时间点之前完成博客。
- 做完这些计划之后我们就会开始各自的编码阶段。就这一阶段,我们也会就各自的实现细节以及一些问题进行频繁的交流以保证程序的正确性。
我认为我们整体的流程虽然与书中介绍的结对过程不太一样,但在编程过程中也保持了较为密集的交流和规划,这些交流与规划确保了我们能以一个较好的质量完成前两阶段的作业。即使如此,也确实有一些缺陷/bug是由于结对过程中沟通的疏忽导致的:
- 第一个缺陷是接口调用方面的问题。也就是有一些操作,原先希望是系统过调用某一接口来实现的,但是由于在规划的时候没有沟通好或是实现时疏忽了,导致在接口调用方面出现了与原先设计不符的情况,有时候这不会立马导致bug,但可能在之后的修改中使得一些修改的覆盖面减小从而导致bug。
- 第二个问题是由于前面提到的.和..目录特殊处理导致的,在这种实现下所有与路径有关的操作都要对他们进行特殊判断,但一开始双方并没有都意识到这个问题,从而导致在实现的过程中由于此原因出现了一些bug,所幸在单元测试时都发现了。这些bug既是由于起初设计规划遗留的缺陷导致的,也是在结对过程中没有充分沟通导致的。
(5)提出建议:根据三个阶段的结对项目的实践经验,对如何更好的实施和管理结对项目提出自己的建议。
- 首先是选择适合自己结对模式。课本的结对模式固然有其依据与实践支撑,但有时我们所处的环境并不一定就适合这种模式,所以在最开始的探索阶段,应该根据实际情况调整具体的结对过程,只有最适合自己的过程才是最好的。
- 对整个结对过程在时间上应有较为详细的规划,虽然结对的两人空闲的时间不一定有较大的重叠,但两人应当都有差不多长短的空闲时间,这就意味着我们可以根据任务量合理在初期就计划好各个阶段的DDL,两人严格按照各阶段DDL完成任务。这样的安排可以使得结对过程比较稳定。
- 做好充分的沟通。由于整体的设计是集中了两个人的想法完成的,所以在面对分歧的时候,不论这个问题是不是完全处于自己的职责范围,都不应该自己埋头解决而应当充分与队友交换意见,以保证两个人在实现上具有一致性。
(6)效能分析
效能分析主要用于衡量程序的运行时间分布的大致情况,进而寻找程序的瓶颈问题,并进行优化分析。在前两次结对作业中,我们其实没有过多考虑程序的性能问题。所以在本次作业中,我们简单使用 IDEA 提供的 JProfiler 插件对我们编写的程序进行如下效能分析。
JProfiler 支持两种效能分析的分析方法,即抽样和代码注入。
抽样通常是指效能分析工具会时不时看一看这个程序运行在哪个函数内,并记录下来。程序结束后,效能分析工具就能得出一个关于程序运行时间的大致印象。抽样方法通常运行快,但不够精确,不能准确表示程序的调用树。
代码注入则是指将检测代码加入到每一个函数中,检测代码会记录程序运行的一举一动,程序的各个效能数据都可以被精确的测量。但是代码注入通常会影响程序的运行时间,可能会影响程序的真实运行情况。
-
采用抽样(Sampling)分析方法时,得到的分析截图如下
-
采用代码注入(Instrumentation)分析方法时,得到的分析截图如下
(7)PSP 时间统计
为更好地使用PSP模型衡量我们在完成结对作业过程中的团队能力情况,这里简要给出我们完成整个结对项目的 PSP 时间统计表格。
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 720 + 960 | 600 + 800 |
Development` | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 60 + 120 | 60 + 200 |
· Design Spec | · 生成设计文档 | 20 + 20 | 25 + 25 |
· Design Review | · 设计复审 (和同事审核设计文档) | 15 + 15 | 20 + 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0 + 0(直接采用OO课程规范) | 0 + 0(直接采用OO课程规范) |
· Design | · 具体设计 | 60 + 60 | 65 + 65 |
· Coding | · 具体编码 | 240 + 480 | 300 + 500 |
· Code Review | · 代码复审 | 120 + 240 | 150 + 240 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 + 120 | 150 + 150 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 30 + 30 | 45 + 45 |
· Size Measurement | · 计算工作量 | 30 + 30 | 35 + 35 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 + 30 | 45 + 45 |
合计 | 1415 + 2105 - 720 - 960 = 1840 | 1495 + 2125 - 600 - 800 = 2220 |
3.CI体验感想
(1)通过这次结对编程,你对CI的使用体验如何?你对这一工具有何认识?
CI 作为工程开发的重要工具,应当是现代专业开发人员熟练掌握的技术之一。在本次结对作业中,我们通过 Gitlab 平台上的 CI/CD 工具实现了流畅的结对开发,使用后的体验感受大致可以总结为以下几点。
- CI 工具相对容易上手,且辅助功能丰富。在第一次结对作业中,我们对 CI 工具的使用还都不够熟练,既不了解代码单元测试覆盖率的输出文件应该如何保存至 artifacts 中,也不清楚如何导入 maven 工程依赖的 jar 包。但经过资料搜寻以及几次简单的尝试后,我们已经基本掌握 CI 工具的具体使用方法,并能够简单编写属于自己项目的 yml 文件。
- CI 工具的自动化部署特性相当方便。对于这几次结对作业,只需要简单编写好这次作业所需的 yml 文件,就可以实现每次仓库代码修改后的自动化构建和部署,免除每次重新构建指令的重复性操作,为开发工作节省了大量的消耗。
- CI 工具能够很方便地帮助集成团队开发人员新编写的代码。我们在结对编程的过程中,采用了分工加多分枝的策略。而 CI 工具能够在多个分支自动构建并部署代码,能够帮助我们不必合并其他分支,就能获得其他分支修改后再次部署测试所得到的结果,从而最终决定对这次修改内容的取舍。
4.结对编程感想
(1)描述你们结对的方法、结对过程中遇到的困难与收获,结合自己的结对经历,说明结对编程的优点和缺点,分享可以推广的结对妙招。
结对方式
为确保我们二人的编程标准相对一致,并且具有较高的合作效率,我们结对编程的方式大致包含以下流程。
- 约定时间见面,共同阅读结对作业指导书,讨论确定程序的具体结构设计。同时确定编程分工,以及任务推进时间点。
- 分工完成程序编程内容,随时交流编程遇到的问题,定期对完成的代码进行合并。
- 在完成作业及单元测试内容的代码编写,并提交确认弱侧通过后,再次约定时间见面,共同讨论程序仍可能出现的bug,进行测试修改。最后共同完成博客的撰写。
困难与收获
- 在阅读指导书并进行初期程序的结构设计过程中,我们共同探讨各个类之间的继承关系,以及确定部分应当预留好的接口方法。这使得我们在接下来正式编写代码的过程中能够保证两人调用的方法标准一致,也保证了我们之间的合作效率。
- 在编写代码的过程中,我们会随时交流编程中遇到的问题,也共同检查代码中的问题与漏洞,解决问题的能力相较于单一的个人有所提升,因而也能够提供更好的设计质量和代码质量。
- 在第二次结对作业中,指导书要求的内容量较大,且逻辑分支较为复杂,因而单元测试在这次作业中具有极其重要的作用。我们也在单元测试中检测出了相当多的疏漏以及理解上的错误。
结对编程的优缺点
- 优点
- 结对编程有利于结对成员之间进行及时且有效的交流,进而避免对部分需求的错误理解,并且能够让结对成员在交流的过程中做到相互学习,相互纠正,进一步提升个人的代码水平。
- 结对编程的过程中,通过结对成员对程序结构设计的讨论以及对代码中存在的问题和漏洞进行共同检查,能够确保最终得到的程序具有更好的设计质量和代码质量。
- 缺点
- 结对编程对参与成员的交流具有较高的要求,未经深层次思考而进行的交流反而容易降低结对编程的效率。
(2)评价你的队友,使用汉堡点评法评价你的结对伙伴,务必给TA 提改进意见。
hqx->lmx:
-
先来一片面包
lmx 同学的逻辑思维能力很强,代码编写缜密,不仅能在讨论中对程序架构提出十分精妙的设计,也能及时发现我所编写代码部分的 bug 。这些都足以体现 lmx 同学较为专业的工程能力。此外,lmx 同学对结对作业也十分负责,定期与我主动交流任务完成进度,并对接下来的工作进行分配。
-
再把肉放上
但是在代码编写的过程中,lmx 同学不太爱写 javadoc 部分对接口方法功能进行介绍的内容,导致我们后期在对接时,容易出现接口内容不完全规范的情况。
-
然后再来一片面包
但经过与 lmx 同学对这部分的问题进行交流商量后,他在这方面也有了极大改善,也进一步让我体会到我们二人之间结对工作的配合十分流畅。
lmx->hqx:
-
先来一片面包
跟h同学的合作还是很愉快的,我们就各种问题都能很快达成共识,并且在关键设计上的想法很多时候也比较一致,所以沟通的过程十分流畅。
-
再把肉放上
但从路径处理疏漏这一问题上来看,h同学可能还存在以下两个问题中的一个问题:
- 如果是一开始没有对路径处理的规范达成共识的话,这就说明在交流上还不够主动积极。
- 如果是了解路径处理的规范,那就说明在处理问题时忽略了一些边界条件。
如果属于前者,我希望h同学在以后与他人结对时能更积极地抒发自己的观点,并充分与他人交换意见。如果是后者,希望以后在面对各种问题时都能关注到边界条件。
-
然后再来一片面包
总之,这是一次很愉快的合作,双方在项目上的参与程度都很高,对各种问题也都很积极,预祝能在软工课程中趣得好成绩。
(3)描述在本次结对编程的过程中,你们使用了哪些软件工具,是如何应用于实践的。
本次结对编程的过程中,我们主要使用了 IDEA IntelliJ 、maven 、cobertura 和 typora 四类工具。
- IDEA IntelliJ 作为从 OO 就开始使用的 java 语言开发集成环境,具有很方便的代码补全功能和较齐全的插件市场。所以我们这次依然选择使用 IDEA IntelliJ 作为结对作业的开发软件。
- maven 主要用来作业项目的工具管理,我们结对作业的官方包导入以及 cobertura 插件的安装工作,都是通过 maven 来实现的。
- cobertura 用于本地生成单元测试覆盖率的分析报告,我们也是通过阅读其生成的分析报告,逐步完善所写的单元测试代码,进一步提升单元测试的覆盖率。
- typora 用于每次完成结对编程后进行博客总结,由于其很好支持 markdown 类型的文档编写,所以我们采用这个软件来进行博客的撰写。
(4)描述通过本次结对编程的感悟和体会,对本次作业的有哪些想吐槽的,觉得本次结对作业内容可以在哪些方面做出改进?
主要想吐槽的内容,就是第二次作业的指导书,部分内容的逻辑性并不明显,其中的要求也说得较为模糊,在初版的指导书中这个问题体现的尤为明显。但是,对于采取了 OO 评测模式的作业要求,这种难以确定的指导书需求就带给我们一种两难处境的感受,也一定程度上降低了我们的编程积极性。