201971010121-李健康 实验三 结对项目——《{0-1}KP数据集算法实验平台》项目报告
项目 | 内容 |
---|---|
课程班级博客链接 | 2019级卓越工程师 |
作业要求链接 | 作业要求 |
课程学习目标 | (1)体验软件项目开发中的两人合作,练习结对编程 (2)掌握Github协作开发软件的操作方法 |
该作业在哪些方面帮助我实现学习目标 | (1)熟练掌握软件项目结对开发流程 (2)通过{0-1}KP项目上传Github,掌握Github写作开发软件的操作方法 |
结对方学号-姓名 | 201971010118-梁春云 |
结对方本次博客作业链接 | 201971010118-梁春云 实验三 |
本项目Github仓库链接地址 | Is-Cool-3 |
任务一:阅读《现代软件工程—构建之法》第3-4章内容,理解并掌握代码风格规范、代码设计规范、代码复审、结对编程概念
- 代码风格规范
原则上是简明、易读、无二义性
1.缩进
4个空格,在Visual Studio和其他的一些编辑工具中都可以定义Tab键扩展成为几个空格键。不用Tab键的理由是,Tab键在不同的情况下会显示不同的长度,严重干扰阅读体验。4个空格的距离从可读性来说,正好。
2.行宽
行宽必须限制,但是以前有些文档规定的80字符行宽太小了(以前的计算机/打字机显示行宽为80字符),现在时代不同了,可以限定为100字符。
3.括号
在复杂的条件表达式中,用括号清楚地表示逻辑优先级。
4.断行与空白的{}
每个“{”和“}”都独占一行
5.分行
不要把多条语句放在一行上
6.命名
(1)在变量名中不要提到类型或其他语法方面的描述
(2)避免过多的描述
(3)如果信息可以从上下文中得到,那么此类信息就不必写在变量名中
(4)避免可要可不要的修饰词
7.下划线
下划线用来分隔变量名字中的作用域标注和变量的语义
8.大小写
一个通用的做法是:所有的类型/类/函数名都用Pascal形式,所有的变量都用Camel 形式
9.注释
复杂的注释应该放在函数头,很多函数头的注释都用来解释参数的类型等,如果程序正文已经能够说明参数的类型in/out,就不要重复!
注释也要随着程序的修改而不断更新,一个误导的(Misleading)注释往往比没有注释更糟糕。另外,注释(包括所有源代码)应该只用ASCII字符,不要用中文或其他特殊字符,否则会极大地影响程序的可移植性。
在现代编程环境中,程序编辑器可以设置各种美观得体的字体,我们可以使用不同的显示风格来表示程序的不同部分。 - 代码设计规范
代码设计规范不光是程序书写的格式问题,而且牵涉到程序设计、模块之间的关系、设计模式等方方面面,这里又有不少内容与具体程序设计语言息息相关(如C、C++、Java、C# ),但是也有通用的原则,这里主要讨论通用的原则。如果你写的程序会被很多人使用,并且你得加班调试自己的程序,那最好还是遵守下面的规定
1.函数
只做一件事,并且要做好
2.goto
函数最好有单一的出口,为了达到这一目的,可以使用goto
3.错误处理
当程序的主要功能实现后,一些程序员会乐观地估计只需要另外20%的时间,给代码加一些错误处理就大功告成了,但是这20%的工作往往需要全部项目80%的时间。
(1)参数处理
(2)断言
4.如何处理C++中的类
注意,除了关于异常(Exception)的部分,大部分其他原则对C#也适用。
(1)类
<1>使用类来封装面向对象的概念和多态;
<2>避免传递类型实体的值,应该用指针传递。换句话说,对于简单的数据类型,没有必要用类来实现;
<3>对于有显式的构造和析构函数的类,不要建立全局的实体,因为你不知道它们在何时创建和消除;
<4>仅在必要时,才使用“类”。
(2)class vs.struct
如果只是数据的封装,用struct 即可
(3)公共/保护/私有成员
按照这样的次序来说明类中的成员:public, protected 、private
(4)数据成员
<1>数据类型的成员用m_name说明;
<2>不要使用公共的数据成员,要用inline访问函数,这样可兼顾封装和效率。
(5)虚函数
<1>使用虚函数来实现多态;
<2>不要使用公共的数据成员,要用inline访问函数,这样可兼顾封装和效率。
(6)构造函数
<1>不要在构造函数中做复杂的操作,简单初始化所有数据成员即可;
<2>构造函数不应该返回错误,把可能出错的操作放到HrInit()或FInit()中。
(7)所构函数
<1>把所有的清理工作都放在析构函数中。如果有些资源在析构函数之前就释放了,记住要重置这些成员为0或NULL;
<2>析构函数也不应该出错。
(8)new和delete
<1>如果可能,实现自己的new/delete,这样可以方便地加上自己的跟踪和管理机制。自己的new/delete可以包装系统提供的new/delete;
<2>检查new的返回值。new不一定都成功;
<3>?释放指针时不用检查NULL。
(9)运算符
<1>在理想状态下,我们定义的类不需要自定义操作符。确有必要时,才会自定义操作符;
<2>运算符不要做标准语义之外的任何动作。例如,"==”的判断不能改变被比较实体的状态;
<3>运算符的实现必须非常有效率,如果有复杂的操作,应定义一个单独的函数;
<4>当你拿不定主意的时候,用成员函数,不要用运算符。
(10)异常
<1>异常是在“异乎寻常”的情况下出现的,它的设置和处理都要花费“异乎寻常”的开销,所以不要用异常作为逻辑控制来处理程序的主要流程;
<2>了解异常及处理异常的花销,在C++语言中,这是不可忽视的开销;
<3>当使用异常时,要注意在什么地方清理数据;
<4>异常不能跨过DLL或进程的边界来传递信息,所以异常不是万能的。
(11)类型继承
<1>仅在必要时,才使用类型继承;
<2>用const标注只读的参数;
<3>用const标注不改变数据的函数。 - 代码复审
看代码是否在代码规范的框架内正确地解决了问题
1.代码复审前的步骤
(1)代码必须成功地编译,在所有要求的平台上,同时要编译Debug / Retail版本
(2)程序员必须测试过代码
(3)程序员必须提供新的代码,以及文件差异分析工具
(4)在面对面的复审中,一般是开发者控制流程,讲述修改的前因后果
(5)复审者必须逐一提供反馈意见
(6)开发者必须负责让所有的问题都得到满意的解释或解答,或者在TFS中创建新的工作项以确保这些问题会得到处理
(7)对于复审的结果,双方必须达成一致的意见 - 结对编程概念
1.萌芽阶段
两人刚刚互相认识,这时大家都有礼貌,一般交流不少,每个人都想得到对方的接纳,试图避免冲突和容易引起挑战的观点。对即将进行的工作,有不同的期望值,但是双方彼此并不了解。
2.磨合阶段
接触之后,才感到手足无措,力气无处使,无法配合。
3.规范阶段
工作逐渐和谐、合拍,团队成员就很多事情取得了一致。一些成文或不成文的规则逐步建立起来了。
4.创造阶段
工作二人合而为一。并不是所有的合作都能达到这一阶段,磨合太多后,我们还可能进入“解体阶段”。
5.解体阶段
散伙,各走各的独木桥。
任务2:两两自由结对,对结对方《实验二 软件工程个人项目》的项目成果进行评价
项目 | 内容 |
---|---|
结对方 | 梁春云-201971010118 |
接对方实验二作业链接地址 | 实验二 |
结对方Github项目仓库 | {0-1}KP |
评论地址 | 评论 |
- 对结对方的作业博客评论如下:
1.博文结构清晰直观,每一步都详细明确,条理突出鲜明,富有参考意义。
2.博文内容较充实,美中不足的是缺少一张程序运行流程图,这样可以更能清楚地描述出算法结构。
3.博文结构并不能完全突出PSP“任务内容”列,二者的相关性更多的是递进的关系。
4.PSP中“计划共完成需要的时间”与“实际完成需要的时间”中,实际计划时间较少,猜测是应用的数据结构少于理想设计中额数据结构;实际开发时间更多,猜测是博主程序调试时间所致;实际报告时间较少,猜测是总结能力强,条理性清晰。
- 克隆结对方项目源码到本地机器,阅读并测试运行代码,参照《现代软件工程—构建之法》4.4.3节核查表复审同伴项目代码并记录。
(1)克隆对方项目
(2)将代码用VScode打开并测试运行代码
(3)代码复审核查表
概要部分 | 内容 |
---|---|
代码符合要求和规范说明吗? | 否 |
代码设计是否考虑周全? | 是 |
代码可读性如何? | 良好 |
代码容易维护吗? | 容易 |
代码的每一行都执行并检查过了吗? | 是 |
设计规范部分 | 内容 |
设计是否遵循从已知的设计模式或项目中常用的模式? | 是 |
有没有硬编码或字符串\数字等存在? | 有 |
代码有没有依赖于某一平台,是否会影响将来的移植(如Win32到Win64) ? | 否 |
开发者新写的代码能否用已有的Library/SDK/Framework中的功能实现?在本项目中是否存在类似的功能可以调用而不用全部重新实现? | 否 |
有没有无用的代码可以清楚? | 无 |
代码规范部分 | 内容 |
修改的部分符合代码标准和风格吗 | 否 |
具体代码部分 | 内容 |
有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常? | 否 |
参数传递有无错误? | 无 |
字符串的长度是字节的长度还是字符的长度? | 字符 |
是以0开始计数还是以1开始计数? | 0 |
边界条件是如何处理的? | +1 |
switch语句的default分支是如何处理的? | 无default分支 |
循环有没有可能出现死循环? | 可能 |
有没有使用断言来保证我们认为不变的条件真的得到满足? | 否 |
对资源的利用,是在哪里申请,在哪里释放的? | 内存 |
有无可能存在资源泄露? | 否 |
有没有优化的空间? | 有 |
数据结构中有没有用不到的元素? | 无 |
效能 | 内容 |
代码的效能如何? | 高 |
最坏的情况是怎样的? | 死循环 |
代码中,特别是循环中是否有明显可优化部分? | 有 |
对于系统和网络的调用是否会超时? | 是 |
可读性 | 内容 |
代码的可读性如何? | 良好 |
有没有足够的注释? | 有 |
可测试性 | 内容 |
代码是否需要更新或创建新的单元测试? | 否 |
(4)依据复审结果尝试利用github的Fork、Clone、Push、Pull request、Merge pull request等操作对同伴个人项目仓库的源码进行合作修改。
<1>改善其代码命令规范;
<2>进行Fork操作;
任务3:采用两人结对编程方式,设计开发一款{0-1}KP 实例数据集算法实验平台
- 需求分析陈述
1.平台基础功能:实验二 任务3;
2.{0-1}KP 实例数据集需存储在数据库;
3.平台可动态嵌入任何一个有效的{0-1}KP 实例求解算法,并保存算法实验日志数据;
4.人机交互界面要求为GUI界面(WEB页面、APP页面都可);
5.查阅资料,设计遗传算法求解{0-1}KP,并利用此算法测试要求(3);
6.附加功能:除(1)-(5)外的任意有效平台功能实现。 - 软件设计说明
1.本次实验采用Python语言、VScode编译器中调试,可在PC操作系统下运行;
2.用户可在人机交互界面选择使用何种算法解决问题; - 软件实现及核心代码展示
1.程序整体路程图
2.类及功能的介绍
(1)基础GUI界面类:Mysel()及GUI设计
def Mysel():
dic = {0: '散点图', 1: '动态规划算法', 2: '回溯法', 3: '贪心法', 4: '遗传算法'}
s = "您选了" + dic.get(var.get()) + "文件"
lb.config(text=s)
var = IntVar()
rd1 = Radiobutton(root, text="散点图", variable=var, value=0, command=Mysel)
rd1.pack()
rd2 = Radiobutton(root, text="动态规划算法", variable=var, value=1, command=Mysel)
rd2.pack()
rd3 = Radiobutton(root, text="回溯法", variable=var, value=2, command=Mysel)
rd3.pack()
rd4 = Radiobutton(root, text="遗传算法", variable=var, value=3, command=Mysel)
rd4.pack()
(2)任意一组D{0-1} KP数据的最优解、求解时间和解向量可保存为txt文件或导出EXCEL文件。
data.write("\n循环运行时间:")
data.write(str(end - start))
data.write("秒")
data.close()
elif x == '2':
data = open("result.txt", "w") # 创建保存结果文件
data.write('\n背包中所装物品为:') # 写入文件
visit = np.zeros(number)
start = time.time()
question = backTrackingMethod(weight,
profit,
capacity,
cw=0,
cp=0,
bestp=0)
weight, profit, index = question.value_per()
visit, best = question.back_tracking(0, visit)
end = time.time()
print("\n循环运行时间:%.2f秒" % (end - start))
list = []
for i in range(visit.size):
if (visit[i] != 0):
list.append(index[i] + 1)
print("\n\n***回溯算法***")
print("最优解序号为:")
for a in sorted(list):
print('第', a, '个', end=" ")
s = str(i) + ' '
data.write(s)
print("\n最大价值为:", best)
# 任意一组D{0-1} KP数据的最优解、求解时间和解向量可保存为txt文件或导出EXCEL文件。
data.write("\n循环运行时间:")
data.write(str(end - start))
data.write("秒")
data.close()
else:
print("输入错误!")
(3)按性价比进行非递增排序
w_np = np.array(weight)
p_np = np.array(profit)
ratio = p_np / w_np
print("价值与重量之比:")
for a in ratio:
print(format(a, '.3f'), end=" ")
print("\n\n非递增排序后:")
res = sorted(ratio, reverse=True)
for b in res:
print(format(b, '.3f'), end=" ")
(4)遗传算法
<1>遗传算法解决{0-1}背包问题基本思想:
遗传算法的搜索从一个被称作种群的候选解集开始,新的种群由旧的种群中产生以期得到更好的种群。从旧种群中按照解的适应度来选择解以产生新的解;适应度越大,解被选择生成后代的机率也越大。这个从已有种群中选择双亲并产生后代的迭代过程持续到遗传算法的停止条件满足为止。
<2>进行宏常量定义:
# 定义终止界限
FINISH_LIMT = 0 []
# 重量界限
WEIGHT_LIMIT = 100 # 题目要求的
# 染色体长度
CHROMOSOME_SIZE =6
# 精选次数
SELECT_NUMBER = 4
# 记录上一代的最大值
max_last = 0
# 记录上一代和上上一代的适应函数的差
diff_last = 10000
<3>随机精选出四个个体,用来初始化整个种群
def init():
chromosomes_state1 = '100100'
chromosomes_state2 = '101010'
chromosomes_state3 = '010101'
chromosomes_state4 = '101011'
chromosomes_states = [chromosomes_state1, chromosomes_state2, chromosomes_state3, chromosomes_state4]
return chromosomes_states
<4>计算种群的适应度,将所有存入包中大的物品的重要的和作为当前种群的适应度
def fitness(chromosomes_states):
fitnesses = []
for chromosomes_state in chromosomes_states:
value_sum = 0
weight_sum = 0
# enumerate将数据对象组合为索引序列,同时列出数据下标和数据
for i,v in enumerate(chromosomes_state):
if int(v)==1:
weight_sum += x[i+1][0]
value_sum += x[i + 1][1]
fitnesses.append([value_sum,weight_sum])
return fitnesses
<5>当这次的差异值和上次的差异值都小于终止界限,就可以认为适应度函数这时候已经开始收敛了,可以认为当前的种群已经达到最优的一代
def is_finished(fitnesses):
global max_last
global diff_last
max_current = 0
# 获得当前的适应度函数的最大值
for v in fitnesses:
if v[1]>max_current:
max_current = v[1]
diff = max_current - max_last
# 判断差异值,来决定是否已经到达了最优的种群
if diff<FINISH_LIMT and diff_last < FINISH_LIMT:
return True
else:
# 对最大值和和差异值进行更新
diff_last = diff
max_last = max_current
return False
<6>精选下一代,先淘汰掉不能适应环境的,即淘汰重量大于题目要求的,随机从上一代能适应环境的种群个体中选出几个个体进行下一代的繁衍,记录下精选个体的位置(同一个个体可能被选多次),因此slelect_index中可能有两个值是相同的。
def filter(chromosomes_states,fitnesses):
index = len(fitnesses) - 1
while index >= 0:
index -= 1
if fitnesses[index][1] > WEIGHT_LIMIT:
chromosomes_states.pop(index)
fitnesses.pop(index)
select_index = [0] * len(chromosomes_states)
# 开始进行精选
for i in range(SELECT_NUMBER):
j = chromosomes_states.index(random.choice(chromosomes_states))
select_index[j] += 1
return select_index
<7>产生下一代,从精选的四个个体里面依次取一个个体,再从能适应环境的个体中随机的取一个个体,交配产生新的下一代。
def crossover(chromosomes_states,select_index):
chromosomes_states_new = []
tmp = chromosomes_states[:]
index = len(chromosomes_states) - 1
while index >= 0:
index -= 1
chromosomes_state = tmp.pop(index)
for i in range(select_index[index]):
chromosomes_state_x =random.choice(chromosomes_states)
# 随机产生基因序列的交配位置
pos = random.choice(range(1,CHROMOSOME_SIZE-1))
chromosomes_states_new.append(chromosomes_state[:pos]+chromosomes_state_x[pos:])
return chromosomes_states_new
- 运行
1.整体界面
2.选择算法
3.选择数据
4.散点图
5.结果
- PSP
PSP2.1 | 任务内容 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
---|---|---|---|
Planning | 计划 | 10 | 14 |
Estimate | 估计这个任务需要多少时间,并规划大致工作步骤 | 10 | 14 |
Development | 开发 | 299 | 449 |
Analysis | 需求分析(包括学习新技术) | 14 | 13 |
Design Spec | 生成设计文档 | 15 | 19 |
Design Review | 设计复审(和同事审核设计文档) | 20 | 14 |
Coding Standard | 代码规范(为目前的开发制定合适的规范) | 15 | 22 |
Design | 具体设计 | 120 | 240 |
Coding | 具体编码 | 40 | 60 |
Code Review | 代码复审 | 45 | 40 |
Test | 测试(自我测试,修改代码, 提交修改) | 30 | 41 |
Reporting | 报告 | 49 | 76 |
Test Report | 测试报告 | 25 | 40 |
Size Measurement | 计算工作量 | 14 | 24 |
Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 10 | 12 |
-
结对编程
-
总结
这次实验收获还是比较丰富的,尤其是在两人结对编程这一方面,不但让我加深了对同伴编程这一方面的理解,更让我进一步懂得了其重要性。另一方面,在代码的书写和设计也有了进一步的提升,让我懂得了1+1在某些时候页能大于2.