目录请见:模块化(零):综述
第 1 步:修改变量名
如果你改的是自己的代码,可以暂时不做这一步,跳到第二步。(毕竟你 现在暂时 还能看得懂前几天写的代码)
从初始代码可以看出,很多变量名不合适。例如:
int useNo = 0;// 控制操作符
这里的 useNo 用于控制是否能生成乘号和除号。通过 useNo 你无法知道这个变量名表示什么,就算你看了这行后面的注释,你也不知道这个变量是用来干嘛的。
这一行一开始被我改成:
int baseNum = 0; // 控制操作符是否能为乘号和除号
控制符号是否能为乘除号的代码是这样的(三个点表示省略中间的代码):
char[] op = { ' ', '+', '-', '*', '÷' };
...
if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
baseNum = 4;
} else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
baseNum = 2;
}
...
opIndex[k] = (int) (Math.random() * baseNum + 1);
...
switch(op[opIndex[g]){
...
这里使用 baseNum 作为基数,Math.random() 生成 0.0 到 1.0 之间的数。如果 baseNum 等于2,那么随机出来的数乘以基数后最大为2,因此无法取到乘号和除号。
在不对源代码进行较大改动的情况下,我能想到的最合适命名就是 baseNum 了。搭配代码之后的注释,读者应该比较容易理解。
变量名不是主角,就不讲太多了。
变量名重新命名后的代码:
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Scanner;
public class lastdemo {
/**
* 求两个整数的最大公约数
*
* @param num1
* @param num2
* @return num1 和 num2 的最大公约数
*/
private static int Gcd(int num1, int num2) {
num1 = Math.abs(num1);
num2 = Math.abs(num2);
int min = Math.min(num1, num2);
int maxSubmultiple = 1;
for (int i = min; i >= 1; i--) {
if (num1 % i == 0 && num2 % i == 0) {
maxSubmultiple = i;
break;
}
}
return maxSubmultiple;
}
public static void main(String[] args) {
Scanner inputs = new Scanner(System.in);
System.out.print("请输入你想要作答的题目数量:");
int problemsCount = inputs.nextInt();
System.out.print("请选择你要进行的运算:1.整数;2.真分数;");
int choice = inputs.nextInt();
if (choice == 1) {
double rightCount = 0;
for (int i = 1; i <= problemsCount; i++) {
int leftNum = (int) (Math.random() * 10 + 1);/* 防止出现不好处理的0,很不严谨不可取 */
int rightNum = (int) (Math.random() * 10 + 1);
int result = 0;
int operator = (int) (Math.random() * 4);
switch (operator) {
case 0:
System.out.print("第" + i + "题" + ": ");
System.out.print(leftNum + " + " + rightNum + " = ");
result = leftNum + rightNum;
break;
case 1:
if (leftNum < rightNum) {
int t = leftNum;
leftNum = rightNum;
rightNum = t;
}
System.out.print("第" + i + "题" + ": ");
System.out.print(leftNum + " - " + rightNum + " = ");
result = leftNum - rightNum;
break;
case 2:
System.out.print("第" + i + "题" + ": ");
System.out.print(leftNum + " × " + rightNum + " = ");
result = leftNum * rightNum;
break;
case 3:
System.out.print("第" + i + "题" + ": ");
if (leftNum < rightNum) {
int t = leftNum;
leftNum = rightNum;
rightNum = t;
}
if (leftNum % rightNum != 0) {
leftNum = (int) (Math.random() * 10 + 1) * rightNum;/* 保证能整除 */
}
System.out.print(leftNum + " ÷ " + rightNum + " = ");
result = leftNum / rightNum;
break;
}
int answer = inputs.nextInt();
if (answer == result) {
rightCount++;
System.out.println("答对了,恭喜
");
} else {
System.out.println("答错了,加油
");
}
}
System.out.println("True Rate:" + rightCount / problemsCount);
} else if (choice == 2) {
final int OP_MAX = 4;
char[] op = {' ', '+', '-', '*', '÷'};
int[] opIndex = new int[OP_MAX]; // 保存操作符下标
int baseNum = 0; // 控制操作符是否能为乘号和除号
boolean shouldOutputOp = true;
int[] nums = new int[4];
final int PROBLEMS_COUNT_MAX = 100;
String[] userAnswer = new String[PROBLEMS_COUNT_MAX];
String[] standardAnswer = new String[PROBLEMS_COUNT_MAX];
DecimalFormat decimal = new DecimalFormat("#.##");
decimal.setRoundingMode(RoundingMode.HALF_UP);
int leftNumerator;
int rightNumerator;
int resultDenominator;
int resultNumerator;
int gcd; // 最大公约数
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;
String simplestNumerator = "";
String simplestDenominator = "";
System.out.print("请选择是否需要乘除法算术题(Y or N):");
char useMultiAndDiv = inputs.next().charAt(0);
if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
baseNum = 4;
} else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
baseNum = 2;
}
System.out.println("请计算下列真分数计算题。(若无法运算请填入null)");
System.out.println("***************************************");
final int MAX_NUM = 10;
final int MIN_NUM = 1;
for (int i = 0; i < problemsCount; i++) {
System.out.print("(" + (i + 1) + ") ");
// 第一个真分数。nums[0]为分子,nums[1]为分母
for (int index = 0; index < 2; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 1) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
// 第二个真分数。nums[2]为分子,nums[3]为分母
for (int index = 2; index < 4; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 3) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
// 产生两个操作符下标。乘以2最多得2
for (int index = 0; index < 2; index++) {
opIndex[index] = (int) (Math.random() * baseNum + 1);
}
// 输出整个式子,index 表示正在输出的数字的位置
for (int index = 0; index < 4; index++) {
if (index % 2 == 0) {
System.out.print("(" + nums[index] + "/");
} else if (index % 2 == 1) {
System.out.print(nums[index] + ")");
if (shouldOutputOp) {
System.out.print(op[opIndex[0]]);
shouldOutputOp = false;
} else {
System.out.println("=");
}
}
}
shouldOutputOp = true;
// 求结果
for (int index = 0; index < 1; index++) {
// 不求最大公倍数,直接乘对方分母
resultDenominator = nums[1] * nums[3];
leftNumerator = nums[0] * nums[3];
rightNumerator = nums[2] * nums[1];
isDividedByZero = false;
isMultipliedByZero = false;
switch (op[opIndex[index]]) {
case '+':
resultNumerator = leftNumerator + rightNumerator;
gcd = Gcd(resultNumerator, resultDenominator);
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '-':
resultNumerator = leftNumerator - rightNumerator;
gcd = Gcd(resultNumerator, resultDenominator);
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '*':
resultNumerator = nums[0] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
// 分子有0则结果为0
if (nums[0] == 0 || nums[2] == 0) {
isMultipliedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '/':
// 除以一个数,等于乘以它的倒数
resultNumerator = nums[0] * nums[3];
resultDenominator = nums[1] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
if (nums[0] == 0 || nums[2] == 0) {
isDividedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
}
}
if (isDividedByZero) {
standardAnswer[i] = "null"; // 当第二个数的分子为零时无法进行除法运算
} else if (isMultipliedByZero) {
standardAnswer[i] = "0";
} else if (simplestNumerator.equals(simplestDenominator)) {
standardAnswer[i] = "1";
} else if (simplestDenominator.equalsIgnoreCase("1")) {
standardAnswer[i] = simplestNumerator;
} else {
standardAnswer[i] = simplestNumerator + "/" + simplestDenominator;
}
}
// 用户答题
int rightCount = 0;
System.out.println("请输入你的答案:");
for (int i = 0; i < problemsCount; i++) {
System.out.print((i + 1) + ":");
userAnswer[i] = inputs.next();
if (userAnswer[i].equals(standardAnswer[i])) {
rightCount++;
}
}
System.out.println("标准答案是 : ");
for (int i = 0; i < problemsCount; i++) {
System.out.println((i + 1) + ":" + standardAnswer[i]);
}
double trueRate = ((double) rightCount / (double) problemsCount) * 100;
System.out.println("True rate:" + decimal.format(trueRate) + "%");
System.out.println("**************************************");
} else {
System.out.println("请输入1或2:");
}
}
}
第 2 步:标记模块
在读懂代码的基础上,标出代码运行中每个步骤都做了什么。我在这里特别标出了各个分支的等级和分支号。等级从外到内递增,分支号从上到下递增。
在 (等级一)选择分支二
中有个 for 循环。它的代码太长,我在结尾处加上了一个注释:
// 输出题目并计算题目的答案
...
for (int i = 0; i < problemsCount; i++) {
...
} // 输出题目并计算答案结束
你可能要拉 3 - 4 个屏幕才能看到。
在一段很长的代码中,会有几个比较大的功能块,可以将其理解为一台机器的 "零件"。
还是以 (等级一)选择分支二
为例,标记结束并整理如下:
- 初始化用户答案和标准答案数组
- 让用户选择是否出乘除法的题目
- 生成题目并输出,还有计算答案 的 for 循环
- 生成第一个真分数
- 生成第二个真分数
- 随机获取两个运算符的坐标
- 输出整个式子
- 求式子的标准答案
- 加法的情况
- 减法的情况
- 乘法的情况
- 除法的情况
- 根据标准答案的计算情况选择标准答案的正确形式
- 获取用户输入的答案、与标准答案比较
- 输出标准答案
- 计算并输出正确率
这里的每一项基本都能抽成一个模块。
以下是标记完模块后的代码:
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Scanner;
public class LastDemo {
/**
* 求两个整数的最大公约数
*
* @param num1
* @param num2
* @return num1 和 num2 的最大公约数
*/
private static int Gcd(int num1, int num2) {
num1 = Math.abs(num1);
num2 = Math.abs(num2);
int min = Math.min(num1, num2);
int maxSubmultiple = 1;
for (int i = min; i >= 1; i--) {
if (num1 % i == 0 && num2 % i == 0) {
maxSubmultiple = i;
break;
}
}
return maxSubmultiple;
}
public static void main(String[] args) {
// 入口控制,基本输入
Scanner inputs = new Scanner(System.in);
System.out.print("请输入你想要作答的题目数量:");
int problemsCount = inputs.nextInt();
System.out.print("请选择你要进行的运算:1.整数;2.真分数;");
int choice = inputs.nextInt();
// (等级一)选择分支一
if (choice == 1) {
int rightCount = 0;
for (int i = 1; i <= problemsCount; i++) {
int leftNum = (int) (Math.random() * 10 + 1);/* 防止出现不好处理的0,很不严谨不可取 */
int rightNum = (int) (Math.random() * 10 + 1);
int result = 0;
// 根据随机的运算符进行相应的操作
int operator = (int) (Math.random() * 4);
switch (operator) {
case 0:
// (等级二)选择分支一
System.out.print("第" + i + "题" + ": ");
System.out.print(leftNum + " + " + rightNum + " = ");
result = leftNum + rightNum;
break;
case 1:
// (等级二)选择分支二
if (leftNum < rightNum) {
int t = leftNum;
leftNum = rightNum;
rightNum = t;
}
System.out.print("第" + i + "题" + ": ");
System.out.print(leftNum + " - " + rightNum + " = ");
result = leftNum - rightNum;
break;
case 2:
// (等级二)选择分支三
System.out.print("第" + i + "题" + ": ");
System.out.print(leftNum + " × " + rightNum + " = ");
result = leftNum * rightNum;
break;
case 3:
// (等级二)选择分支四
System.out.print("第" + i + "题" + ": ");
if (leftNum < rightNum) {
int t = leftNum;
leftNum = rightNum;
rightNum = t;
}
// 保证能整除
if (leftNum % rightNum != 0) {
leftNum = (int) (Math.random() * 10 + 1) * rightNum;
}
System.out.print(leftNum + " ÷ " + rightNum + " = ");
result = leftNum / rightNum;
break;
}
// 获取用户输入并判断
int answer = inputs.nextInt();
if (answer == result) {
rightCount++;
System.out.println("答对了,恭喜
");
} else {
System.out.println("答错了,加油
");
}
}
System.out.println("True Rate:" + (double) rightCount / problemsCount);
} else if (choice == 2) {
// (等级一)选择分支二
final int PROBLEMS_COUNT_MAX = 100;
String[] userAnswer = new String[PROBLEMS_COUNT_MAX];
String[] standardAnswer = new String[PROBLEMS_COUNT_MAX];
// 是否出现乘除法的题目
System.out.print("请选择是否需要乘除法算术题(Y or N):");
char useMultiAndDiv = inputs.next().charAt(0);
int baseNum = 0; // 控制操作符是否能为乘号和除号
if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
baseNum = 4;
} else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
baseNum = 2;
}
System.out.println("请计算下列真分数计算题。(若无法运算请填入null)");
System.out.println("***************************************");
// 输出题目并计算题目的答案
final int MAX_NUM = 10;
final int MIN_NUM = 1;
int[] nums = new int[4];
for (int i = 0; i < problemsCount; i++) {
System.out.print("(" + (i + 1) + ") ");
// 第一个真分数。nums[0]为分子,nums[1]为分母
for (int index = 0; index < 2; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 1) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
// 第二个真分数。nums[2]为分子,nums[3]为分母
for (int index = 2; index < 4; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 3) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
final int OP_MAX = 4;
int[] opIndex = new int[OP_MAX]; // 保存操作符下标
// 产生两个操作符下标。乘以2最多得2
for (int index = 0; index < 2; index++) {
opIndex[index] = (int) (Math.random() * baseNum + 1);
}
char[] op = {' ', '+', '-', '*', '÷'};
// 输出整个式子,index 表示正在输出的数字的位置
boolean shouldOutputOp = true;
for (int index = 0; index < 4; index++) {
if (index % 2 == 0) {
System.out.print("(" + nums[index] + "/");
} else if (index % 2 == 1) {
System.out.print(nums[index] + ")");
if (shouldOutputOp) {
System.out.print(op[opIndex[0]]);
shouldOutputOp = false;
} else {
System.out.println("=");
}
}
}
// 求结果
int leftNumerator;
int rightNumerator;
int resultDenominator;
int resultNumerator;
int gcd; // 最大公约数
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;
String simplestNumerator = "";
String simplestDenominator = "";
// 不求最大公倍数,直接乘对方分母
resultDenominator = nums[1] * nums[3];
leftNumerator = nums[0] * nums[3];
rightNumerator = nums[2] * nums[1];
switch (op[opIndex[0]]) {
case '+':
// (等级二)选择分支一
resultNumerator = leftNumerator + rightNumerator;
gcd = Gcd(resultNumerator, resultDenominator);
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '-':
// (等级二)选择分支二
resultNumerator = leftNumerator - rightNumerator;
gcd = Gcd(resultNumerator, resultDenominator);
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '*':
// (等级二)选择分支三
resultNumerator = nums[0] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
// 分子有0则结果为0
if (nums[0] == 0 || nums[2] == 0) {
isMultipliedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '/':
// (等级二)选择分支四
// 除以一个数,等于乘以它的倒数
resultNumerator = nums[0] * nums[3];
resultDenominator = nums[1] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
if (nums[0] == 0 || nums[2] == 0) {
isDividedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
}
if (isDividedByZero) {
standardAnswer[i] = "null"; // 当第二个数的分子为零时无法进行除法运算
} else if (isMultipliedByZero) {
standardAnswer[i] = "0";
} else if (simplestNumerator.equals(simplestDenominator)) {
standardAnswer[i] = "1";
} else if (simplestDenominator.equalsIgnoreCase("1")) {
standardAnswer[i] = simplestNumerator;
} else {
standardAnswer[i] = simplestNumerator + "/" + simplestDenominator;
}
} // 输出题目并计算答案结束
// 用户答题
int rightCount = 0;
System.out.println("请输入你的答案:");
for (int i = 0; i < problemsCount; i++) {
System.out.print((i + 1) + ":");
userAnswer[i] = inputs.next();
if (userAnswer[i].equals(standardAnswer[i])) {
rightCount++;
}
}
System.out.println("标准答案是 : ");
for (int i = 0; i < problemsCount; i++) {
System.out.println((i + 1) + ":" + standardAnswer[i]);
}
DecimalFormat decimal = new DecimalFormat("#.##");
decimal.setRoundingMode(RoundingMode.HALF_UP);
double trueRate = ((double) rightCount / (double) problemsCount) * 100;
System.out.println("True rate:" + decimal.format(trueRate) + "%");
System.out.println("**************************************");
} else {
// (等级一)判断分支三:输入非指定选项
System.out.println("请输入1或2:");
}
}
}
第 3 步:把标记的每个部分抽取到新的方法里
新创建的方法要尽量满足以下两个条件:
- 方法内不能直接使用全局变量。如果要使用外部的变量,使用参数传递;
- 每个方法在处理完一件事之后,使用 return 返回结果。
也就是说,把新创建的方法放在任何一个地方都不会编译错误。
3.1 先干掉大家伙
标记 (等级一)选择分支一
和 (等级一)选择分支二
分别表示整数的处理和真分数的处理。它们是在一块 if- else if - else
的分支结构中。
在某一个分支下,不应该有太多的代码。如果实在需要很多代码才能完成功能,那么就创建转发函数(方法)。
分析两个分支代码:
- 输入部分
仔细看一遍上面的代码,你会发现这两个分支只用到存在于分支外的一个变量problemsCount
。换句话说,分支只依赖于一个输入problemsCount
。 - 输出部分
在每一个选择分支结束后,程序就结束了,没有其他处理。这里就不需要返回任何值。
分析完输入输出之后,创建空方法:
private static void IntegerMode(int problemsCount) {
}
private static void FractionMode(int problemsCount) {
}
接着将标记为 (等级一)选择分支一
和 (等级一)选择分支二
的两块代码分别放入以上的 IntegerMode(int)
和 FractionMode(int)
里面。
这时候原来的分支就变成这样:
if (choice == 1) {
} else if (choice == 2) {
} else {
// (等级一)判断分支三:输入非指定选项
System.out.println("请输入1或2:");
}
此时在分支里面填上相应的转发函数。
public static void main(String[] args) {
// 入口控制,基本输入
Scanner inputs = new Scanner(System.in);
System.out.print("请输入你想要作答的题目数量:");
int problemsCount = inputs.nextInt();
System.out.print("请选择你要进行的运算:1.整数;2.真分数;");
int choice = inputs.nextInt();
if (choice == 1) {
IntegerMode(problemsCount);
} else if (choice == 2) {
FractionMode(problemsCount);
} else {
// (等级一)判断分支三:输入非指定选项
System.out.println("请输入1或2:");
}
}
至此,我们将 222 行的 main() 方法压缩到了 19 行。现在 main() 方法做了哪些事已经很清楚了。
但是还没结束,新创建的 IntegerMode(int)
和 FractionMode(int)
代码行数分别为 63 行和 172 行。
软件工程师通常一次只能看到 30-80 行源代码(相当于显示器的一屏) —— 《构建之法》第二版p23
我的小分辨率屏幕只能显示 30 行左右的代码。一旦一块代码超出这个数,就会对阅读代码的人造成一定的影响。
3.2 开始处理真分数模式
既然 FractionMode(int)
的行数比较多(172行),那么就帮它减肥吧!
以下是它的完整代码:
private static void FractionMode(int problemsCount) {
final int PROBLEMS_COUNT_MAX = 100;
String[] userAnswer = new String[PROBLEMS_COUNT_MAX];
String[] standardAnswer = new String[PROBLEMS_COUNT_MAX];
// 是否出现乘除法的题目
System.out.print("请选择是否需要乘除法算术题(Y or N):");
Scanner inputs = new Scanner(System.in);
char useMultiAndDiv = inputs.next().charAt(0);
int baseNum = 0; // 控制操作符是否能为乘号和除号
if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
baseNum = 4;
} else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
baseNum = 2;
}
System.out.println("请计算下列真分数计算题。(若无法运算请填入null)");
System.out.println("***************************************");
// 输出题目并计算题目的答案
final int MAX_NUM = 10;
final int MIN_NUM = 1;
int[] nums = new int[4];
for (int i = 0; i < problemsCount; i++) {
System.out.print("(" + (i + 1) + ") ");
// 第一个真分数。nums[0]为分子,nums[1]为分母
for (int index = 0; index < 2; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 1) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
// 第二个真分数。nums[2]为分子,nums[3]为分母
for (int index = 2; index < 4; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 3) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
final int OP_MAX = 4;
int[] opIndex = new int[OP_MAX]; // 保存操作符下标
// 产生两个操作符下标。乘以2最多得2
for (int index = 0; index < 2; index++) {
opIndex[index] = (int) (Math.random() * baseNum + 1);
}
char[] op = {' ', '+', '-', '*', '÷'};
// 输出整个式子,index 表示正在输出的数字的位置
boolean shouldOutputOp = true;
for (int index = 0; index < 4; index++) {
if (index % 2 == 0) {
System.out.print("(" + nums[index] + "/");
} else if (index % 2 == 1) {
System.out.print(nums[index] + ")");
if (shouldOutputOp) {
System.out.print(op[opIndex[0]]);
shouldOutputOp = false;
} else {
System.out.println("=");
}
}
}
// 求结果
int leftNumerator;
int rightNumerator;
int resultDenominator;
int resultNumerator;
int gcd; // 最大公约数
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;
String simplestNumerator = "";
String simplestDenominator = "";
// 不求最大公倍数,直接乘对方分母
resultDenominator = nums[1] * nums[3];
leftNumerator = nums[0] * nums[3];
rightNumerator = nums[2] * nums[1];
switch (op[opIndex[0]]) {
case '+':
// (等级二)选择分支一
resultNumerator = leftNumerator + rightNumerator;
gcd = Gcd(resultNumerator, resultDenominator);
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '-':
// (等级二)选择分支二
resultNumerator = leftNumerator - rightNumerator;
gcd = Gcd(resultNumerator, resultDenominator);
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '*':
// (等级二)选择分支三
resultNumerator = nums[0] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
// 分子有0则结果为0
if (nums[0] == 0 || nums[2] == 0) {
isMultipliedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '/':
// (等级二)选择分支四
// 除以一个数,等于乘以它的倒数
resultNumerator = nums[0] * nums[3];
resultDenominator = nums[1] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
if (nums[0] == 0 || nums[2] == 0) {
isDividedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
}
if (isDividedByZero) {
standardAnswer[i] = "null"; // 当第二个数的分子为零时无法进行除法运算
} else if (isMultipliedByZero) {
standardAnswer[i] = "0";
} else if (simplestNumerator.equals(simplestDenominator)) {
standardAnswer[i] = "1";
} else if (simplestDenominator.equalsIgnoreCase("1")) {
standardAnswer[i] = simplestNumerator;
} else {
standardAnswer[i] = simplestNumerator + "/" + simplestDenominator;
}
} // 输出题目并计算答案结束
// 用户答题
int rightCount = 0;
System.out.println("请输入你的答案:");
for (int i = 0; i < problemsCount; i++) {
System.out.print((i + 1) + ":");
userAnswer[i] = inputs.next();
if (userAnswer[i].equals(standardAnswer[i])) {
rightCount++;
}
}
System.out.println("标准答案是 : ");
for (int i = 0; i < problemsCount; i++) {
System.out.println((i + 1) + ":" + standardAnswer[i]);
}
DecimalFormat decimal = new DecimalFormat("#.##");
decimal.setRoundingMode(RoundingMode.HALF_UP);
double trueRate = ((double) rightCount / (double) problemsCount) * 100;
System.out.println("True rate:" + decimal.format(trueRate) + "%");
System.out.println("**************************************");
}
当看到 switch (op[opIndex[0]]) {...}
的时候,我的强迫症犯了,我想先从这里开始。其实应该先简化它外层的 for 循环,不过这次让强迫症赢了……
switch 这部分的完整代码如下:
int leftNumerator;
int rightNumerator;
int resultDenominator;
int resultNumerator;
int gcd; // 最大公约数
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;
String simplestNumerator = "";
String simplestDenominator = "";
// 不求最大公倍数,直接乘对方分母
resultDenominator = nums[1] * nums[3];
leftNumerator = nums[0] * nums[3];
rightNumerator = nums[2] * nums[1];
switch (op[opIndex[0]]) {
case '+':
// (等级二)选择分支一
resultNumerator = leftNumerator + rightNumerator;
gcd = Gcd(resultNumerator, resultDenominator);
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '-':
// (等级二)选择分支二
resultNumerator = leftNumerator - rightNumerator;
gcd = Gcd(resultNumerator, resultDenominator);
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '*':
// (等级二)选择分支三
resultNumerator = nums[0] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
// 分子有0则结果为0
if (nums[0] == 0 || nums[2] == 0) {
isMultipliedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
case '/':
// (等级二)选择分支四
// 除以一个数,等于乘以它的倒数
resultNumerator = nums[0] * nums[3];
resultDenominator = nums[1] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
if (nums[0] == 0 || nums[2] == 0) {
isDividedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
break;
}
-
输入部分
可以看出,每个分支都会依赖于三个数:resultDenominator 、 leftNumerator 、 rightNumerator 。
我们希望转发函数的名字为: FracAdd()、FracSub()、FracMulti()、FracDiv()。如果将上面三个数作为参数,转发函数的命名就会变得奇怪。
Frac 为 Fraction (分数) 的简写,这里不简写可能会更好
往上面的代码看,这三个数跟 nums[] 的四个数有关。四个数分别是运算符左边的分子分母和运算符右边的分子分母。将这四个数作为参数比较合适。
-
输出部分
每个分支都会产生的结果: simplestNumerator 、 simplestDenominator 。这两个结果组成了一个分数,可以将它们放到一个数组中,然后返回。
不过乘法和除法比较特殊,它们还会分别产生一个布尔变量: isMultipliedByZero 、 isDividedByZero 。可以让 simplestNumerator 为 0 表示 isMultipliedByZero ; 让 simplestDenominator 为 0 表示 isDividedByZero 。
老样子,在分析完输入输出之后,创建空方法:
private static int[] FracAdd(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
}
private static int[] FracSub(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
}
private static int[] FracMulti(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
}
private static int[] FracDiv(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
}
将 (等级二)选择分支一
、 (等级二)选择分支二
、 (等级二)选择分支三
、 (等级二)选择分支四
的代码分别放进去。不过由于之前直接使用 resultDenominator 这些结果,而现在用的参数是 nums[] 来的数,因此要先做处理。
处理之后的代码如下:
// (等级二)选择分支一
private static int[] FracAdd(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
// 不求最大公倍数,直接乘对方分母
int newLeftNumerator = leftNumerator * rightDenominator;
int newRightNumerator = rightNumerator * leftDenominator;
int resultDenominator = leftDenominator * rightDenominator;
int resultNumerator = newLeftNumerator + newRightNumerator;
int gcd = Gcd(resultNumerator, resultDenominator);
int[] simplestFrac = new int[2];
simplestFrac[0] = resultNumerator / gcd;
simplestFrac[1] = resultDenominator / gcd;
return simplestFrac;
}
// (等级二)选择分支二
private static int[] FracSub(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
// 不求最大公倍数,直接乘对方分母
int newLeftNumerator = leftNumerator * rightDenominator;
int newRightNumerator = rightNumerator * leftDenominator;
int resultDenominator = leftDenominator * rightDenominator;
int resultNumerator = newLeftNumerator - newRightNumerator;
int gcd = Gcd(resultNumerator, resultDenominator);
int[] simplestFrac = new int[2];
simplestFrac[0] = resultNumerator / gcd;
simplestFrac[1] = resultDenominator / gcd;
return simplestFrac;
}
// (等级二)选择分支三
private static int[] FracMulti(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
int[] simplestFrac = new int[2];
// 分子有0则结果为0
if (leftNumerator == 0 || rightNumerator == 0) {
simplestFrac[0] = 0;
} else {
int newLeftNumerator = leftNumerator * rightDenominator;
int newRightNumerator = rightNumerator * leftDenominator;
int resultDenominator = leftDenominator * rightDenominator;
int resultNumerator = newLeftNumerator * newRightNumerator;
int gcd = Gcd(resultNumerator, resultDenominator);
simplestFrac[0] = resultNumerator / gcd;
simplestFrac[1] = resultDenominator / gcd;
}
return simplestFrac;
}
// (等级二)选择分支四
private static int[] FracDiv(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
int[] simplestFrac = new int[2];
if (leftNumerator == 0 || rightNumerator == 0) {
simplestFrac[1] = 0;
} else {
// 除以一个数,等于乘以它的倒数
int resultNumerator = leftNumerator * rightDenominator;
int resultDenominator = leftDenominator * rightNumerator;
int gcd = Gcd(resultNumerator, resultDenominator);
simplestFrac[0] = resultNumerator / gcd;
simplestFrac[1] = resultDenominator / gcd;
}
return simplestFrac;
}
此时的 switch 变成了这样:
// 求结果
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;
int[] fracResult = new int[2];
switch (op[opIndex[0]]) {
case '+':
break;
case '-':
break;
case '*':
if (fracResult[0] == 0){
isMultipliedByZero = true;
}
break;
case '/':
if (fracResult[1] == 0){
isDividedByZero = true;
}
break;
}
String simplestNumerator = String.valueOf(fracResult[0]);
String simplestDenominator = String.valueOf(fracResult[1]);
将转发函数填充进去:
// 求结果
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;
int[] fracResult = new int[2];
switch (op[opIndex[0]]) {
case '+':
fracResult = FracAdd(nums[0], nums[1], nums[2], nums[3]);
break;
case '-':
fracResult = FracSub(nums[0], nums[1], nums[2], nums[3]);
break;
case '*':
fracResult = FracMulti(nums[0], nums[1], nums[2], nums[3]);
if (fracResult[0] == 0){
isMultipliedByZero = true;
}
break;
case '/':
fracResult = FracDiv(nums[0], nums[1], nums[2], nums[3]);
if (fracResult[1] == 0){
isDividedByZero = true;
}
break;
}
String simplestNumerator = String.valueOf(fracResult[0]);
String simplestDenominator = String.valueOf(fracResult[1]);
3.3 用户答题
刚解决掉一个,在统一屏幕又看到了另一个模块的标题 “用户答题”。
-
输入部分
这个就简单了,它只需要两个参数: problemsCount 和 standardAnswer 。甚至连 problemsCount 都可以去掉,只剩下一个。
-
输出部分
这个更简单:无。
老套路,创建空方法:
private static void fracHandleUserAnswer(int problemsCount, String[] standardAnswer) {
}
然后把代码转移进去。
...
实在不想再复制了,就把结果略了吧。
3.4 生成标准答案
在 3.2 这一节中,完成了对 switch 的简化。正如 switch 上面的模块注释所说:
// 求结果
switch 连同它下面的 if - else if - else
一起,是为了根据 nums[] 的内容生成答案。
- 输入部分
数值 nums[] 和运算符 op[opIndex[0]] 。这个运算符可以提前获取,因此不必将 op[] 和 opIndex[] 都传进去。 - 输出部分
String 类型的结果
创建空方法:
private static String fracGetStandardAnswer(int[] nums, char operator) {
}
这次的移入只需将 op[opIndex[0]] 替换成 operator 。
3.5 随机生成分数
再往上看,最明显的需要提取的部分就是随机生成真分数这部分了。
// 第一个真分数。nums[0]为分子,nums[1]为分母
for (int index = 0; index < 2; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 1) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
// 第二个真分数。nums[2]为分子,nums[3]为分母
for (int index = 2; index < 4; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 3) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
生成第一个真分数和生成第二个真分数的代码几乎完全一样,只有 index 不一样。
这部分的代码可以简化,不过还是先尽量保持原样,等以后再简化吧。
- 输入部分
只有 MAX_NUM 和 MIN_NUM 。不过这两个是我为了消灭 magic number 而创建的,完全可以放到新建的方法里面。 - 输出部分
第一个部分生成 nums[0] 和 nums[1] ,第二个部分生成 nums[2] 和 nums[3] 。它们共同的特征是:生成两个数。
这个套路我们是不是见过?在 FracAdd() 那里就是生成两个数。那么这次我们也这么做。
创建一个空壳函数:
private static int[] getRandomFrac(){
}
转移代码:
private static int[] getRandomFrac(){
final int MAX_NUM = 10;
final int MIN_NUM = 1;
int[] nums = new int[2];
for (int index = 0; index < 2; index++) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
if (index == 1) {
// 保证分子不大于分母,以及分母不为零
while (nums[index - 1] > nums[index] || nums[index] == 0) {
nums[index] = (int) (Math.random()
* (MAX_NUM - MIN_NUM) + MIN_NUM);
}
}
}
return nums;
}
原来的地方变成:
int[] randomNums;
// 第一个真分数。nums[0]为分子,nums[1]为分母
randomNums = getRandomFrac();
nums[0] = randomNums[0];
nums[1] = randomNums[1];
// 第二个真分数。nums[2]为分子,nums[3]为分母
randomNums = getRandomFrac();
nums[2] = randomNums[0];
nums[3] = randomNums[1];
现在 for 循环已经从 131 行缩减到 40 行了,还能更少吗?能!
3.6 随机产生操作符
在原来的代码中,先随机生成运算符的下标,当用到运算符的时候再去 op[] 里取出字符。有点儿绕。
final int OP_MAX = 4;
int[] opIndex = new int[OP_MAX]; // 保存操作符下标
// 产生两个操作符下标。乘以2最多得2
for (int index = 0; index < 2; index++) {
opIndex[index] = (int) (Math.random() * baseNum + 1);
}
char[] op = {' ', '+', '-', '*', '÷'};
...
char operator = op[opIndex[0]];
可以把这部分的代码抽取出来,直接做成返回运算符的模块。
- 输入部分
只有一个: baseNum 。当产生操作符的时候,需要它的值来控制是否能生成乘除运算符。不过它的值是由更上面的代码决定的,能否延迟决定它的值呢?因为这样可以让看代码的人不必知道 baseNum 的值。 - 输出部分
字符型的运算符。
先进入支线任务:解决 baseNum 的问题。哪些代码决定 baseNum 的值呢?
Scanner inputs = new Scanner(System.in);
char useMultiAndDiv = inputs.next().charAt(0);
int baseNum = 0; // 控制操作符是否能为乘号和除号
if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
baseNum = 4;
} else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
baseNum = 2;
}
如果延迟 baseNum 的赋值,如何保证其效果仍然按照用户的输入来决定?可以用布尔值来代替 baseNum 作为是否使用乘除的状态。
char choice = inputs.next().charAt(0);
boolean useMultiAndDiv = false;
if (choice == 'Y' || choice == 'y') {
useMultiAndDiv = true;
} else if (choice == 'N' || choice == 'n') {
useMultiAndDiv =false;
}
那么在后面的代码中,只需判断 useMultiAndDiv 的值就知道该给 baseNum 赋什么值了。
回到提取代码的主线任务来,创建空壳新方法:
private static char getRandomOp(boolean useMultiAndDiv){
}
转移代码:
private static char getRandomOp(boolean useMultiAndDiv){
int baseNum = useMultiAndDiv ? 4:2;
char[] ops = {' ', '+', '-', '*', '÷'};
int opIndex = (int) (Math.random() * baseNum + 1);
return ops[opIndex];
}
在原来的地方,只需这样调用:
char operator = getRandomOp(useMultiAndDiv);
麻烦的随机运算符终于没了。继续?
3.7 输出真分数式子
for 循环里面剩下的比较大块的代码就剩下面这部分了:
// 输出整个式子,index 表示正在输出的数字的位置
boolean shouldOutputOp = true;
for (int index = 0; index < 4; index++) {
if (index % 2 == 0) {
System.out.print("(" + nums[index] + "/");
} else if (index % 2 == 1) {
System.out.print(nums[index] + ")");
if (shouldOutputOp) {
System.out.print(operator]);
shouldOutputOp = false;
} else {
System.out.println("=");
}
}
}
这部分其实可以简化得很简单的,不过还是按照 “尽量不修改原来代码” 的原则来,把优化放到后面。
- 输入部分
由于是输出式子,因此只需两样: nums[] 、 operator 。 - 输出部分
这个部分只是将式子 print 出来,不需要传出什么结果。
创建空方法:
private static void printFrac(int[] nums,char operator){
}
代码迁移:
// 输出整个式子,index 表示正在输出的数字的位置
private static void printFrac(int[] nums,char operator){
boolean shouldOutputOp = true;
for (int index = 0; index < 4; index++) {
if (index % 2 == 0) {
System.out.print("(" + nums[index] + "/");
} else if (index % 2 == 1) {
System.out.print(nums[index] + ")");
if (shouldOutputOp) {
System.out.print(operator);
shouldOutputOp = false;
} else {
System.out.println("=");
}
}
}
}
原来的地方就剩下一句话:
printFrac(nums,operator);
3.8 真分数模式最后的处理
猜猜 for 循环剩下几行?
// 输出题目并计算题目的答案
int[] nums = new int[4];
for (int i = 0; i < problemsCount; i++) {
System.out.print("(" + (i + 1) + ") ");
int[] randomNums;
randomNums = getRandomFrac();
nums[0] = randomNums[0];
nums[1] = randomNums[1];
randomNums = getRandomFrac();
nums[2] = randomNums[0];
nums[3] = randomNums[1];
char operator = getRandomOp(useMultiAndDiv);
printFrac(nums,operator);
standardAnswer[i] = fracGetStandardAnswer(nums, operator);
} // 输出题目并计算答案结束
只剩下 19 行啦!
赶紧运行一下试试,看能否正常运行。居然没什么问题!因为一开始就坚持 “尽量不修改原来代码” 的原则,只要原来的代码不出现问题,提取之后也不会出问题。
等等!原来的代码有问题怎么办?经过一番测试之后……果然有!
来看看原来的分数乘法部分(...三个点表示省略中间代码):
// 不求最大公倍数,直接乘对方分母
resultDenominator = nums[1] * nums[3];
leftNumerator = nums[0] * nums[3];
rightNumerator = nums[2] * nums[1];
...
// (等级二)选择分支三
resultNumerator = nums[0] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
// 分子有0则结果为0
if (nums[0] == 0 || nums[2] == 0) {
isMultipliedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);
在统一分母时,分子也与分母同乘一个数。两分母相乘得到新的统一的分母。这时候的状态是产生了新的表达式,还没开始乘法。接下去应该是两个新分子相乘,两个新分母相乘。
问题来了!上面的代码中,分母没有乘,分子相乘时用的是旧的值。
不过神奇的是,在当前版本的代码中,我们只需往 FracMulti()
里添加:
resultDenominator = resultDenominator * resultDenominator;
这是因为之前往里面填充代码的时候,看到加减乘除这四个操作基本相同,就从上面的减法里复制代码过来做少量的修改。
3.9 轮到整数模式了
不知道你发现没,整数模式和分数模式的用户输入处理方式不一样!整数模式下是出一题回答一题,分数模式下是题目全部出来再回答。为何不统一起来呢?这样就可以把这两部分合并起来了。
在 3.3 用户答题
中,已经将分数的处理封装起来了。它接收两个参数: problemsCount 和 standardAnswer[] 。
problemsCount 很容易,在进入 IntegerMode 的时候就有了。现在需要创建 standardAnswer[] 数组,在原来处理输入的地方换上存储答案到 standardAnswer 。接着在 for 循环外面使用 handleUserAnswer() 。
其他没什么说的,比真分数模式的处理简单很多。修改后的代码如下:
// (等级一)选择分支一
private static void IntegerMode(int problemsCount) {
String[] standardAnswer = new String[PROBLEMS_COUNT_MAX];
for (int i = 0; i < problemsCount; i++) {
System.out.print("(" + (i + 1) + ") ");
// 防止出现不好处理的0,很不严谨不可取
int[] nums = new int[2];
nums[0] = (int) (Math.random() * 10 + 1);
nums[1] = (int) (Math.random() * 10 + 1);
// 根据随机的运算符进行相应的操作
int standardResult;
char operator = getRandomOp(true);
switch (operator) {
case '+':
standardResult = addInteger(nums);
break;
case '-':
standardResult = subInteger(nums);
break;
case '×':
standardResult = multiInteger(nums);
break;
case '÷':
standardResult = divInteger(nums);
break;
default:
standardResult = 0;
break;
}
System.out.println(nums[0] + " " + operator + " " + nums[1] + " = ");
standardAnswer[i] = String.valueOf(standardResult);
}
handleUserAnswer(problemsCount,standardAnswer);
}
// (等级二)选择分支一
private static int addInteger(int[] nums) {
int result = 0;
for (int num : nums) {
result += num;
}
return result;
}
// (等级二)选择分支二
private static int subInteger(int[] nums) {
if (nums[0] < nums[1]) {
int t = nums[0];
nums[0] = nums[1];
nums[1] = t;
}
return nums[0] - nums[1];
}
// (等级二)选择分支三
private static int multiInteger(int[] nums) {
int result = 1;
for (int num : nums) {
result *= num;
}
return result;
}
// (等级二)选择分支四
private static int divInteger(int[] nums) {
if (nums[0] < nums[1]) {
int t = nums[0];
nums[0] = nums[1];
nums[1] = t;
}
// 保证能整除
if (nums[0] % nums[1] != 0) {
nums[0] = (int) (Math.random() * 10 + 1) * nums[1];
}
return nums[0] / nums[1];
}
3.10 接下去是另一个世界
可算是把大部分代码都模块化了!真是不容易啊!
要结束了么?
We still have a long way to go.
为什么?尽管进行了一定的模块化,但所有的代码仍然在一个 .java
文件里面。并且随着模块化的进行,方法越来越多,又杂又乱。
我们似乎可以将这些方法划分开来,并且放到不同的 .java
(或者说不同的类) 里面。好像有点麻烦,不过所幸之前为此做了准备。
还记得 第 3 步
刚开始说的两点吗?
- 方法内不能直接使用全局变量。如果要使用外部的变量,使用参数传递;
- 每个方法在处理完一件事之后,使用 return 返回结果。
这为之后的工作提供了便利。
接下去就是另一个世界,你准备好了吗?