实验三 软件工程结对项目
项目 | 内容 |
---|---|
课程班级博客链接 | 课程链接 |
这个作业要求链接 | 作业要求 |
我的课程学习目标 | 1.了解软件工程过程中结对项目的开发流程 2. 理解结对编程的重要性 3.提高个人编码能力 |
这个作业在哪些方面帮助我实现学习目标 | 1.通过本次作业的具体流程,使得我了解到了软件工程中结对任务的开发流程 2.填写PSP表格 3.个人进行学习相关算法及编码 4.了解到结对编程中代码规范,代码复审的重要性 |
结对编程队友学号与姓名 | 201871010109-常龙龙 |
项目Github的仓库链接地址 |
前台页面 后端数据接口 |
1、实验目的与要求
(1) 体验软件项目开发中的两人合作,练习结对编程(Pair programming)。
(2) 掌握Github协作开发程序的操作方法。
2、实验内容和步骤
任务1:阅读《现代软件工程—构建之法》第3-4章内容,理解并掌握代码风格规范、代码设计规范、代码复审、结对编程概念;
任务2:两两*结对,对结对方《实验二 软件工程个人项目》的项目成果进行评价,具体要求如下:
(1)对项目博文作业进行阅读并进行评论,评论要点包括:博文结构、博文内容、博文结构与PSP中“任务内容”列的关系、PSP中“计划共完成需要的时间”与“实际完成需要的时间”两列数据的差异化分析与原因探究,将以上评论内容发布到博客评论区。
(2)克隆结对方项目源码到本地机器,阅读并测试运行代码,参照《现代软件工程—构建之法》4.4.3节核查表复审同伴项目代码并记录。
(3)依据复审结果尝试利用github的Fork、Clone、Push、Pull request、Merge pull request等操作对同伴个人项目仓库的源码进行合作修改。
博客作业中针对任务2的评分要点:
- 结对方博客链接(1分);
- 结对方Github项目仓库链接(1分);
- 符合(1)要求的博客评论(18分);
- 符合(2)要求的代码核查表(15分);
- 结对方项目仓库中的Fork、Clone、Push、Pull request、Merge pull request日志数据(5分)
任务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)外的任意有效平台功能实现。
结对编程项目实施要求及代码部分评分细则(30分):
- 结对编程开发进度计划的要求:在项目正式之前,预估本次结对项目任务的PSP环节的消耗时间,并在PSP过程中统计实际耗时,填写PSP表格。
- 尝试采用汉堡包法实施项目结对中两个人的沟通,关于汉堡包法的阐述参见:http://www.cnblogs.com/xinz/archive/2011/08/22/2148776.html
- 理解领航员和驾驶员两种角色关系:两人都必须参与编码工作,在结对编程中两个人轮流做对方的角色。
- 将结对编程项目的源码以增量方式提交到指定同学Github账号的项目仓库中,Github结对项目仓库的代码提交日志要体现两人合作过程,项目仓库中要能看到项目多次commit的记录,和两人各自的commit记录。(5分)
- 项目必须包含src文件夹;
- 编撰两人合作开发遵守共同认可的编码规范,提交项目代码规范文档到Github项目仓库根目录下。(5分)
- 程序功能评测。( 20分)
任务4:完成结对项目报告博文作业(30分,以下给出评分细目)
3、实验完成情况
任务1:阅读《现代软件工程—构建之法》第3-4章内容,理解并掌握代码风格规范、代码设计规范、代码复审、结对编程概念(已完成)
代码规范: 变量名等的规范,缩进格式的规范,函数接口的规范。 原则:代码简单易懂,逻辑清晰 面向变化编程,而不是面向需求编程 。代码风格规范的原则是简明、易读、无二异性。
代码复审: 代码审查,是有意识地、有系统地与其他程序员一起检查彼此的代码是否有错误的行为。可以节省一些不必要的时间和资源,提高开发效率。
结对编程: 两人合作工作与两个人各自独立工作相比,结对编程能编写出质量更高的代码。在面对一个问题的时候,经过讨论会得出一些更好的双方都支持的解决方案,使问题能够达到最优程度的解决。
任务2:结对编程(已完成)
-
2.1 结对编程名单(常龙龙博客园链接)
-
2.2 常龙龙的github仓库链接
-
2.3 符合相关要求的评论
-
2.4 代码核查表
-
项目的开发者:常龙龙
-
项目的复审者:刘佳华
1、概要部分 代码符合需求和规格说明么? 代码符合需求 代码设计是否考虑周全? 考虑周全 代码可读性如何? 代码可读性较强,有着比较好的可读性。 代码容易维护么? 容易维护 代码的每一行都执行并检查过了吗? 是的,都可以执行 2.设计规范部分 设计是否遵从已知的设计模式或项目中常用的模式? 遵从 有没有硬编码或字符串/数字等存在? 没有 代码有没有依赖于某一平台,是否会影响将来的移植(如Win32到Win64)? 没有,不会影响移植,任何平台都可以 开发者新写的代码能否用已有的Library/SDK/Framework中的功能实现?在本项目中是否存在类似的功能可以调用而不用全部重新实现? 可以用、存在,有些代码是可以调用的 有没有无用的代码可以清除?(很多人想保留尽可能多的代码,因为以后可能会用上,这样导致程序文件中有很多注释掉的代码,这些代码都可以删除,因为源代码控制已经保存了原来的老代码) 基本清除完毕了 3.代码规范部分 修改的部分符合代码标准和风格么? 符合,全都按照代码标准修改的 4.具体代码部分 有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常? 对错误都进行了处理,没有异常 参数传递有无错误,字符串的长度是字节的长度还是字符(可能是单/双字节)的长度,是以0开始计数还是以1开始计数? 无错误、本项目中不涉及字符串 边界条件是如何处理的?switch语句的default分支是如何处理的?循环有没有可能出现死循环? switch语句的default分支返回false,没有出现死循环 有没有使用断言(Assert)来保证我们认为不变的条件真的得到满足? 有 对资源的利用是在哪里申请,在哪里释放的?有没有可能导致资源泄露(内存、文件、各种GUI资源、数据库访问的连接,等等)?有没有优化的空间? 在对数据库进行操作之前申请数据库连接资源,操作完毕之后释放申请的资源、不会导致资源泄露、可以优化使用断言来保证我们认为不变的条件 数据结构中有没有用不到的元素? 没有 5.效能 代码的效能(Performance)如何?最坏的情况如何? 达到了具体任务的要求 代码中,特别是循环中是否有明显可优化的部分(C++中反复创建类,C#中 string 的操作是否能用StringBuilder 来优化)? 没有,已经比较优化了 对于系统和网络调用是否会超时?如何处理? 目前没有出现超时的现象。假如出现了我们会杀毒;整理系统,减少运行的进程,释放内存、cpu,释放c盘空间; 6.可读性 代码可读性如何?有没有足够的注释? 可以顺利读取、代码有足够的注释让我们读懂 7.可测试性 代码是否需要更新或创建新的单元测试? 有创新点,呈现良好,值得体会 -
2.5 clone 后运行
任务3:程序设计(以完成)
-
需求分析:
- 后台要能够从给定的文件中读取出正确的数据并保存到数据库
- java后端给前端传递正确的数据,前端根据后端传的数据绘制散点图
- java后端实现对自定义数据类型的列表的排序(实现Comparator接口),并向前端传数据
- 实现java后台解决D{0-1}背包问题的动态规划和回溯算法
- 后台将求解后的数据写入文件并保存,前端展示文件下载阅览
- 后台接收用户发送的算法文件并运行,将运行结果保存到文件然后返回前端
- 后台编写遗传算法求解D{0-1}KP
- 后台要能够从给定的文件中读取出正确的数据并保存到数据库
-
功能设计
- D{0-1}KP数据可以保存到数据库,也可以从数据库中清除
- 平台可绘制任意一组D{0-1}KP数据以重量为横轴、价值为纵轴的数据散点图
- 平台可对任意一组D{0-1}KP数据按项集第三项的价值:重量比进行非递增排序;
- 在平台上,用户能够自主选择动态规划算法、回溯算法求解指定D{0-1} KP数据的最优解和求解时间(以秒为单位)
- 在平台上,任意一组D{0-1} KP数据的最优解、求解时间和解向量可保存为txt文件或导出EXCEL文件
- 平台可动态嵌入任何一个有效的D{0-1}KP 实例求解算法,并保存算法实验日志数据
- 平台可使用遗传算法求解D{0-1}KP
- D{0-1}KP数据可以保存到数据库,也可以从数据库中清除
-
设计实现
-
1.类之间的关系
-
2.数据库表的设计
file_data表
volume表
- 测试运行
-
结对编程
-
任务分功
- 1、项目刚开始我们在讨论是使用哪种人机交互界面,考虑到了使用Java GUI,安卓app等方案,但是基于实验二的实验基础最终出于页面效果和开发时间进度方面我们选择javaWeb页面,并且使用Springboot+vue的开发方式(因为在上次项目常龙龙同学就使用的网站开发,这次只需要在上次的基础上进行功能扩展);
- 2、在项目的具体开展过程中,我们各司其职,常龙龙同学负责网站页面开发,将前后端分离,负责算法设计,网站开发完毕后,我将项目克隆下来,将算法嵌入到后台代码中,再push上去等等。最终在我们两个人的合作之下,完成了本次项目开发
-
结对编程照片
-
任务分功
-
项目总结
-
1、结对编程有利有弊,但是总的来说是利大于弊的,1+1的效果>2。
-
2、结对编程也是一次互相学习的过程,在结对的过程中,我们可以互相帮助,分享学习资源,出现问题也可以一起讨论解决,大大加快了项目的开展进度。而且我们分工明确,队友(常龙龙)负责编写网站,我负责编写算法,我们各司其职,互相交流,一起合作,最终很好的完成了本次项目。
-
3、在项目开展过程中,遇到很多的问题,例如网络质量不佳,GitHub官网进不去,克隆项目也会出现网络超时的情况,在提交项目的时候也会出现github连接超时的情况,这个比较影响开发效率,还有遗传算法求解D{0-1}KP,我经过查阅资料,最终只做出了遗传算法求解0-1问题,而对于使用遗传算法实现折扣背包问题而言,没有实现。所以最终根据自己的情况,将实验要求中和了一下,只实现了0-1背包问题的求解,所以项目在这方面还有所欠缺。
-
4、在结对过程我也看到了自己能力上的缺陷,以前学习的时候重视理论课而疏于实践课,导致和别人在动手实践上面的差距,看着队友(常龙龙)能够使用当前比较热门的技术去实现比较精美的网页时,内心还是比较失落的,但是同时,更多的是鼓舞。以队友的强大时刻来警醒自己。此外,我还对于一些关于网站设计的思想,我也请教了队友,总体来说,是一次比较不错的合作体验。
-
附录:
一、结对编程遵守代码规范
-
开始时间: 2021年4月1日 下午午5时35分
结束时间: 2021年4月14日 晚上11时22分 -
格式
A. 代码行数
(1) 代码文件不超过500行
(2) 函数的行数不超过50行
B. 换行
(1) 函数与函数之间空一行
(2) 函数内部变量声明与函数内部逻辑代码之间空一行
(3) 函数内部不同逻辑代码之间空一行
C. 大括号
“{”和“}”必须单独在一行上,且上下对齐。
D. 缩进
代码缩进一致使用Tab实现,规定Tab键宽度为4个字符,不建议使用空格进行缩进。
E. 空格
(1) 函数参数列表中不同参数之间应该用一个空格分开,即逗号后面应跟一个空格。如:void updateData(int source, int target)
(2) for循环中表达式之间应该用一个空格分开,即分号后面应跟一个空格。如:for(exp1; exp2; exp3)
(3) 操作符与操作数之间应该用一个空格分开,一元操作符(例如自增“++”,自减“--”等)与操作符不作此限定。如:a += b + c; ++a; -
命名
变量主要分为类的公有和私有变量,方法的参数变量与内部使用的临时变量。
A. 变量的名称由多个名词单词或一个名词单词组成时,第一个单词首字母小写,其他单词首字母均大写。如makeRule。
B. 对于const常量,所有单词均大写,且单词之间由下划线”_”连接,如APP_NAME。
C. 函数名称的第一个单词为小写的动词。如isValidExp(表达式是否合法)。
D. 变量名和函数名能够表达它所具有的实际含义。如函数名isValid表示返回值是否合法,res表示是返回值。
E. 尽量避免单词缩写,即便它可能是众所周知的。 -
注释
A. 采用“//”作为单行注释符,采用“/**/”作为函数或者方法的注释。
B. 注释的位置
(1) 注释在被注释函数定义的顶部 (2) 注释在函数内部变量声明的右边
(3) 注释在函数内部逻辑代码的顶部
C. 注释的内容
(1) 代码的功能 (2) 对读者阅读代码时可能产生的疑问做解释,比如,该段代码使用了堆栈,为什么需要堆栈而并非数组,在此处代码部分给出注释
二、结对编程psp时间安排表
PSP | 任务内容 | 计划共完成需要的时间(h) | 实际完成需要的时间(h) |
Planning | 计划 | 60 | |
·Estimate | 估计这个任务需要多少时间,并规划大致工作步骤 | 60 | |
Development | 开发 | 40 | |
·Analysis | 需求分析 (包括学习新技术) | 6 | 7 |
·Design Spec | 生成设计文档和思路 | 2 | 2.4 |
·Design Review | 设计复审 | 1 | 0.8 |
·Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 0.3 | 0.3 |
·Design | 具体设计 | 2 | |
·Coding | 具体编码 | 24 | |
·Code Review | 代码复审 | 3 | |
·Test | 测试(自我测试,修改代码,提交修改) | 2 | |
Reporting | 博客 | 6 | |
Summer | 任务+总结 |
三、遗传算法
import java.io.*;
import java.util.*;
class MaxValue {
public static int[] have = null;
public static int max_value = 0;
}
class Global {
public final static int M = 200;
public final static int T = 1000;
public final static double pc = 0.8;
public final static double pv = 0.05;
}
public class GasolvePackage {
private int package_rj = 0;
private int num = 0;
private int[] tiji = null;
private int[] value = null;
private int[][] zq = null;
public GasolvePackage(String profitString, String valueString, String valume) {
// try {
// BufferedReader read = new BufferedReader(new InputStreamReader(new FileInputStream("./data/input.txt")));
String a = profitString;
String b = valueString;
package_rj = Integer.parseInt(valume);
//a = read.readLine();
tiji = strArr_to_intArr(a.split(","));
// b = read.readLine();
value = strArr_to_intArr(b.split(","));
num = value.length;
MaxValue.have = new int[Global.M];
}
private int[] strArr_to_intArr(String[] strArr) {
int size = strArr.length;
int[] int_arr = new int[size];
for (int i = 0; i < size; i++) {
int_arr[i] = Integer.valueOf(strArr[i].trim());
}
return int_arr;
}
private int[][] dCopy(int[][] source) {
int row_num = source.length;
int col_num = source[0].length;
int[][] des = new int[row_num][col_num];
for (int i = 0; i < row_num; i++) {
for (int j = 0; j < col_num; j++) {
des[i][j] = source[i][j];
}
}
return des;
}
public int[][] init() {
Random rand = new Random();
zq = new int[Global.M][num];
for (int i = 0; i < Global.M; i++) {
for (int j = 0; j < num; j++) {
zq[i][j] = rand.nextInt(2);
}
if (get_singleWeight(zq[i]) > package_rj) {
i--;
}
}
return zq;
}
private int get_singleWeight(int[] single) {
int total_weight = 0;
int size = single.length;
for (int i = 0; i < size; i++) {
total_weight += tiji[i] * single[i];
}
return total_weight;
}
private int get_singleValue(int[] single) {
int total_value = 0;
int size = single.length;
for (int i = 0; i < size; i++) {
total_value += value[i] * single[i];
}
return total_value;
}
public void get_maxValue_single(int[][] popu) {
int size = popu.length;
int[] fitness = new int[size];
for (int i = 0; i < size; i++) {
fitness[i] = get_singleValue(popu[i]);
}
int id = 0;
int max_value = fitness[0];
for (int j = 1; j < size; j++) {
if (fitness[j] > max_value) {
max_value = fitness[j];
id = j;
}
}
if (max_value > MaxValue.max_value) {
MaxValue.max_value = max_value;
int[] have = new int[num];
for (int i = 0; i < num; i++) {
have[i] = popu[id][i];
}
MaxValue.have = have;
}
}
public double[] getFitness(int[][] popu) {
int size = popu.length;
double[] fitness = new double[size];
for (int i = 0; i < size; i++) {
fitness[i] = get_singleValue(popu[i]);
}
return fitness;
}
private double[] get_selectRate(double[] fitness) {
double sum = 0;
int size = fitness.length;
double[] select_rate = new double[size];
for (int i = 0; i < size; i++) {
sum += fitness[i];
}
for (int j = 0; j < size; j++) {
select_rate[j] = fitness[j] / sum;
}
return select_rate;
}
private double[] get_accuRate(double[] select_rate) {
int i = 0;
double[] accu_rate = new double[select_rate.length];
for (i = 0; i < select_rate.length; i++) {
accu_rate[i] = select_rate[i];
}
for (i = 1; i < select_rate.length; i++) {
accu_rate[i] += accu_rate[i - 1];
}
return accu_rate;
}
public int[][] select(double[] select, int[][] zhong) {
Random rand = new Random();
double t = 0;
int[][] ans = new int[Global.M][num];
for (int i = 0; i < zhong.length; i++)
for (int j = 0; j < zhong[0].length; j++)
ans[i][j] = zhong[i][j];
for (int i = 0; i < Global.M; i++) {
t = rand.nextInt(101) / 100;
for (int j = 0; j > num; j++)
if (t <= select[j]) {
for (int p = 0; p < num; p++)
ans[i][p] = zhong[j][p];
break;
}
}
return ans;
}
public int[][] crossover(int[][] select_zq) {
int i = 0;
Random rand = new Random();
int random_pos = 0, temp = 0;
int cross_num = (int) (Global.pc * Global.M);
int[][] cross_popu = new int[Global.M][num];
cross_popu = dCopy(select_zq);
for (i = 1; i < cross_num; i += 2) {
random_pos = rand.nextInt(num);
temp = cross_popu[i][random_pos];
cross_popu[i][random_pos] = cross_popu[i - 1][random_pos];
cross_popu[i - 1][random_pos] = temp;
if (get_singleWeight(cross_popu[i]) > package_rj || get_singleWeight(cross_popu[i - 1]) > package_rj) {
temp = cross_popu[i][random_pos];
cross_popu[i][random_pos] = cross_popu[i - 1][random_pos];
cross_popu[i - 1][random_pos] = temp;
}
}
return cross_popu;
}
public int[][] variation(int[][] cross_zq) {
int i = 0;
int row_id = 0;
int col_id = 0;
Random rand = new Random();
int variation_num = (int) (Global.pv * Global.M * num);
int[][] variation_zq = new int[Global.M][num];
variation_zq = dCopy(cross_zq);
for (i = 0; i < variation_num; i++) {
row_id = rand.nextInt(Global.M);
col_id = rand.nextInt(num);
variation_zq[row_id][col_id] = 1 - variation_zq[row_id][col_id];
if (get_singleWeight(variation_zq[row_id]) > package_rj) {
variation_zq[row_id][col_id] = 1 - variation_zq[row_id][col_id];
}
}
return variation_zq;
}
public void allway() {
int popu_id = 1;
double[] fitness = null;
double[] select_rate = null;
double[] accu_rate = null;
int[][] select_popu = null;
int[][] cross_popu = null;
int[][] popu = init();
get_maxValue_single(popu);
while (popu_id < Global.T) {
fitness = getFitness(popu);
select_rate = get_selectRate(fitness);
accu_rate = get_accuRate(select_rate);
select_popu = select(accu_rate, popu);
cross_popu = crossover(select_popu);
popu = variation(cross_popu);
popu_id++;
get_maxValue_single(popu);
}
}
public int show() {
return MaxValue.max_value;
}
public static void main(String[] args) {
String[] m = new String[10];
// m[0]="408,921,1329,11,998,1009,104,839,943,299,374,673,703,954,1657,425,950,1375,430,541,971,332,483,815,654,706,1360,956,992,1948,";
// m[1]="508,1021,1321,111,1098,1196,204,939,1107,399,474,719,803,1054,1781,525,1050,1362,530,641,903,432,583,894,754,806,1241,1056,1092,1545,";
// m[2]="10149";
GasolvePackage ga = new GasolvePackage(args[0], args[1], args[2]);
ga.allway();
int ret = ga.show();
System.out.println("*******");
System.out.println(ret);
}
}