201971010140-魏瑾川 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告
前文
项目 | 内容 |
---|---|
课程班级博客链接 | 2019级卓越工程师班 |
这个作业要求链接 | 实验三 软件工程结对项目 |
我的课程学习目标 |
|
这个作业在哪些方面帮助我实现学习目标 |
|
项目Github的仓库链接地址 | 仓库链接 |
结对方学号姓名 | 201971020107_郭清华 |
结对方本次博客作业链接 | 结对方作业链接 |
任务1:阅读《现代软件工程—构建之法》第3-4章内容,理解并掌握代码风格规范、代码设计规范、代码复审、结对编程概念
- 代码风格规范
- 代码风格的原则是:简明,易读,无二义性。
- 缩进:4个空格,不用Tab键是因为在不同的情况下显示的长度可能不一样。
- 行宽:限定为100字符。
- 括号:在复杂的条件表达式中,可以清晰地表示逻辑优先级。
- 断行与空白的
{}
行:断行在程序调试时可以清晰的表达变量的变化情况,{}
来判断程序的结构。 - 分行:不要把多个变量定义在一行上。
- 命名:
- 在变量名中不要提到类型或其他语法方面的描述。
- 避免过多的描述。
- 如果信息可以从上下文中得到,那么此类信息就不必写在变量名中。
- 避免可要可不要的修饰词。
- 下划线:用来分割变量名字中的作用域标注和变量的语义。
- 大小写:
- 所有类型/类/函数名都用Pascal形式(所有单词第一个字母都大写)。
- 所有变量都用Camel形式(第一个单词全部小写,随后单词用Pascal形式)。
- 类/类型/变量:名词或组合名词。
- 函数则用动词或动宾组合词来表示。
- 注释:注释是为了解释程序做什么(What),为什么这样做(Why)以及要特别注意的地方。
- 代码设计规范
- 概念:代码设计规范不光是程序书写的格式问题,而且涉及到程序设计、模块之间的关系、设计模式等方方面面,又有不少内容与具体程序设计语言息息相关(如C,C++,JAVA,C#),但是也有通用的原则。
- 函数:原则:只做一件事,并且要做好。
- goto:函数最好有单一出口,为了达到这一目的,可以使用goto。
- 错误处理:
- 参数处理:在Debug版本中,所有的参数都要验证其正确性,在正式版本中,对从外部(用户或别的模块)传递过来的参数,要验证其正确性。
- 断言:验证正确性就要用断言。
- 如何处理C++中的类
- 类:
- 使用类来封装面向对象的概念和多态。
- 避免传递类型实体的值,应该用指针传递。换句话说,对于简单的数据类型,没有必要要用类来实现。
- 对于有显示的构造和析构的类,不要建立全局的实体,因为不知道它们在何时创建和消除。
- 仅在有必要时,才是用“类”。
class
vsstruct
:如果只是数据的封装,用struct即可。- 公共/保护/私有成员:按照这样的次序来说明类中的成员。
- 数据成员:
- 数据类型的成员用m_name说明。
- 不要使用公共的数据成员,要用inline访问函数,这样可兼顾封装和效率。
- 虚函数
- 使用虚函数来实现多态。
- 仅在很有必要时,才使用虚函数。
- 如果一个类型要实现多态,在基类中的析构函数应该是虚函数。
- 构造函数
- 不要在构造函数中做复杂的操作,简单初始化所有成员即可。
- 构造函数不应该返回错误。
- 析构函数
- 把所有的清理工作都放在析构函数中。如果有些析构函数在之前就释放了,要重置这些成员为0或NULL。
- 析构函数也不应该出错。
new
和delete
- 如果可能,实现自己的new/delete,这样可以方便地加上自己的跟踪和管理机制。自己的new/delete可以包装系统提供的new/delete。
- 检查new的返回值。new不一定都成功。
- 释放指针时不用检查NULL。
- 运算符
- 在理想情况下,我们定义的类不需要自定义操作符。确有必要时,才会自定义操作符。
- 运算符不要做标准语义之外的任何动作。
- 运算符的实现必须非常有效率,如果有复杂的操作,应定义一个单独的函数。
- 当拿不定注意时,用成员函数,不要用运算符。
- 异常
- 不要用异常作为逻辑控制来处理程序的主要流程。
- 当使用异常时,要注意在什么地方清理数据。
- 异常不能跨过DLL或进程的边界来传递消息,所以异常不是万能的。
- 类型继承
- 仅在有必要时,才使用类型继承。
- 用const标注只读的参数。
- 用const标注不改变数据的函数。
- 类:
- 代码复审:看代码是否在代码规范的框架内正确的解决了问题。代码复审的形式包括:自我复审、同伴复审、团队复审。
- 结对编程:结对编程中有两个角色:领航员和驾驶员。在个人编写的过程中,很多人喜欢根据个人喜好来规定代码规范,而且存在的bug自己难以发现,因此,在结对编程时,我们可以互换角色,在开始写代码之前,规定两个人都认可的一套代码规范,并且不间断地进行复审,以减少软件中存在的问题,修复bug,提高软件质量。
任务2:两两自由结对,对结对方《实验二 软件工程个人项目》的项目成果进行评价
-
结对方信息
姓名 & 学号 结对方仓库 郭清华_201971020107 传送门 -
分工
姓名 & 学号 工作任务 魏瑾川_201971010140 后端设计实现 郭清华_201971020107 前端设计实现 -
点评对方博客
博客地址 实验二 个人项目 博文结构 博文结构清晰,版式整洁,错落有致,具有段落感。同时对一些特别基础的概念又有自己的理解,令人耳目一新。 博文内容 对于博文内容,这里按每个任务模块进行点评:任务1:优点是对于每个点评给出了超链接,且定位到了该评论的位置,极为方便;缺点是最好能使用表格,这样更清晰直观。 任务2:最好能够将《 构建之法》PSP那两章进行提炼概括。 任务3:在需求分析上,对0-1背包给出了自己的解释,相信博主一定对该问题有了较为深入的理解。同时对于任务三功能本身,博主完成度极高,但缺点在于图形交互界面没有实现,体验感稍差。 博文结构与PSP中“任务内容”列的关系 博主的项目实现流程是按照PSP的主要流程一步一步走下来的,整体构思明确,执行力高。 PSP数据的差异化分析与原因探究 博主实际完成时间远大于计划完成时间,主要是在具体编码这个阶段。结合我自己的开发经验,主要原因可能在于对PSP具体流程的不熟悉,极大可能是在开发过程中遇到了一些预料之外的突发情况等 -
代码核查表
-
概要部分
概要部分 实现情况 代码符合需求和规格说明么? 基本符合,但部分功能完成度较差。 代码设计是否考虑周全? 考虑周全 代码可读性如何? 采用模块化编程,代码可读性好。 代码容易维护么? 对于不同的功能模块,分别存储在不同的类中,维护较为容易。 代码的每一行都执行并检查过了吗? 是的 -
设计规范部分
设计规范 实现情况 设计是否遵从已知的设计模式或项目中常用的模式? 遵从 有没有硬编码或字符串/数字等存在 没有 代码有没有依赖于某一平台,是否会影响将来的移植(如Win32到Win64)? 不会影响移植 在本项目中是否存在类似的功能可以调用而不用全部重新实现? 没有 有没有无用的代码可以清除? 没有 -
代码规范部分
代码规范部分 实现情况 修改的部分符合代码标准和风格吗? 符合规范 -
具体代码部分
具体代码部分 实现情况 有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常? 处理了异常值 参数传递有无错误,字符串的长度是字节的长度还是字符(可能是单/双字节)的长度是以0开始计数还是以1开始计数? 没有 边界条件是如何处理的? switch语句的default分支是如何处理的?循环有没有可能出现死循环? 采用哨兵处理边界;没有使用switch语句;循环不会出现死循环。 有没有使用断言( Assert)来保证我们认为不变的条件真的得到满足? 程序较为简单,所以没有使用断言。 数据结构中有没有用不到的元素? 没有 -
效能
效能 实现情况 代码的效能(Performance)如何?最坏的情况是怎样的? 运行效率较低。 代码中,特别是循环中是否有明显可优化的部分(? 无 对于系统和网络的调用是否会超时?如何处理? 没有对网络的调用 -
可读性
可读性 实现情况 代码可读性如何?有没有足够的注释? 关键语句都有注释,可读性高 -
可测试性
可测试性 实现情况 代码是否需要更新或创建新的单元测试? 不需要
-
-
依据复审结果尝试利用 Github 的
Fork
、Clone
、Push
、Pull
、request
、Merge pull request
等操作对同伴个人项目仓库的源码进行合作修改。
任务3:采用两人结对编程方式,设计开发一款D{0-1}KP 实例数据集算法实验平台
主要技术:
- 前端:Html、CSS、JQuery、Bootstrap
- 后端:Django
- 数据库:sqlite
平台基础功能:
- 实验二 任务3;
- D{0-1}KP 实例数据集需存储在数据库;
- 平台可动态嵌入任何一个有效的D{0-1}KP 实例求解算法,并保存算法实验日志数据;
- 人机交互界面要求为GUI界面(WEB页面、APP页面都可);
- 查阅资料,设计遗传算法求解D{0-1}KP,并利用此算法测试要求(3);
- 实现一个额外的附加功能;
PSP展示:
PSP2.1 | 任务内容 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
Estimate | 估计这个任务需要多少时间,并规划大致工作步骤 | 20 | 20 |
Development | 开发 | 1090 | 1200 |
Analysis | 需求分析 (包括学习新技术) | 120 | 155 |
Design Spec | 生成设计文档 | 30 | 20 |
Design Review | 设计复审 (和同事审核设计文档) | 20 | 20 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 35 |
Design | 具体设计 | 60 | 50 |
Coding | 具体编码 | 600 | 650 |
Code Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 200 | 240 |
Reporting | 报告 | 100 | 100 |
Test Report | 测试报告 | 30 | 40 |
Size Measurement | 计算工作量 | 40 | 30 |
Postmortem & Process Improvement Plan | 事后总结 ,并提出过程改进计划 | 30 | 30 |
设计实现:
-
项目基本框架如图所示:
- static: 用于存放静态资源,项目前端的 CSS、Js、Img 文件都保存在此文件夹下;
- models: 用来存放项目的数据库关系模型;
- migrations: 用来作为数据库迁移的存储文件夹;
- sqlite: 轻量级数据库,用来存放整个项目所使用的数据;
- urls: 存放整个项目的路由;
- views: 存放项目的后端逻辑;
- template: 用来存放前端页面,主要是 Html 文件;
- setting.py: 配置文件;
-
项目后端设计实现
-
数据库设计实现
此次项目数据库主要使用 sqlite 作为主要数据库,sqlite是 Django 自带的一款轻量级数据库,使用方便,在 Django 中,后端与数据库的交接主要采用映射的方法,将
Class
映射成Table
,从而实现数据库的建立和存储。在此次项目中,主要创建了三个关系——背包数据、日志记录和用户数据。代码如下所示(以背包数据为例):
from django.db import models class Knapsack(models.Model): number = models.CharField(default=None, max_length=256, verbose_name="背包编号") volume = models.IntegerField(default=0.0, verbose_name="体积") worth = models.IntegerField(default=0.0, verbose_name="价值") class Meta: verbose_name = '背包数据' verbose_name_plural = '背包数据' ordering = ('number',) def __str__(self): return str(self.id)
-
遗传算法设计实现
遗传算法(genetic algorithm,GA)是计算数学中用于解决最优化问题的搜索算法,是进化算法的一种。进化算法最初是借鉴了达尔文进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择以及杂交等。
遗传算法通常实现方式为一种计算机模拟。对于一个最优化问题,一定数量的候选解(称为个体)可抽象表示为染色体,使种群向更好的解进化。传统上,解用二进制表示(即0和1的串),但也可以用其他表示方法。进化从完全随机个体的种群开始,之后一代一代发生。在每一代中评价整个种群的适应度,从当前种群中随机地选择多个个体(基于它们的适应度),通过自然选择和突变产生新的生命种群,该种群在算法的下一次迭代中成为当前种群。
基本的遗传算法以初始种群为起点,经过自然选择、交叉和突变操作生成新的种群,经过反复更新种群直到寻找到最优解。其计算步骤如下:
- 编码:将问题空间转换为遗传空间;
- 生成初始种群:随机生成P个染色体;
- 种群适应度计算:按照确定的适应度函数,计算各个染色体的适应度;
- 选择:根据染色体适应度,按照选择算子进行染色体的选择;
- 交叉:按照交叉概率对被选择的染色体进行交叉操作,形成下一代种群;
- 突变:按照突变概率对下一代种群中的个体进行突变操作;
- 返回第3步继续迭代,直到满足终止条件。
算法流程如下:
代码如下:
##初始化,N为种群规模,n为染色体长度 def init(self,N,n): C = [] for i in range(N): c = [] for j in range(n): a = np.random.randint(0,2) c.append(a) C.append(c) return C ##评估函数 # x(i)取值为1表示被选中,取值为0表示未被选中 # w(i)表示各个分量的重量,v(i)表示各个分量的价值,w表示最大承受重量 def fitness(self,C,N,n,W,V,w): S = []##用于存储被选中的下标 F = []## 用于存放当前该个体的最大价值 for i in range(N): s = [] h = 0 # 重量 f = 0 # 价值 for j in range(n): if C[i][j]==1: if h+W[j]<=w: h=h+W[j] f = f+V[j] s.append(j) S.append(s) F.append(f) return S,F ## 适应值函数,B位返回的种族的基因下标,y为返回的最大值 def best_x(self,F,S,N): y = 0 x = 0 B = [0]*N for i in range(N): if y<F[i]: x = i y = F[x] B = S[x] return B,y ## 计算比率 def rate(self,x): p = [0] * len(x) s = 0 for i in x: s += i for i in range(len(x)): p[i] = x[i] / s return p ## 选择 def chose(self,p, X, m, n): X1 = X r = np.random.rand(m) for i in range(m): k = 0 for j in range(n): k = k + p[j] if r[i] <= k: X1[i] = X[j] break return X1 ## 交配 def match(self,X, m, n, p): r = np.random.rand(m) k = [0] * m for i in range(m): if r[i] < p: k[i] = 1 u = v = 0 k[0] = k[0] = 0 for i in range(m): if k[i]: if k[u] == 0: u = i elif k[v] == 0: v = i if k[u] and k[v]: # print(u,v) q = np.random.randint(n - 1) # print(q) for i in range(q + 1, n): X[u][i], X[v][i] = X[v][i], X[u][i] k[u] = 0 k[v] = 0 return X ## 变异 def vari(self,X, m, n, p): for i in range(m): for j in range(n): q = np.random.rand() if q < p: X[i][j] = np.random.randint(0,2) return X
-
散点图绘制设计实现
散点图的绘制主要依靠 echarts 来实现,前端从数据库获得数据后,将数据分类处理,依次在页面画出散点图。核心代码如下图所示。
$(document).ready(function() { var MyScatter = echarts.init(document.getElementById('Scatter')); var data = [ [volum_list], [worth_list], ]; var textStyle = { color: '#333', fontStyle: 'normal', fontWeight: 'normal', fontFamily: '微软雅黑', fontSize: 14, }; option = { xAxis: { type: 'time', name: '体积', }, yAxis: { type: 'value', name: '价值', max: 1, min: 0, }, series: [{ name: '', data: data, type: 'scatter', symbolSize: 40 }] }; MyScatter.setOption(option); });
-
前后端数据交互
前端使用
ajax
作为接口,后端在views
写好后端逻辑,在urls
里面配置好路由,项目运行后,后端的数据就可以与前端进行交互。以动态规划算法为例,前端接口代码如下:$(document).ready(function(){ $.ajax({ url:"http://127.0.0.1:8000/getinfo/", type:"GET", success: function (resp){ if(resp.result == "success"){ alert(resp.maxvalue) } } }) });
后端逻辑代码如下:
from django.http import JsonResponse from DP_Project.models.knapsack import Knapsack def Dynamic(request): knapsacks = Knapsack.objects.filter(number='1') v_list = [] # 体积列表 w_list = [] # 价值列表 v_list.append(0) # 体积列表初始化 w_list.append(0) # 价值列表初始化 for knapsack in knapsacks: v_list.append(int(knapsack.volume)) w_list.append(int(knapsack.worth)) V = 200 N = len(v_list) f = [0] * (V + 1) for i in range(1, N): for j in range(V, v_list[i] - 1, -1): f[j] = max(f[j], f[j - v_list[i]] + w_list[i]) maxvalue = f[V] return JsonResponse({ 'result':'success', 'maxvalue':maxvalue, })
-
附加功能——登录注册功能
此次项目的附加功能是登录注册,用户进入平台后首先需要完成登录与注册,才可以进入平台。
具体逻辑为,在前端设计对象的数据接受接口,当用户输入数据后进入数据库进行查询或者增加,完成后跳转到使用界面。前端逻辑如下:
const inputs = document.querySelectorAll(".input"); function focusFunction(){ let parentNode = this.parentNode.parentNode; parentNode.classList.add('focus'); } function blurFunction(){ let parentNode = this.parentNode.parentNode; if(this.value == ''){ parentNode.classList.remove('focus'); } } inputs.forEach(input=>{ input.addEventListener('focus',focusFunction); input.addEventListener('blur',blurFunction); });
后端逻辑如下:
from django.contrib import auth from django.contrib.auth import authenticate from django.http import HttpResponseRedirect from django.shortcuts import render # 用户登录 def login(request): if request.method == "GET": context = {'previous_page': request.GET.get('from_page', '/index')} return render(request, 'login.html', context) else: username = request.POST['username'] password = request.POST['password'] try: user = authenticate(request, username=username, password=password) auth.login(request, user) return HttpResponseRedirect(request.GET.get('from_page', '/index')) except: context = {'login_info': True, 'previous_page': request.GET.get('from_page', '/index')} return render(request, 'login.html', context)
-
项目测试
-
用户登录注册
-
注册
用户可以现在平台进行注册,注册成功后,数据库会添加对应数据。
-
登录
用户注册好个人信息之后就可以进行登录。
-
-
用户写入数据
用户可以通过后端管理页面写入背包数据,在前端运行相应结果。
-
用户运行结果
-
排序
-
计算最大价值
-
绘制散点图
-
-
日志记录功能
用户在平台完成操作后,后端管理页面会产生对应的日志记录,方便管理员维护。
总结与回顾
-
个人收获总结
本次结对编程可谓是收获满满,不仅将自己学习到的知识运用到此次项目开发中,而且学习了新的知识,对软件工程这门课程也有了更加深刻的理解和认识,我在此次项目中的收获总结具体如下:
- 在组员积极交流并且按照软件开发的规范方式进行项目开发是可以带来1+1>2的效果;
- 结对编程要特别注意代码的规范问题,增加代码的可读性,这样不论是后期维护还是理解都很方便;
- 结对编程时要多保持交流,遇到的问题要积极反馈,一个人想不通的问题两个人一定能想出解决方法;
- 两人互相监督工作,可以增强代码和产品质量,并有效的减少BUG;
- 在编程中,相互讨论,可以更快更有效地解决问题,互相请教对方,不仅可以得到能力上的互补,而且可以学到一些新的知识;
- 由于时间紧张和个人水平能力有限,无法将后端运行的解向量输入到前端页面中。
-
结对回顾
-
项目记录
-
结对交流
-