• 结对项目


    Github地址

    博客地址

    PSP 2.1 表格

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

    第一阶段

    接口设计说明

    Information Hiding

    接口隐藏实现细节和实例属性,接口的调用者无法直接修改对象的属性。对成员形成安全保护。

    Interface Design

    接口设计方便代码复用,是对一种操作或实现的抽象表示。对于现代软件工程极其重要,我们撰写的软件会使用大量标准库和第三方库的接口。

    Loose Coupling

    松耦合。模块之间的依赖有限。修改其中的任意一个模块内部的实现细节,不应对其他模块造成影响。其他模块不需要进行任何代码上的修改,也能保持功能的正常。

    我们在阶段一进行接口设计时,就利用了这些思想。比如,接口对内部信息的隐藏。一个调用者对接口的调用,不用知道其具体实现,也不用担心对其内部的副作用。松耦合也是我们特别关注的。命令行程序与GUI程序公用一个Core.DLL,对Core.dll的更新不会影响其他程序的实现。

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

    计算模块名为Core,包含2个命名空间(每人开发一个),包括9个类,大约30个函数。他们之间的关系组织见后面的UML图。

    关键函数,生成唯一解的数独残局:

    关键部分:每挖一个空,就判断是否是唯一解

    独到之处:比起挖完空之后,再判断是否唯一解,减少了大量试错,提高了时间效率

    UML图

    接口1

    void generate(int number, ref int[][,] result)

    我们个人项目已经实现了生成特定数目的数独并写入文件的功能,此接口与之不同的是:需要将结果存入result中。

    我们需要在生成过程中,维护一个3维数组,生成一个新的数独就存入其中(之前是直接写入文件,可扩展性比较差,且IO消耗巨大)。

    接口2

    void generate(int number, int mode, ref int[][,] result)

    • [number]的范围限定为1 - 10000。
    • [mode]的范围限定为1 - 3,不同的数字代表了数独游戏的难度级别,如下表所示:
    编号 级别
    1 简单
    2 中等
    3 困难

    我们个人项目已经实现了生成特定数目的数独并写入文件,和生成数独残局的功能。此接口的不同之处在于:

    • 不能将结果直接写入文件,而是写入result参数;
    • 增加生成特定难度数独的功能
    • 生成数独残局

    我们对难度的严格定义

    一般情况下,给定的数字个数越多,数独相对越简单。我们主要参考了这些资料:

    参照这2本数独游戏书籍旅途中的数独数独的难度设定,
    我们将我们的数独游戏的难度定义为:

    难度 挖空数
    简单 45~49
    中等 50~54
    困难 55~59

    接口3

    void generate(int number, int lower, int upper, ref int[][,] result)

    • [lower] 的值最小为20,
    • [upper] 的值最大为55,
    • [upper] >= [lower]。

    接口4

    bool solve(int[,] puzzle, int[,] solution)
    对于输入的数独题目puzzle,返回一个bool值表示是否有解。如果有解,则将一个可行解存储在solution中。

    Design by Contract, Code Contract的优缺点

    感觉Design by Contract和OO中学到的方法注释很像,一个接口接受输入,产生输出,并对一些对象的域可能产生副作用,同时,还会有可能的异常产生。

    优点:

    • 接口定义规范
    • 副作用可控
    • 输入要求严格

    缺点:

    • 需要所有人(实现者,调用者)都遵守,一旦有人违反,会产生很大的副作用。

    我们在实现自己的接口时,严格定义的接受参数的范围,并定义了清晰的异常在调用者不遵守要求时抛出;对于可能产生的副作用,接口的设计对其进行隐藏,即所有的副作用都不会对接口的调用者产生影响。

    单元测试

    代码覆盖率

    Visual Studio Community不自带单元测试的代码覆盖率工具,但是Visual Studio Enterprise自带。刚开始,我们尝试使用一些开源的单元测试代码覆盖率工具,如Xunit, OpenCover。无奈不是很会使用,网上找到的许多教程都是比较古老的,已经不适合当前的VS 2017了。所以,最后我们还是通过安装Enterprise的试用版,完成了代码覆盖率的工作。不得不说,VS自带的工具才是最方便,最好用的。单元测试代码覆盖率如下图所示:

    错误处理

    参考众多命令行工具的错误处理机制,我们改进了自己的错误处理。

    首先,尽量给出详细的错误信息;这些可以在在选项判断和参数范围判断过程中实现。最后,向标准输出该命令打印所有的用法;这个需要一个方法集中管理,无论哪个地方出错,最后都调用这一方法。

    出错示例

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

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

    这张效能分析图运行的参数为-n 100 -r 20~25 -u,需要生成100个挖空数目在20~25的唯一解数独。最费时的操作是判断挖空所生成的数独是否是唯一解。如果不是唯一解,重新生成。这样的做法会做大量的无用功,因为随机挖空生成的数独残局,很大概率上不是唯一解。所以,我们根据网上的一些提示,改进了算法。改进后的算法为:每随机挖一个空,就判断是否是唯一解;而不是所有的空挖完后,再判断时候唯一解。

    改进计算模块性能上所花费的时间:1h

    消耗最大的函数

    public void getcount()
            {
                int i, j, k;
                for (i = 0; i < 9; i++)
                {
                    for (j = 1; j < 10; j++)
                    {
                        this.rows[i].count[j] = 0;
                        this.columns[i].count[j] = 0;
                        this.areas[i].count[j] = 0;
                        for (k = 0; k < 9; k++)
                        {
                            if (this.blocks[this.rows[i].blockids[k]].numbers[j] == 1)
                            {
                                this.rows[i].count[j]++;
                            }
                            if (this.blocks[this.columns[i].blockids[k]].numbers[j] == 1)
                            {
                                this.columns[i].count[j]++;
                            }
                            if (this.blocks[this.areas[i].blockids[k]].numbers[j] == 1)
                            {
                                this.areas[i].count[j]++;
                            }
                        }
                    }
                }
    

    第二阶段

    计算模块的异常处理

    我们为接口设计了共3种异常:

    • BoundOutOfRange:表示挖空上下界不符合范围标准
    • GenerateNumberOutOfRange:表示生成数独终局和残局的数目不符合标准
    • ModeOutOfRange:表示生成数独残局的难度Mode不符合标准

    单元测试样例

    BoundOutOfRange

            [TestMethod]
            [ExpectedException(typeof(BoundOutOfRange))]
            public void testBoundOutOfRange1()
            {
                int[][,] result = null;
                Core.SudokuFounctionLibrary.generate(1000, 4, 5, true, ref result);
            }
    

    对应场景:调用生成数独残局是,调用者传入的lower或upper参数不符合要求。

    GenerateNumberOutOfRange

            [TestMethod]
            [ExpectedException(typeof(GenerateNumberOutOfRange))]
            public void testGenerateNumberOutOfRange2()
            {
                int[][,] result = null;
                Core.SudokuFounctionLibrary.generate(1001, 2, ref result);
            }
    

    对应场景:调用生成数独残局是,调用者传入的number参数不符合要求。

    ModeOutOfRange

            [TestMethod]
            [ExpectedException(typeof(ModeOutOfRange))]
            public void testModeOutOfRange1()
            {
                int[][,] result = null;
                Core.SudokuFounctionLibrary.generate(1001, 4, ref result);
            }
    

    对应场景:调用生成数独残局是,调用者传入的mode参数不符合要求。

    第三阶段

    界面模块的详细设计过程

    这个项目主要有以下三个界面:

    • 开始界面

    开始界面是程序的起始界面。在开始界面可以选择难度,开始游戏,或打开最佳纪录。这个界面比较简单,没有什么复杂的逻辑。

    • 最好记录界面

    在这里可以查看不同难度的最佳纪录。

    • 游戏界面

    游戏界面是数独游戏最重要的界面。

    这个界面由最上面显示难度和时间的文本框,中间的9*9数独棋盘和下面的两排按钮组成。

    数独棋盘我是用81个按钮实现的,这81个按钮会在生成数独题目的时候分为两类,一类是不可改变的,也就是数独题目原本有数字的格子,另一类是可以改变的,也就是需要玩家填数字的格子。当一个可改变的按钮被点击时,这个格子就会被标记,这时如果点击底部的按钮,被标记的格子就会被改变。按钮1到9可以给该格子填上相应的数字,按钮C可以清除该格子的内容,提示按钮可以直接显示出被标记格子应该填的数字。提交按钮用来检查当前的数独局面。

    游戏界面的逻辑部分主要包括一下几个函数:

    choose函数监听数独棋盘中的81个按钮被按下的事件,如果被按下的是可改变的按钮,就标记被按下的按钮。

    void choose(object sender, EventArgs e)
            {
                Button button = (Button)sender;
                char c1 = button.Name[1];
                char c2 = button.Name[2];
                if (noChange[c1 - '0', c2 - '0'] == 0)
                {
                    (this.FindName(lastChoice) as Button).Background = startColor;
                    lastChoice = button.Name;
    
                    (this.FindName(lastChoice) as Button).Background = chooseColor;
    
                }
            }
    

    fill函数监听按钮1到9,按钮C和提示按钮被按下的事件,对被标记的格子做出相应的改变。

    void fill(object sender, EventArgs e)
            {
                Button button = (Button)sender;
                char c = button.Name[1];
                char c1 = lastChoice[1];
                char c2 = lastChoice[2];
                if (noChange[c1 - '0', c2 - '0'] == 0)
                {
                    if (c == 'C')
                    {
                        (this.FindName(lastChoice) as Button).Content = (" ");
                    }
                    else if (c == 'H')
                    {
                        (this.FindName(lastChoice) as Button).Content = Start.solvedPuzzle[lastChoice[1] - '0', lastChoice[2] - '0'];
                        (this.FindName(lastChoice) as Button).FontSize = FSS;
                        (this.FindName(lastChoice) as Button).FontStyle = FontStyles.Italic;
                        (this.FindName(lastChoice) as Button).Foreground = helpColor;
                    }
                    else
                    {
                        (this.FindName(lastChoice) as Button).Content = c;
                        (this.FindName(lastChoice) as Button).FontSize = FS;
                        (this.FindName(lastChoice) as Button).FontStyle = FontStyles.Normal;
                        (this.FindName(lastChoice) as Button).Foreground = Black;
                    }
                }
            }
    

    BS_Click函数监听的是提交按钮。这个函数检查当前的数独局面并弹出相应的提示。

    private void BS_Click(object sender, RoutedEventArgs e)
            {
                int i, j;
                for (i = 0; i < 9; i++)
                {
                    for (j = 0; j < 9; j++)
                    {
                        var s = ("B" + i) + j;
                        try
                        {
                            var n = Int32.Parse((this.FindName(s) as Button).Content.ToString());
                            if (n != Start.solvedPuzzle[i, j])
                            {
                                MessageBox.Show(String.Format("Not a right answer; check mistake at ({0}, {1}). Your time spent: {2} s", i + 1, j + 1, seconds));
                                return;
                            }
                        }
                        catch(FormatException)
                        {
                            MessageBox.Show(String.Format("Not a right answer; check mistake at ({0}, {1}). Your time spent: {2} s", i + 1, j + 1, seconds));
                            return;
                        }
                    }
                }
                MessageBox.Show(String.Format("Congradulations! Your time spent: {0} s", count));
    
                switch (Start.mode)
                {
                    case 1:
                        if (seconds < App.BestRecordEasy)
                            App.BestRecordEasy = seconds;
                        break;
                    case 2:
                        if (seconds < App.BestRecordMedium)
                            App.BestRecordMedium = seconds;
                        break;
                    case 3:
                        if (seconds < App.BestRecordHard)
                            App.BestRecordHard = seconds;
                        break;
                    default:
                        break;
                }
            }
    

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

    程序主要通过以下两个接口的调用实现Core和GUI两个模块的对接。

    • 当开始界面的开始游戏按钮被点下时,调用generate接口生成数独题目。
      SudokuFounctionLibrary.generate(1, mode, ref GUIpuzzle);
    • 之后调用solve接口保存题目的答案。
      Core.SudokuFounctionLibrary.solve(Start.GUIpuzzle[0], ref Start.solvedPuzzle);

    结对编程

    结对过程

    大多数时候是在新主的二层走廊里找一个桌子,找不到的话,就用沙发凑活。两人公用一个笔记本编程,另一个可以用来查资料。有一点不方便,就是不同笔记本的键盘格局有所不同,容易按错,降低了效率。以后或许可以尝试外接一个标准的键盘,不过这样还是比较麻烦。

    有图有真相。

    结对编程的优缺点

    优点:

    • 实时地code review
    • 2双眼睛更容易发现问题
    • 2人知识和技能形成互补,互相学习提高快

    缺点:

    • 需要2人抽出共同的时间,不方便
    • 需要找到合适的场所,可以交谈,网络还好,环境舒适,不容易
    • 公用一个人的机器,不熟练

    结对的每一个人的优点和缺点

    要至少列出3个优点和1个缺点,好尴尬。

    赵晓宇

    优点:

    • 性格沉稳
    • 热爱学习
    • 有Android开发经验

    缺点:

    • 对很多工具的使用不熟练

    杨森

    优点:

    • 时间多,肯花时间在软工上
    • 搜索技术更高一点
    • 对Visual Studio熟悉

    缺点:

    • 生性浮躁
  • 相关阅读:
    jquery 动态选中radio
    在Struts2的Action中取得请求参数值的几种方法
    Collections类sort方法的用法
    struts2 action 之间跳转
    JavaScript 解析 xml 文件 如 rss订阅
    jquery 获取 选中的checkbox的值
    velocity 时间显示 时间格式化 时间转化
    firefox3.6 ie8 jQuery选择checkbox
    IFormattedTextSymbol接口 设定Anchor点的水平或者垂直对其方式
    gisbase网站,因其购买的空间提供商,“涉黄”暂行关闭。
  • 原文地址:https://www.cnblogs.com/YoungForest/p/7669448.html
Copyright © 2020-2023  润新知