软件工程第三周 结对编程(201521123084)
软件工程第三周 结对编程(201521123084)
==========================================
1.项目成员
张翔:201521123107
李嘉廉:201521123091
林正晟:201521123084
结对编程码云地址:https://gitee.com/ljl36/pair_programming
2.需求分析
(1)现有代码的改进
①启用了错误历史记录;
②改正一些错误的编码和用法;
③修改代码样式;
④修改掉了如果错题集只有一道题,复习多道题时出现一模一样的多道题的bug;
⑤修正了界面中“开始”按钮可以重复点击的问题;
⑥修改了计时框中的计时可以随意更改的问题;
⑦发现并修改了界面左下角正确率显示不正确的问题。
(2)新开发功能的分析
①加入了乘方和括号参与运算
②随机产生更加复杂的表达式
③更加强大的去重算法
3.程序设计
(1)代码规范
ⅰ.使用tab进行代码缩进
ⅱ.断行与空白的{}行
‘{’与if和for在同一行,操作符的两边各留一个空格,逗号和分号也各留一个空格,如下图:
ⅲ.命名规范沿用原项目命名规则:
Arithmetic类
astr 返回答案字符串
qstr 返回问题字符串
int_operation() 整数计算函数
fra_operation() 分数计算函数
common_divisor(int m,int n)
公约数计算函数
toString() 输出函数
Frame类
hs 实例化History
hh 实例化QA_List
timer 实例化Work_Time
Time 计时器动态窗口
answer1~10 输入窗口
Review 复习按钮
question1~10 题目显示标签
Right_answer1~10 正确答案显示标签
Tip1~10 提示正确与否标签
Time_cost 所花时间显示标签
Right_percent 正确率显示标签
Set_question 开始按钮
jLabel5 历史正确题数
jLabel7 历史总题数
rn 单次正确题数
tot 单次总题数
Frame() Frame构造函数
initComponents() 窗体显示
xxxClicked(KeyEvent evt)
点击事件
KeyPressed(JLabel i,JLabel j,JLabel l,int x,JTextField k)
点击事件批量处理函数
Hide() 使不需要的输入窗口关闭
Tip(String answer,int i)提示对错并计入
Histroy类
qstr 问题字符串
astr 答案字符串
str 答案+问题字符串(写入文件)
str2 正确题数+总题数字符串(写入文件)
tot 总题数
rn 正确题数
qstrlist 问题字符串列(读入文件)
astrlist 答案字符串列(读入文件)
scan(String qstr,String astr)
qstr+" "+astr
scan2(int tot,int rn)
tot+" "+rn
Histroy_create() 历史文档生成
Histroy_save() 存储历史题目与答案
Histroy_saveNum() 存储历史题目数目
Histroy_read() 读入历史题目与答案
History_num() 读入历史题目数目
QA_List类
i 加入列题目个数
Qusetion 问题字符串列
Answer 答案字符串列
QA_List() QA_List构造函数
Test_Number类
i 显示Test_Number窗体次数
l 语言转化参数标识
Cancel_Button 取消按钮
Sure_Button 确定按钮
English 英文界面标签
Simplified_Chinese 简体中文界面标签
Traditional_Chinese 繁体中文界面标签
Number 所需题数
Test_Number() Test_Number构造函数
initComponents() Test_Number窗体显示
xxxClicked(KeyEvent evt)
点击事件
Test类
main(String[] args)
Work_Time类
x 00:00.x
y 00:y.00
z z:00.00
ⅳ.局部变量和方法按照驼峰风格命名,类名采用Pascal风格
(2)PSP表格
PSP2.1 | Personal Software Process Stages | Estimated time(min) | Actual time(min) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
· Estimate | 估计这个任务需要多少时间 | 60 | 60 |
Development | 开发 | 720 | 687 |
· Analysis | 需求分析 | 120 | 60 |
· Coding Standard | 代码规范 | 30 | 45 |
· Design | 具体设计 | 90 | 72 |
· Coding | 具体编码 | 300 | 240 |
· Code Review | 代码复审 | 60 | 120 |
· Test | 测试(自我测试,修改代码,提交修改) | 120 | 150 |
Reporting | 报告 | 150 | 168 |
测试报告 | 60 | 60 | |
计算工作量 | 30 | 48 | |
并提出过程改进计划 | 60 | 60 |
(3)流程
(4)关键函数类图
(5)单元测试、回归测试、覆盖率
①单元测试
由于我们要进行单元测试的方法为私有方法,但
[深入JUnit] 为什么别测试private函数
建议我们不对私有方法进行单元测试。即便如此我们还是尝试着做了一下:
②回归测试
回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。我们队伍是只保留了原有项目的UI界面,故不具备进行回归测试的条件。
③覆盖率
覆盖率解读:
根据生成的数据可以看到
ⅰ.QA_List.java的覆盖率为100%;
ⅱ.Test.java中有一个Test方法没有执行影响到了覆盖率,这也是我们百思不得其解的一个问题;
ⅲ.Work.java的覆盖率问题是因为我们在测试时的记录耗时太短而没有记录到;
ⅳ.Frame.java的覆盖率不高是因为我们在执行时没有选择进行语言的切换,在之后的测试中我们进行语言切换后,覆盖率会大幅增加;
ⅴ.Histroy.java中,因为在错题文件已经存在的情况下,创建文件的代码不会被覆盖,从而影响了覆盖率;
ⅵ.Arithmetic.java由于我们保留了必要的无参构造器而降低了覆盖率。
(6)效能分析
效能分析我们使用老师提供的JProfiler,JProfiler的内存视图部分可以提供动态的内存使用状况更新视图和显示关于内存分配状况信息的视图。所有的视图都有几个聚集层并且能够显示现有存在的对象和作为垃圾回收的对象。
①整体效能分析:
②活动内存:
③线程:
④CPU:
⑤垃圾回收器:
4.核心代码展示
public class Arithmetic {
private static int count(char c) {
if (c == '=')
return 0;
if (c == '(')
return 1;
if (c == '+')
return 2;
if (c == '-')
return 3;
if (c == '*')
return 4;
if (c == '/')
return 5;
if (c == '^')
return 6;
return 7;
}
private static boolean isNum(String exp, int pos) {
char ch = exp.charAt(pos);
if (ch >= '0' && ch <= '9') {
return true;
}
return false;
}
private static String trans(String exp) {
// 负数都用括号括起来
for (int i = 0; i < exp.length(); i++) {
if (exp.charAt(i) == '-' && exp.charAt(i - 1) == '(') {
exp = exp.substring(0, i) + '0' + exp.substring(i, exp.length());
i++;
}
}
// System.out.println(exp);
String postexp = new String();
int lpri[] = { 0, 1, 3, 3, 5, 5, 7, 8 }; // =(+-*/^)
int rpri[] = { 0, 8, 2, 2, 4, 4, 6, 1 };
ArrayList<Character> op = new ArrayList<>();
op.add('=');
for (int i = 0; i < exp.length();) {
if (isNum(exp, i)) {
while (i < exp.length() && isNum(exp, i)) {
postexp += exp.charAt(i);
i++;
}
postexp += '#';
} else {
if (lpri[count(op.get(op.size() - 1))] < rpri[count(exp.charAt(i))]) {
op.add(exp.charAt(i));
i++;
} else if (lpri[count(op.get(op.size() - 1))] == rpri[count(exp.charAt(i))]) {
op.remove(op.size() - 1);
i++;
} else {
postexp += op.get(op.size() - 1);
op.remove(op.size() - 1);
}
}
// for (Character character : op) {
// System.out.print(character + " ");
// }
// System.out.println();
}
while (!op.isEmpty() && op.get(op.size() - 1) != '=') {
postexp += op.get(op.size() - 1);
op.remove(op.size() - 1);
}
return postexp;
}
// 操作数不超过5个,操作符不超过4个,括号不超过3对,乘方只能有一个,而且次数只能小于等于3
private static final int MAX_NUMS = 5;
private static final int MIN_NUMS = 2;
private static final int MAX_PAIRS = 2;
private static String generateNewExp() {
// 产生基本的算式
String exp = new String();
Random random = new Random();
int nums = random.nextInt(MAX_NUMS - MIN_NUMS + 1) + MIN_NUMS;
int pairs = random.nextInt(MAX_PAIRS + 1);
HashMap<Integer, Integer> leftPosMap = new HashMap<>();
HashMap<Integer, Integer> rightPosMap = new HashMap<>();
for (int i = 0; i < nums; i++) {
leftPosMap.put(i + 1, 0);
rightPosMap.put(i + 1, 0);
}
for (int i = 0; i < pairs; i++) {
int leftPos = random.nextInt(nums) + 1;
int rightPos = random.nextInt(nums - leftPos + 1) + leftPos;
// System.out.println(leftPos + " " + rightPos);
leftPosMap.put(leftPos, leftPosMap.get(leftPos) + 1);
rightPosMap.put(rightPos, rightPosMap.get(rightPos) + 1);
}
boolean hasPow = false;
for (int i = 0; i < nums; i++) {
boolean needContinue = false;
if (i != 0) {
int op = random.nextInt(5);
switch (op) {
case 0:
exp += "+";
break;
case 1:
exp += "-";
break;
case 2:
exp += "*";
break;
case 3:
exp += "/";
break;
default:
if (hasPow) {
needContinue = true;
i--;
} else {
exp += "^";
hasPow = true;
}
break;
}
if (needContinue) {
continue;
}
}
int leftNums = leftPosMap.get(i + 1);
for (int j = 0; j < leftNums; j++) {
exp += "(";
}
if (exp.length() > leftNums && exp.charAt(exp.length() - leftNums - 1) == '^') {
int matchPos = i + 1;
for (int j = 0; j < leftNums; j++) {
exp = exp.substring(0, exp.length() - 1);
while (rightPosMap.get(matchPos) <= 0) {
matchPos++;
}
rightPosMap.put(matchPos, rightPosMap.get(matchPos) - 1);
}
exp += random.nextInt(4);
} else {
int generateNum = (random.nextInt(40) - 20);
if (generateNum < 0 && (leftPosMap.get(i + 1) == 0 || rightPosMap.get(i + 1) == 0)) {
exp += "(" + generateNum;
rightPosMap.put(i + 1, rightPosMap.get(i + 1) + 1);
} else {
exp += generateNum;
}
}
int rightNums = rightPosMap.get(i + 1);
for (int j = 0; j < rightNums; j++) {
exp += ")";
}
// System.out.println(exp);
}
return exp;
// 加括号
}
5.程序运行
主界面:
各语言下的答题界面:
复习:
错题集:
答题历史:
6.码云提交
7.结对编程过程展示
8.小结
在此次结对编程的实践中,我发现自己受益匪浅。我认为结对编程的好处,就在于身边有个领航员角色的存在,在编写代码的时候,一旦出现了错误,就会有人在旁边及时提醒自己的错误。在开发软件时,有同伴在身边,可以一起探讨,融合两个人不同的观点和见解,从而得出更加高效的设计思路,为之后的debug过程省去大把时间。
在此次结对中,我们比较特殊,是三个人共同对上届的代码进行改进和debug,过程其实十分有趣。我在当中主要是负责后缀表达式求值还有基本的数据结构,多亏有领航员的帮助,才能比较完整的完成自己的模块。在编写过程中,我时常会犯下一些非常简单确难以发现的错误,却会被身边的人及时发现,这样大大提高了编程效率。
在结对编程中,两个人轮流编程(虽然我们是三个人。。)不会太过疲惫,这极大地改善了我们的编程体验,使编程不会变得枯燥无味,debug也不会变的那么困难了,因为我们会努力地去做到1+1>2。