201971020107-郭清华 实验二 个人项目—— 《KP Project》项目报告
| 项目 | 内容 |
|---|---|
| 班级博客 | 2022年春软件工程课程班 |
| 作业要求 | 实验二 软件工程个人项目 |
| 学习目标 | 掌握软件项目个人开发流程;掌握Github发布软件项目的操作方法 |
| 目标实现 | 已掌握Git及GitHub的基本操作;对软件开发中项目代码的规范要求有了新的认识;对PSP进行了应用,对个人开发进行实践; |
| 项目地址 | KPProject |
任务1
- 点评链接1:
- 点评链接2:
- 点评链接3:
任务2
构建之法第一章——概论
- 软件=程序+软件工程 软件企业=软件+商业模式
- 程序(算法、数据结构)是基本功,但是在算法和数据结构之上,软件工程决定了软件的质量;商业模式决定了一个软件企业的成败。软件从业人员和软件企业的道德操守会极大地影响软件用户的利益。
- 软件工程
- 是把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上的过程。
- 软件工程包括下列领域:软件需求分析、软件设计、软件构建、软件测试和软件维护。
- 软件工程和下列的学科相关:计算机科学、计算机工程、管理学、数学、项目管理学、质量管理、软件人体工学、系统工程、工业设计和用户体验设计。
- 软件的特性
- 复杂性
- 不可见性
- 易变性
- 服从性
- 非连续性
- 软件工程与计算机科学的关系:计算机理论的进展会帮助软件工程(例如对程序正确性的分析);软件工程的进展(更好的工具,更多的应用领域)会帮助计算机科学家更有效地进行实验和探索。
构建之法第二章——个人技术和流程
- 好的单元测试的标准:
- 单元测试应该在最基本的功能/参数上验证程序的正确性。
- 单元测试必须由最熟悉代码的人来写。
- 单元测试过后,机器状态保持不变。
- 单元测试要快。
- 单元测试应该产生可重复、一致的结果。
- 独立性——单元测试的运行/通过/失败不依赖于别的测试,可以人为构造数据,以保持单元测试的独立性。
- 单元测试应该覆盖所有代码路径。
- 单元测试应该集成到自动测试的框架中。
- 单元测试必须和产品代码一起保存和维护。
- 回归测试的目的:
- 验证新的代码的确改正了缺陷
- 同时要验证新的代码有没有破坏模块的现有功能,有没有Regression
- 效能分析:
- 抽样(Sampling),抽样就是当程序运行时,Visual Studio时不时看一看这个程序运行在哪一个函数内,并记录下来。
- 代码注入(Instrumentation),代码注人就是将检测的代码加入到每一个函数中,这样程序的一举一动都被记录在案,程序的各个效能数据都可以被精准地测量。
- 个人开发流程(Personal Software Process)的特点:
- 不局限于某一种软件技术,而是着眼于软件开发的流程。
- 不依赖于考试,而主要靠工程师自己收集数据,然后分析,提高。
- 在小型、初创的团队中,很难找到高质量的项目需求,这意味着给程序员的输入质量不高。在这种情况下,程序员的输出(程序/软件)往往质量也不高,然而这并不能全部由程序员负责。
- PSP依赖于数据。
- PSP的目的是记录工程师如何实现需求的效率,而不是记录顾客对产品的满意度。
任务3
需求分析
- 正确读入实验数据文件的有效{0-1}KP数据
- 能够绘制任意一组{0-1}KP数据以价值重量为横轴、价值为纵轴的数据散点图
- 能够对一组{0-1}KP数据按价值重量比进行非递增排序
- 用户能够自主选择贪心算法、动态规划算法、回溯算法求解指定{0-1} KP数据的最优解和求解时间(以秒为单位)
- 任意一组{0-1} KP数据的最优解、求解时间和解向量可保存为txt文件或导出EXCEL文件
功能设计
- 数据读入:将用户选择的数据导入
- 散点图绘制:将用户选择的数据进行散点图的绘制,横轴为重量,纵轴为价值
- 数据排序:将用户选择的数据按照价值重量比非递增排序
- 01背包问题求解:将用户选择的数据及方法进行问题求解,有以下三种方法:
- 动态规划
- 贪心
- 回溯
- 将01背包问题求解得到的最优解,解向量,求解时间保存到相应的txt文件里
- 扩展功能:将散点图绘制得到的散点图进行保存(已实现)
设计实现
总体设计
该系统运行时会提示用户进行输入从而选择对应的功能,如果输入为“0”,则退出该系统;如果输入为1—4,则会转到对应的模块来实现用户的功能;当执行完对应功能后便会回到起始,即——提示用户选取相应的功能,直到用户输入“0”退出该系统为止。
工程结构
-
data:存放01背包实验数据
-
lib:存放引入的JFreeChart包
-
pic:存放导出的散点图
-
res:存放导出的解
-
src:源码
-
algorithm:该包中分别存放算法代码
- BT.class 回溯法对应的代码
- DP.class 动态规划法对应的代码
- Greedy.class 贪心法对应的代码
-
entity:实体类包
- Data.class 数据文件对应的实体类
-
utils:存放所用到的各种工具类
- DataUtils.class 与数据处理有关的类
- PicUtils.class 与散点图绘制的类
-
Main.class 程序的入口类
类之间的关系
-
模块实现
实验数据查看
数据查看的思路是通过Java的IO流中的FileReader来将文件按行读取并将数据封装在Data类中。
绘制散点图并导出
散点图的绘制是通过JFreeChart这个图表绘制类库来绘制的,通过将数据保存在一个二维数组中,然后通过JFreeChart的DefaultXYDataset和ChartFactory.createScatterPlot()来进行散点图的绘制;
散点图的导出是通过JFreeChart的工具类ChartUtilities.writeChartAsPNG()来将绘制的图像保存为一个png格式的图片。
对数据进行排序
该任务的实现是通过冒泡排序的方式进行。
问题求解并导出
- 动态规划法
\(0/1\)背包问题满足最优性原理,因此可以通过动态规划法进行求解。
该问题可以看作为一个决策序列\((x_1, x_2, ...,x_n)\) ,对任意一个变量 \(x_i\) 的决策是决定\(x_i\) 是\(1\)还是\(0\)。设 \(V(n,C)\) 表示将\(n\)个物品装入容量为\(C\)背包获得的最大价值,则初始子问题为把前\(i\)个物品装入容量为\(0\)的背包和把\(0\)个物品装入容量为\(j\)的背包中。即:
\[V(i, 0) = V(0, j) = 0 \qquad0 \leq i\leq n, 0\leq j\leq C \] 设\(V(i,j)\)表示将前\(i(1≤i
-
背包容量不足以装入物品\(i\),则装入前i个物品和前\(i-1\)个物品的价值是一样的;
-
背包容量可以装入物品\(i\),则选择装入前i个物品和前\(i-1\)个物品中价值大的哪个。
有如下递推式:
\[V(i,j)= \left\{ \begin{aligned} &V(i-1,\quad j) &j流程图
- 贪心法
先将物品按照价值重量比非递增排序,然后依次选取物品装入背包,直到无法装入为止。\(0/1\)背包问题不适合用贪心法,因为物品不能够被分割,背包可能无法装满,从而导致闲置的背包容量使得物品的单位重量价值降低。
- 回溯法
回溯法也就是从解空间树根节点出发,按照深度优先的策略对满足条件的解进行搜索。在该问题中,如果没有剪枝,则会导致程序运行时间过长,导致部分数据无法得到结果。
流程图
- 结果导出
通过Java IO流的FileWriter的writer方法将求得的结果存入对应的txt文件中
测试运行
- 文件读入
- 绘制散点图并导出
- 拓展功能——将图片导出
- 对数据进行排序
-
问题求解及导出
-
动态规划
- 贪心
- 回溯
- 结果导出
代码片段
项目代码规范说明
| 方面 | 规范 |
|---|---|
| 缩进 | 缩进采用4个空格,禁止使用tab字符。 |
| 变量命名 | 使用lowerCamelCase风格,必须遵从驼峰形式。 |
| 每行最多字符数 | 单行字符数限制不超过 120个,超出需要换行。 |
| 函数、类命名 | 类名使用UpperCamelCase风格,必须遵从驼峰形式;方法名使用lowerCamelCase风格,必须遵从驼峰形式。 |
| 常量 | 常量命名全部大写,单词间用下划线隔开。 |
| 空行规则 | 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。 |
| 注释规则 | 类、类属性、类方法的注释必须使用Javadoc规范;所有的类都必须添加创建者信息;方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。 |
| 操作符前后空格 | 任何运算符左右必须加一个空格;if/for/while/switch/do等保留字与左右括号之间都必须加空格;方法参数在定义和传入时,多个参数逗号后边必须加空格。 |
| 包名 | 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。 |
- 散点图的绘制及导出
/**
* 画散点图
*
* @param data 数据
* @param dataIndex 文件序号
* @throws IOException
*/
public static void scatterPlotPaint(Data data, int dataIndex) throws IOException {
double[][] tempData = new double[2][data.getN()];
//将Integer转换为int
for (int i = 0; i < data.getN(); i++) {
tempData[0][i] = Integer.parseInt(data.getW().get(i).toString());
}
for (int i = 0; i < data.getN(); i++) {
tempData[1][i] = Integer.parseInt(data.getV().get(i).toString());
}
DefaultXYDataset defaultXYDataset = new DefaultXYDataset();
//添加数据
defaultXYDataset.addSeries(" ", tempData);
//设置表头,x轴,y轴
JFreeChart chart = ChartFactory.createScatterPlot("", "Weight", "Value", defaultXYDataset, PlotOrientation.VERTICAL, true, true, false);
XYPlot xyplot = (XYPlot) chart.getPlot();
//设置背景面板颜色
xyplot.setBackgroundPaint(Color.white);
ValueAxis valueAxis = xyplot.getDomainAxis();
//设置坐标轴粗细
valueAxis.setAxisLineStroke(new BasicStroke(1.0f));
//输出PNG文件
OutputStream os_png = new FileOutputStream("pic/beibao" + dataIndex + ".png");
ChartUtilities.writeChartAsPNG(os_png, chart, 500, 500);
System.out.println("文件已导出到KP/pic/beibao" + dataIndex + ".png");
//以面板显示
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new java.awt.Dimension(560, 400));
//创建一个主窗口来显示面板
JFrame frame = new JFrame("散点图");
frame.setLocation(500, 400);
frame.setSize(600, 500);
//将主窗口的内容面板设置为图表面板
frame.setContentPane(chartPanel);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
- 动态规划
/**
* 动态规划
*
* @param w 物品重量
* @param v 物品价值
* @param n 物品个数
* @param C 背包容量
* @param x 解向量
* @return 最大价值
*/
public static int KnapsackDP(int[] w, int[] v, int n, int C, int[] x) {
int[][] V = new int[n + 1][C + 1];
//初始化第0列
for (int i = 0; i <= n; i++)
V[i][0] = 0;
//初始化第0行
for (int j = 0; j <= C; j++)
V[0][j] = 0;
//计算第i行,进行第i次迭代
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= C; j++) {
if (j < w[i - 1]) {
V[i][j] = V[i - 1][j];
} else {
V[i][j] = Math.max(V[i - 1][j], V[i - 1][j - w[i - 1]] + v[i - 1]);
}
}
}
//求装入背包的物品
for (int j = C, i = n; i > 0; i--) {
if (V[i][j] > V[i - 1][j]) {
x[i - 1] = 1;
j = j - w[i - 1];
} else {
x[i - 1] = 0;
}
}
//返回背包取得的最大价值
return V[n][C];
}
GitHub的使用
- 仓库
- commit
- release
- branch master为主分支,dev为开发分支
总结
该系统完全符合模块化设计,该项目将不同的功能划分为不同的模块,每个模块可以单独解决一个问题,所有的模块是通过Main类来进行调用从而完整的实现项目要求的功能,而且如果以后要添加,删除或修改功能均可快速找到对应的模块来进行修改,并且不影响其他模块的执行。
PSP
| PSP2.1 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
|---|---|---|
| Planning(计划) | 20 | 20 |
| Estimate(估计任务时间,规划大致步骤) | 20 | 20 |
| Development(开发) | 290 | 1030 |
| Analysis(需求分析) | 15 | 10 |
| Design Spec(生成设计文档) | 15 | 10 |
| Design Review(设计复审) | 10 | 10 |
| Coding Standard(代码规范) | 5 | 5 |
| Design(具体设计) | 30 | 60 |
| Coding(具体编码) | 180 | 900 |
| Code Review(代码复审) | 30 | 30 |
| Test(测试) | 5 | 5 |
| Reporting(报告) | 15 | 154 |
| Test Report(测试报告) | 5 | 5 |
| Size Measurement(计算工作量) | 5 | 5 |
| Postmortem & Process Improvement Plan(事后总结,提出过程改进计划) | 5 | 5 |
开发环节耗时最多,并且估计和实践相差巨大。
原因:
- 在设计的时候没有考虑全面,导致在开发过程中有些方法之间的调用出现问题
- 部分知识欠缺,在使用的时候不熟练
实践总结
对于本次实践,我使用了PSP来进行整体活动的安排,因为是第一次使用,部分环节的时间估计与实际差异很大。本次实践活动相比之前的课程设计之类的开发更具有条理性,活动更加科学,因此开发起来也是比较顺利的。通过此次实践,我也发现了我在开发方面的不足之处,比如:在根据需求来设计软件整体结构的时候考虑不全面,导致开发速度大幅降低。在以后的开发时,一定要重视前期的规划工作,这将会为之后的编码等工作带来极大的好处。