项目 | 内容 |
---|---|
课程班级博客链接 | 班级博客链接 |
这个作业要求链接 | 作业要求链接 |
我的课程学习目标 | (1)体验软件项目开发中的两人合作,练习结对编程(Pair programming)。 (2)掌握Github协作开发程序的操作方法。 |
这个作业在哪些方面帮助我实现学习目标 | 了解到结对编程的概念; 体会到了结对编程的乐趣; 了解关于利用遗传算法求解折扣背包问题。 |
结对方学号-姓名 | 201871010123-吴丽丽 |
结对方本次博客作业链接 | 链接 |
本项目Github的仓库链接地址 | 链接 |
实验内容和步骤
任务1: 阅读《现代软件工程—构建之法》第3-4章内容,理解并掌握代码风格规范、代码设计规范、代码复审、结对编程概念;
(1)代码风格规范:
原则:简明,易读,无二义性。
1.缩进:4个空格。
2.行宽:可以限定100字符。
3.括号:在复杂的条件表达式中,用括号清楚地表达逻辑优先级。
4.断行与空白的{}行:格式D:每个“{”和“}”都独占一行。
5.分行:不要把多条语句放在一行上。
6.命名:在变量名中不要提到类型或者其他语法方面的描述。避免过多的描述。如果信息可以从上下文中得到,那么此类信息就不必写在变量名中。避免可要不可要的修饰词。
7.下划线:下划线用来分隔名字中作用域标注和变量的语义。
8.大小写:由多个单词组成的变量名,如果全部都是小写,很不易读,一个简单的解决方案就是用大小写区分它们。
Pascal——所有单词的第一个字母都大写。
Camel——第一个单词全部小写,随后单词随Pascal形式,这种方式也叫lowerCamel。
一个通用的做法是:所有的类型/ 类/函数名都用Pascal形式,所有的变量都用Camel形式。
类/类型/变量:名词或组合名词,如Member,PorductInfo等。
函数则用动词或动宾组合词来表示,如get/set、RenderPage()。
9.注释:注释是为了解释程序做什么(what),为什么这样做(why)。
需要特别注意的地方:复杂的注释应该放在函数头,很多函数头的注释都用来解释参数的类型等,如果程序正文已经能够说明参数的类型in/out,就不要重复!
注释也要随着程序的修改而不断更新。
注释(包括所有源代码)应该只用ASCII字符,不要用中文或者其他特殊字符,否则会极大地影响程序的可移植性。
(2)代码设计规范:
相关规定:
1.函数:最重要的原则:只做一件事,并且要最好。
2.goto:函数最好有单一的出口,为达到这一目的,可以使用goto。只要有助于程序逻辑的清晰体现,什么方法都可以使用,包括goto。
3.错误处理:当程序的主要功能实现后,给代码加一些错误处理。
1) 参数处理:在debug版本中,所有的参数都要验证其正确性。在正式版本中,对从外部(用户或别的模块)传递过来的参数,要验证其正确性。
2) 断言:使用断言来验证正确性。
4.如何处理c++中的类:
注:处理关于异常的部分,大部分其他原则对C#也使用。
1)类:
使用类来封装面向对象的概念和多态( Polymorphism)。
避免传递类型实体的值,应该用指针传递。换句话说,对于简单的数据类型,没有必要用类来实现。
对于有显式的构造和析构函数的类,不要建立全局的实体,因为你不知道它们在何时创建和消除。
仅在必要时,才使用“类”。
2)class vs.struct
如果只是数据的封装,用struct即可。
3)公共/保护/私有成员( public. protected和private )
按照这样的次序来说明类中的成员: public、 protected. private。
4)数据成员
数据类型的成员用m_ name说明。
不要使用公共的数据成员,要用inline访问函数,这样可兼顾封装和效率。
5)虚函数 ( Virtual Function )
使用虚函数来实现多态( Polymorphism)。
仅在很有必要时,才使用虚函数。
如果一个类型要实现多态,在基类( Base Class )中的析构函数应该是虚函数。
6)构造函数( Constructors )
不要在构造函数中做复杂的操作,简单初始化所有数据成员即可。
构造函数不应该返回错误(事实上也无法返回)。把可能出错的操作放到HrInit()或FInit()中。
7)析构函数 ( Destructor )
把所有的清理工作都放在析构函数中。如果有些资源在析构函数之前就释放了,记住要重置这些成员为0或NULL。
析构函数也不应该出错。
8)new 和delete
如果可能,实现自已的new/delete,这样可以方便地加上自已的跟踪和管理机制。自已的new/delete可以包装系统提供的new/delete.
检查new的返回值。new 不一定都成功。
释放指针时不用检查NULL。
9)运算符 ( Operators )
在理想状态下,我们定义的类不需要自定义操作符。确有必要时,才会自定义操作符。
运算符不要做标准语义之外的任何动作。例如.“==”的判断不能改变被比较实体的状态。
运算符的实现必须非常有效率,如果有复杂的操作,应定义一个单独的函数。
当你拿不定主意的时候,用成员函数.不要用运算符。
10)异常( Exceptions )
异常是在“异乎寻常”的情况下出现的,它的设置和处理都要花费“异乎寻常”的开销,所以不要用异常作为逻辑控制来处理程序的主要流程。
了解异常及处理异常的花销,在C++语言中,这是不可忽视的开销。
当使用异常时,要注意在什么地方清理数据。
异常不能跨过DLL或进程的边界来传递信息,所以异常不是万能的。
11)类型继承( Class Inheritance )
仅在必要时,才使用类型继承。
用const标注只读的参数(参数指向的数据是只读的,而不是参数本身)。
用const标注不改变数据的函数。
(3)代码复审:看按代码是否在代码规范的框架内正确地解决了问题。
代码复审的形式:
名称 | 形式 | 目的 |
---|---|---|
自我复审 | 自己vs.自己 | 用同伴复审的标准来要求自己。不一定最有效, 因为开发者对自己总是过于自信。如果能持之以恒,则对个人有很大好处。 |
同伴复审 | 复审者vs.开发者 | 简便易行 |
团队复审 | 团队vs.开发者 | 有比较严格的规定和流程,适用于关键的代码,以及复审后不再更新的代码。覆盖率高——有很多双眼睛盯着程序,但效率可能不高(全体人员都要到会)。 |
代码复审的目的在于:
1、找出代码的错误,比如:
1)编码错误,比如一些碰巧骗过了编译器的错误
2)不符合团队代码规范的地方
2、发现逻辑错误, 程序可以编译通过,但是代码的逻辑是错的
3、发现算法错误, 比如使用的算法不够优化,边界条件没有处理好等
4、发现潜在的错误 和回归性错误——当前的修改导致以前修复的缺陷又重新出现
5、发现可能需要改进的地方
6、教育(互相教育)开发人员,传授经验,让更多的成员熟悉项目各部分的代码,同时熟悉和应用领域相关的实际知识
代码复审的步骤:
在复审前——
1.代码必须成功地编译, 在所有要求的平台上,同时要编译Debug | Retail版本。编译要用团队规定的最严格的编译警告等级(例如C/C++中的W4)。
2.程序员必须测试过代码。什么叫测试过?最好的方法是在调试器中单步执行。
3.程序员必须提供新的代码, 以及文件差异分析工具。用Windif或VSTS自带的工具都可以。VSTS中可以通过Shelveset 来支持远程代码复审。在复审中,复审者可以选择面对面的复审、独立复审或其他方式。
4.在面对面的复审中,一般是开发者控制流程,讲述修改的前因后果。但是复审者有权在任何时候打断叙述,提出自己的意见。
5.复审者必须逐一提供反馈意见。注意,复审者有权提出很多看似吹毛求疵的问题,复审者不必亲自调查每一件事,开发者有义务给出详尽的回答。
例如:
复审者:你在这里申请了这个资源,你是如何保证它在所有路径下都能正确释放的?
开发者:这个....我要再检查一下。
或者——
开发者:这个是这样保证的,我用了SmartPointer, 然后这里有trlatch./fi......
要记住复审者是通过问这些问题来确保软件质量的,而不是有意找碴儿。
6.开发者必须 负责让所有的问题都得到满意的解释或解答,或者在TFS中创建新的工作项以确保这些问题会得到处理。
例如:
复审者:这一段代码可能会被多个线程调用,代码是线程安全( thread-safe)的么?我怎么没有看到对共享资源的保护?
开发者:我一时得不出结论,让我在TFS中开一个“任务”来跟踪此事。
7. 对于复审的结果,双方必须达成一致的意见。
1)打回去——复审发现致命问题,这些问题在解决之前不能签人代码;
2)有条件地同意一发现了一些小问题,在这些问题得到解决或记录之后,代码可以签人,不需要再次复审;
3)放行——代码可以不加新的改动,签人源码控制服务器。
(4)结对编程概念:
是一种敏捷软件开发的方法,两个程序员在一个计算机上共同工作。一个人输入代码,而另一个人审查他输入的每一行代码。
输入代码的人称作驾驶员,审查代码的人称作观察员(或导航员)。两个程序员经常互换角色。
和现实生活中的例子类似,一个人负责具体的执行(驾驶,用键盘编写程序等),另一人负责导航、检查、掩护等。
- 驾驶员( Driver) :控制键盘输人。
2.领航员( Navigator) :起到领航、提醒的作用。
任务2:两两*结对,对结对方《实验二 软件工程个人项目》的项目成果进行评价,具体要求如下 :
(1)对项目博文作业进行阅读并进行评论,评论要点包括:博文结构、博文内容、博文结构与PSP中“任务内容”列的关系、PSP中“计划共完成需要的时间”与“实际完成需要的时间”两列数据的差异化分析与原因探究,将以上评论内容发布到博客评论区。
已对结对编程伙伴的项目博文作业进行阅读评价
结对编程伙伴博文作业链接
(2)克隆结对方项目源码到本地机器,阅读并测试运行代码,参照《现代软件工程—构建之法》4.4.3节核查表复审同伴项目代码并记录。
对方的项目源码未能成功上传到仓库
(3)依据复审结果尝试利用github的Fork、Clone、Push、Pull request、Merge pull request等操作对同伴个人项目仓库的源码进行合作修改。
任务3:采用两人结对编程方式,设计开发一款D{0-1}KP 实例数据集算法实验平台,使之具有以下功能:
(1)平台基础功能:实验二 任务3;(2)D{0-1}KP 实例数据集需存储在数据库;
(3)平台可动态嵌入任何一个有效的D{0-1}KP 实例求解算法,并保存算法实验日志数据;
(4)人机交互界面要求为GUI界面(WEB页面、APP页面都可);
(5)查阅资料,设计遗传算法求解D{0-1}KP,并利用此算法测试要求(3);
(6)附加功能:除(1)-(5)外的任意有效平台功能实现。
D{0-1}KP:
分析:
关于遗传算法:
通过查阅相关资料我了解到了遗传算法:
利用遗传算法求解D{0-1}KP问题,可假设一个项集的物品扩展为A、B、C三个物品,将三个物品间两两组合的折扣关系分别假设为物品D、E、F,选择三个物品时的折扣关系假设为物品G。在该项集中选择情况包含:全部不选、选择物品A、选择物品B、选择物品C、选择物品D(物品A和B的组合)、选择物品E(物品A和C的组合)、)、选择物品F(物品B和C的组合)以及选择物品G(物品A、B和C的组合)共八种情况。在同一时刻,每个项集的选择只能是八种情况之一。
遗传算法:基于随机的进化算法;希望通过多次将高质量的个体繁衍后,生出所需要的个体。随机:新的解会在原有的解上发生随机变化。
基本思想:遗传算法的搜索从一个被称作种群的候选解集开始,新的种群由旧的种群中产生以期得到更好的种群。从旧种群中按照解的适应度来选择解以产生新的解;适应度越大,解被选择生成后代的机率也越大。这个从已有种群中选择双亲并产生后代的迭代过程持续到遗传算法的停止条件满足为止。
遗传算法的基本元素:由染色体组成的种群,根据适应度进行选择以及交叉产生后代。
关键步骤如下:
(1)基因编码:在这个过程中,尝试对一些个体的基因做一个描述,构造这些基因的结构,有点像确定函数自变量的过程。
(2)设计初始群体:在这里需要造一个种群出来,这些种群有很多生物个体但基因不同。
(3)适应度计算:这里对那些不符合要求的后代进行剔除,不让他们产生后代。否则他们产生的后代只会让计算量更大而对逼近目标没有增益。
(4)产生下一代:有3种方法,即:直接选择,基因重组,基因突变
而后回到步骤(3)进行循环,适应度计算,产生下一代,这样一代一代找下去,直到找到最优解为止。
遗传算法流程图:
要采用遗传算法,则首先确定以下几个要素:染色体的编码方法、适值函数、染色体交叉和变异采用的方案、选择策略
设计说明:
在本次设计中我们采用一个函数来进行读取文件,用draw_scatter()函数来绘制重量——价值的散点图,datasorted()函数来进行价值与重量的比值进行非递增排序,再通过pack()函数来利用动态规划算法来进行分组背包问题的求解,求出在不超过容量的限制下能装入的最大价值和该算法的运行时间,再将其保持到txt文件中。之后我们设计了遗传算法类,里面设置了初始化种群、评估个体的适配值、交叉、突变、选择个体、产生新后代,产生下一代这些方法。
关于模块:
交流:
在本次结对编程过程中,我们各自学习了遗传算法,之后两人互相交流了一下自己对遗传算法的一些看法,进一步了解了遗传算法的基本思想,因我们两的能力有限,编码能力不足,在上一次的作业中我们都没能够把其对应的功能实现,因而在本次结对编程中,我们互相协作,将各自的代码给了对方看,从中寻找解决问题的方法,在编程时我们老是编译不过去,总是出现各自各样的问题,各自针对其问题提出意见,并且结合网上的一些资料,进行不断的测试、运行。以下是我们在交流过程中的一些截图:
Personal Software Process Table (PSP)
PSP2.1 | 任务内容 | 计划共完成需要的时间(min) | 实际共完成需要的时间(min) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
· Estimate | 计划 | 10 | 10 |
Development | 开发 | 240 | 295 |
· Analysis | · 需求分析 (包括学习新技术) | 20 | 15 |
Design Spec | · 生成设计文档 | 10 | 10 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 40 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 30 |
· Design | · 具体设计 | 60 | 70 |
· Coding | · 具体编码 | 70 | 100 |
· Code Review | · 代码复审 | 20 | 20 |
· Test | · 测试(自我测试,修改代码,提交修改) | 10 | 10 |
Reporting | 报告 | 20 | 20 |
· Test Report | · 测试报告 | 10 | 10 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 20 |
合计 | 320 | 355 |
小结感受:
我觉得通过两人合作能够带来1+1>2的效果,经过两个人合作探讨,设计解决问题,两个人的思维比一个人的活跃,带来的效果也是不一样的。此次的结对编程让我了解到了队友的想法,以及我自身的缺陷问题。这是一个很好的反映。此次的编程实践,任务还是挺艰难的,虽然没有实现出来最终的成功,但是还是收获很多。