集大软件工程15级结对编程week1

0. 团队成员

姓名 学号 博客园首页 码云主页
孙志威 20152112307 Agt Eurekaaa
孙慧君 201521123098 野原泽君 野原泽君

1. 需求分析:针对现有代码的改进分析,新开发功能的分析。

1. 题目需求:

  • 原题要求:

    • 写一个能自动生成小学四则运算题目的命令行 “软件”:

      • 除了整数以外,还要支持真分数的四则运算,真分数的运算,例如:1/6 + 1/8 = 7/24;
      • 运算符为 +, −, ×, ÷;
      • 并且要求能处理用户的输入,并判断对错,打分统计正确率
      • 要求能处理用户输入的真分数, 如 1/2, 5/12 等;
      • 使用 -n 参数控制生成题目的个数,例如执行下面命令将生成10个题目 :Myapp.exe -n 10。
    • 把这个程序做成GUI:
      • 记录用户的对错总数,程序退出再启动的时候,能把以前的对错数量保存并在此基础上增量计算;
      • 有计时功能,能显示用户开始答题后的消耗时间;
      • 界面支持中文简体/中文繁体/英语,用户可以选择一种。
    • 两个任务:
      • 把计算模块提取出来,单独创建一个类。
      • 针对提取出来的计算类的接口函数做单元测试。
  • 功能改进与扩展:
    • 增加括号操作符;
    • 减少重复题目;
    • 增加一个运算符(乘方):用符号 ^ 表示乘方,例如:4 ^ 2=16;
    • 回归测试:在开发新功能时避免损坏旧的功能,以确保新的功能不与原有功能冲突,在确认修改的功能正确之后再嵌入代码;
    • 效能分析。

2. 代码分析

  • 获取代码

    • 引用老师提供的代码为: 码云地址
    • Clone该项目

  • 分析结构
    • 类之间关系

      比较码云的链接中给出的代码只有两个类:

      TrivialCalculatorTrivialCalculatorTest

    • 类图

    • 逻辑分析:该项目一共只有4个函数,分别为

      • gcd 求出最大公约数
      • cal 实现两个数字的加减乘除计算
      • zfcal 实现两个分数的加减乘除
      • calinput 处理输入的字符串

        基本上都是逻辑清晰的分支结构,所以没有“逻辑泥球”
    • 测试用例

      测试用例完整覆盖了所有函数的各个分支

    • 分析现有代码需要改进的地方:

      1. 没有实现真分数和整数的同时存在:使用随机实现真分数和整数的可能同时出现。
      2. 没有实现表达式中数字个数的随机:利用宏定义统一设置表达式数字个数的最多个数,从2到设置的最多位数随机取一个数,决定表达式的数字长度。

        【表达式的生成利用循环“随机符号+随机数字”,最终去除第一位符号来得到】
      3. 码云里的代码不能运行;
      4. GUI界面有待改善;
      5. 代码可读性不强,缺少相应的注释。
      • 新开功能的分析:
      1. 添加括号:

        • 表达式中数字个数大于2时,可随机添加括号;
        • 左括号的添加位置可以为表达式前,或各个运算符的后面(除了最后一个运算符),右括号的添加位置可以为表达式的最后,或与左括号相隔一个符号开始到表达式最后的每个运算符的左边。
      2. 减少重复题目:
        • 将表达式分解为多个“符号+数字”组合的数组,第一位数字前符号为“+”;
        • 对组合数组进行排序,并放入二叉树,左子节点为该父节点组合在数组中的下一位,右子节点为在数组中拥有相同上几位组合的同位的不同组合;
        • 每生成一个表达式就v在该二叉树内检索插入,已存在则剔除重新生成。(直说好难理解,下面会用图解释)
      3. 添加乘方:
        • 为了减小使用者和计算机内部的计算量,将最多只添加一个二次幂或三次幂;
        • 遍历表达式字符串,得到运算符的位置,并在这些位置和表达式的末尾中随机抽取一个位置,在其右边添加"^2"或 "^3"。

2. 程序设计:针对新开发功能做设计,建议使用思维导图。

  • 程序总体设计

  • 所使用的二叉树算法介绍

  • 程序主要部分介绍(C++,C++/QT,Python,C#)

    • 生成表达式(C++):采用随机产生字符拼接成字符串的方式生成
    • 加工表达式(C++)
      • 对生成的表达式进行合法性检查
      • 在合理位置添加括号、 幂运算等操作
      • 将表达式分割为以“符号+数字”的更小元素
      • 将表达式以“元素”为单元进行统一排序
    • 避免表达式广义重复(C++)
      • 构建了一个二叉树结构用来解析排序后的表达式是否重复
      • 二叉树将添加并维护存在过的所有表达式
    • 解析表达式(python)
      • 使用C++调用python接口
      • 使用python的eval函数动态解析表达式

        所以就不用正常的中缀-后缀 + 栈解析的做法了(py大法好)
    • 前端显示(C++/QT)
      • 用QT简单的做了个显示表达式、能检测输入框答案是否正确的GroupBox
      • 然后运行时在MainWindows里new几个Box就酱
    • 单元测试部分(C#)
      • 单元测试用的是MS测试框架
      • 测试项目不需要与正常代码放在同一项目下,‘引用’功能和测试资源管理器都十分好用
  • 各新增功能演示

    • 支持括号

    • 表达式不重复

      表达式重复的几率为0,具体算法见二叉树算法处

    • 乘方运算

  • 测试分析结果

    • 回归测试:

      测试方法大纲



      部分测试用例

    • 代码覆盖率测试

    • 效能分析

    • 消耗最大模块

      • 发现占用绝大部分时间的都不是用户的函数

        基本都是系统调用
      • 但是在用户代码中有一个 Evaluator::initPython方法被调用了124次,实际上这里是可以优化的
      • 优化建议:将Evaluator的一些需要全局经常调用的方法设置为static的类方法,一旦init后就不需要再调用了,可以大大减少该函数的调用次数

3. 代码展示:展示每个功能的核心代码。

  • 字符串生成类

    class ExpGenerator
    {
    public:
    ExpGenerator();
    ~ExpGenerator();
    int getGcd(int x, int y);
    string generateRawExp();
    vector<int> findOperator(string exp);
    string addBracket(string exp);
    string addPower(string exp);
    vector<string> apartExp(string exp);
    vector<string> sortExp(vector<string> items);
    vector<string> generate(int amount,
    vector<double>&results);
    };
    • 生成字符串
      string ExpGenerator::generateRawExp()
      {
      //产生初始表达式
      string exp = "";
      char ch[5] = { '+','-','*','~','^' };
      int maxNum; if (NUMBER < 2)
      {
      //表达式位数小于2则报错
      exp = "ERROR!";
      return exp;
      }
      else if (NUMBER == 2)
      maxNum = 2;
      else
      maxNum = rand() % NUMBER + 2; for (int i = 0; i < maxNum; i++)
      {
      //以(符号+数字)*maxNum形成第一位为符号的“伪表达式” //随机生成符号
      char c = ch[rand() % 4];
      exp.push_back(c); //随机生成数字
      stringstream ss;
      string s;
      int flag = rand() % 10;
      if (flag != 0)
      //生成整数(90%)
      ss << rand() % MAX + 1;
      else
      {
      //生成分数(10%)
      int mumNum = rand() % MAX + 2;
      int sonNum = rand() % (mumNum - 1) + 1;
      sonNum /= getGcd(mumNum, sonNum);
      mumNum /= getGcd(mumNum, sonNum);
      ss << sonNum << "/" << mumNum;
      }
      ss >> s;
      exp += s;
      } //截去第一位符号,生成初始表达式
      exp = exp.substr(1, exp.length());
      ////cout << exp << endl;
      return exp;
      }
    • 拆分字符串并排序
      vector<string>ExpGenerator::apartExp(string exp)
      {
      //遍历初始表达式,以“符号+数字”的结构分割返回
      vector<string> items;
      int begin = 0;
      int end;
      unsigned int i;
      string str;
      exp = "+" + exp;
      char c;
      for (i = 0; i < exp.length(); i++)
      {
      c = exp.at(i);
      if ((c == '+' || c == '-' || c == '*' || c == '~')
      && (i != 0))
      {
      end = i;
      str = exp.substr(begin, end - begin);
      items.push_back(str);
      begin = i;
      }
      }
      str = exp.substr(begin, exp.length() - begin);
      items.push_back(str); return items;
      } vector<string> ExpGenerator::sortExp(vector<string> items)
      {
      //将分割后的表达式项进行排序,用于二叉树的形成
      sort(items.begin(), items.end());
      return items;
      }
  • 二叉树相关

    class UniqueBinaryTree
    {
    public:
    UniqueBinaryTree();
    ~UniqueBinaryTree(); // check if the tree contains the expression
    // if contains return true
    // else return false
    // and add the unique new nodes to the tree
    bool add(vector<string>expressionVec, double answer);
    // clear the tree but not destoryed it
    void clear();
    // get amount of the nodes
    int nodeAmount();
    // get amount of all the resultsNodes
    int resultsAmount();
    // amount of both result node and the mormal node
    int amount();
    // Debug: show contents to the console
    void showLayer();
    private:
    // add the result node to the last node (levelNode)
    // return true if new result added
    // which represent that the new expression
    // has the same expression(without brackets) but
    // different answers
    bool mountAnswerToNode(PNode levelNode, double answer);
    int countAnswerNode(PNode levelNode);
    void clearAnswerNode(PNode levelNode); Node * head = NULL;
    };
    • add函数
      bool UniqueBinaryTree::add(vector<string> expressionVec , double answer)
      {
      // treat empty string as true/contains and ignore it
      if (expressionVec.empty())
      return true; int length = expressionVec.size();
      PNode curNode = this->head->right; // compare each node from the first
      PNode fatherNode = this->head;
      string currentStr; int index = 0; // the current index of stringVector
      bool isContained = false;
      while (curNode != NULL)
      { currentStr = expressionVec.at(index);
      // record the father node to support the insertion later
      fatherNode = curNode;
      if (curNode->data == currentStr)
      {
      curNode = curNode->left;
      // jump to the next string
      index++;
      // break when the whole expression finish
      if (index >= length)
      break;
      }
      else
      {
      curNode = curNode->right;
      }
      }
      //if not then it's an new expression
      // mount it to the tree
      // begin from the last part they're same
      // the current node is the last node matches part of the new expression
      bool first = true;
      // if it reached the end of the expression in the last loop
      // will skip it
      while (index < length)
      {
      string curStr = expressionVec.at(index); PNode newNode = new Node();
      if (first)
      {
      // mount the first newNode as the rightChild
      fatherNode->right = newNode;
      first = false;
      }
      else
      {
      fatherNode->left = newNode;
      } newNode->data = curStr;
      newNode->left = NULL;
      newNode->right = NULL;
      newNode->answer = NULL;
      fatherNode = newNode;
      index++;
      }
      // now the new expression has been added to the tree // add the answer of the expression as well
      // and check if the answer is inside the tree
      // if it's inside it return false as well
      PNode lastNode = fatherNode;
      bool newAnswerAdded = this->mountAnswerToNode(lastNode,answer);
      // new answer node is unique , and been added to the tree
      return !newAnswerAdded;
      }
  • 具体代码见码云Agt-TrivialCalculator


4. 程序运行:程序运行及每个功能的使用截图。

  • 以下图片分别演示了

    1. 随机生成字符串
    2. 表达式的加减乘除
    3. 表达式的括号运算符以及幂运算
    4. 用户的输入和结果正确反馈
    5. 表达式的不重复
    6. 表达式的正确解析
    7. etc...


5. 结对编程记录

  • 码云记录

  • 编码规范文档

  • PSP表格

PSP2.1 个人开发流程 预估耗费时间(分钟) 实际耗费时间(分钟)
Planning 计划 20 30
Estimate 明确需求和其他相关因素,估计每个阶段的时间成本 10 30
Development 开发 413 940
Analysis 需求分析 (包括学习新技术) 20 60
Design Spec 生成设计文档 60 60
Design Review 设计复审 60 30
Coding Standard 代码规范 3 10
Design 具体设计 30 60
Coding 具体编码 120 400
Code Review 代码复审 60 200
Test 测试(自我测试,修改代码,提交修改 60 120
Reporting 报告 60 120
· 测试报告 10 20
· 计算工作量 30 30
· 并提出过程改进计划 60 60

6. 小结感受:结对编程真的能够带来1+1>2的效果吗?通过这次结对编程,请谈谈你的感受和体会。

我认为1+2是大于2的,结对编程还是有很多收获的。

例如在考虑问题的解决方案的时候,一个人自己思考的时候很容易被自己过去的经验所影响,容易被自己的思维定势束缚住。而在结对编程的时候,由于一边写一边审,互相之间会交流为什么用这种算法,算法有什么优缺点等,能更好地找到更佳的解决方案,所以结对编程也还是有好处的。

队友的感想见博客野原泽君

集大软件工程15级结对编程week1的更多相关文章

  1. 集大软件工程15级个人作业Week1

    集大软件工程15级个人作业Week1 孙志威 201521123077 博客园主页 码云地址 阅读参考材料,并回答下面几个问题 (1)回想一下你初入大学时对网络工程专业的畅想 当初你是如何做出选择网络 ...

  2. 集大软件工程15级个人作业Week2

    集大软件工程15级个人作业Week2 快速通读教材<构建之法>,并参照提问模板,提出5个问题. 在每个问题后面,请说明哪一章节的什么内容引起了你的提问,提供一些上下文 列出一些事例或资料, ...

  3. 现代软件工程HW2:结对编程-生成五则运算式-Core10组 [PB16110698+PB16120162]

    作业具体要求点 这里 Core组要求: 1.Calc() 这个Calc 函数接受字符串的输入(字符串里就是算术表达式,例如 “5*3.5”,“7/8 - 3/8 ”,“3 + 90 * 0.3”等等) ...

  4. 11061160_11061151_Pair Project: Elevator Scheduler软件工程结对编程作业总结

    软件工程结对编程作业总结 11061160  顾泽鹏 11061151  庞梦劼 一.关于结对编程 这次的软工任务既不是单打独斗的个人任务,也不是集思广益的团队项目,而是人数为两人的结对编程.两个人合 ...

  5. [2019BUAA软件工程]结对编程感想

    结对编程感想 写在前面   本博客为笔者在完成软件工程结对编程任务后对于编程过程.最终得分的一些感想与经验分享.此外笔者还对于本课程的结对编程部分提出了一些建议. Tips Link 作业要求博客 2 ...

  6. 关于软件工程结对编程作业 PairProject : Elevator Scheduler(电梯调度算法的实现与测试)的总结

    1)结对编程队友 1106xxxx 张扬 1106xxxx 杨军 其中,此项目的编程实现主要由前者完成. 2)关于结对编程 结对编程的优点: 最直接的一点:在结对编程中,由于有另一个人在你身边和你配合 ...

  7. 2017-2018-2 165X 『Java程序设计』课程 结对编程练习_四则运算

    2017-2018-2 165X 『Java程序设计』课程 结对编程练习_四则运算 经过第一阶段的学习,同学们已经熟悉了这门语言基本的用法.在一次又一次对着电脑编写并提交代码,进行练习的时候,有没有觉 ...

  8. 软件工程启程篇章:结对编程和进阶四则运算(197 & 199)

    0x01 :序言:无关的事 I wrote a sign called "Dead End" in front of myself, but love crossed it wit ...

  9. BUAA软件工程_结对编程

    1.写在前面 项目 内容 所属课程 2020春季计算机学院软件工程(罗杰 任健) (北航) 作业要求 结对项目作业 课程目标 培养软件开发能力 本作业对实现目标的具体作用 培养结对编程开发项目的能力 ...

随机推荐

  1. 关于svn上传.classpath等问题

    1. svn版本:客户端版本与服务器版本 要尽量适配. 2. svn管理项目:有人将.classpath, .project, .mymetadata, .myumldata等文件也纳入到版本控制,如 ...

  2. oracle USING 用法

    提问 using(xx)中可以接多个列吗? using(xx)中的列可以接表名或别名吗? 在使用using的语句中,select * 可以使用吗? 如果表有别名t,select t.* from ta ...

  3. Linux 小知识翻译 - 「如何成为 Linux 内核开发者」

    新年的开始,聊聊「怎么做才能成为Linux内核开发者」. Linux内核的开发都是由志愿开发者们完成的.他们并不属于某些特定的企业. 因此,你也有参加Linux内核开发的资格.不用说,卓越的编码技术以 ...

  4. Java strictfp

    strictfp关键字 用于强制Java中的浮点计算(float或double)的精度符合IEEE 754标准. 不使用strictfp:浮点精度取决于目标平台的硬件,即CPU的浮点处理能力. 使用s ...

  5. 阿里云短信服务调用例子-Python

    阿里云短信服务调用例子 阿里云官方文档https://helpcdn.aliyun.com/document_detail/101893.html 首先需要安装阿里云PythonSDK(下面是pyth ...

  6. Java多线程(三)如何创建线程

    点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...

  7. 推酷文章中编写js组件系列文章整理

    一步一步实现JS拖拽插件 http://www.tuicool.com/articles/RBbmMjY JS组件系列——基于Bootstrap Ace模板的菜单和Tab页效果分享 http://ww ...

  8. X的平方根的golang实现

    实现 int sqrt(int x) 函数. 计算并返回 x 的平方根,其中 x 是非负整数. 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去. 输入: 输出: 输入: 输出: 说明: 的 ...

  9. 【技术与商业案例解读笔记】095:Google大数据三驾马车笔记

     1.谷歌三驾马车地位 [关键词]开启时代,指明方向 聊起大数据,我们通常言必称谷歌,谷歌有“三驾马车”:谷歌文件系统(GFS).MapReduce和BigTable.谷歌的“三驾马车”开启了大数据时 ...

  10. vue中父组件调用子组件函数

    用法: 子组件上定义ref="refName",  父组件的方法中用 this.$refs.refName.method 去调用子组件方法 详解: 父组件里面调用子组件的函数,父组 ...