软件工程结对编程博客

Posted kirito12138

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件工程结对编程博客相关的知识,希望对你有一定的参考价值。

结对编程博客

1、GitHub项目地址

项目GitHub地址

2、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(小时) 实际耗时(小时)
Planning 计划 0.5
· Estimate · 估计这个任务需要多少时间 20.4
Development 开发 18.6
· Analysis · 需求分析 (包括学习新技术) 5
· Design Spec · 生成设计文档 0.5
· Design Review · 设计复审 (和同事审核设计文档) 0.5
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 0.1
· Design · 具体设计 0.5
· Coding · 具体编码 10
· Code Review · 代码复审 1
· Test · 测试(自我测试,修改代码,提交修改) 1
Reporting 报告 1
· Test Report · 测试报告 0.2
· Size Measurement · 计算工作量 0.1
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 0.5
合计 21.9

3、看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的

  • Information Hiding:信息的隐藏和封装,其中信息的隐藏是目的,而封装是方法,这一行为是为了保证接口的安全性,避免在他人使用接口时修改了意料之外的数据,而出现问题。在结对编程中,类属性、方法等尽量定义为私有,只有其他类需要调用的方法才设置为公有。
  • Interface Design:接口设计。接口作为不同模块之间相连接的部分,其设计的合理性与安全性直接关系到不同模块接合时的难易度与安全性。好的接口设计应符合诸多原则如接口隔离原则、迪米特法则、开闭原则等,其目的都是为了保证接口的安全性与实用性。结对编程中,通过事前规定、参照作业要求等,接口设计基本合理。
  • Loose Coupling:松耦合,简单地说就是降低功能、函数之间的依赖程度,即一个方法尽量只完成一个功能,方面修改,避免出现一个函数修改,所有函数都需要再次修改,这样难免导致每次修改时工作量变大,且正确性存疑。可在接下来的模块中看到,结对编程中尽量符合了该要求。

4、计算模块接口的设计与实现过程

计算模块主要有6个类,Core类DFS类Error类Input类PreProcess类RingDfs类

Input类用于从输入中提取所需要的信息,如模式、开头结尾、路径等。

PreProcess类用于处理数据,包括分割单词,并依据分割出的单词生成图,输出结果等。

DFS类用于寻找图中是否有环。

RingDfs类用于根据生成的图进行搜索,寻找符合要求的最长路径。

Core类用于封装。

Error类用于异常时报错。

Input类

方法名 作用 调用方法
Input(int num, char * paras[]) 根据程序参数,提取相应信息,如各类参数,文件路径等 本类私有方法,Error(string str)方法
Input(string str); 根据输入字符串提取相应信息 本垒四哟
int getMode() 返回指定的单词链类型,1为单词数,2为字母数
int getIfRing() 返回是否允许环
int getHead() 返回指定的单词链开头,0为无限制,否则为字母的ASCII码值 s
int getTail() 返回指定单词链结尾,规则同上
string getPath() 返回输入的文件路径
string getInput() 返回输入的完整字符串,用于调试

PreProcess类

方法名 作用 调用方法
PreProcess(char* wordss[], int len, int r); 根据输入的单词表、长度、是否有环,判断是否合法并生成图 本地私有方法,Error(string str)方法
PreProcess(string str, int n, char* words[], int r) 根据输入的包含所有单词的字符串、长度、是否有环,生成单词表与图。单词表char* []是为了满足接口要求。 本地私有方法
PreProcess(string str, int r) 根据输入的路径与是否有环,生成单词表与图 本地私有方法
void printGraph() 打印合并同首尾单词后的图
void printRingGraph() 答应元素
void print(vector ary) 根据输入的容器,将其中单词按顺序输出至solution.txt
void VecToStr(char* result[], vector ans) 将图转化为单词表格式,以满足同接口要求 Error(string str)方法

DFS类

方法名 作用 调用方法
DFS(PreProcess pp) 生成实例
void getGraph() 获取已生成好的图
bool hasRing() 判断图中是否有除了自环以外的环

RingDFS类

方法名 作用 调用方法
RingDFS(PreProcess pp) 生成实例,pp中含有图等信息
vector initDFS(int mode, int head, int tail, int ring) 根据输入的各类参数,以及获取到的图,搜索符合要求的单词链,并返回结果 本地私有方法,Error(string str)方法

Core类

方法名 作用 调用方法
int gen_chain_word(char* words[], int len, char* result[], char h ead, char tail, bool enable_loop) 根据输入的单词表、长度、参数等,搜索单词数量最多的符合要求的单词链 Input::Input
int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop) 根据输入的单词表、长度、参数等,搜索字母数量最多的符合要求的单词链 同上

Error类

方法名 作用 调用方法
void Error(std::string str) 根据输入的报错信息抛出异常

算法基于bfs的改进(改进原理见第6部分),基本原理简单易懂。对于无环的情况下,建图时合并了同首尾单词,只保留字母数量最大的单词。

5、画出UML图显示计算模块部分各个实体之间的关系

技术图片

6、计算模块接口部分的性能改进。?记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数

以下为无环,单词数量9900的运行情况:

技术图片

可见,大部分时间花在了建图时的各种关于内存的操作上。这是因为在优化了无环的搜索算法后,将搜索部分的复杂度限制在了O(26x26),速度很快,因此关于内存的各类操作成为了最占用时间的操作。

对于算法的具体优化如下

对于无环的搜索,按如下方法建图:因为无环,所以以26个字母为节点,单词为边,以a开头b结尾的单词,表现为由节点a指向节点b的一条边。

在搜索算法上,以递归BFS搜索为基础,在节点b的递归退出时,即可确保b往后的所有路径均已遍历,因此可以得到以b为开头,能搜索到的最长符合要求的单词链长度,保存该长度,记为L(b)。之后若其他节点访问节点b时,比如上述例子中的a节点访问b节点,不需要再次进入b递归,只需要比较(L(b))与(由a指向b的边长度)之和,与原本的L(a)的大小,并将大者保存为新的L(a)即可。

这样,在所有节点递归结束后,得到了每个节点往后所能达到的符合要求的单词链的最大长度。根据此信息输出最长路径即可。

该算法确保了每条边只走一次,不会重复走同一条边(同一个单词),因此,在无环的基础上其复杂度为O(26x26)。

但是,在有环的问题中,因为环的存在以及该方法无法判断L(b)中有哪些节点,因此可能会导致重复走同一条边,该算法无法使用。

7、看Design by Contract, Code Contract的内容 ,描述这些做法的优缺点, 说明你是如何把它们融入结对作业中的

契约式设计的优点是,义务、责任分工明确,各自负责各自部分的正确性,简化了开发流程,明确了各自任务。契约的存在保障了双方的利益,对需求者来说,不需要担心所需部分的正确性,只需要按照契约使用即可。对于供应者来说,不需要担心提供的模块如何使用等, 只需要按照契约完成模块并确保正确性即可。

在结对编程中,我们会规定不同模块之间的接口,在发现bug后将bug交给相应模块的实现者负责修复,另一方只在实现者遇到困难时提供帮助。

8、计算模块部分单元测试展示。?展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路

部分单元测试代码如下:

技术图片

这两部分测试数据,测试的是方法Input Input(string str),方法Input Input(int num, char* paras[])。两个方法的作用分别是根据输入的字符串,或程序的参数提取相应的设置信息,文件路径等。

测试的思路是构造符合被测方法输入数据结构要求的数据,并尽量复杂,以达到更高的分支覆盖率。

以图中的测试用例为例,因为测试的是处理输入的方法,因此在构造输入数据时尽量构造较为复杂的输入,即尽量输入更多的参数,以覆盖较多的分支。

对于异常测试,测试代码如下:
技术图片

catch方法中通过判断异常信息是否符合预期的方式,来判断是否触发了所要测试的异常。

同时,在应该触发异常的代码下一行加入一条永假的断言Assert::AreEqual("Error Test Didn‘t Throw Error!", " ");,以确保通过的测试,是触发了正确的异常,而不是没有触发异常导致没有进入catch,根本没有运行catch中的断言。

测试的覆盖率如下:
技术图片

因为VS只有企业版、专业版才能查看单元测试的分支覆盖率,所以该分支覆盖率是将大部分测试中的代码转移到main函数中运行后查看的,其覆盖率低于实际的单元测试分支覆盖率但也达到了90%。

9、计算模块部分异常处理说明

所有的异常如下:

  • 输入错误

    • Chain Type Parameter Error

      在已经输入过-w-c参数,即指定过单词链类型的情况下,再次输入这两个参数。

      单元测试:

      try 
      {
          Input input("-w -c ..Pair_ProgrammingDFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Chain Type Parameter Error";
          Assert::AreEqual(s, str);
      }
    • Head Duplicate Definition Error

      在已经使用-h指令规定过开头的情况下,再次指定开头。

      单元测试:

      try
      {
          Input input("-w -h a -h a ..\\Pair_Programming\\DFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Head Duplicate Definition Error";
          Assert::AreEqual(s, str);
      }
    • Lack Space Error

      在一次性输入所有参数时,参数之间、-h指令与制定的开头字母之间应有空格。没有空格时抛出次异常。

      单元测试:

      try
      {
          Input input("-w -ha ..\\Pair_Programming\\DFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Lack Space Error";
          Assert::AreEqual(s, str);
      }
    • Head Letter Error

      指定的开头字符不是字母。

      单元测试:

      try
      {
          Input input("-w -h + ..\\Pair_Programming\\DFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Head Letter Error";
          Assert::AreEqual(s, str);
      }
    • Tail Duplicate Definition Error

      使用-t指令指定过结尾的情况下再次使用该指令。

      单元测试:

      try
      {
          Input input("-w -t a -t a ..\\Pair_Programming\\DFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Tail Duplicate Definition Error";
          Assert::AreEqual(s, str);
      }
    • Tail Letter Error

      制定的结尾字符不是字母。

      单元测试:

      try
      {
          Input input("-w -t + ..\\Pair_Programming\\DFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Tail Letter Error";
          Assert::AreEqual(s, str);
      }
    • Ring Parameter Duplicate

      多次使用-r参数。

      单元测试:

      try
      {
          Input input("-w -r -r ..\\Pair_Programming\\DFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Ring Parameter Duplicate";
          Assert::AreEqual(s, str);
      }
    • Parameter Type Error

      输入的参数不符合规定。

      单元测试:

      try
      {
          Input input("-w -z ..\\Pair_Programming\\DFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Parameter Type Error";
          Assert::AreEqual(s, str);
      }
    • Lack Chain Type Parameter

      在输入中没有使用-w-c制定指定单词链类型。

      单元测试:

      try
      {
          Input input("-h a ..\\Pair_Programming\\DFSTest2.txt");
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Lack Chain Type Parameter";
          Assert::AreEqual(s, str);
      }
  • 数据错误

    • Input File Doesn‘t Exit

      输入的文件路径不存在。

      单元测试:

      try
      {
          PreProcess pp ("noFile.txt", 0);
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Input File Doesn't Exit";
          Assert::AreEqual(s, str);
      }
    • Multiple Self Ring Found

      在不允许有环的情况下,出现了多个自环。

      单元测试:

      try
      {
          PreProcess pp("..\\Pair_Programming\\PPErrorTest1.txt", 0);
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Multiple Self Ring Found";
          Assert::AreEqual(s, str);
      }

      其中,PPPErrorTest1.txt的内容如下:

      bc
      cc
      cd
      df
      ff
      beee
      eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
      eeeeeeeee
      ef
      fg
      gb
    • No Word Chain in File

      输入的文件中单次数量小于2,不构成单词链。

      单元测试:

      try
      {
          PreProcess pp("..\\Pair_Programming\\PPErrorTest2.txt", 0);
      }
      catch (string str)
      {
          string s = "No Word Chain in File";
          Assert::AreEqual(s, str);
      }

      其中,PPErrortEST2.TXT文件内容如下:

      bc
      
    • Ring Detected When not Allowed

      在没有使用-r参数,的情况下,单词链中出现了环。

      单元测试:

      try
      {
          PreProcess pp("..\\Pair_Programming\\DFSTest1.txt", 0);
          DFS dfs(pp);
          dfs.getGraph();
          dfs.hasRing(0);
          Assert::AreEqual("Error Test Didn't Throw Error!", " ");
      }
      catch (string str)
      {
          string s = "Ring Detected When not Allowed";
          Assert::AreEqual(s, str);
      }

      其中,DFSTest1.txt文件的内容如下:

      bc
      cc
      cd
      df
      ff
      beee
      eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
      ef
      fg
      gb

10、界面模块的详细设计过程

开发平台

使用的是Qt Creator 3.4.2进行开发的,由于Qt自带了前端设计的功能,开发起来比较简单。

1、设计界面

使用的是Qt Creator自带的设计面板,操作简单只需要拖拽即可,以下是具体设计。

技术图片

2、编写后端

在设计好了界面之后,只需要编写好slots函数就可在设计面板中简单的关联(代码未全部贴出)

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    bool H;
    int wc;
    bool R;
    bool T;
    QString tmp;
    bool fFile;
private slots:
    void setC(bool b);
};

#endif // MAINWINDOW_H

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <iostream>
#include "qtextbrowser.h"
#include <string>
#include <QFile>
#include <QDir>
#include <QTextStream>
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //ui->hideButton->hide();
}

MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::setC(bool b){
    this->wc = b?2:1;
}

编写完成后,需要做的就是将函数与组件关联起来,同样在设计面板中操作。

技术图片

关于Qt的信号槽机制,具体可以看这篇博客,由于是在设计模板中关联的,所以并不需要手动编写connect函数。

11、界面模块与计算模块的对接

首先,将计算模块打包成dll文件,为了使用方便,将统一的接口gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop)与接口gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop)定义为函数而非类方法。

打包好后,将生成的Core.dll文件复制到工程的debug文件夹或release文件夹中,通过以下代码导入两个上述接口:

typedef int(*ptr_word)(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
typedef int(*ptr_char)(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
HINSTANCE CoreDLL = LoadLibrary("Core.dll");
ptr_word gen_chain_word = (ptr_word)GetProcAddress(CoreDLL, "gen_chain_word");
ptr_char gen_chain_char = (ptr_word)GetProcAddress(CoreDLL, "gen_chain_char");

导入后,便可以直接调用两个函数。

12、描述结对的过程

在结对的过程中,首先我们在一起预估各个难度的复杂度,之后对于部分逻辑极为简单且不重要的部分如读入等,进行分工,并行开发提高速度。对于核心的部分,如搜索算法的完成、优化等,我们按照结对编程的原则,一人写一人检查,确保没有BUG以及算法的正确性。

技术图片

13、看教科书和其它参考书,网站中关于结对编程的章节, 明结对编程的优点和缺点。结对的每一个人的优点和缺点在哪里

正如《构建之法》中所述

既然代码复审能发现这么多问题,有这么好的效 果,如果我们每时每刻都处在代码复审的状态, 那不是很好么?

结对编程是将代码复审发挥到极致后的形式,也就是说结对编程完全继承了代码复审的所有优点:增强了代码的规范性,减少了bug出现的可能性,提高了代码的可维护性等等。在合理的使用方法下,结对编程的效率甚至能超过并行开发的效率,考虑到测试、维护等等问题的时间成本。但是,这样的“极限”形式也有一定的局限,即对于程序中非常简单,逻辑复杂度极地的部分,进行实时的代码复审就有了“杀鸡用牛刀”的意味,在这些部分上,结对编程显然有矫枉过正的嫌疑,其开发效率在程序的简单部分中显然没有并行开发高。

结对中我的优缺点:

优点:承担任务主动,投入时间长、任务完成的较为完整,进行了许多优化。

缺点:与同伴沟通不足。

我的同伴的优缺点:

优点:配合良好,完成任务优秀、负责,测试完整。

缺点:没有及时反馈进度与问题。

14、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(小时) 实际耗时(小时)
Planning 计划 0.5 0.5
· Estimate · 估计这个任务需要多少时间 20.4 55+
Development 开发 18.6 50+
· Analysis · 需求分析 (包括学习新技术) 5 1
· Design Spec · 生成设计文档 0.5 0.2
· Design Review · 设计复审 (和同事审核设计文档) 0.5 0.1
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 0.1 0.1
· Design · 具体设计 0.5 1
· Coding · 具体编码 10 40+
· Code Review · 代码复审 1 1
· Test · 测试(自我测试,修改代码,提交修改) 1 10+
Reporting 报告 1 2
· Test Report · 测试报告 0.2 0.5
· Size Measurement · 计算工作量 0.1 0.1
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 0.5 0.5
合计 21.9 55+

15、松耦合

我们与学号16061093、16061155的队伍进行了松耦合测试,结果正确,对方使用我方Core.dll的运行结果截图如下:
技术图片
在刚开始耦合时出现了问题,因为在对于head的定义中,我们组的逻辑是当head不为0即认为是规定的开头字母,但接口内部没有检查其正确性,而对方认为head不规定时为‘0‘
出现问题后,我们组在接口内部加2上了对于参数的合法性判断,之后没有其他问题。

之后我们还与16061167、16061170组进行了耦合,运行正确,没有问题。

以上是关于软件工程结对编程博客的主要内容,如果未能解决你的问题,请参考以下文章

软件工程网络15结对编程作业

软件工程结对编程博客

软件工程网络15结对编程作业(201521123062)

201521123072 结对编程

关于结对编程的感受

第四次软件工程项目---结对编程