作业基本信息
这个作业属于哪个课程 | <2301-计算机学院-软件工程> |
---|---|
这个作业要求在哪里 | 软件工程实践第一次作业 |
这个作业的目标 | <完成一个具有可视化界面的计算器> |
其他参考文献 | 同类型计算器 |
Gitcode项目地址
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 10 | 20 |
Development | 开发 | 440 | 800 |
• Analysis | • 需求分析 (包括学习新技术) | 120 | 80 |
• Design Spec | • 生成设计文档 | 20 | 50 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 15 | 20 |
• Design | • 具体设计 | 20 | 70 |
• Coding | • 具体编码 | 180 | 415 |
• Code Review | • Code Review | 15 | 20 |
• Test | • 测试(自我测试,修改代码,提交修改) | 40 | 80 |
Reporting | 报告 | 180 | 180 |
• Test Repor | • 测试报告 | 90 | 80 |
• Size Measurement | • 计算工作量 | 40 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 50 | 70 |
合计 | 630 | 1000 |
解题思路描述
- 本次作业是使用js,html,css来实现具有可视化界面的计算器。
- 创建图形化界面: 通过tkinter库创建了一个GUI窗口,同时创建显示数学表达式的标签、输入数学表达式和显示算式结果的文本框和一个包含数字、运算符的按钮网格,每个按钮都与一个函数相关,单击按钮时调用改命令。
- 数学表达式计算: 本次作业为了简化运算逻辑,在每次键入功能性按键时,会对存储在缓存中的算数表达式先运算出结果,再将运算结果以及新键入的符号拼接,所以只需要使用正则将符号替换为标准运算符再使用js的math库进行运算即可
接口设计和实现过程
- package.json
{
"name": "calculator",
"version": "1.0.0",
"description": "software homework",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "jest"
},
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^26.2.3",
"jest": "^29.7.0"
}
}
- Calculator
使用Calculator类保存栈信息,算数优先级等
Calculator = function () {
this.stack = [],
this.num = 0,
this.res = 0,
this.buff = [false, false];
this.curr = true;
this.rank = {
'=': 0, '+': 1, '-': 1, '/': 2, '*': 2, 'yx': 3, 'x√y': 3
};
};
- HTML界面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="button.css" rel="stylesheet" type="text/css" />
<link href="index.css" rel="stylesheet" type="text/css" />
<title>Calculator</title>
</head>
<body>
<div class="calc-main">
<div class="calc-display" style="line-height: 61px;">
<span style="font-size: 45px;">0</span>
</div>
<div class="calc-left">
....
</div>
<div class="calc-right">
....
</div>
</div>
<script type="text/javascript" src="calc.js"></script>
</body>
</html>
- 响应鼠标事件
使用document的监听事件,对press,keydown,keyup进行监听,修改样式以及触发相应的计算逻辑
document.addEventListener('keypress', function (e) {
....
}, false);
document.addEventListener('keydown', function (e) {
....
}, false);
document.addEventListener('keyup', function () {
....
}, false);
- 计算与显示
主要方法介绍
-
calc 方法用于处理计算表达式。
-
init 方法用于初始化计算器状态。根据传入的参数 key,它可以清空计算堆栈或清空缓冲区。
-
render 函数用于将计算结果显示在计算器的显示屏上,并根据结果的宽度动态调整字体大小,以确保结果适应显示屏。
-
evalKey 函数根据用户输入的键执行相应的操作。它根据输入的键来调用不同的计算器方法,包括基本运算符和特殊函数,然后更新显示结果。
function evalKey(key) {
var dispVal = resBuffer.replace(/\s/g, '').replace(/Error|∞|-∞/, '0') + '',
_PI = Math.PI,
lastKey;
buffStr.push(key);
if ((buffStr[buffStr.length - 2] === '=' && key !== '=' &&
calculator.curr) || key === 'AC') {
buffStr = [key];
}
lastKey = buffStr[buffStr.length - 2];
if (key 为 右侧运算栏dom元素) {
} else {
const operators = {
'=': () => {
render((dispVal = calculator.calc('=', dispVal)))
calculator.curr = true;
},
'1/x': () => render((1 / dispVal) + ''),
'x2': () => render(Math.pow(dispVal, 2) + ''),
'x3': () => render(Math.pow(dispVal, 3) + ''),
'x!': () => render((function fak(n) {
return n < 0 || n > 170 ? NaN : n <= 1 ? 1 : n * fak(n - 1);
})(Math.round(+dispVal)) + ''),
'√': () => render(Math.sqrt(dispVal) + ''),
'log': () => render(Math.log(dispVal) / Math.log(10) + ''),
'sin': () => render(Math.abs(dispVal) === _PI ? '0' : Math.sin(dispVal) + ''),
'cos': () => render(Math.cos(dispVal) + ''),
'tan': () => render(Math.abs(dispVal) === _PI ? '0' : Math.tan(dispVal) + ''),
'ln': () => render(Math.log(dispVal) + ''),
'sinh': () => render(((Math.pow(Math.E, dispVal) - Math.pow(Math.E, -dispVal)) / 2) + ''),
'cosh': () => render(((Math.pow(Math.E, dispVal) + Math.pow(Math.E, -dispVal)) / 2) + ''),
'tanh': () => {
const e1 = Math.pow(Math.E, dispVal);
const e2 = Math.pow(Math.E, -dispVal);
render((e1 - e2) / (e1 + e2) + '');
},
'ex': () => render(Math.exp(dispVal) + ''),
};
if (operators[key])return operators[key]();
}
}
- calc方法注册到Calculator中,方便访问Calculator实例属性
Calculator.prototype.calc = function (key, val) {
var rank = this.rank;
//使用堆栈进行 '+|-|x|/' 运算
return this.res;
};
- 结果渲染
主要为错误代码显示以及数字过长时fontsize调整
function render(val) {
var fontSize = 45,
displayStyle = display.style,
displayParentStyle = display.parentNode.style;
if (val.match(/NaN|Inf|Error/)) {
val = 'Error';
} else{
resBuffer = val;
}
display.firstChild.data = val;
while (display.offsetWidth > display.parentNode.offsetWidth - (40)) {
displayStyle.fontSize = (fontSize--) + 'px';
displayParentStyle.lineHeight = (fontSize + 18) + 'px'
}
return val;
}
关键功能展示
- 加减乘除运算
-
附加功能
-
删除、清空功能
使用AC键快速删除stack以及置显示值为0
单元测试与性能分析
1. 单元测试
使用jtest框架进行测试
- 部分代码
const Calculator = require('./calculator');
describe('Calculator', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
it('should add two numbers', () => {
const result = calculator.calc('+', 2, 3);
expect(result).toBe('5');
});
it('should multiply two numbers', () => {
const result = calculator.calc('*', 4, 3);
expect(result).toBe('12');
});
it('should handle special functions', () => {
const result1 = calculator.calc('sin', Math.PI / 2);
const result2 = calculator.calc('ln', Math.E);
expect(result1).toBe('1');
expect(result2).toBe('1');
});
});
2. 性能改进
异常处理
由于js math库会将运算错误以NaN|Inf|Error形式抛出,所以只需要匹配这这字符并渲染即可
if (val.match(/NaN|Inf|Error/)) {
val = 'Error';
} else{
resBuffer = val;
}
心得体会
在为这个项目编写测试用例时,我使用了Jest测试框架来编写多个测试用例,覆盖了计算器的各种功能和边界情况。这包括基本运算、特殊函数、操作符优先级、清空操作、错误处理、自动调整字体大小等方面。
我意识到单元测试的重要性,通过测试可以及早发现代码中的问题,确保代码在修改和扩展时不会引入新的错误。选择合适的测试工具和持续集成流程有助于提高测试的效率和可维护性。
此外,编写清晰的注释和文档对于项目的可读性和可维护性非常重要。