BUAA软工——结对项目作业

项目 内容
这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) (北京航空航天大学 - 计算机学院)
这个作业的要求在哪里 结对项目作业
我的教学班级 005
这个项目的GitHub地址 https://github.com/Eadral/SE_Pair_Project/tree/master/src
PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 20 20
Development 开发
· Analysis · 需求分析 (包括学习新技术) 90 90
· Design Spec · 生成设计文档 30 30
· Design Review · 设计复审 (和同事审核设计文档) 20 20
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 10
· Design · 具体设计 60 40
· Coding · 具体编码 240 210
· Code Review · 代码复审 60 40
· Test · 测试(自我测试,修改代码,提交修改) 120 150
Reporting 报告
· Test Report · 测试报告 50 50
· Size Measurement · 计算工作量 60 60
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
合计 840 780
看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。(5')

设计时,由于松耦合的要求,Core和GUI的功能必须充分地区分开来。所有的计算工作由Core来完成,图形绘制工作则全部交给GUI。

在设计接口时,我们首先从前端出发,考虑GUI会需要使用哪些接口,然后去实现后端新增功能,并对各种可能出现的异常进行定义。定义好前后端所需要的接口后,前端和后端的工作基本上是各自开展的,但在实现的过程中,均考虑到项目后期的对接与松耦合的问题,将函数的功能做出层次性比较强的划分,避免代码的过度耦合。后端实现后,前端选取和组合后端的一些函数进行对接,从结果来看,对接起来还是非常方便的。

计算模块接口的设计与实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。(7')

这次计算模块的实现,基本延续了之前的计算方法,对于新增的射线和线段,首先当做直线计算与其他几何对象的交点,然后通过判断得到的交点是否在射线(线段)上,来判断这个交点是否确实存在,若存在,则加入交点集合中;若不存在,则将其丢弃。这样,只需将不同的几何对象分类保存,然后两两计算交点(也包括同种几何对象之间的交点)。这样的好处是实现起来难度较低,同时能够比较好地利用个人项目作业代码所做的优化,例如先使用vector保存交点后进行去重,根据规定的最大交点数值缩减vector规模等。

接口设计时,考虑到GUI中需要的添加与删除几何对象的功能,故在将CLI的文件添加几何对象和计算交点两个功能单独封装后,新增添加和删除各类几何对象的接口,与GUI进行对接。

具体的函数与接口设计可以参看下面的类图和接口定义。

阅读有关 UML 的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。画出 UML 图显示计算模块部分各个实体之间的关系(画一个图即可)。(2’)

计算模块采用单例模式实现,只定义了一个Solver类,读取输入时,通过建立一个Solver实例进行求解。

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

性能改进上,本次作业中只进行了功能的扩展,并未在上次作业的基础上使用能够改进性能的算法。在对这次作业进行设计时,结对伙伴提出采用扫描算法计算线段之间交点的方法,可以将计算线段之间交点的时间复杂度由\(O(n^{2})\)降低到\(O(nlogn)\),但该方法只能应用于线段,性能提升比较有限,故并未做更多优化,性能上与第一次作业处在同一水平。

程序中消耗最大的函数是LineLIneIntersect,不过这是因为测试用输入全为直线有关。

看 Design by Contract,Code Contract 的内容:http://en.wikipedia.org/wiki/Design_by_contract http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx 描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。(5')

Design by Contract(契约式设计),主要目的是希望程序员能够在设计程序时明确地规定一个模块单元在调用某个操作前后应当属于何种状态。Code Contract是开源的.NET的Contracts。去年OO课上学过的JML也与此类似。参考知乎-怎样解释 Design by Contract (契约式设计)?

这个做法的优点在于,通过语言级别的前置后置条件和不变式的定义,规定每个模块应该完成的工作,从而避免出现bug。缺点在于,一方面定义这些条件和不变式可能需要花费很多精力,这些精力可能并不少于码代码或者单元测试;另一方面,契约式设计所能定义的东西是具有局限性的,比如无法定义程序性能上的要求。

在结对作业中,我更多是模仿之前的项目中已有代码,仿照已有代码的语义和功能设定书写我自己的代码,比如在同学的LineLineIntersect函数的基础上,仿写出LineSectionIntersect, RayRayIntersection等函数。这些函数的语义比较近似,相当于无形中的语义约束,但各自的实现方式又有所区别,特别是在交点的判定和重合异常的判断上。例如,直线与线段/射线的重合判断比较简单,只需判断两个几何对象所在直线是否重合即可;而线段之间或线段与射线之间的重合判断则相对复杂,在共线的情况下,有0交点/1交点(端点重合)/无限交点的情况,需要根据坐标值进行具体的判断与处理。

计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。(6')

计算模块部分异常处理说明。在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。(5')

异常设计如下表所示:

异常代码 异常名 异常说明
E100 WrongFormatOfN N值格式错误(第一行有且仅有一个整数,否则即报错)
E101 InvalidValueOfN N值非法(N<1时报错)
E200 WrongFormatOfObjectIdentifier 几何对象标识符格式错误(无标识符,或包含非法字符/字符串,大小写敏感)
E300 WrongFormatOfObjectDescription 几何对象描述错误(输入格式错误,包括行内输入过多/过少,数字输入非法)
E301 CoordinateValueExceed 输入坐标分量超限(包括线的两点坐标分量和圆心坐标分量)
E302 TwoPointsCoincideOfLine 线的两点坐标重合(两点坐标不能相同)
E303 InvalidRadiusOfCircle 圆的半径非法(半径值必须为正,且在规定范围内)
E400 ObjectInputTooLittle 输入几何对象过少(少于N值)
E401 ObjectInputTooMuch 输入几何对象过多(多于N值)
E500 InfiniteIntersectionsFound 有无穷多交点
E600 ElementToDeleteNotFound 找不到要删除的元素

由于篇幅所限,针对异常的单元测试部分只展示输入而省去其他代码。

//E100
L 0 0 1 1

//E100
*#06#
L 0 0 1 1

//E101
0

//E200
1
X 0 0 1 1
    
//E300
2
L 0 0 1 1
C 4 3 1 2

//E301
2
L 0 0 1 100000
R 4 3 1 2

//E302
1
L 1 1 1 1
    
//E303
2
L 0 0 1 1
C 4 2 0

//E400
2
L 0 0 1 1

//E401
2
L 0 0 1 1
C 0 2 1
R 1 1 3 4
    
//E500
3
C 0 2 1
C 0 2 1
L 1 1 2 2
界面模块的详细设计过程。在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。(5')

界面模块使用WPF实现。添加模块可以使用从文件添加与手动输入的方式;删除模块则使用手动输入几何对象然后对应删除的模式。

添加与删除几何对象的代码如下:

        private void ButtonAdd(object sender, RoutedEventArgs e) {
            // listView.Items.Add(new { });
            string x1 = text1.Text;
            string y1 = text2.Text;
            string x2 = text3.Text;
            string y2 = text4.Text;
            string r = text3.Text;
            //
            if (selectType.SelectedIndex == 0) {
                IntersectAPI.AddLine(int.Parse(x1), int.Parse(y1), int.Parse(x2), int.Parse(y2));
            } else if (selectType.SelectedIndex == 3) {
                IntersectAPI.AddCircle(int.Parse(x1), int.Parse(y1), int.Parse(r));
            } else if (selectType.SelectedIndex == 1) {
                IntersectAPI.AddRay(int.Parse(x1), int.Parse(y1), int.Parse(x2), int.Parse(y2));
            } else if (selectType.SelectedIndex == 2) {
                IntersectAPI.AddSection(int.Parse(x1), int.Parse(y1), int.Parse(x2), int.Parse(y2));
            }
            
            Draw();
        }

        private void ButtonRemove(object sender, RoutedEventArgs e) {
            string x1 = text1.Text;
            string y1 = text2.Text;
            string x2 = text3.Text;
            string y2 = text4.Text;
            string r = text3.Text;
            //
            if (selectType.SelectedIndex == 0)
            {
                IntersectAPI.RemoveLine(int.Parse(x1), int.Parse(y1), int.Parse(x2), int.Parse(y2));
            }
            else if (selectType.SelectedIndex == 3)
            {
                IntersectAPI.RemoveCircle(int.Parse(x1), int.Parse(y1), int.Parse(r));
            }
            else if (selectType.SelectedIndex == 1)
            {
                IntersectAPI.RemoveRay(int.Parse(x1), int.Parse(y1), int.Parse(x2), int.Parse(y2));
            }
            else if (selectType.SelectedIndex == 2)
            {
                IntersectAPI.RemoveSection(int.Parse(x1), int.Parse(y1), int.Parse(x2), int.Parse(y2));
            }
            // IntersectAPI.Clear();
            int size = IntersectAPI.GetIntersectionsSize();

            Draw();
        }
界面模块与计算模块的对接。详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能。(4')

界面模块中,左侧为绘图与显示交点的界面,右侧显示几何对象与交点的详细信息。用户可以通过Import按钮手动从文件添加几何对象,也可从下方输入框中选择几何对象并点击Add按钮添加。输入已存在的几何对象后,可以通过Remove进行删除。除此之外,在左侧界面中,用户可以通过鼠标滚轮调整所绘图形的缩放比例。

GUI与对接的部分主要由我的队友实现,这里展示一下Core中的extern函数定义,和GUI中对DLL的调用。

WPF对接C++还是比较方便的,可以直接使用Visual Studio中“动态链接库”导出的DLL,而不需要做其他处理。

//Core
extern "C" {

    INTERSECT_API void Clear();

    INTERSECT_API void Input(char *input);

    INTERSECT_API void AddLine(int x1, int y1, int x2, int y2);

    INTERSECT_API void RemoveLine(int x1, int y1, int x2, int y2);

    INTERSECT_API int GetLinesSize();

    INTERSECT_API void GetLines(int* x1s, int* y1s, int* x2s, int* y2s, int size);

    INTERSECT_API void AddCircle(int x, int y, int r);

    INTERSECT_API void RemoveCircle(int x, int y, int r);

    INTERSECT_API int GetCirclesSize();

    INTERSECT_API void GetCircles(int* xs, int* ys, int* rs, int size);

    INTERSECT_API void AddRay(int x1, int y1, int x2, int y2);

    INTERSECT_API void RemoveRay(int x1, int y1, int x2, int y2);

    INTERSECT_API int GetRaysSize();

    INTERSECT_API void GetRays(int* x1s, int* y1s, int* x2s, int* y2s, int size);

    INTERSECT_API void AddSection(int x1, int y1, int x2, int y2);

    INTERSECT_API void RemoveSection(int x1, int y1, int x2, int y2);

    INTERSECT_API int GetSectionsSize();

    INTERSECT_API void GetSections(int* x1s, int* y1s, int* x2s, int* y2s, int size);

    INTERSECT_API int GetIntersectionsSize();

    INTERSECT_API void GetIntersections(double* xs, double* ys, int size);

}

//GUI
namespace GUI
{
    internal class NativeMethods
    {
        [DllImport("intersect_core.dll")]
        public static extern void Clear();

        [DllImport("intersect_core.dll")]
        public static extern void Input([MarshalAs(UnmanagedType.LPStr)]string input);

        [DllImport("intersect_core.dll")]
        public static extern void AddLine(int x1, int y1, int x2, int y2);

        [DllImport("intersect_core.dll")]
        public static extern void RemoveLine(int x1, int y1, int x2, int y2);

        [DllImport("intersect_core.dll")]
        public static extern int GetLinesSize();

        [DllImport("intersect_core.dll")]
        public static extern void GetLines(int[] x1s, int[] y1s, int[] x2s, int[] y2s, int size);

        [DllImport("intersect_core.dll")]
        public static extern void AddCircle(int x, int y, int r);

        [DllImport("intersect_core.dll")]
        public static extern void RemoveCircle(int x, int y, int r);

        [DllImport("intersect_core.dll")]
        public static extern int GetCirclesSize();

        [DllImport("intersect_core.dll")]
        public static extern void GetCircles(int[] xs, int[] ys, int[] rs, int size);

        [DllImport("intersect_core.dll")]
        public static extern void AddRay(int x1, int y1, int x2, int y2);

        [DllImport("intersect_core.dll")]
        public static extern void RemoveRay(int x1, int y1, int x2, int y2);

        [DllImport("intersect_core.dll")]
        public static extern int GetRaysSize();

        [DllImport("intersect_core.dll")]
        public static extern void GetRays(int[] x1s, int[] y1s, int[] x2s, int[] y2s, int size);

        [DllImport("intersect_core.dll")]
        public static extern void AddSection(int x1, int y1, int x2, int y2);

        [DllImport("intersect_core.dll")]
        public static extern void RemoveSection(int x1, int y1, int x2, int y2);

        [DllImport("intersect_core.dll")]
        public static extern int GetSectionsSize();

        [DllImport("intersect_core.dll")]
        public static extern void GetSections(int[] x1s, int[] y1s, int[] x2s, int[] y2s, int size);

        [DllImport("intersect_core.dll")]
        public static extern int GetIntersectionsSize();

        [DllImport("intersect_core.dll")]
        public static extern void GetIntersections(double[] xs, double[] ys, int size);



        [DllImport("intersect_core.dll")]
        public static extern void resetRes();

        [DllImport("intersect_core.dll")]
        public static extern int getResultOfIntersect();

        [DllImport("intersect_core.dll")]
        public static extern void getPoint(double[] x, double[] y);

        [DllImport("intersect_core.dll")]
        public static extern void addLine(char l, int x1, int y1, int x2, int y2);

        [DllImport("intersect_core.dll")]
        public static extern void delLine(char l, int x1, int y1, int x2, int y2);

        [DllImport("intersect_core.dll")]
        public static extern void addCircle(int x, int y, int r);

        [DllImport("intersect_core.dll")]
        public static extern void delCircle(int x, int y, int r);

        [DllImport("intersect_core.dll")]
        public static extern int getNumOfLines();

        [DllImport("intersect_core.dll")]
        public static extern void getLines(int[] flag, int[] x1, int[] y1, int[] x2, int[] y2, int size);

        [DllImport("intersect_core.dll")]
        public static extern int getNumOfCircles();

        [DllImport("intersect_core.dll")]
        public static extern void getCircles(int[] x, int[] y, int[] r, int size);
    }
}
描述结对的过程,提供两人在讨论的结对图像资料(比如 Live Share 的截图)。关于如何远程进行结对参见作业最后的注意事项。(1')

结对过程以微信交流为主,可能由于作息和个人任务上的差异,使用Live Share的机会并不是很多。结对图像资料如下:

看教科书和其它参考书,网站中关于结对编程的章节,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,说明结对编程的优点和缺点。同时描述结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)。(5')

结对编程的优点:可以有效保证两人工作的同步性,避免因为两人设备差异而导致的反复调试的问题。并且在结对编程的过程中,代码质量基本接近于水平比较高的一人,

结对编程的缺点:需要双方同时腾出大量时间,在沟通不足的情况下不易实现。另外,结对时“一人写代码,一人进行审查”的模式,不能够同时调动两人码代码的能力,同时也在一定程度上限制了编码的内容。在两人分工比较明显的情况下,结对编程操作起来并不顺利。

Name 优点01 优点02 优点03 缺点
朱雨聪 编程能力非常强 非常有耐心,能够回答很多问题 项目管理做得特别好 前期沟通不够多
苏海翔 紧迫感比较强 能够投入大量精力用于项目 比较诚实,知道自己没有第三个优点 编程能力不够,bug写的太多
posted @ 2020-03-23 23:22  P.R.E.T.T.Y  阅读(214)  评论(3编辑  收藏  举报