2021年软工 结对项目第二阶段总结
教学班级:2021春季软件工程(罗杰 任健)
结对人员(学号后四位): 3584 ,3622
一、结对方式与结对过程
在结对编程第一阶段中,我们按照《构建之法》中介绍的结对编程模式,主要采用线下面对同一台电脑进行结对的方式,但是经过第一阶段代码的编写,我们一致同意这样的结对方式效率比较低,且体验不佳。因此在本次的第二阶段中,我们坐在同一个屋子里,使用IDEA的Code With Me插件进行结对编程。
Code With Me插件是IDEA 2020.2.x 引入的亮点功能,它可以较好地支持两人同步查看和修改同一个项目的代码,满足结对编程的需求:
- 可以实时显示对方的光标所在处,实时跟踪对方对代码的修改
- 可以同时编辑文件的不同地方,也可以同时编辑项目中的不同文件
- 可以支持运行、调试等功能,能够同时看到运行结果、单元测试结果和调试信息
- 主机能显式控制所有人的权限(如编辑、运行等)
但同时,经过几天的使用,我们发现该插件仍存在一些问题或局限性:
- 有时候会出现代码补全功能缺失的问题
- 对撤销功能支持不好,有时会撤销别人正在写的代码
- 奇奇怪怪的一些问题,如一直在recover editor states,可能与网络质量有关
与结对编程第一阶段相比,第二阶段的指令更为复杂,特别是 link
, copy
, move
相关的指令,涉及到 srcPath
, dstPath
二者对应的实体是否存在,是目录还是文件,是否非空等多种组合情况。针对这些复杂指令,我们采用"测试样例和代码编写者分离"的方式,由一个人构造的测试样例,则由另一个人进行该方法代码的编写,这样的方式不仅提高了代码和单元测试编写效率,而且能保证两人对同一个方法的理解相同且与指导书一致,从而保证代码质量。
二、项目设计与实现思路
实现概述
此次用户管理部分相较于文件系统的实现来说相对简单,主要用来对用户与用户组的创建删除、用户与用户组二者的联系、用户的切换这三部分进行管理。而文件系统这次的需求相对来说更为复杂繁琐,因此也是设计的重头部分。除了需要对某些非原子的操作进行原子性保证,而且新添加了逻辑较为复杂的软硬链接、移动复制的指令,这一部分的关键是对逻辑的梳理,尤其是何时进行重定向,以及重定向所可能产生的不良后果,以及修改创建时间的问题,因此要反复斟酌指导书的措辞。文件系统新添加的部分在下文我们会作比较充分的说明。
用户管理的实现
1.用户/用户组 设计
我们给每个用户与用户组维护了一个HashMap
的数据结构,并采用了名称 -> 实体
的映射关系,来存储与自己有联系的对方实体。同时我们为每个用户设置了一个主组标识,通过遍历所有用户的主组标识可以区分某个用户组是不是主组。
类属性设计如下:
public class User{
private String name;
private HashMap<String/**groupName**/, Group> map;
private Group mainGroup;
...
}
public class Group {
private String name;
private HashMap<String/**UserName**/, User> map;
...
}
2.用户系统设计
因为用户系统需要知道所有的用户用户组情况,因此我们在用户系统中设置了一个全局的groups
与users
,用来存储当前用户系统所含有的所有用户与用户组情况。对用户与用户组的增删时,我们会通过groups,users
直接获取到对应的实体,以便于利用实体内的信息进行下一步的操作判断,然后根据情况对groups
与users
进行增删操作,并修改各自内部所存储的联系信息。
同时由于除了exit
与whoami
的用户系统指令都需要root
权限,因此用户系统中也保存了当前正在使用系统的用户信息。
3.用户切换
用户切换主要对应su
与exit
指令,由于普通用户退出后,需要将文件系统的工作目录返回到root
用户最后所处的工作目录,因此我们需要在root
用户切换时进行其工作目录的保存,同时在普通用户exit
时进行此工作目录的读取与切换。
文件系统的实现
1.原子性保证
由于上次系统中mkdir -p
指令可能存在无法保证原子性的问题,即创建不存在的目录过程中,发现了错误(如某一文件为上层目录),会进行异常抛出。因此这次需要将这条指令转化成原子性的操作,即要么都成功创建,要么都不创建并退出。
我们的主要实现方式是在递归创建之前,对整条路径进行正确性检测,即保证输入路径所对应的所有上级路径(与自己),均要么是目录(包括重定向后的),要么不存在的合法情形。只有通过正确性检测后,我们才会进行递归目录的创建。
2.链接文件的设计
因为软链接需要指向其所链接文件或目录的绝对路径,并且在连接时可能会出现失效的情况,因此每次软链接在重定向是需要对绝对路径对应的结点进行重新获取(由此来判断是否失效),因此我们选择软链接文件中存储其所连文件/目录的绝对路径,并提供了重定向方法,直接返回所连接的文件或目录。
而硬链接由于源文件从文件系统中移除后也能通过改硬链接找到该文件,因此我们在硬链接文件中直接存储了源文件的指针。
3.重定向情形
我们会在所有需要进行重定向的函数中,在确保获得到<dstpath>
对应的链接文件(此时还未重定向)时,优先进行重定向操作,因此确认需要进行重定向的情况,不过这里指导书已经详细给出:
注意,默认情况下程序需将所有出现的软链接视为它指向的路径下的文件或目录,但在以下情况时程序要特殊处理:
当软链接指向一个文件时,指令 rm, info, mv, cp 中的软链接路径不做重定向。
当软链接指向一个目录时,指令 rm, rm -r, info, mv, cp 中完全匹配软链接路径的参数不做重定向,但软链接路径作为参数的上层目录时需重定向。例如,若 /home/target 是软链接路径,且其链接到 /home/source 目录,rm /home/target 将删除软链接,但 rm /home/target/1.txt 将删除 /home/source 下的 1.txt
但是在此,我们还需要对一些词语的定义再进行剖析。如在需要对srcpath
进行重定向的情况中,srcname
指的应当是重定向前(即srcpath
字符串)中的名称;
4.mv/cp指令的dst继承选择
需要考虑的继承包括:创建信息(创建用户、创建用户组、创建时间)、名称
move指令中:
情况 | 属性继承 | 名称继承 |
---|---|---|
src存在,dst不存在 | src | dstname |
src:file, dst:file | src | 此时srcname=dstname |
src:file dst/srcname:null | src | srcname |
src:file dst/srcname:file | src | 此时srcname=dst/srcname |
src:dir dst/srcname:null | src | srcname |
src:dir dst/srcname:empty dir | dst/srcname | 此时srcname=dst/srcname |
copy指令中:
情况 | 属性继承 | 名称继承 |
---|---|---|
src存在,dst不存在 | now | dstname |
src:file, dst:file | dst | 此时srcname=dstname |
src:file dst/srcname:null | now | srcname |
src:file dst/srcname:file | dst/srcname | 此时srcname=dst/srcname |
src:dir dst/srcname:null | now | srcname |
src:dir dst/srcname:empty dir | dst/srcname | 此时srcname=dst/srcname |
注:now表示(当前用户、当前用户组、当前时间)
三、PSP表格记录
PSP2.1 | 阶段 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 5 | 10 |
Estimate | 估计完成所需时间 | 5 | 10 |
Development | 开发 | 860 | 1120 |
Analysis | 需求分析 | 30 | 40 |
Design Spec | 设计文档 | 60 | 90 |
Design Review | 设计复审 | 30 | 40 |
Coding Standard | 代码规范 | 0 | 0 |
Design | 具体设计 | 40 | 50 |
Coding | 具体编码 | 400 | 450 |
Code Review | 代码复审 | 0 | 0 |
Test | 测试 | 300 | 450 |
Reporting | 报告 | 70 | 70 |
Test Report | 测试报告 | 0 | 0 |
Size Measurement | 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan |
总结与提出改进计划 | 60 | 60 |
合计 | 935 | 1200 |
注:代码规范沿用第一阶段采用的规范
分析以上表格,并与第一阶段进行对比,可以发现:
- 整体而言,预估耗时与实际耗时的差值较第一阶段有了很大的降低,这表明我们对工作量的估计能力有所提高。
- 具体编码和具体测试所耗费的时间已经达到了 1:1,在测试过程中修复了大量Bug,充分说明了单元测试的必要性