软工-结对编程作业#2
教学班级地址:BUAA_SE_2021_LR
GitLab项目地址:2021_Yilong_Li-Changyao_Tian_pair_work/task2
成员学号 | |
---|---|
L同学 | 3580 |
T同学 | 3636 |
结对编程感受分享
From T同学
本次结对编程项目基本延续了上一次的分工,L同学主要负责测试,我主要负责实现。由于L同学有实习任务,所以前期不太抽不开身;因此这次的关于软硬链接和用户组相关的设计也主要是由我来完成。同样,我们继续借助在上一篇博客中提到的Code with Me
插件帮助我们完成本次的编程任务。
但这些并非我这次想主要分享的。我更想借这一机会,谈谈我对于本次结对编程与未来的团队项目以及将来有可能参与的更大型的企业级项目之间的联系与个人的一些看法。
MVP还是MBP?
在软工课上,我们了解到了MVP
与MBP
这两个概念,MVP即为最小可用产品(Minimal Viable Product),MBP即为最全最美产品(Most Beautiful Product)。那么问题来了,如果说过去我们完成的OO课程作业以及其他编程作业是MBP,需要考虑各种极端情况各种边界数据;如果说未来我们将要完成的软工团队项目是MVP,在最短的时间内完成一个尽可能有用的产品;那么,这次结对编程的定位是什么呢?
MVP还是MBP?
首先,从考核方式来看,结对编程采用的仍然是和OO课程一样的弱测+强测的线上评测方式,这显然是MBP的路数。毕竟作为开发者的我们,必须要考虑到指导书中所提到的全部可能的情况,才有可能通过测试;因为没有谁知道,评测机或者课程组真正想要考察的是哪几种特殊的情况。于是,issue区里的各路大神也就自然而然地开动脑筋思考形如软链接链
这样的极端样例出现的可能性了——这显然不是想和指导书过不去,不是非要在鸡蛋里挑骨头;而是因为大家潜意识里都在把它当成一个MBP的项目去做,为了拿分,在设计时就做到尽善尽美是必不可少的。这个时候,指导书如果有任何不完备的地方,或者总是在反复地改来改去,显然都会引起同学们的极度不适,造成心理上的额外负担。
但是,从开发过程来看,如果把它当成一个MBP的项目去做,那心累是一方面,另一方面,这真的就是课程组想看到的结果吗?因为这样一来,大家首先要做的就是去死扣指导书的字眼,不放过每一个细节,指导书说什么就是什么;但可能反而会不小心忘了,最自然最合理的实现方式应该是什么样的,某个地方为什么要这么去做,是指导书的笔误还是其有意为之?
所以,我更倾向于把结对编程看作是从MBP向MVP的思维上的一种转变的过程。那么,为了帮助同学们实现这一思维过程的转变,指导书应该在其中扮演什么样的角色?是要把每个细节卡的一板一眼,还是尽可能以简洁的语言表达最终想要做的结果?评测机又应该扮演什么样的角色?是盯着边界数据不放,还是强调功能的完备性?
更进一步的,现有的这种传统的基于测试点的评测方法是否合适?是否要尽可能地额外多考虑一些架构的合理与优美,考虑如何展示同学们每一次迭代的过程?特别地,当指导书本身也有不完美的地方的时候,如何与同学们之间相互协调,实现设计上的向后兼容,我觉得这都是课程组值得作进一步思考的地方。
需求的迭代与实现的升级
不是什么东西都有标准答案的。 —— 某牟姓同学
除了MVP与MBP之争外,本次结对编程带给我的另一点很深的感触就是——我第一次真正意义上感受到了什么是真正的面向现实的开发。
为什么说是面向现实呢?因为现实中,用户的需求总是在反反复复地更新的,不存在一个所谓的像指导书一样的绝对权威告诉你这里那里要怎么做,并且信誓旦旦地跟你保证以后都按这个来。
为什么说是第一次呢?因为,以前虽然OO每一单元地每个task都是在上一个task的基础上进行迭代的,但是奈何人家每个task本身的指导书在开发周期内都是确定不变的。而这次就不一样了:1周之内,指导书本身的issue就有39个,来来回回共计commit了17次。
我知道这不是课程组有意如此,但其实我还真有点希望课程组有意如此。因为惟其如此,才能真正体现出现实的不完美:如果说数学是这世上最完美的体系,那么物理就是对它最无情的拆穿;如果说程序是这世界上最完美的存在,那么来自客户的需求就是对这一命题最好的反驳。
那该怎么应对这样疯狂的变动呢?
我能想到的比较合适的方法就是设计先行+测试驱动+文档注释,毕竟文档和测试改起来总是要比程序内部的逻辑本身要容易的多;所以,面对如此频繁的需求变动,最好的办法应该就是:
- 首先设计出整体上大致的编程框架,明确各个实体的抽象级别;
- 然后再针对频繁变化的需求,针对那各种
if else
组成的分支,构建相应的测试单元; - 最后,对于程序中的核心方法,给出尽可能详细的注释和文档,便于后期的复审和修正。
三者结合,从而实现整体上效率与正确性的兼顾与平衡。
From L同学
敏捷软工第二次结对编程给我的感受很差,我从第二次软工作业学到的唯一知识就是,我没有从这次结对编程中学到任何知识。
在这里我还想自说自话地给我的舍友们道个歉,学期初我们四人并没有选择敏捷软工,但在听了第一节课PPT和看到了群里HansXXX的安利后,我最先改选了敏捷软工,舍友们也陆续选择了该课程。
在改选的那一刻,我至少是做了一定心理准备的,虽然现在看来这所谓的准备是多么狂妄而可笑啊。我的成绩不能保研,也没有考研的打算,所以我虽然没有力争上游的期望与实力,但还是有着踏踏实实完成课程任务与团队项目的目标。所以我虽然一直听说敏捷软工任务量大,但我相信更多的投入会带来更美妙的产出。
而现在,看着已经连续一周心照不宣地打破了12点熄灯上床铁律的宿舍,看着凌晨四点依然在地下室结对的舍友,看着为了各种模糊的定义而热闹非凡的Issue区……我忍不住问自己:这真的值得吗?
当然,我没有资格提出这个问题。在这次的结对编程中,我和队友T同学的重合时间比第一次结对时更少,因此我们的分工是T同学负责实现、我负责测试,T同学的工作量想必是比我更大的。而如果要说单元测试90%覆盖率和新增的文件与用户组管理功能,其实和上一次任务差别并不大。
但有一把悬在每个人头上的达摩克利斯之剑——强测。
诚然,弱测强测机制是督促课程成员考虑各种特例,提升程序正确性的有效手段;但当强测范围过于宽泛,需要考虑的特殊情况也急剧增长:这个行为在第一次指导书下是这样定义的,但第二次指导书修改了描述,但Ubuntu上的效果又是这样……于是乎,我们时而成为新时代孔乙己,精通指导书上对行为定义的四种写法;时而成为阅读理解大师,对“不再赘述”的深层含义如数家珍。
最大的恐惧来源于未知,强测亦如此。事实上很多所考虑的情况并不会考察,测试数据中不会出现这样的数据点。但在issue区没有明确回复之前,在没有看到issue区的这条回复之前,在甚至没读懂不再赘述之前,就像是在黑暗里乱窜的小白鼠,怎么找也找不到出口在哪。而这样是否真的就达到了结对编程培养协作意识,为团队项目做准备的目的呢?一只小白鼠乱窜和两只小白鼠乱窜有什么区别呢?面对着不断修改的需求再好的架构也会变成废纸吧?
来之前我期待着惊喜,今年罗杰班有改革,能上评测机的地方上评测机,不能上的地方也有安排,没有体验是不能否定的,躺平毕业不可取,好好锻炼才是真。
来之后我意识到了自己的渺小,梦回面向对象的那个夏天,还是熟悉的配方,熟悉的助教头子,两门老师不同、培养目标不同的课程竟有如此相似的体验,妙哉!
当然吐槽了这么多,等到发布指导书3时,我依然会像斯德哥尔摩患者一样凑上去,一天不写软工就像得了戒断反应,毕竟能不能及格不是我想的算。我对所谓的改革已经没有什么好说的了,期待之后的团队作业吧。
另外不管怎样在Issue区答疑和写指导书的助教们都辛苦了,非常感谢你们的付出。
以上,这周6天里我有4天在实习,趁着编译不过的空隙摸鱼写下了这些文字,希望从刚刚开始就不断在我周围晃悠的上司没有看到。
项目设计与实现思路
在第一次作业的基础上,本次项目由实现了简单的用户和用户组系统,同时增加了对软硬链接和移动复制的支持。前者在本次作业中相对而言实现起来较为容易,后者虽然看似只是增加了几条指令,但复杂度却平白无故上升了好几个数量级。
为了能够对此有一个更直观的理解,且看下表(基于IDEA的Calculate Metrics
插件):
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
MyFileSystem.catFile(String) | 1.0 | 2.0 | 1.0 | 2.0 |
MyFileSystem.changeDirectory(String) | 1.0 | 2.0 | 1.0 | 2.0 |
MyFileSystem.changeFileContent(String,String,boolean) | 5.0 | 5.0 | 4.0 | 5.0 |
MyFileSystem.copy(String,String) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFileSystem.fileAppend(String,String) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFileSystem.fileWrite(String,String) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFileSystem.information(String) | 1.0 | 2.0 | 1.0 | 2.0 |
MyFileSystem.linkHard(String,String) | 18.0 | 9.0 | 7.0 | 10.0 |
MyFileSystem.linkSoft(String,String) | 20.0 | 9.0 | 6.0 | 10.0 |
MyFileSystem.list(String) | 1.0 | 2.0 | 1.0 | 2.0 |
MyFileSystem.makeDirectory(String) | 4.0 | 3.0 | 2.0 | 3.0 |
MyFileSystem.makeDirectoryRecursively(String) | 11.0 | 7.0 | 6.0 | 8.0 |
MyFileSystem.move(String,String) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFileSystem.moveOrCopy(String,String,boolean) | 36.0 | 17.0 | 13.0 | 20.0 |
MyFileSystem.MyFileSystem() | 0.0 | 1.0 | 1.0 | 1.0 |
MyFileSystem.readLink(String) | 1.0 | 2.0 | 1.0 | 2.0 |
MyFileSystem.removeFile(String) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFileSystem.removeFileOrDir(String,NodeType) | 4.0 | 4.0 | 2.0 | 4.0 |
MyFileSystem.removeRecursively(String) | 0.0 | 1.0 | 1.0 | 1.0 |
MyFileSystem.touchFile(String) | 6.0 | 4.0 | 3.0 | 4.0 |
Total | 109.0 | 75.0 | 55.0 | 81.0 |
Average | 5.45 | 3.75 | 2.75 | 4.05 |
这其中,MyFileSystem.moveOrCopy(String,String,boolean)
函数可以说是独树一帜、傲视群雄,而这还是将原有的move
和copy
中的大部分共性逻辑合并到一起,并且把一些子操作封装到Node
类中之后的结果。MyFileSystem.linkHard(String,String)
与MyFileSystem.linkSoft(String,String)
也同样是丝毫不落下风。
这三个方法真的是撑起了整个task2的半边天。
项目整体的UML图如下:
可以看到,MyUserSystem
与MyFileSystem
基本上实现了相互解耦,通过SystemInfo
静态类共享全局的一些基本配置信息。
MyUserSystem
方面,由于是多对多关系,因此为了便于高效查询,故在全局的user表和group表之外,还给每个user和group分别存储了其对应的group或user的信息。这样虽然一方面增加了数据冗余,但另一方面也有效提升了查询效率。
MyFileSystem
方面,为了支持硬链接,这里对上一次的作业做了不小的重构。主要是仿照Linux系统,将文件的一些元数据提炼出来,封装成了Inode
类。这样,原先的Node
类只需存储一个指向Inode
的指针即可;这样做最大的好处在于可以不用为硬链接单独构建类,而是直接把其当作另一种初始化文件的方式即可。如此一来,整个项目中与硬链接直接相关的除了linkHard
方法外,就只剩下一个public MyFile(String name, MyDirectory fatherDir, MyFile srcFile)
的构造方法了——可以说是极大地减少了编程的额外负担。
至于MySoftLink
,这里其实也是把其当作一种特殊的文件来看,但为了保证其与一般的MyFile
在对外方法上有所区别,还是对其单独构建了一个类(依然继承自Node
)加以实现。
此外,在上一次设计的基础上,本次项目仍然继续为所有的异常构建了不同的exception
子类,从而保证处理过程中可能发生的所有异常行为均能被顶层有效地捕捉到。最终全部构建的异常子类如下图所示:
如此可以始终保证项目本身具有较好的可读性与可维护性。
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 90 | 90 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 1200 | 1300 |
· Analysis | · 需求分析 (包括学习新技术) | 30 | 45 |
· Design Spec | · 生成设计文档 | 25 | 40 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | 40 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 15 | 15 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 800 | 800 |
· Code Review | · 代码复审 | 60 | 75 |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 | 420 |
Reporting | 报告 | 60 | 90 |
· Test Report | · 测试报告 | 10 | 10 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1500 | 1745 |