? 仅就在测试中出现的Bug而言,大多数Bug都是由于测试不充分导致的(部分路径未覆盖或压力测试的缺失),而这种测试的不充分又是由于时间的紧迫性导致的。我们在分析与编码过程中花费了过多的时间,导致留给测试的时间严重不足,且心理上比较疲乏,最终往往是针对覆盖率写单元测试就万事大吉。而这种在编码过程中花费过量时间的原因,既有对指导书的分析上的,也有架构设计上的,也有具体编码过程中的。
? 在这一部分提出一点G同学的个人看法,欢迎助教不吝赐教。
? 在我看来,软件工程的需求分析是一个开发人员经过调研与分析,准确理解用户和项目的功能、性能、可靠性等具体要求,并将用户的非形式的需求表述转化为完整的需求定义的过程,它强调的是将不明确的需求明确化,甚至帮助用户发现自己都没有意识到的需求的过程。而观察课程组所给出的指导书,可能是出于评测方便的考虑,已经明确给出了各条指令的具体实现方式、异常顺序等一系列内容,观感上比较类似一个规格说明书,对这样的规格说明书不存在所谓需求分析的过程。因此我提出这样一个观点,本次项目的需求分析是由课程组完成的,而结对团队仅是拿到需求分析的结果并进行实现而已。是课程组对于“基于内存的文件系统”这样一个比较宽泛的需求进行了分析,设计了数条指令,规范了指令行为,才形成了指导书,并交予结对团队进行实现。
? 很多同学都认为第二阶段的指导书让自己体验极差(例如这里),在我看来,这正体现了需求分析对于软件工程全过程的重要性,正是由于指导书(这里仍将指导书理解为需求分析产生的结果)在细节上存在比较多的问题(具体问题将在3.4节中详述),才导致了负责实现的结对团队难以在较短时间内完成一个比较好的版本,且开发体验不佳。当然这并不是在甩锅给课程组,在改革的第一年就形成这样体量的指导书已经是比较大的工作量了。
? 在本次结对作业的过程中,我们在架构设计与迭代上出现了比较大的问题。
? 在第一次指导书发布后,我们首先进行了分别阅读,并由H同学初步设计了整体架构,在此设计中,我们用一个全局的HashMap
保存文件系统中的所有文件,实现了一个与文件系统无关的将任意绝对或相对路径转化为一个完美的绝对路径的getAbsolutePath
方法,文件系统的每个方法都先调用此方法以转化输入的路径后直接从全局Map
中取得实体,虽然这样做能满足第一次作业的全部要求,甚至还可以简化文件树结构(不需要存父节点),简化文件系统具体方法实现方式,但实际上已经埋下了巨大的隐患。
? 击溃这一架构的重要指令便是mkdir -p
,由于其路径中出现的部分可能在文件系统中不存在,加之一些其他类似\
结尾的问题,导致了我们前文中设计的工具类方法的有关特判大大超出了预期,复杂度过高,代码难以阅读和维护,也难以针对指导书随时的修改而快速修改代码实现,因此我们在讨论后决定更改为实现一棵完整的文件树,将路径视作分步在树上移动的方式。
? 在更换架构,重写工具类的过程中,我们一心想着赶进度,觉得在第一阶段中构造的myFileSystem
各方法的测试已经较完备地覆盖了工具类,因而没有针对新增的TreePath
类进行单元测试,两人都默认了这种方式的后果就是此新增类直接忽视了路径的4096字符限制,在第三阶段的测试中造成了错误。
? 架构的设计是软件工程开发阶段的基石,具有举足轻重的地位,结合自己在此次作业中出现的问题,我们提出以下两点:
? 进度管理上,我们采用了一种类似燃尽图的形式。以第二阶段为例,我们将主要编码工作分为修改数据结构和工具类,增加用户系统类并实现与文件系统交互,实现用户有关指令,实现用户组有关指令,实现硬、软连接指令,实现复制和移动指令等六个阶段,虽然没有按时间顺序具体将燃尽图画出来(考虑到从指导书发布到截止提交只有数天的时间,在真正的软件开发过程中可能只够一次“冲刺”),但我们对已经完成和还未完成的工作量都有了一个大概的认识,有助于估计仍需耗费的时间,便于及时调整进度。但是,在此过程中的一个问题是,我们对每一阶段的任务量估计不够准确,因而没有做到尽量等量划分任务单元(如第五和第六阶段的任务量其实比前四个加起来都多),最终造成了开发时间上的延误,对测试阶段造成了一定的影响。在之后的团队项目中,将继续尝试使用燃尽图方式进行进度管理。
? 质量管理上,我们在代码实现中使用了很多TODO块。在等待Issue区的助教盖棺定论过程中,亦或是对细节有不清楚的地方而暂时不便打扰另一方时(如在实现一个逻辑比较复杂的方法,需要沉思),亦或是可能需要抽象出重复代码或做性能上的优化时,我们都会先在对应地点加上一个TODO,并详细注明TODO的理由和内容,在一次小的“冲刺”结束后,会针对现有代码中的所有TODO进行讨论,最终达成一致。
? 此外,我们还尝试过在具体实现代码之前先针对复杂方法写类似JavaDoc
的注释的方式,但我们最终发现这种方式效率比较低,且可以被单元测试样例所取代,没有达到预期的效果,遂放弃。
? 沟通管理上,由于二人可以随时线下交流,因此省去了很多的无效交流时间。对于一些对指导书理解不一致的地方,或者通过Issue区的助教回复,或者通过Ubuntu的现场实验,最终都能达到统一的理解,因而没有在这方面上出现Bug。
? 《构建之法》中指出:“在结对编程模式下,一对程序员肩并肩、平等地、互补地进行开发工作。他们并排坐在一台电脑前,面对同一个显示器,使用同一个键盘、同一个鼠标一起工作。他们一起分析,一起设计,一起写测试用例,一起编码,一起做单元测试,一起做集成测试,一起写文档,等等”。然而,在结对编程的实践过程中,可以不拘泥于以上的形式和内容,保证适当的独立性也是可以接受的。结对编程的目的是提高工作效率和降低错误率,不需要为了结对编程而结对编程。具体而言,可以尝试使用以下方式进行结对项目的实施与管理:
? 整体而言CI的使用体验较好。虽然在第一次作业提交前花费了一些时间用来配置CI,但在之后的每次提交基本上都不需要对其做出太大的改动。另外,需要感谢my同学提出的#Issue12中对CI单元测试和导出覆盖率的方法分享。
? 本次项目中的CI仅涉及了build,test,commit等流程,而没有涉及到部署或发布等流程,因此其主要便利之处在于能自动进行单元测试并生成覆盖率报告。能将一部分工作交由CI自动完成,使得我们有更多的时间对代码本身进行思考。
? 本次结对项目中我们主要采用线下实时使用Code With Me插件协作的方式,并且我们认为这种方式很适合结对编程的需求。线上改动代码的情况少之又少,仅在写单元测试的时候出现过一段时间的分治。
? 与第一阶段总结中提到的一样,经过一整个结对项目的实践,我们认为,结对编程最主要的优势就是其随时复审的特性会使得低级错误的出现率大大降低。在较长时间的编码后,编码者往往会犯一些低级错误,如将 absolutePath
误写为 path
,将 &&
误写为 ||
等,这样的错误往往在测试前难以发现,且经过漫长的 debug 后发现低级错误也会极大增加挫败感。而两人共同面对同一份代码时同时犯低级错误的概率将远低于单人,节省了大量的测试时间。结对编程的主要缺点就是当两人对某一具体细节各执一词时可能会消耗大量时间用于讨论和争辩,从而造成开发效率上的问题,这一点在面对第二阶段的指导书时尤为严重,我们经常针对到底改不改父目录或子目录的modify_time
等问题进行争论,难以盖棺定论,为此消耗了大量时间,延误开发进度。
? 结对“妙招”已在1.5节中给出,在此不再赘述。
? H同学思维敏锐,执行力强,在简要阅读指导书后就可以快速形成基础架构。但是在具体编码过程中犯低级错误的频数有些过高,如同一段代码中上面使用的还是absolutePath
,下面就变成了path
。如果可以做到形成一套自己的变量命名逻辑,在动手写代码之前想清楚代码目的,写完后先进行一遍自我复审,将极大地提高代码质量,从而缩减debug时间。
? 丁酱,我的超人。
? G同学无论是在代码的思考构建过程中,还是在代码的编写过程中,都给人一种很细腻的感觉。讨论架构的时候,他会就某一点询问其它方式的可行性,只有在权衡所有方法的优劣之后,他才会做出相应的决策。例如有一次在讨论路径的存储结构应当是字符串还是数组时,我一开始提出应当用字符串进行存储,以便进行更加灵活的处理。但是他首先询问了数组存储的可能性,虽然我一开始觉得还是字符串存储较好,但是在听他说明了数组处理的效率高的特点以及数组结构与路径结构的相匹配性后,我最终决定采用他所说的方法。在我编写代码的过程中,他也会指除我某些逻辑方面的漏洞以及提出更加合理的解决方案。
? 不过与G同学一起编写代码的过程中,我也发现了他对所使用的编程语言的语法与结构熟悉程度不太高,以至于无法写出一些比较常规的操作。还有就是他在结对编程的过程中表现得不太积极,大部分都是我主动去邀请他进行结对编程,而且他有时还会在结对过程中去偷偷写案例分析作业,这让我很恼火。??
? 虽然我指出了他的部分不足之处,但是瑕不掩瑜,在合作过程中他也教会了我很多东西,很期待下一次再与他一起合作!
? 本次结对编程实践过程中,我们主要使用了以下工具:
? 在前文中“指导书是需求分析的产物”的观点的基础上,在这里,想对指导书做出一些具体的建议,整体上,期望课程组能多从读者(即结对团队)的角度看待指导书:
move
和copy
指令带来的迷惑性尤为严重,在Issue区中也出现了大量的类似于“到底改不改父目录的modify_time”,“覆盖文件究竟继承谁的create_time”等问题。在我看来,这些问题很大程度上是“视作修改一次xxx”的表述不能令人满意所导致的,可以考虑更改为#Issue15中my同学所使用的表格的形式。? 以上给出了一些具体的针对指导书的建议,但是如果我们跳出这一限制,展开想象,也可以给出一些七七八八的可能是异想天开的其他建议:
给出结对团队能自行设计输入并得到标程给出的输出结果的接口。在本次结对过程中,很多时间都被花费在争论小的指导书细节/迷惑处和等待助教回复Issue上,如果课程组给出这样的标程接口,结对团队一试便知何为正确操作,将极大提高开发效率。但是这样做可能会存在反编译等一些风险,有待进一步讨论。
给出更大的实现自由度。作为一个软工项目而言,我认为“指令形式的文件系统”这样的选题和“对输出一字不差的进行比对”这样的评测方式可能不太适合,反倒是比较类似OO的第三、四单元,对实现限制较大。类似OO第二单元的“合理即正确”的输出评测方式(虽然很大程度上是因为它是多线程)是否更适合软工课程呢?我目前也没有什么好的想法。
? 以上两条仅是一些不成熟的想法,欢迎大家进行讨论。
原文:https://www.cnblogs.com/gottfriede/p/14635567.html