软工_第一次结对编程作业博客

项目 内容
此作业属于北航软件工程课程 班级博客链接
作业要求见右方链接 作业要求
我在这门课程的目标是 培养专业的软件开发能力
这个作业在哪个具体方面帮助我实现目标 对结对编程有了一定的实践经验,对合作开放项目的大致步骤有了一定了解和实践;
本次作业GitHub项目地址 https://github.com/NanonaN/WordChain

1、github 项目地址:见上表

2、PSP表格

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

3、浅谈Information Hiding, Interface Design, Loose Coupling

3.1、Information Hiding在程序中的体现

​ Information Hiding即信息隐藏,很容易理解,就是把一个类中重要铭感的信息隐藏在自身内部,类之外的代码无法获取并更改这些信息,以此来提高程序的安全性;

​ 在此次的项目实现中,因为程序实现难度并不大(若不是对性能有极致的要求),所以计算模块仅一个类——Core,该类中有八个属性(下一部分中有介绍),分别存储计算单词链相应的信息,这些信息对于不同的功能需求是不同的,但是若是直接生成get,set方法甚至将属性改为public属性,这将大大降低程序的安全性,也是破坏了Information Hiding的原则;

​ 所以对于每次调用程序的信息,我们采用了先存储在构造Core实例的方法,比如GUI界面下,用户可以实时改变功能选项,这时程序所做的事只是存储选项的改变,当用户点击“开始计算”时,才构造core实例(传入之前存储的信息)进行计算 ;

3.2、Interface Design和Loose Coupling在程序中的体现

​ Interface Design和Loose Coupling在我看来是结合一体的吧,接口设计实际上就是实现松耦合(个人观点);

之前看过一个博客中的例子简明的指出了两者的好处:

在做一个团队项目时,有人可能负责领域模型m(model),有人负责前台v(view),有人负责业务逻辑c(controller),在这种mvc的设计模式模式驱动下,我们首先想到的就是:定义一套公共的接口,方便各个模块之间的通讯。面向接口的程序设计思想是一种有效的手段。比如一个做业务逻辑的team,他们并不清楚业务数据的curd实现,但他们只要通过面向于数据组提供的一整套接口进行编程即可,同时,数据组的开发可以并行进行,这样,不需要等待一个模块的完成就可以预先“使用"这个模块,极大的提高了团队的效率。

​ 在该程序实现中,程序的主体就分为三个部分,第一是处理输入,第二是进行计算,第三是输出(这里也包括GUI的调用);

  • 处理输入部分接收字符串或者功能选项(GUI中如此),然后返回一个存储所有单词的List;
  • 计算部分则是传入一个单词序列(即list),返回最长单词链;
  • 输出部分即获取最长单词链进行输出,当然在GUI中还需要判断输出的方式;

三个部分之间的连接简单明了,相互之间没有什么冲突,故不需要等一个模块完成在进行下一个模块,而是并行编码;

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

程序计算模块就一个类:core类;

  • 类属性如图:

其中,charMode和wordMode分别代表(-c和-w选项),head和tail代表限定的首尾单词,input代表输入的字符串,inputIsFile代表文本输入还是文件输入(GUI中需要此选项);

  • 类方法如图:

  • 计算单词链的核心流程为:

​ main函数构造core类实例,构造函数中通过调用ParseCommandLineArguments()函数将命令函输出的字符串分割并提取出其中的信息;

​ 然后交由GenerateChain()函数进行计算,ReadContentFromFile()为读取字符串,DivideWord()函数为将字符串分割为单词,FindLongestChain()函数为计算最长单词链。

  • 其中核心算法函数为FindLongestChain函数,该函数通过递归的方法求得其中的单词链并取其中(单词或者字母)最长的一条单词链返回;

5、UML图

6、计算模块的性能改进

​ 这一部分花费了我们大约三个小时吧,该问题可以等价为寻找有向有环图中最长路径的问题,是一个NPC问题,所以对于允许单词环的情况下,算法复杂度无法在数量级层面进行优化,故对一些细节进行优化,比如访问寻找单词的效率,无-r选项时及时退出;核心算法上,没有做根本上的优化,采取BFS算法遍历找出最长的单词链。

​ VS性能分析工具分析结果如图:

7、浅谈Design by Contract, Code Contract

按合同设计类似于之前OO课中编写JSF的规格化设计思路,我的理解实际上就是上文中接口设计的细化和量化,即对于整个程序编码,细化到每一个函数,都进行量化的规范设计,如链接中的前置条件、后置条件等规范;

这样做的好处是:

  • 比较严格的遵循了先设计在编写的原则,编写代码时井然有序不容易出错;
  • 各个方法功能与属性非常明确,耦合度低;
  • 有利于测试和发现程序BUG;
  • 这样的合同设计算是设计文档的进一步细化,更有利于团队合作开发;

缺点是:

  • 略显得固化死板,若是整个程序即每一个方法都遵循这样的设计思路,可能对于一些及其简单的函数反而增加了无用的工作量。

8、单元测试

8.1、构造测试数据的思路

​ 核心思路就是尽可能覆盖所有的情况,即包括正确和错误的输入样例,正确的输入样例分别测试该程序支持的各种功能,错误的输入样例则测试程序在遇到不合法输入时能否正确报错和退出;

8.2、单元测试部分代码
  • 单词链输入文本样例:
   private readonly List<string> _wordList1 = new List<string>()
  {
      "Element",
      "Heaven",
      "Table",
      "Teach",
      "Talk"
  };

  private readonly List<string> _wordChain1WithR = new List<string>()
  {
      "table",
      "element",
      "teach",
      "heaven"
  };

  private readonly List<string> _wordList2 = new List<string>()
  {
      "Algebra",
      "Apple",
      "Zoo",
      "Elephant",
      "Under",
      "Fox",
      "Dog",
      "Moon",
      "Leaf",
      "Trick",
      "Pseudopseudohypoparathyroidism"
  };

  private readonly List<string> _wordChain2 = new List<string>()
  {
      "algebra",
      "apple",
      "elephant",
      "trick"
  };

  private readonly List<string> _wordChain2WithC = new List<string>()
  {
      "pseudopseudohypoparathyroidism",
      "moon"
  };

  private readonly List<string> _wordChain2WithHe = new List<string>()
  {
      "elephant",
      "trick"
  };

  private readonly List<string> _wordChain2WithTt = new List<string>()
  {
      "algebra",
      "apple",
      "elephant"
  };
  • 方法测试:
 public unsafe void gen_chain_wordTest()
    {
        TestGenChain(_wordList1, _wordChain1WithR, enableLoop: true);
        TestGenChain(_wordList2, _wordChain2);
        TestGenChain(_wordList2, _wordChain2WithHe, head: 'e');
        TestGenChain(_wordList2, _wordChain2WithTt, tail: 't');
    }
    [TestMethod()]
    public void gen_chain_charTest()
    {
        TestGenChain(_wordList2, _wordChain2WithC, mode: 1);
    }
    private unsafe void TestGenChain(List<string> wordList, List<string> expectedChain, int mode = 0, char head = '', char tail = '', bool enableLoop = false)
    {
        var resultArray = CreateStringArray(wordList.Count, 100);
        var wordListArray = ConvertToArray(wordList);
        var len = 0;
        switch (mode)
        {
            case 0:
                len = Core.gen_chain_word(wordListArray, wordListArray.Length, resultArray, head, tail, enableLoop);
                break;
            case 1:
                len = Core.gen_chain_char(wordListArray, wordListArray.Length, resultArray, head, tail, enableLoop);
                break;
            default:
                Assert.Fail();
                break;
        }
        var result = ConvertToList(resultArray, len);
        CollectionAssert.AreEqual(expectedChain, result);
    }
     [TestMethod()]
        public void ParseCommandLineArgumentsTest()
        {
            TestWrongArgs("-w");
            TestWrongArgs("-c");
            TestWrongArgs("-h");
            TestWrongArgs("-t");
            TestWrongArgs("-");
            TestWrongArgs("-h 1");
            TestWrongArgs("-t 233");
            TestCorrectArgs("-w input.txt");
            TestCorrectArgs("-c input.txt");
            TestCorrectArgs("-w input.txt -h a");
            TestCorrectArgs("-w input.txt -t b");
            TestWrongArgs("abcdefg");
            TestWrongArgs("-w input.txt -h 9");
            TestWrongArgs("-w input.txt -t 0");
            TestWrongArgs("-c input.txt -h 7 -t 1");
            TestWrongArgs("-w input.txt -h 123");
            TestWrongArgs("-c input.txt -t 321");
            TestWrongArgs("-h a");
            TestWrongArgs("-w input.txt -w input.txt");
            TestWrongArgs("-w input.txt -c input.txt");
            TestWrongArgs("-c input.txt -c input.txt");
            TestWrongArgs("-c input.txt -w input.txt");
            TestWrongArgs("-w input.txt -h a -h c");
            TestWrongArgs("-w input.txt -t a -t c");
            TestWrongArgs("-w input.txt -r -r");
            TestCorrectArgs("-w input.txt -r");
        }
  • 附上单元测试覆盖率截图

9、异常处理

程序中的异常分为读入参数时发生的异常和运行发现单词环(没有-r选项)

  • 异常类型图:

  • 对应的程序的中测试样例为:

     public void ParseCommandLineArgumentsTest()
        {
            TestWrongArgs("-w");
            TestWrongArgs("-c");
            TestWrongArgs("-h");
            TestWrongArgs("-t");
            TestWrongArgs("-");
            TestWrongArgs("-h 1");
            TestWrongArgs("-t 233");
            TestWrongArgs("abcdefg");
            TestWrongArgs("-w input.txt -h 9");
            TestWrongArgs("-w input.txt -t 0");
            TestWrongArgs("-c input.txt -h 7 -t 1");
            TestWrongArgs("-w input.txt -h 123");
            TestWrongArgs("-c input.txt -t 321");
            TestWrongArgs("-h a");
            TestWrongArgs("-w input.txt -w input.txt");
            TestWrongArgs("-w input.txt -c input.txt");
            TestWrongArgs("-c input.txt -c input.txt");
            TestWrongArgs("-c input.txt -w input.txt");
            TestWrongArgs("-w input.txt -h a -h c");
            TestWrongArgs("-w input.txt -t a -t c");
            TestWrongArgs("-w input.txt -r -r");
        }
    

    错误对应场景和报错信息将在之后GUI模块中进一步介绍;

10、界面模块设计及其与计算模块的对接

  • GUI界面总体设计如图:

注:计算默认为计算单词最多和直接输入单词文本,可改变选项为文件输入,文件输入分为输入文件路径或者点击“选择单词文件”按钮选择指定文件;

结果默认为输出到当前界面,如:

亦可选择导出到指定文件,导出完成会有弹窗提示:

若发生异常,会弹窗提示错误信息,如:

下面简单介绍实现过程:

​ 在显示的GUI界面中,每一个选项框都是一个窗体控件,由checkBox控件实现可选可不选的功能选项(比如允许单词环选项),由radioButton实现多选一的必选项(这里需要用pannel容器框起来);由Button实现按钮点击(如开始计算按钮);由textBox实现文本输入;由label实现显示功能;通过MessageBox.show函数实现弹窗提示;

​ 编写各控件的事件代码,除“开始计算单词链”按钮以外,其余事件主要功能为将用户选择的信息(做相应处理)存储起来,当用户点击“开始计算单词链”按钮后,程序将GUI中的信息及之前存储的信息传入构造函数Core()以此构造一个示例,然后调用core.GenerateChain()方法计算出最长单词链,最后进行输出。

11、结对讨论照片

12、结对编程优缺点

12.1、结对编程的优点
  • 可以在彼此合作中学习对方的优点并及时发现自己的问题;
  • 可以锻炼与他人合作开发工程项目的能力;
  • 通过明确的分工可以锻炼模块化能力;
  • 可以增进与同学的友谊;
12.2、结对编程的缺点
  • 一部分时间将花费在讨论和安排分工上;
  • 工作中可能有可能出现重合或者冲突的情况(当然这其实是自己的问题,需要实践经验吧);
12.3、结对中每个人的优缺点
  • 我的缺点:

    在系统的工程开发中存在部分短板,比如写单元测试不够熟练;

    编码规范还有待提高。

    至于优点…还是由队友评价比较客观吧hh;

  • 队友的优点:

    恩…有点多…受益良多,一一道来:

    编码能力比较强,代码规范且效率很高;

    工程能力很强,各方面能力全面;

    学习能力强且工作效率高;

    至于缺点…没发现…(是实际感觉)。

13、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 30 30
Development 开发
· Analysis · 需求分析 (包括学习新技术) 120 100
· Design Spec · 生成设计文档 30 30
· Design Review · 设计复审 (和同事审核设计文档) 30 30
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 30
· Design · 具体设计 100 60
· Coding · 具体编码 420 480
· Code Review · 代码复审 90 120
· Test · 测试(自我测试,修改代码,提交修改) 240 180
Reporting 报告
· Test Report · 测试报告 50 40
· Size Measurement · 计算工作量 30 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 90 60
合计 1260 1250
原文地址:https://www.cnblogs.com/zgj982393649/p/10534160.html