• 结对项目博客


    项目 内容
    这个作业属于哪个课程 2020计算机学院软件工程(罗杰 任健)
    这个作业的要求在哪里 结对项目作业
    教学班级 006
    项目地址 https://github.com/CrapbagMo/PairProgramIntersect

    1. PSP表格

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

    2. Information Hiding,Interface Design,Loose Coupling

    • Information Hiding 是指信息隐藏原则,在1972年被 David Parnas 提出,他指出:代码模块应该采用定义良好的接口来封装,这些模块的内部结构应该是程序员的私有财产,外部是不可见的。在结对编程中,我们对于信息隐藏的应用是:
      • 在多层设计中的层与层之间加入接口层
      • 所有类与类之间都通过接口类访问
      • 类的所有数据成员都是private,所有访问都是通过访问函数实现的
    • Interface Design 是指接口设计,接口被定义为职责(或角色、能力),接口决定了一种类能够做什么,赋予了他在某种情况下的权力和义务。在结对编程中,我们专注于定义行为,把某个具体功能提取出来,先把这个功能弄成策略,然后各自具体用类去继承这个接口。
    • Loose Coupling 是指松耦合,是指减少一个代码单元与其他代码单元的配合关系。最理想、最松散的耦合,是一个单元无需其他代码单元的配合可以单独完成它的功能。松耦合的目标是最小化依赖。松耦合这个概念主要用来处理可伸缩性、灵活性和容错这些需求。在结对编程中,我们通过加入中间层,来减少各个代码单元之间的耦合度。

    3. 计算模块接口的设计与实现

    1. 函数及类:

      • 五个主要类

        点类:class Point

        图形类:class Figure

        线条类:class Line: Figure

        圆形类:class Circle: Figure

        平面容器类:class PlaneContainer

      • 五个主要函数:

        添加图形:int add_Figure(std::string buf)

        初始化容器:void initial_PlaneContainer()

        释放容器:void dispose_PlaneContainer()

        获取交点序列:double* get_IntersectionPoints()

        获取交点数目:int get_NumOfIntersectionPoints()

    2. 主要作用:

      • CircleLine类继承自Figure类,实现std::set<Point>intersect(Figure*)方法。该方法返回两个图形的交点集合。
      • 五个函数都是对外提供的接口函数。init_PlaneContainer、dispose_PlaneContainer采用单例模式用于维护全局静态变量PlaneContainer* pcadd_Figureget_NumOfIntersectionPointsget_IntersectionPoints用于提供添加图形、获取交点序列、交点数目功能。
      • 异常由add_Figure函数捕获并处理,处理完后已错误代码形式返回,具体处理方式本节不做介绍。
    3. 算法关键及独到之处:

      • 首先考虑直线和圆的情况(先不考虑射线和线段):

        按照直线和直线, 直线和圆, 圆和圆在平面上的关系分为下面三种情况考虑:

        直线和直线:

        • 判断直线是否相交: (A_1*B_2-A_2*B_1!=0)则相交.

        • 若相交则求交点: ((frac{B_1*C_2-B_2*C_1}{A_1*B_2-A_2*B_1},frac{A_2*C_1-A_1*C_2}{A_1*B_2-A_2*B_1}))

          直线和圆:

        • 联立直线和圆方程(为了起见简便, 若(B!=0), 化为斜截式再联立), 求得系数(tA), (tB), (tC).

        • 根据(Delta=tB^2-4*tA*tC)判断交点个数

        • (Deltage0) , 根据求根公式求得交点横坐标, 进而求出交点.

          圆和圆:

        • 计算圆心距(dis=sqrt{(x_1-x_2)^2+(y_1-y_2)^2}).

        • 比较圆心距(dis) 和半径和(r_1+r_2), 半径差(|r_1-r_2|) .

        • 若有交点则两圆相减求出相交弦方程, 进而转化为直线和圆得交点.

      • 注意到直线、射线、线段的不同点仅在于其坐标范围不同。因此为Line类引入范围属性((R leq x leq S))即可同时表示三种线,其中射线、直线的无穷端以宏INF表示。求交点时,将LineFigure求交点,求得的交点再判断是否在Line所在范围内即可,若不在范围内则剔除。这就将直线、射线、线段统一了。

    4. UML实体关系图

    uml

    5. 计算模块接口部分的性能改进

    • 改进之前,一分钟之内只能运算两百张图不到,运行一分钟(处理了大约150个图形)后性能探测截图:

    1.png

    可以看到,主要时间都花在了,PlaneContainer.insert方法的set_union函数上。

    2.png

    经查阅资料发现,在set_union函数中,存在了多次set复制,效率极低。于是修改了此处逻辑如下:

    3.png

    • 修改之后,可以在20秒左右完成2000个图形的计算,性能大大提高,与改前可谓“天壤之别”再次执行了性能探查:

    4.png

    5.png

    此时PlaneContainer.insert方法已经不耗费太多时间了。

    6. Design by Contract & Code Contract

    Design by ContractCode Contract 是指契约式设计和代码遵守的契约,是按照某种规定对一些数据等做出约定,如果超出约定,程序将不再运行,例如要求输入的参数必须满足某种条件,否则不会运行。我们在声明一个函数/方法的时候,对函数的输入和输出所具备的性质是有所期望和规定的。有时候这种性质会被我们明确的写出来,有时候会被我们忽略掉。这些期望和规定就是Contract。

    契约编程在面向对象设计课程中有所涉及,第一次接触 JML,我的反应是:这也太傻了,不就是帮程序员把程序用伪代码实现了吗!其实这并不对,契约式编程的重要原则就是推迟对于过程的思考。JML是在代码中增加了一些符号,这些符号只表述一个方法要干什么,并不关心它的实现过程。

    这样的契约编程在当时看来是非常耗时的,我们需要首先在设计好接口的条件下,先对所有的函数仔细思考其“输入” 及 “输出”,再进行具体的编码。

    我们在结对编程中由于时间关系,并没有使用契约式编程的方法。经过个人项目的磨练,core 中的核心代码涉及的函数在我们看来,其输入输出参数的条件都已经非常明了,特意地为了完成契约式编程的任务而改变相对更熟悉的工作方式,在我们看来是不明智的。

    7. 计算模块部分单元测试展示

    代码覆盖率情况:(使用VS2017 Enterprise代码覆盖率工具生成)

    image-20200321234124390

    对项目中的类的单元测试覆盖率为 (93.07\%) ,对类 Circle 的覆盖率为 (100\%), 对类 Line 的覆盖率为 (98.5\%)

    我们设置了针对功能的测试和针对异常的测试。

    针对功能性测试,我们测试了圆、直线、线段和射线的一些函数在正常情况下的功能性,同时也构造了一些特殊情况下的测试用例,比如

    // 射线 圆 内部相交
    PlaneContainer pc;
    pc.insert(new Circle(0, 0, 2));
    pc.insert(new Line(1, 0, 2, 2, RL));
    int count = pc.countIntersectionPoints();
    Assert::AreEqual(count, 1);
    
    // 射线 射线 一个交点
    PlaneContainer pc;
    pc.insert(new Line(0, 0, 1, 1, RL));
    pc.insert(new Line(0, 0, -1, -1, RL));
    int count = pc.countIntersectionPoints();
    Assert::AreEqual(count, 1);
    
    // 精度测试
    PlaneContainer pc;
    pc.insert(new Line(0, -100000, 1, 100000, SL));
    pc.insert(new Line(0, 0, 0, 1, SL));
    pc.insert(new Line(0, -99999, 1, -99999, SL));
    int count = pc.countIntersectionPoints();
    Assert::AreEqual(count, 3);
    

    8. 计算模块部分异常处理说明

    本次结对项目中,我们共设计了五类异常,

    • 输入不满足标准格式

      我们使用正则表达式来识别输入是否满足标准格式,正则表达式如下:

      std::regex segREGEX("S\s+-?\d+\s+-?\d+\s+-?\d+\s+-?\d+\s*\n?");
      std::regex lineREGEX("L\s+-?\d+\s+-?\d+\s+-?\d+\s+-?\d+\s*\n?");
      std::regex rayREGEX("R\s+-?\d+\s+-?\d+\s+-?\d+\s+-?\d+\s*\n?");
      std::regex circleREGEX("C\s+-?\d+\s+-?\d+\s+-?\d+\s*\n?");
      
    • 参数不在标准范围 (-100000, 100000) 内

    • 定义直线的两点重合

    • 圆的半径不大于零

    • 计算中出现无穷交点

      • 直线、线段或射线与直线、线段或射线出现部分或完全重合

      • 两圆完全重合

    对于单元测试,我们对每一类异常场景及正常场景设计了3-4个测试样例,测试代码如下,

    TEST_METHOD(TestMethod1)
    {	// 格式错误
        int res = add_Figure("acsd");
        Assert::AreEqual(res, -1);
        delete(pc);
    }
    
    TEST_METHOD(TestMethod2)
    {	// 格式错误
        int res = add_Figure("C 5 3 -2 1");
        Assert::AreEqual(res, -1);
        delete(pc);
    }
    
    TEST_METHOD(TestMethod3)
    {	// 格式错误
        int res = add_Figure("L -5 3 -2 0 4");
        Assert::AreEqual(res, -1);
        delete(pc);
    }
    
    TEST_METHOD(TestMethod4)
    {	// 格式错误
        int res = add_Figure("R 5 -3 2");
        Assert::AreEqual(res, -1);
        delete(pc);
    }
    
    TEST_METHOD(TestMethod5)
    {	// 格式错误
        int res = add_Figure("c 5 -3 2");
        Assert::AreEqual(res, -1);
        delete(pc);
    }
    
    TEST_METHOD(TestMethod6)
    {	// 格式错误
        int res = add_Figure("S 5 -3 2 0-1
    ");
        Assert::AreEqual(res, -1);
        delete(pc);
    }
    
    TEST_METHOD(TestMethod7)
    {	// 正常
        int res = add_Figure("R 5 -3 2 3");
        Assert::AreEqual(res, 0);
        delete(pc);
    }
    
    TEST_METHOD(TestMethod8)
    {	// 正常
        int res = add_Figure("C 5 -3 3
    ");
        Assert::AreEqual(res, 0);
        delete(pc);
    }
    
    TEST_METHOD(TestMethod10)
    {	// 半径小于0
        int res = add_Figure("C 5 -3 -2 
    ");
        Assert::AreEqual(res, 3);
    }
    
    TEST_METHOD(TestMethod11)
    {	// 半径等于0
        int res = add_Figure("C -5 -3 0
    ");
        Assert::AreEqual(res, 3);
    }
    
    TEST_METHOD(TestMethod12)
    {	// 点超出坐标轴范围
        int res = add_Figure("L 963214 -3 2 3
    ");
        Assert::AreEqual(res, 1);
    }
    
    TEST_METHOD(TestMethod13)
    {	// 点超出坐标轴范围
        int res = add_Figure("R 5 -3 526151 3
    ");
        Assert::AreEqual(res, 1);
    }
    
    TEST_METHOD(TestMethod14)
    {	// 点超出坐标轴范围
        int res = add_Figure("C -3 526151 3
    ");
        Assert::AreEqual(res, 1);
    }
    
    TEST_METHOD(TestMethod15)
    {	// 两点重合
        int res = add_Figure("R 2 -3 2 -3 ");
        Assert::AreEqual(res, 2);
    }
    
    TEST_METHOD(TestMethod16)
    {	// 两点重合
        int res = add_Figure("L 99999 88888 99999 88888 
    ");
        Assert::AreEqual(res, 2);
    }
    
    TEST_METHOD(TestMethod17)
    {	// 两点重合
        int res = add_Figure("S 0 2 0 2 
    ");
        Assert::AreEqual(res, 2);
    }
    
    TEST_METHOD(TestMethod18)
    {	// 无穷交点
        add_Figure("L 2 2 3 3 ");
        int res = add_Figure("L 0 0 -1 -1 
    ");
        Assert::AreEqual(res, 4);
    }
    
    TEST_METHOD(TestMethod19)
    {	// 无穷交点
        add_Figure("S 2 2 4 4 ");
        int res = add_Figure("S 3 3 0 0 
    ");
        Assert::AreEqual(res, 4);
    }
    
    TEST_METHOD(TestMethod20)
    {	// 无穷交点
        add_Figure("R 1 1 4 4 ");
        int res = add_Figure("S 2 2 0 0 
    ");
        Assert::AreEqual(res, 4);
    }
    
    TEST_METHOD(TestMethod21)
    {	// 无穷交点
        add_Figure("R 1 1 1 0 ");
        int res = add_Figure("S 1 0 1 5 
    ");
        Assert::AreEqual(res, 4);
    }
    
    TEST_METHOD(TestMethod22)
    {	// 无穷交点
        add_Figure("C 1 1 1  ");
        int res = add_Figure("C 1 1 1 
    ");
        Assert::AreEqual(res, 4);
    }
    

    9. 界面模块的详细设计过程 & 10. 界面模与计算模块的对接

    • 界面模块是基于.NET 4.7.2 的窗体应用,采用C#语言编写。

    • Program用于定义Main函数外,整个模块一共只有类,下面结合图形、代码介绍该类的组成

    1585101564193

    namespace GUI
    {
        unsafe public partial class MainForm : Form
        {
            //下面为控件类实例,由VS自动生成
            //Panel是主要的控件,用于绘图
            private System.Windows.Forms.Panel panel1;
            //打开输入文件对话框
            private System.Windows.Forms.OpenFileDialog openFileDialog1;
            //文件输入按钮和手工输入按钮
            private System.Windows.Forms.Button inputButton;
            private System.Windows.Forms.Label pathInput;//保存并显示输入文件路径
            private System.Windows.Forms.Button button2;
            //1-6用于获取手工输入图形的数据,7用于显示多行提示信息
            private System.Windows.Forms.TextBox textBox1;
            private System.Windows.Forms.TextBox textBox2;
            private System.Windows.Forms.TextBox textBox3;
            private System.Windows.Forms.TextBox textBox4;
            private System.Windows.Forms.TextBox textBox5;
            private System.Windows.Forms.TextBox textBox6;
            private System.Windows.Forms.TextBox textBox7;
            //以下label用于显示单行信息
            private System.Windows.Forms.Label num;
            private System.Windows.Forms.Label label2;
            private System.Windows.Forms.Label label3;
            private System.Windows.Forms.Label label4;
            private System.Windows.Forms.Label label5;
            private System.Windows.Forms.Label label6;
            private System.Windows.Forms.Label label7;
            private System.Windows.Forms.Label label8;
            private System.Windows.Forms.Label label9;
            
            //下面为用户变量
            //绘图画笔
            private Pen pen;
            //绘图类
            private Graphics g;
            //直线容器,每四个数字代表一条直线
            private LinkedList<int> StraightLines;
            //射线容器,每四个数字代表一条射线
            private LinkedList<int> RayLines;
            //线段容器,每四个数字代表一条线段
            private LinkedList<int> LineSegments;
            //圆形容器,每三个数字代表一个圆形
            private LinkedList<int> Circle;
            
    		//下面的五个函数皆为从core.dll中导入的接口函数
            //添加图形并返回添加结果
            [DllImport("core.dll")]
            private static extern int add_Figure(StringBuilder buf);
            //初始化容器
            [DllImport("core.dll")]
            private static extern void initial_PlaneContainer();
            //摧毁容器
            [DllImport("core.dll")]
            private static extern void dispose_PlaneContainer();
            //获取交点序列
            [DllImport("core.dll")]
            private static extern double* get_IntersectionPoints();
            //获取交点数目
            [DllImport("core.dll")]
            private static extern int get_NumOfIntersectionPoints();
            
           	//下面为成员方法(包括回调函数)
            //构造方法,主要为变量分配。
            public MainForm();
            //窗体打开和关闭的回调,用于显示欢迎和再见提示
            private void MainForm_Load(object sender, EventArgs e);
            private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
            //输入按钮的回调函数,用于处理文件输入
            private void inputButton_Click(object sender, EventArgs e);//文件输入
            private void button2_Click(object sender, EventArgs e)//手工输入
            //画板的重绘回调函数,用于绘制图形
            private void panel1_Paint(object sender, PaintEventArgs e);
            //处理add_Figure函数返回来的错误代码
            private bool processRetVal(int retCode, string line);
            //存储图形,以供绘图函数使用
            private void store(string buf);
            //四个小的绘图函数
            private void drawLine();
            private void drawCircle();
            private void drawRay();
            private void drawLineSegment();
            //坐标系变换函数
            private void change(ref Point p);
            private void change(ref PointF p)
        }
    }
    
    1. 重点介绍以下几个函数的运行逻辑

      • private void inputButton_Click(object sender, EventArgs e);//文件输入

        • 若打开不成功则不进行处理并给出提示。

        1585101935226

        • 否则,按行循环读取文件送string line,调用add_Figure处理,并获取处理结果送int retCode

        • lineretCode交给processRetVal处理。

        • 通过get_NumOfIntersectionPoints更新交点数目并显示

      • private void button2_Click(object sender, EventArgs e);//手工输入

        • 通过6个TextBox输入框获取输入数据。
        • 根据LRSC输入框输入的类型代码,将LRSC和后面的相应输入框中内容组成string line,调用add_Figure处理,并获取处理结果送int retCode
        • lineretCode交给processRetVal处理。
        • 通过get_NumOfIntersectionPoints更新交点数目并显示
      • private bool processRetVal(int retCode, string line);

      • retCode==0,未发生异常,调用storeline存储。

      • 否则,不进行存储并提示相应的错误,提示用户,例如。

      1585102896985

      • private void panel1_Paint(object sender, PaintEventArgs e);
        • 绘制坐标系、刻度等。
        • 调用drawLinedrawCircledrawRaydrawSegment绘制相应图形。
    2. 模块对接

      1. core模块不提供内存管理,由接口函数init_PlaneContainerdispose_PlaneContainer管理。
      2. 核心接口add_Figure供界面模块调用。
        1. 正则匹配,如果格式不符合要求,不进入下一步并返回相应的错误代码。否则予以解析进入下一步。
        2. 根据解析结果,构造相应的Figure并捕获可能的异常,如果构造过程中出现两点重合、半径非正等异常,不进入下一步,并反回相应的错误代码。
        3. 将构造的Figure假如容器pc,并捕获可能的异常,如果出现无穷交点等异常,不进入下一步,并返回相应错误代码。
        4. 正常返回。代码代码0;
    3. 使用图例

      1585103528641

    11. 结对过程描述

    我们在结对过程中使用了 Visual Studio 中的 Live Share 和微信进行交流,截图如下。

    批注 2020-03-18 151434

    批注 2020-03-25 174009

    12. 结对编程的优缺点

    优点:

    • 结对编程中,两个人都必须对代码熟悉,所以两个人都处于对代码不断地处于 “复审‘ 的过程,不断地审核、提高的过程。这样可以提高代码质量。
    • 结对编程可以让程序员更专注于工作,更注重代码质量、代码风格等。

    结对编程的过程也是一个互相督促的过程,每个人的一举一动都在别人的视线之内,所有的想法都要受到对方的评价。由于这种督促的压力,使得程序员更认真地工作。

    • 结对编程使工作更容易、简单,因为一个人工作时遇到困难很容易陷入颓废无力的境况。而结对编程中,往往比较少产生两个人都无力解决的问题,即使遇到了,双方也会互相鼓励,而不是陷入恶性循环。

    缺点:

    • 结对编程中如果双方的水平差不多,就能具有更高的效率。但如果双方的水平差距较大,则会出现强的一方拉着弱的一方走的情形。对于强的一方,可能承包了大部分工作,既累又没办法保障代码的质量;;而对于弱的一方,没有参与感,对项目一知半解,更产生了挫败感。在这种情况下,双方的交流也不在同一个层次上,结对编程不如独自编程。

    结对伙伴的优点:

    • 有责任心
    • 自学能力强
    • 思路清晰

    结对伙伴的缺点:

    • 有点贪玩,使项目完成时间略微滞后

    我的优点:

    • 有责任心
    • 认真
    • 经常主动交流

    我的缺点:

    • 没有脑子,做不了设计,只能跟着队友走
  • 相关阅读:
    测试策略如何制定
    python atexit模块和register函数
    使用Redis实现异步消息队列
    python 处理中文 读取数据库输出全是问号
    TCP和UDP的区别和优缺点
    怎样ping网络
    ImportError: libpng12.so.0: cannot open shared object file: No such file or directory
    tensorrtx/retinaface/calibrator.cpp:4:31: 致命错误:opencv2/dnn/dnn.hpp:没有那个文件或目录
    编译tensorrtx/retinaface遇到报错/usr/local/cuda/include/vector_types.h(421): error: identifier "constexpr" is undefined
    RetinaFace.cpp:112:37: 错误:‘std::chrono’尚未声明
  • 原文地址:https://www.cnblogs.com/zkksx/p/12560849.html
Copyright © 2020-2023  润新知