项目 | 内容 |
---|---|
本次作业所属课程 | 2019BUAA软件工程 |
本次作业要求 | 结对编程作业 |
我在本课程的目标 | 完成该完成的任务,增强团队协作能力,熟悉软件开发 |
本次作业的帮助 | 切身体会结对编程,深入了解其优缺点,加强与他人合作能力 |
1.本次项目的Github地址
2.开发前PSP表
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 1480 | 2430 |
· Analysis | · 需求分析 (包括学习新技术) | 200 | 360 |
· Design Spec | · 生成设计文档 | 60 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 360 | 480 |
· Coding | · 具体编码 | 500 | 900 |
· Code Review | · 代码复审 | 120 | 240 |
· Test | · 测试(自我测试,修改代码,提交修改) | 200 | 360 |
Reporting | 报告 | 110 | 130 |
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 20 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 60 |
合计 | 1600 | 2570 |
3.接口的设计
看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的
即信息隐藏原则
David Parnas在1972年最早提出信息隐藏的观点。他在其论文中指出:代码模块应该采用定义良好的接口来封装,这些模块的内部结构应该是程序员的私有财产,外部是不可见的。
参照此原则,在本次项目中,诸如我们的input类中的属性是私有的,所有的访问都是通过成员函数来实现。
接口设计有六大原则,即单一职责原则、里式替换原则、依赖倒置原则、接口隔离原则、迪米特法则、开闭原则。本次项目的接口按照给定的要求实现,并且进行了封装,各个函数的功能也比较单一,接口的实现基本符合上述原则。
松耦合,即函数功能尽量单一,尽量避免修改底层函数,功能相近的函数,可以设计两个以上,不要为了减少代码量,把一个函数的功能设计太多。
本次项目中,每个类都有相应的职责,如input类用于处理输入参数的处理,类的成员函数也有不同的功能,提取参数,返回属性等。words类负责处理文件,提取单词。保证了每个模块的功能独立性。
4.计算模块接口的设计与实现过程
计算模块接口的设计与实现过程,设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。
本次项目中计算模块用了面向对象类的方式进行封装。计算模块涉及到两个类,结点类Node和计算和接口在Core类中。通过Core的接口,传入接口规定的各类参数,构造单词相应结点对象,将结点对象存起来,对其进行拓补排序和动态规划计算。下面是几个函数的关系。
算法的关键在于怎么采用怎样的数据结构来存储数据。本次项目我们用了26个vector来存储构造的结点对象,每个vector中的结点对象都是相同字母开头的,并分别用一个二维数组和一位数组来存储结点间的边关系和相应字母开头的结点的入度。根据拓补排序再对26个vector进行排序,并用排好的序列进行动态规划求出最长链。对于有-r的情况,我们决定用枚举的方式强行找出,但是还没来得及实现。
独到之处就在于用的数据结构不同,这种存储方法可以节省内存,不用为每个结点建一个相连的结点vector,并且使用二维数组表示两类结点的边关系更能容易判断出有没有环。
5.UML图
6.计算模块接口部分的性能改进
之前用的是每个结点有一个vector来存储与其相连的结点的形式来实现接口,这样拓补排序的实现会相对较慢,采用上面第4点的结构进行接口实现的优化之后,性能得到了改进,下面是改进之后的效能分析图:
7.Design by Contract, Code Contract
Design by Contract即契约编程,是设计软件的一种方法。它规定软件设计者应为软件组件定义正式,精确和可验证的接口规范,这些规范扩展了具有前置条件,后置条件和不变量的抽象数据类型的普通定义。而Code Contract规定了接口的数据类型,接口执行之前的条件和接口执行之后的条件。
我认为契约编程的好处是在于由于契约的存在,相当于给代码进行了严格的约束,一定程度上保证了代码的功能。相对的缺点是比较繁琐,增加工作量。
本次项目并未严格按照契约式编程规定前置条件,后置条件以及不变量,我们是按照给定的接口格式和文件格式来实现
8.计算模块部分单元测试展示
计算模块的单元测试覆盖率达到了93%,如下图所示:
单元测试测试了正常情况(前两个)和异常情况(后一个)。第一个测试的是get_chain_word的情况,第二个测试的是get_chain_char的情况,异常情况测试了不存在-r参数时输入有环的异常情况。如下:
9.计算模块部分异常处理说明
-
没有-r参数时有环的异常情况;设计目标是发现输入中没有-r参数进行“输入文件中存在环”的提示并退出程序。
-
对于输入文件不存在的异常情况;设计目标是给出提示“Error opening file",并结束程序。
-
对于输入中诸如同时出现-w -c的异常情况,设计目标是给出提示“para error",并结束程序。
10.界面模块的详细设计过程
我们只实现了命令行模块。
本次项目专门设计了一个Input类来实现命令行输入地处理,其中类中的属性有is_w、is_c、is_r、filePath等,都是私有属性,必须通过相应的成员函数访问。主要用类中的spilt方法来分割各个参数。
spilt方法根据传入的命令行参数,辨识出具体是那个参数,并且出现一次后进行标记,相同参数再出现则报错,-w -c亦是如此。提取出相应参数后给相应属性赋值。
11.命令行模块与计算模块的对接
我们将计算模块暴露出来两个接口,命令行在处理完并构造出单词表之后可以使用这两个接口和计算模块进行连接。
static int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop); // 计算最多单词数量
static int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop); // 计算最多字母数量
12.结对的过程
驾驶员和领航员,一个在写代码,一个在旁边看
13.结对编程总结
-
结对编程的好处
1. 多双眼睛,少点 bug:两人互相监督工作,可以增强代码和产品质量,并有效的减少 BUG
2. 互相学习编程技巧:在编程中,相互讨论,可以更快更有效地解决问题,互相请教对方,可以得到能力上的互补
3. 互相监督,不容易偷懒:两个人一起工作需要互相配合,如果想偷懒去干别的,就会拖延工作进度 -
结对编程的坏处
1. 开发人员可能会在工作时交谈一些与工作无关的事,分散注意力,造成效率低下
2. 需要高强度下编程时效率会低 -
成员的优缺点
成员 优点 缺点 曾林思 工作积极、 对算法进行改进、 更好地提出思路 不够细心 张峻槐 细心、 对搭档友好、 能提出很多好建议 对算法不敏感
14.实际花费时间
见第2条