[BUAA软工]第二次博客作业---结对编程
[BUAA软工]结对作业
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 北航软工 |
这个作业的要求在哪里 | 2019年软件工程基础-结对项目作业 |
我在这个课程的目标是 | 学习如何以团队的形式开发软件,提升个人软件开发能力 |
这个作业在哪个具体方面帮助我实现目标 | 了解结对开发的流程,并亲自体验学习 |
项目地址 | https://github.com/sephyli/wordlist_BUAA |
项目作者信息 | 16231030 焦云鹏 、16231031 李天羽 |
运用Information Hiding, Interface Design, Loose Coupling方法设计接口
Information Hiding
In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change).
Written another way, information hiding is the ability to prevent certain aspects of a class or software component from being accessible to its clients, using either programming language features (like private variables) or an explicit exporting policy. --- Wikipedia
具体在程序中的体现是,我们使用面向对象的编程思想,通过调用类的Public成员函数的方式,执行程序的核心逻辑。在这个过程中,我们通过将一些不应该被外部更改的成员属性(如一个单词的具体信息),设计为私有成员,并设计Public成员函数,作为访问的接口(无写权限),从而达到了Information Hiding思想中的对于信息保护的需求。
Interface Design
User interface design (UI) or user interface engineering is the design of user interfaces for machines and software, such as computers, home appliances, mobile devices, and other electronic devices, with the focus on maximizing usability and the user experience. The goal of user interface design is to make the user's interaction as simple and efficient as possible, in terms of accomplishing user goals (user-centered design). --- Wikipedia
具体在程序中的体现是,我们通过封装核心逻辑为Core类,再通过Core类生成相应的dll,从而将int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop)
,与int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop)
等核心功能函数设计为接口。这样的话,新的项目只需要设置相关路径,让Core.dll
与Core.h
的位置能被探测到,并设置Core.lib
为工程的链接库。这样的话,就可以提供相关接口给别的程序使用。
Loose coupling
In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. Subareas include the coupling of classes, interfaces, data, and services.[1] Loose coupling is the opposite of tight coupling. --- Wikipedia
具体在程序中的体现是,我们在使用面向对象的思想设计程序的时候,将从属于一个类的成员方法与成员属性均放在这个类自己这里,而不要放在别的地方。这样的好处就是可以防止因为一个对象成员的改动,而导致很多不想管的类也要做调整,从而伤害程序的可维护性。
计算模块接口的设计与实现过程
类关键信息
- Word类
- 属性
- char head : 头字母
- char tail :尾字母
- int length :单词长度
- std::string s :单词
- bool use :标志是否被使用
- 方法
- Word(const char* s, int length) :构造函数
- 属性
- WordSet类
- 属性
- std::vector set[26][26] :单词组
- 方法
- void append(Word w) :添加单词
- 属性
- Mode类
- 属性
- bool recurMode :单词成环模式
- bool headMode:头字母指定模式
- bool tailMode:尾字母指定模式
- bool wordNumMaxMode:单词数量最大模式
- bool charNumMaxMode:单词字母最多模式
- char head:指定的头字母
- char tail:指定的尾字母
- 方法
- void append(Word w) :添加单词
- 属性
- Searcher类
- 属性
- Data data :数据信息
- Mode mode :模式信息
- std::vector maxWordList :历史max单词list
- std::vector tmpWordList :当前max单词list
- 方法
- bool exe() :执行函数
- 属性
程序分析
我们在拿到题目后,第一反应是直接使用暴力深搜解决问题。但是看到程序正确性要求中的300s运行时间限制(尽管我们现在也没能弄明白是针对-w
模式,-c
模式,还是两者兼有),我们意识到纯粹使用深搜是不合理的。在对算法进行一定的分析之后,我们意识到,不遍历所有满足条件的单词链,是不可能找出其中满足要求的最长/最多单词链的。也就是说,深度优先搜索,将会是我们必须要使用的算法。
完成这部分分析之后,我们将优化的视角投向了数据结构部分。我们注意到,程序要求单词链满足如下条件。
单词链的定义为:由至少2个单词组成,前一单词的尾字母为后一单词的首字母,且不存在重复单词
这就意味着,我们在从文件中读取单词的时候就该避免重复单词的读入。不仅如此,我们还设计了独到的数据结构来放置单词,从而实现了访查效率的最大化。
举例来说,我们在WordSet
类中,设置了26*26的二位vector
来放置头字母相同、尾字母相同的单词,并在vector
中按照单词的长度来排列,这样的话,不管对于-w
模式还是-c
模式,都可以采取同一套访查算法。不仅如此,如此组织数据结构,可以让我们的核心搜索函数(深搜函数)快速通过找到以目标字母开头的单词,相比传统的深度优先搜索,我们在这一步的复杂度从O(N)
降到了O(1)
。考虑到深搜核心函数的调用次数是一个随着单词链长度增长而成阶乘级增长的,我们的优化方法,应该来说也是有一定作用的。
类关系UML图
计算模块接口部分的性能改进
在构造数据结构时,我将问题的思维模式转变为一种带权重的有向图。如hello,即结点h到结点o的有向边,若-w
模式,权重为1,-c
则为5。那么我存下了每个节点到另一个节点的边的信息并以节点号进行索引,构造出了vector<Word> WordSet[26][26]
这样的结构,每个vector
内用权重进行排序。
由此问题便转变为了不允许重复节点和路径的最长路径问题。考虑过DP,但无法构造高效的子问题,及子问题合并时需要判断重复的路径和节点,猜测并不会提升效率。由此使用了深度优先搜索,在非-r
模式,的复杂度约为26!这个数量级,因此构造出了最复杂的样例后,是绝无可能在300s之内完成计算的。由此放弃了在总体算法层次上的优化,仅仅追求最短的搜索路径和较小的访存开销。
结果: 算法在-r
模式下的100个词中,可以在20ms内完成单词链搜索。
性能统计图如下所示:
Design by Contract, Code Contract
Design by contract (DbC), also known as contract programming, programming by contract and design-by-contract programming, is an approach for designing software. It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants. These specifications are referred to as "contracts", in accordance with a conceptual metaphor with the conditions and obligations of business contracts.
The DbC approach assumes all client components that invoke an operation on a server component will meet the preconditions specified as required for that operation. Where this assumption is considered too risky (as in multi-channel client-server or distributed computing) the opposite "defensive design" approach is taken, meaning that a server component tests (before or while processing a client's request) that all relevant preconditions hold true, and replies with a suitable error message if not. --- Wikipedia
这个概念与我在面向对象程序设计中所学到过的设计契约思想比较相似,都是要求调用它的客户模块都保证一定的进入条件,保证退出时给出特定的属性,即满足一定的先验条件与后验条件。
具体来看,在程序设计的时候,我们就遵循了这样的规范。例如,在进行Mode
类设计的时候,对于set
方法(即命令行信息赋值函数),程序就要求传入的参数是有意义的。这就是对于先验条件的实现。
程序在进行getWordFromVec
函数实现的时候,需要返回一个满足条件的Word
对象。但是,如果没有找到这样的Word
对象,程序也不能什么都不做,而是应该通过调用Word
的默认构造函数,返回一个空的Word
。这样的话,就保证了getWordFromVec
函数所约定的后验条件。
计算模块部分单元测试展示
使用工具为OpenCppCoverage。经过merge过后的结果展示:
单元测试部分代码:
TEST_METHOD(TestRecur)
{
FILE *fin;
fopen_s(&fin, "../test/recurtest1.txt", "r");
char *words[105], *result[105];
Inputer *inputer = new Inputer();
int wordNum = inputer->getWord(fin, words);
for (int i = 0; i < 105; i++) {
result[i] = new char(105);
}
int len = 0;
len = Core::gen_chain_char(words, wordNum, result, 0, 0, true);
Assert::AreEqual(len, 60);
}
在这个例子中,程序测试了Core::gen_chain_char
接口,通过对文件·../test/recurtest1.txt
内容测试,检查了在指定字母数最多,无头尾字母指定,允许单词环存在的情况下,得到的单词链长度的测试。
计算模块部分异常处理说明
单词超长
- 用途:检测输入文本中含有超过长度限制的单词的情况。
- 预期结果:通过try-catch的方法,捕获命令行报错信息。其中,
./test/testfile.txt
文件中包含一个连续长度超过100的字母串。 - 单元测试如下:
TEST_METHOD(TestTooLongWord)
{
FILE *fin;
fopen_s(&fin, "../test/testfile.txt", "r");
char *words[10000], *result[105];
Inputer *inputer = new Inputer();
int wordNum = inputer->getWord(fin, words);
for (int i = 0; i < 105; i++) {
result[i] = new char(100);
}
int len = 0;
len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
}
隐含单词环
- 用途:检测输入文本中隐含单词环的情况。
- 预期结果:通过try-catch的方法,捕获命令行报错信息。其中,
./test/testfile.txt
文件中包含若干个可以构成单词环的单词。 - 单元测试如下:
TEST_METHOD(TestNonRecureFalse)
{
FILE *fin;
fopen_s(&fin, "../test/testfile.txt", "r");
char *words[10000], *result[105];
Inputer *inputer = new Inputer();
int wordNum = inputer->getWord(fin, words);
for (int i = 0; i < 105; i++) {
result[i] = new char(100);
}
int len = 0;
len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
}
隐含单词环
- 用途:检测输入文本中隐含单词环的情况。
- 预期结果:通过try-catch的方法,捕获命令行报错信息。其中,
./test/testfile.txt
文件中包含若干个可以构成单词环的单词。 - 单元测试如下:
TEST_METHOD(TestNonRecureFalse)
{
FILE *fin;
fopen_s(&fin, "../test/testfile.txt", "r");
char *words[10000], *result[105];
Inputer *inputer = new Inputer();
int wordNum = inputer->getWord(fin, words);
for (int i = 0; i < 105; i++) {
result[i] = new char(100);
}
int len = 0;
len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
}
单词链长度过短
- 用途:检测无法找到长度超过1的单词链的情况。
- 预期结果:通过try-catch的方法,捕获命令行报错信息。其中,
./test/testfile.txt
文件中仅包含一个单词,或所包含的单词无法形成长度超过1的单词链。 - 单元测试如下:
TEST_METHOD(TestNonRecureFalse)
{
FILE *fin;
fopen_s(&fin, "../test/testfile.txt", "r");
char *words[10000], *result[105];
Inputer *inputer = new Inputer();
int wordNum = inputer->getWord(fin, words);
for (int i = 0; i < 105; i++) {
result[i] = new char(100);
}
int len = 0;
len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
}
命令行参数无法正确解析
- 用途:检测命令行参数无法正确解析的情况。
- 预期结果:通过try-catch的方法,捕获命令行报错信息。
文件读取错误
- 用途:检测无法找到文件的情况。
- 预期结果:通过try-catch的方法,捕获命令行报错信息。其中,
./test/testfile.txt
文件不存在。 - 单元测试如下:
TEST_METHOD(TestNonRecureFalse)
{
FILE *fin;
fopen_s(&fin, "../test/testfile.txt", "r");
char *words[10000], *result[105];
Inputer *inputer = new Inputer();
int wordNum = inputer->getWord(fin, words);
for (int i = 0; i < 105; i++) {
result[i] = new char(100);
}
int len = 0;
len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
}
命令行模块描述
参数约定
程序支持通过命令行的方式输入参数以及文件位置信息。参数及其约定如下。
参数名字 | 参数意义 | 范围限制 | 用法示例 |
---|---|---|---|
-w |
需要求出单词数量最多的单词链 | 绝对或相对路径 | 示例:Wordlist.exe -w input.txt [表示从input.txt 中读取单词文本,计算单词数量最多的单词链] |
-c |
需要求出字母数量最多的单词链 | 绝对或相对路径 | 示例:Wordlist.exe -c input.txt [表示从input.txt 中读取单词文本,计算字母数量最多的单词链] |
-h |
指定单词链首字母 | a-z ,A-Z |
示例:Wordlist.exe -h a -w input.txt [表示从input.txt 中读取单词文本,计算满足首字母为a 的、单词数量最多的单词链] |
-t |
指定单词链尾字母 | a-z ,A-Z |
示例:Wordlist.exe -t a -c input.txt [表示从input.txt 中读取单词文本,计算满足尾字母为a 的、字母数量最多的单词链] |
-r |
允许单词文本中隐含单词环 | NONE |
示例:Wordlist.exe -r -w input.txt [表示从input.txt 中读取单词文本,计算单词数量最多的单词链,即使单词文本中隐含单词环也需要求解] |
实现过程
程序将命令行参数的提取部分放在了main函数中,通过引用int main(int agrc, char* agrv[])
函数的方式,将命令行参数的数量读取在agrc
中,分词读取在agrv[]
中。
通过判断分词是否为-
开头,来判断该词是否代表着命令行参数,在通过该词的第二个字母,判断具体属于哪个命令行参数(支持大小写)或者报错。同时通过判断第三个字母是否为\0
来判断参数是否过长。如果为参数有后续的范围限制,如-h
、-t
后需要指定开头、结尾的字母,则继续判断字母是否符合要求(个数为一个且在有意义)。
全部判断完成后,将判断的结果传入构造的Mode
对象中,为后续程序所使用。
具体来看,程序声明了如下变量,在对不同参数做解析的时候,就针对不同情况对变量进行赋值,这样就收集全部的命令行参数信息。
bool rMode = false;
bool hMode = false;
bool tMode = false;
bool wMaxMode = false;
bool cMaxMode = false;
char h = 0;
char t = 0;
char filePath[1000] = "\0";
再通过对Mode
对象的赋值,从而实现了命令行模式的判断。
Mode *mode = new Mode();
mode->Set(rMode, hMode, tMode, wMaxMode, cMaxMode, h, t);
结对编程的优缺点
在结对编程模式下,一对程序员肩并肩地、平等地、互补地进行开发工作。两个程序员并排坐在一台电脑前,面对同一个显示器,使用同一个键盘,同一个鼠标一起工作。他们一起分析,一起设计,一起写测试用例,一起编码,一起单元测试,一起集成测试,一起写文档等。
关于结对编程是否是一种高效的编程手段,这我不好评说,我就来分享一下我和我的小伙伴在结对编程时的一些体会。
在第一周中,因为我的小伙伴白天要去公司实习,而我则要在白天做实验室的项目,所以程序开发的大部分环节其实没有遵循结对编程的要求。大多数情况下,我在白天实现我们在前一天晚上所共同设计的部分,在下午或晚上则由他来审查我的代码,做一些单元测试。到了我们都有时间的晚上,才能共同坐在同一个电脑前,像结对编程要求的那样,针对我们所实现的不同部分,做协调,并构思第二天的工作。
而到了第二周,情况有些变化,我们发现有一些东西需要我们共同探索,例如如何生成dll
模块,以及如何对程序进行全覆盖的单元测试。这时候,我们拿出了统一的时间,坐在了电脑前,完成了结对编程。
与各自分开编程所不同的是,我们代码的错误率有了明显的下降,单元测试的通过率变高了。不仅如此,在实现的过程之中,由于有了“领航员”的存在,我们在进行类的实现的时候更加高效了,我们思考的更加全面,类方法与属性的设置能更好地满足低耦合、高内聚的要求,整体返工的次数也没有了。在“驾驶员”感到疲惫的时候,我们可以从容地进行角色互换,从而更持久的完成程序的实现。唯一美中不足的是,我们用于结对编程的时间不是很多,如果在第一周就能这样做的话,我们第二周的工作无疑会轻松很对:)
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
· Estimate | · 估计这个任务需要多少时间 | 5 | 0 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 80 | 60 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 0 |
· Design | · 具体设计 | 90 | 60 |
· Coding | · 具体编码 | 180 | 120 |
· Code Review | · 代码复审 | 120 | 180 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 180 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 60 | 20 |
· Size Measurement | · 计算工作量 | 0 | 0 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 0 | 0 |
合计 | 665 | 620 |
结对编程照片
[BUAA软工]第二次博客作业---结对编程的更多相关文章
- [BUAA软工]第零次博客作业---问题回答
[BUAA软工]第0次博客作业 项目 内容 这个作业属于哪个课程 北航软工 这个作业的要求在哪里 第0次个人作业 我在这个课程的目标是 学习如何以团队的形式开发软件,提升个人软件开发能力 这个作业在哪 ...
- [2017BUAA软工]第二次博客作业:代码复审
〇.comment链接 https://github.com/hanayashiki/Sudoku/issues/1 一.代码复审 1.概要部分 (1)代码能符合需求和规格说明么? 经测试,对于合法输 ...
- [2017BUAA软工]第零次博客作业
第一部分:结缘计算机 1. 你为什么选择计算机专业?你认为你的条件如何?和这些博主比呢?(必答) 当年高考前在专业这件事上纠结了好久,因为我对于大学各个专业具体学什么都不甚了解,于是就迟迟没有明确的目 ...
- [2021BUAA软工_助教博客]作业成绩汇总
作业及成绩 作业链接 成绩链接 个人阅读作业#1 个人阅读作业#1_成绩 个人阅读作业#2 个人阅读作业#2_成绩 结对项目-第一阶段 结对项目-第一阶段_成绩 结对项目-第二阶段 结对项目-第二阶段 ...
- [BUAA软工]第0次个人作业
[BUAA软工]第0次个人作业 本次作业所属课程 : 2019BUAA软件工程 本次作业要求: 第0次个人作业 我在本课程的目标: 熟悉软件工程流程,规范开发习惯 本次作业的帮助: 熟悉课程流程 Pa ...
- OO第二次博客作业——电梯调度
OO第二次博客作业——电梯调度 前言 最近三周,OO课程进入多线程学习阶段,主要通过三次电梯调度作业来学习.从单部电梯的傻瓜式调度到有性能要求的调度到多部电梯的调度,难度逐渐提升,对同学们的要求逐渐变 ...
- OO第二次博客作业—17373247
OO第二次博客作业 零.写在前面 OO第二单元宣告结束,在这个单元里自己算是真正对面向对象编程产生了比较深刻的理解,也认识到了一个合理的架构为编程带来的极大的便利. (挂三次评测分数 看出得分接近等差 ...
- Java第二次博客作业
Java第二次博客作业 时间过的很快啊,在不知不觉中这门课程的学习也就快要过去一半了,现在就来总结一下在这个第二个月的学习当中存在的问题以及得到的心得. 1.前言 第四次题目集和第五次题目集给我的感觉 ...
- OO第二次博客作业(第二单元总结)
在我开始写这次博客作业的时候,窗外响起了希望之花,由此联想到乘坐自己写的电梯FROM-3-TO--1下楼洗澡,然后······ 开个玩笑,这么辣鸡的电梯肯定不会投入实际使用的,何况只是一次作业.还是从 ...
随机推荐
- ccf-20170303--Markdown
我的想法如下图: 代码和题目如下: 问题描述 试题编号: 201703-3 试题名称: Markdown 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 Markdown 是一 ...
- emWin及StemWin使用中关于菜单栏的应用与问题
前言:在我看来,emWin和StemWin就是基本相同的库文件,关于这个库文件的移植,网络上有很多教材,比如“ALIENTEK emWin开发手册”,他们家提供了各种STM32系列的开发手册,我这里记 ...
- Beta冲刺(4/5)(麻瓜制造者)
今日已完成 邓弘立:完成了商品管理(下架)和搜索功能 符天愉:完成了后台管理员界面的登录和其他视图的载入 江郑:昨天来决定跨域执行请求,后台参考一些意见以后,操作起来没有那么容易实现,和队友交流以后本 ...
- BSOJ 2423 -- 【PA2014】Final Zarowki
Description 有n个房间和n盏灯,你需要在每个房间里放入一盏灯.每盏灯都有一定功率,每间房间都需要不少于一定功率的灯泡才可以完全照亮. 你可以去附近的商店换新灯泡,商店里所有正整数功率的灯 ...
- Django的settings配置
静态文件 STATIC_URL = '/static/' # 别名 STATICFILES_DIRS = [ os.path.join(BASE_DIR,'static'), os.path.join ...
- WPFのclipToBounds与maskToBounds的区别
UIView.clipsToBounds : 让子 View 只显示父 View 的 Frame 部分,子视图超出frame的部分不显示,默认为NO,设置为YES就会把超出的部分裁掉: maskToB ...
- 用scrapy爬取亚马逊网站项目
这次爬取亚马逊网站,用到了scrapy,代理池,和中间件: spiders里面: # -*- coding: utf-8 -*- import scrapy from scrapy.http.requ ...
- python五十七课——正则表达式(边界字符)
演示匹配锚字符(边界字符)^:从字符串头部开始匹配,在开启多行模式下(re.M),可以尝试匹配每一行的头部数据$:从字符串尾部开始匹配,在开启多行模式下(re.M),可以尝试匹配每一行的尾部数据A:从 ...
- k8s mysql 单点部署
参考官网:https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/ 20-nproc. ...
- Python脱产8期 Day03 2019/4/15
一 变量的命名规范 1.只能由 字母, 数字, _, 组成. 2. 不能以数字开头 3.避免与系统关键字重名:重名不会报错,但系统的功能就被自定义的功能屏蔽掉了(严重不建议这样来做) 4.以_开头的 ...