【软工】week6-结对编程作业-最长英语单词链
一、项目背景
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2022春季软件工程(罗杰 任健) |
这个作业的要求在哪里 | 结对编程项目-最长英语单词链 | CSDN社区 |
我在这个课程的目标是 | 提高开发项目的编程水平,锻炼合作能力 |
这个作业在哪个具体方面帮助我实现目标 | 提高结对编程合作能力,改善代码风格,熟悉单元测试的流程 |
教学班级 | 周二班 |
项目地址 | Aaronhuang-778/PPPair_Programming | GitHub |
队友博客 | 结对编程项目——单词链的前因后果 |
二、做计划并预估任务时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 60 |
- Estimate | - 估计这个任务需要多少时间 | 60 |
Development | 开发 | 2860 |
- Analysis | - 需求分析 (包括学习新技术) | 600 |
- Design Spec | - 生成设计文档 | 120 |
- Design Review | - 设计复审 (和同事审核设计文档) | 60 |
- Coding Standard | - 代码规范 (为目前的开发制定合适的规范) | 60 |
- Design | - 具体设计 | 360 |
- Coding | - 具体编码 | 1000 |
- Code Review | - 代码复审 | 240 |
- Test | - 测试(自我测试,修改代码,提交修改) | 420 |
Reporting | 报告 | 400 |
- Test Report | - 测试报告 | 200 |
- Size Measurement | - 计算工作量 | 100 |
- Postmortem & Process Improvement Plan | - 事后总结, 并提出过程改进计划 | 100 |
Total Work | 合计 | 3320 |
三、接口设计封装方法应用
Information Hiding
定义:
-
一种可以使用户免受内部程序工作影响的方式
-
核心思想:关键的设计系统应该对客户和前端用户隐藏
-
灵活性:允许程序员更容易地修改程序
实际应用:
例如,我们原本报错是直接throw的时候赋值message,但是信息往往比较长,封装成如下形式:
public class InputErrorType : ErrorType
{
public new enum code
{
dupli_para,
not_support, //default
wrong_format,
wrong_head,
wrong_tail,
illegal_path,
illegal_file_type,
file_not_found,
illegal_para_combination,
no_filename,
no_input_text,
no_check_mode,
empty_string
}
}
抛出异常的时候就可以很轻松地调用:
catch
{
throw new InvalidInputException(InputErrorType.code.empty_string);
}
Interface Design
概念:
- User interface (UI)设计重点是最大化可用性和用户体验
- 目标:交互尽可能简单高效,以用户为中心
实际应用:
例如:
①为选择添加捆绑逻辑,文件输入和文本框只能选择一个,没选中时黯淡不可点击,易于理解
②同时引用两个小组的Core,助教测试不需要再去源码注释掉部分代码;默认选中更常用的“我们的Core”,提高效率
③整体简洁美观,逻辑清晰,色调统一有深浅变换
Loose Coupling
定义:
-
耦合:指一个元素对另一个元素的直接依赖程度
-
松耦合:组件(元素)之间在可行的最小范围内相互依赖
-
目标:降低在一个元素中所做的更改会在其他元素中产生意外更改的风险
-
优点:在出现问题时隔离问题并简化测试、维护和故障排除程序
-
方式:加元素、删除元素、重命名元素、重新配置元素、修改内部元素特征以及重新排列元素互连的方式
实际应用:
如下图所示
四、接口设计实现过程
上层模块及系统框架设计
根据上文中对接口设计的学习和体验,我们将上层接口大致分为七个部分,作为上层封装,在内部又具有不同的功能类和属性类进行相应操作,这种横向和纵向的设计基本符合高内聚、低耦合的需求。
整体上来说有七个上层模块:
从功能和任务来上分就是:顶层调用;解析参数;单词读取;指令调用;对应算法调用;结果返回和文件输出;错误异常处理等。下方给出了整个上层模块的调用示意图,以及相关关系,也算作是整个项目的小型系统架构图。
内部细节举例说明
这里列举几个上层模块的内部函数和内部类的简要说明。
-
全局参数
GlobalPara(类):解读后的信息
-
DealWords(类)
dealWords(函数)
checkWords(函数)
-
DealParas(类)
analyseParas(函数)
checkParas(函数)
checkInputFile(函数)
-
Word(属性类)
-
Graph(属性类)
getWordList(函数)
getStartList(函数)
getEndList(函数)
-
ErroeType(属性类)InvalidInputException(功能类)
CircleException(功能类)
ChainErrorType(功能类)
ChainNotFoundException(功能类)
-
以及其他功能类
类之间层次结构
类之间的耦合 以包装接口的Chain
类为核心
建立单词节点图、以及通过拓扑排序寻路的Graph
是最复杂的一个类,相应的,代码行数也最多
关键算法:拓扑排序+DFS图算法
由于读入单词图的特殊性质,我们将会在Graph
当中形成一个完备的单词图,根据指令调用的特性选取较佳的算法。在这其中我们尽可能地减小搜算的消耗以及次数我们将在下面的部分详细介绍一些数据设计、预处理、算法部分。步骤大概如下:
- 建立有向图
- 去除孤立点
- 判断成环性
- 对症下药(选择合适算法,其中无环约束一律采用拓扑+DFS降低搜索次数)
Graph的构建
在完成顶层模块中对于单词的读入之后我们将会在构成图的部分建立以下数据结构。其中:
word_list
:是我们保存整个读入单词的无序链表
start_list
:是一个字典,其中以字母作为索引key,内容是所有以key开头的Word
类组成的列表
end_list
:是一个字典,其中以字母作为索引key,内容是所有以key结尾的Word
类组成的列表
adj
:是一个二维数组,用以保存有向边信息,这里我们构建了具有权重的边属性,在后续算法中会有体现
public static ArrayList word_list = new ArrayList();
private static Dictionary<char, ArrayList> start_list = new Dictionary<char, ArrayList>();
private static Dictionary<char, ArrayList> end_list = new Dictionary<char, ArrayList>();
//权重边
public static int [,] adj = null;
孤立点删除
这里的孤立点就是出入度均为0的单词,在单词图中是孤立的,也不会参与构成单词链所以我们将它删除,减少图搜索的次数。
ArrayList next_list = G.getNextWordList((Word)Graph.word_list[i]);
ArrayList last_list = G.getLastNode((Word)Graph.word_list[i]);
if (next_list == null && last_list == null)
{
Graph.word_list.RemoveAt(i);
i--;
continue;
}
-n
类型算法:难度中上 ⭐⭐⭐⭐
算法核心:拓扑排序;DFS;动态维护
维护动态单词链集合live_set
,结果单词链集合link_set
-
从一个入度为0的起始点开始进行DFS
-
DFS到单词结点n时,将set中所有单词链末尾追加n的单词
若当前边的权重为1,将n的单词加入set,将权重置为0
-
输出动态单词链集合到结果单词链集合中
-
当DFS结束时,从另一个初始起始点继续第3步,直至遍历所有的初始起始点
算法函数:
class n_Method {
public bool startTopo()
public void startDFS()
public void DFS()
}
-m
类型算法:难度中下 ⭐⭐
算法核心:拓扑排序;DFS;动态维护头字母访问集合
- DFS 维护一个数组,记录访问过的单词首字母
class m_Method {
public bool startTopo()
public void startDFS()
public void m_DFS()
}
-w -h -t
无环情况算法:难度中⭐⭐⭐
算法核心:拓扑排序;DFS;无权无环;反图问题
- 通过
-h
的约束指定起始点。进行DFS - 在单纯
-t
条件下使用反图,增强代码复用
class w_Method {
public bool TopologicalSort()
public void longestPathDAG()
}
-c -h -t
无环情况算法:难度中⭐⭐⭐
算法核心:拓扑排序;DFS;有权无环;反图问题
- 通过
-h
的约束指定起始点。进行DFS - 在单纯
-t
条件下使用反图,增强代码复用 - 建立有向加权边,权重为边目的单词的长度
class c_Method {
//相比w_Method多传递有权边
public bool TopologicalSort()
public void longestPathDAG()
}
w/-c -r
有环问题:难度中⭐⭐⭐
算法核心:DFS;有权有环;反图问题
- 通过
-h
的约束指定起始点。进行DFS - 在单纯
-t
条件下使用反图,增强代码复用 - 针对不同类型输入相应需求
- 进行朴素DFS算法
class rc_Method {
public void startDFS()
public void rc_DFS()
}
class rw_Method {
public void startDFS()
public void rw_DFS()
}
五、计算模块UML类图
绘制UML类图,表示计算模块部分各个实体之间的关系如下:
可以看出Chain
类的耦合度最高。
六、计算模块接口性能改进
理论推导
-
非极端情况下算法的时间复杂度为\(O_{(n^2)}\),由于有环情况下复杂情况的单词链问题为NPC问题,很难优化,但是我们尽力使得性能消耗最小
-
清除孤立点:由于单词图当中的孤立点是不参与到任何参数计算当中的,为了减少图遍历的点数目我们会提前清楚所有的孤立点(出入度都为0)
-
无环情况下的拓扑优化:由于在无环情况下不管是四个指令当中的哪一个,我们的最佳输出一定是从入读为0的点进行;同样在
-n
操作中我们也只需要通过拓扑点开始进行DFS,设置好访问数组来进行所有单词链的输出 -
有环情况下的空间优化:因为有环情况下我们要进行大量的DFS过程,所以我们最初的想法是对每个结点生成两个ArrayList来保存出度目标和入度来源,但是这种操作就需要开辟2n个数据结构,如果图的连通性强,那么其中的对象数目又会很多,所以我们采用了,字母字典存储,即以开头字母为key,保存所有到达这个字母的单词对象;结尾字母为key,保存所有可到达的单词对象。这种设计下我们就算有成千上万的单词,我们最终的图信息保存也只需要两个字典,最多2*26个数据结构。
private static Dictionary<char, ArrayList> start_list = new Dictionary<char, ArrayList>(); private static Dictionary<char, ArrayList> end_list = new Dictionary<char, ArrayList>();
修改前
- 性能分析图:
下图可见整体性能使用当中由于会进行读取所以IO的占比仅次于我们的计算处理模块
整体函数使用性能来看还是两个和图计算相关的算法模块占用较大
整理后
在完成了单元测试,改善代码风格,删除死代码,增强代码可维护性之后,通过Python代码生成测试样例和答案:
使用该数据进行性能分析,结果如下:
其中,占用CPU最多的是写入文件函数,和复杂度更高(NPC问题)的 -w -r
和 -c -r
模块:
用火焰图表示各模块占用内存分配:
七、契约式设计编程方法分析
Design by Contract
概念:
- 定义:规定软件设计者应该为软件组件定义正式的、精确的和可验证的接口规范
- 优点:
- 更好地理解面向对象的方法,更普遍地理解软件构造,更好地理解和控制继承机制
- 用于调试、测试以及更普遍的质量保证
- 为异常处理提供了一种安全有效的语言结构
- 缺点:在设计时就做好复杂的规范框架可能会导致“过度优化”,产生很多冗余结构
实际应用:
在我们的结对编程过程中,重要的封装起来的类和方法的定义,都遵循了讨论确认算=>设计规范=>搭框架=>完成代码的过程。
感觉类似于写论文的时候,先写好大纲,再分块写模板,最后往模板的架子里面填充扩写每块的内容。
Code Contract
-
微软开发的用于实现契约式设计的插件
-
在 .NET Framework 代码中指定前置条件(输入方法或属性时必须满足的要求)、后置条件(退出时的期望)和对象不变量
-
优点:提供自动测试工具、静态合约验证、运行时检查、文档生成
-
缺点:过于复杂
因此我们没有使用Code Contract,不过单元测试的时候指定输入、调用函数、Assert验证是否和答案相同,这个思想是很类似的。
八、计算模块部分单元测试展示
测试覆盖率
由于版本限制原因,我们选择了下载企业版VS进行覆盖率测试,由于最初代码结构并未进行管理,测试结果一般,我们调整代码格式之后进行测试,测试覆盖率结果理想。
测试结构
- 我们按照整体框架对计算模块进行了测试,我们使用脚本生成了大量的测试文件。其中每个指令类型包含15个测试文件。
举例:-n test0
inducibly
uncia
spaebook
flivver
darkies
由于单词环的复杂性,以及有环无环的问题,我们对测试文件进行了手工挑选,并且更改,确保了测试过程中能够覆盖所有的指令类型,以及相应的有环无环状态,同时还考虑到了head
和tail
的组合情况。
[TestMethod]
public void TestMethod1()
{
string[] test = { "-w", "w_no_circle/test0.txt", "-h", "a" };
Core.RunCMD.Main(test);
Core.GlobalPara.clearGlobal();
Core.Graph.clearGraph();
}
[TestMethod]
public void TestMethod2()
{
string[] test = { "-w", "w_no_circle/test1.txt", "-t", "b" };
Core.RunCMD.Main(test);
Core.GlobalPara.clearGlobal();
Core.Graph.clearGraph();
}
[TestMethod]
public void TestMethod3()
{
string[] test = { "-w", "w_no_circle/test2.txt", "-h", "a", "-t", "b" };
Core.RunCMD.Main(test);
Core.GlobalPara.clearGlobal();
Core.Graph.clearGraph();
}
- 我们采用了平行式的计算模块测试
-
测试结果与预期结果相符合:下图展示了关于-n指令的测试结果
九、计算模块部分异常处理说明
异常设计方案:
通过继承调用,使用Exception
进行错误处理部分整体设计,整体分类如下:
-
InvalidInputException
:异常输入部分错误处理输入文件不合法、文件不存在、文件类型不合法、文件路径不合法错误等
输入参数不存在、参数组合不合法、参数格式不规范等
-
CircleException
:异常单词环部分错误处理非
-r
条件下出现单词环 -
ChainNotFoundException
所求的单词环不存在
异常处理及其测试:
参数类型
不支持的参数组合
Core.exe -n -h q aaa.txt
输入参数不支持:
Core.exe -b aaa.txt
输入参数格式错误
Core.exe -w h aaa.txt
参数重复出现
Core.exe -w -w -h -h aaa.txt
-h参数后面跟的字母不合法
Core.exe -w -h aaa aaa.txt
-t参数后面跟的字母不t合法
Core.exe -w -t aaa aaa.txt
输入文件错误
文件后缀错误
Core.exe -w aaa.py
文件路径不合法
Core.exe -w ?????.py
单词文件不存在
Core.exe -w aq.txt
默认模式下单词链成环
Core.exe -n aaa.txt
Core.exe -w aaa.txt
单词链算法异常
单词链中不存在以-h要求开头的字母
Core.exe -w -h o aaa.txt
Apple
Zoo
Elephan
Under
Fox
Dog
Moon
Leaf
Trick
Pseudopseudohypoparathyroidism
单词链中不存在以-t要求结尾的字母
Core.exe -w -t i aaa.txtt
Apple
Zoo
Elephan
Under
Fox
Dog
Moon
Leaf
Trick
Pseudopseudohypoparathyroidism
不存在单词链
Core.exe -w aaa.txt
Core.exe -n aaa.txt
Apple
Zoo
Under
Fox
Dog
Moon
输出文件错误
输出文件solution.txt不存在
Core.exe -w aaa.txt
十、界面模块的详细设计过程
我们的GUI也使用C#进行开发,使用WPF(Windows Presentation Foundation)应用设计UI界面,它提供了统一的编程模型、语言和框架,真正做到了分离界面设计人员与开发人员的工作;同时它提供了全新的多媒体交互用户图形界面,是很多桌面应用开发的首选工具。这也是我们最初选择使用C#开发Core的原因——方便GUI的扩展和连接。
外观设计
我们导入了几个组件库进行UI的设计和美化
通过WPF的xaml控件设计对整体界面进行调整
最终界面展示如下:我们认为在如此短的时间内设计一款样式简洁且美观的UI较为不易,所以也尽可能地精简界面,突出更多的用户友好性。
界面特点
- 颜色搭配美观和谐,功能一目了然,用户友好性强
- 支持文件导入,用户只需要点击浏览,将自己的文件导入到UI当中就能进行单词文件的自动读取
- 文本输入,用户可以通过文本输入单词表,后台将自动读入单词
- 命令选择,提供所有命令组合方便用户进行选择
- 特殊指令组合,用户可以通过一键点击是否增添环判断以及
head
和tail
的约束,通过输入字母进行 - 一键式生成,用户完成指令点击之后可以点击生成单词链
使用说明
使用注意:
- 每次只能生成一条单词输出类型,如果需要更换指令,请更换后重新点击
生成单词链
部分代码说明:
WPF通过映射的方式来对应当前的程序集语法:xmlns[:必选映射前缀]="clr-namespace:[命名空间]",通过对命令的绑定支持交互,其中WPF支持五个静态类:
ApplicationCommands 该类提供通用命令,包括Copy、Cut、Paste等等。
NavigationCommands 该类提供了导航的命令。
EditingCommands 该类提供了很多主要的文档编辑命令 如MoveToLineEnd、MoveLeftByWord等等。
CommponentCommands 该类提供了用户界面元素使用的命令。
MediaCommands 该类提供了一组用于处理多媒体的命令。
具体设计举例
按钮组件设计举例
这里举例生成单词链的按钮设计样式
<Button x:Name="buttonCal"
Click="startCal"
Style="{StaticResource MaterialDesignRaisedLightButton}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="0,30,0,0"
Content="生成单词链"
IsEnabled="{Binding DataContext.ControlsEnabled,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
FontFamily="宋体" FontSize="20" FontWeight="SemiBold" Foreground="White"/>
</materialDesign:Badged>
文本框单词输入设计举例
这里给出了输入单词文本框的设计样式
<TextBox x:Name="textBoxInput" Grid.Row="2"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
VerticalAlignment="Top"
HorizontalAlignment="Center"
Margin="0,10,0,0"
AcceptsReturn="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
materialDesign:HintAssist.Hint="This is a text area"
MinWidth="350" MinHeight="200"
FontFamily="宋体" FontSize="20"
IsEnabled="{Binding Path=IsChecked, ElementName=radioButtonText}"/>
文件导入
这里给出了文件导入的前后端设计
前端:设计样式+事件点击响应
<Button x:Name="buttonBrowse"
Style="{StaticResource MaterialDesignRaisedLightButton}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="60,0,0,0"
Content="浏览..."
Click="getInputFile"
IsEnabled="{Binding Path=IsChecked, ElementName=radioButtonFile}"
FontFamily="宋体" FontSize="20" FontWeight="Medium" Foreground="White"/>
后端:事件点击响应反馈
public void getInputFile(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "TXT Files|*.txt";
string path = Directory.GetCurrentDirectory();
if (Directory.Exists(path))
{
openFileDialog.InitialDirectory = path;
}
else
{
openFileDialog.InitialDirectory = @"C:\";
}
if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
useFileInput = true;
string filePath = openFileDialog.FileName;
inputSource = filePath;
textBoxInput.Text = filePath;
Console.WriteLine(filePath);
}
}
单词链结果展示
结果展示UI设计
<TextBox x:Name="textBoxResult" Grid.Row="2"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
VerticalAlignment="Top"
HorizontalAlignment="Center"
Margin="0,20,40,0"
AcceptsReturn="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Text="待输入"
Foreground="Purple"
materialDesign:HintAssist.Hint="计算结果"
materialDesign:HintAssist.FontFamily="宋体"
materialDesign:HintAssist.HelperTextFontSize="18"
MinWidth="350" MinHeight="180"
FontFamily="宋体" FontSize="20"
IsEnabled="False"/>
十一、界面模块与计算模块的对接
中间件预处理
因为UI界面输入的内容是离散的,所以我们在GUI和Core之间设置了一个中间件用来收集前端所传递的参数信息以及单词信息;如果是文本输入单词那么这里直接输入单词数组,如果是文件调用则传递文件路径。
public void startCal(object sender, RoutedEventArgs e)
{
if ((bool) radioButtonFile.IsChecked)
useFileInput = true;
if ((bool) radioButtonText.IsChecked)
useFileInput = false;
if ((bool)radioButton_paraN.IsChecked)
calType = 'n';
else if ((bool)radioButton_paraM.IsChecked)
calType = 'm';
else if ((bool)radioButton_paraW.IsChecked)
{
calType = 'w';
isR = (bool)radioButton_paraWR.IsChecked;
if ((bool)radioButton_paraWH.IsChecked)
{
if (textBoxWH.Text.Length == 0)
charH = '!';
else if (Char.IsLetter((textBoxWH.Text)[0]))
charH = (textBoxWH.Text)[0];
else
charH = '!';
}
if ((bool)radioButton_paraWT.IsChecked)
{
if (textBoxWT.Text.Length == 0)
charT = '!';
else if (Char.IsLetter((textBoxWT.Text)[0]))
charT = (textBoxWT.Text)[0];
else
charT = '!';
}
}
else if ((bool)radioButton_paraC.IsChecked)
{
calType = 'c';
isR = (bool)radioButton_paraCR.IsChecked;
if ((bool)radioButton_paraCH.IsChecked)
{
if (textBoxCH.Text.Length == 0)
charH = '!';
else if (Char.IsLetter((textBoxCH.Text)[0]))
charH = (textBoxCH.Text)[0];
else
charH = '!';
}
if ((bool)radioButton_paraCT.IsChecked)
{
if (textBoxCT.Text.Length == 0)
charT = '!';
else if (Char.IsLetter((textBoxCT.Text)[0]))
charT = (textBoxCT.Text)[0];
else
charT = '!';
}
}
if ((bool)radioButtonText.IsChecked)
inputSource = textBoxInput.Text;
调用Core的DLL过程
由于C#的便利性我们只需要将Core生成后导入GUI的引用中即可
然后通过我们定义的GUI接口gen_for_gui_para
输入我们的前端参数就可以进行调用了
try
{
//Chain.test(useFileInput, inputSource, calType, isR, charH, charT, ref result);
Chain.gen_for_gui_para(useFileInput, inputSource, calType, isR, charH, charT, ref result);
string resultText = "";
for (int i = 0; i < result.Length && result[i] != null && result[i].Length > 0; i++)
resultText += result[i] + "\n";
textBoxResult.Text = resultText;
Console.WriteLine("result=" + resultText);
}
catch (InvalidInputException ex)
{
Console.WriteLine("[get in gui]");
Console.WriteLine(ex.Message);
textBoxResult.Text = "错误提示:\n" + ex.Message;
}
catch (CircleException ex)
{
Console.WriteLine("[get in gui]");
Console.WriteLine(ex.Message);
textBoxResult.Text = "错误提示:\n" + ex.Message;
}
catch (ChainNotFoundException ex)
{
Console.WriteLine("[get in gui]");
Console.WriteLine(ex.Message);
textBoxResult.Text = "错误提示:\n" + ex.Message;
}
catch (Exception ex)
{
Console.WriteLine("[get in gui]");
Console.WriteLine(ex.Message);
textBoxResult.Text = "错误提示:\n" + ex.Message;
}
总的来说我认为我们的前后端接口设计是相对规范的,因为这一层中间件处理的存在,我们将前端的复杂信息可以进行整合以及处理,当我们调用计算模块Core的时候只需要用一个接口传递好我们在中间件处理的信息集合即可,在后续我们进行松耦合测试的时候其他组的小伙伴们也可以直接通过调用gen_for_gui_para
这一个接口来进行交互。
前端演示
错误提示在前端的输出:
十二、结对过程的描述
工程时间表
时间 | 任务 |
---|---|
3.22 | 确定程序语言以及代码管理软件,本来打算使用C++但是考虑到UI设计的便利性,我们还是选择使用C#进行代码编写;在代码托管平台上最终决定使用github; |
3.23 | 各自阅读博客进行需求分析,进行解读并且交流构思基础算法,设计整体框架 |
3.24 | 完成项目初始化并且开始着手进行输入处理 |
3.25~3.26 | 进行复杂部分的图算法编写以及性能处理 |
3.26~3.27 | 改进接口设计以及UI设计 |
3.28 | 处理异常,测试BUG;设计完成动态链接库 |
3.29 | 处理异常,测试BUG;进行单元覆盖性测试 |
3.30~4.5 | 博客撰写;松耦合测试(与其他小组) |
沟通记录
沟通方式主要以微信沟通和几次线下会面进行工程设计
GitHub平台的Commit记录
团队工具使用:Notion
我们使用了Notion(点击跳转) 来记录关于算法的一些伪代码和画图思路;
并将算法步骤记录在上,两人同时编辑更新。
十三、结对编程优劣及评价
结对编程
优点:
-
开拓思路,降低局限性
-
减少错误,提高debug效率
-
提高代码质量和代码风格
缺点:
- 并不能达到担任开发实际的一半,”奢侈“ 提高成本
- 需要take a break或者轮换,不够可持续
自评
优点:
- 💡 idea比较多
- 执行力强
- 强迫症,重视封装框架和代码风格
缺点:
- 拖延
(是的 现在还有3个多小时截止提交了 我还没写完博客) - 容易钻牛角尖,在试错的路上哐哐撞南墙不回头
互评
这次作业多亏好队友hw!ヽ(✿゚▽゚)ノ
优点:
- 计划性,安排非常有条理
- 理解能力强,执行能力强(我:什么?!你这就写完了?)
- 学习速度快,虽然我对c#有一定接触,但是最开始的参数读取部分是他写的
- 考虑周全,我一开始看他定义的很多种错误,有些没理解,还以为是冗余的(兜底的default),后来才发现 原来真的有啊!
缺点:
- 有些过度”抽象“,建的静态类和方法太多,导致造单元测试数据的时候很痛苦
(真的没有必要,返回读参数字符串、检查输入的方法,各自新建一个类啊喂!)
十四、预估与实际用时PSP汇总表
PSP2.1 | Personal Software Process Stages | 预估耗时 (分钟) |
实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
- Estimate | - 估计这个任务需要多少时间 | 60 | 30 |
Development | 开发 | 2860 | 3540 |
- Analysis | - 需求分析 (包括学习新技术) | 600 | 420 |
- Design Spec | - 生成设计文档 | 120 | 60 |
- Design Review | - 设计复审 (和同事审核设计文档) | 60 | 90 |
- Coding Standard | - 代码规范 (为目前的开发制定合适的规范) | 60 | 30 |
- Design | - 具体设计 | 360 | 420 |
- Coding | - 具体编码 | 1000 | 1080 |
- Code Review | - 代码复审 | 240 | 420 |
- Test | - 测试(自我测试,修改代码,提交修改) | 420 | 720 |
Reporting | 报告 | 400 | 150 |
- Test Report | - 测试报告 | 200 | 100 |
- Size Measurement | - 计算工作量 | 100 | 20 |
- Postmortem & Process Improvement Plan | - 事后总结, 并提出过程改进计划 | 100 | 30 |
Total Work | 合计 | 3320 | 3750 |
十五、UI模块和Core模块松耦合测试
- 我们组对接的B组是由19373573和18373466两位同学组成的结对小组
- 我们两组很早就确定了合作关系,因为很巧我们都选择了使用C#来开发,同样在GUI部分都使用了WPF框架进行设计,于是我们提前订好了GUI和Core的调用结构命名和参数格式,最后的耦合过程较为成功
接口
下方代码为两组互相约定的接口函数:
/*** -w -r -h -t ***/
public static int gen_chain_word(List<string> words, int len, List<string> result, char head, char tail, bool enable_loop);
/*** -n ***/
public static int gen_chains_all(List<string> words, int len, List<string> result);
/*** -m ***/
public static int gen_chain_word_unique(List<string> words, int len, List<string> result);
/*** -c -r -h -t ***/
public static int gen_chain_char(List<string> words, int len, List<string> result, char head, char tail, bool enable_loop);
当未找到对应head
、tail
,或者在未运行成环的情况下单词链中有环时,报出异常,在调用方捕获
try
{
useZFCore(); //ZFCore为他们小组的核心
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
打包方式
我们GUI需要的类库
- 给GUI用的类库
Core.dll
:C# Class Library for WPF `.Net Framework 4.7.2
对方GUI需要的类库
- 给另一组GUI用的类库
HYCore.dll
:C# Class Library for Windows Application.Net Core 3.1
对方对拍需要的控制台程序
- 控制台程序
Core.exe
:(不需要同级文件夹.dll的可执行文件)C# Console Application.Net Framework 4.7.2
导入方式
-
只需要将另一组同学的Core生成的dll导入到C#,然后重新生成一遍就可以了
B组的Core我们命名为HYCore
-
为了更加方便的展示另一小组的内核我们将我们的GUI进行了拓展,使得另一小组的效果能很好地展示
-
另一组开始的dll版本不能向调用方抛出异常:
-
我们的运算核心模块在另一组GUI中运行的效果: