文章出處

目錄請見:模塊化(零):綜述

第 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("答對了,恭喜\n");
                } else {
                    System.out.println("答錯了,加油\n");
                }
            }
            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 個屏幕才能看到。

在一段很長的代碼中,會有幾個比較大的功能塊,可以將其理解為一臺機器的 "零件"。

還是以 (等級一)選擇分支二 為例,標記結束并整理如下:

  1. 初始化用戶答案和標準答案數組
  2. 讓用戶選擇是否出乘除法的題目
  3. 生成題目并輸出,還有計算答案 的 for 循環
    1. 生成第一個真分數
    2. 生成第二個真分數
    3. 隨機獲取兩個運算符的坐標
    4. 輸出整個式子
    5. 求式子的標準答案
      1. 加法的情況
      2. 減法的情況
      3. 乘法的情況
      4. 除法的情況
    6. 根據標準答案的計算情況選擇標準答案的正確形式
  4. 獲取用戶輸入的答案、與標準答案比較
  5. 輸出標準答案
  6. 計算并輸出正確率

這里的每一項基本都能抽成一個模塊。

以下是標記完模塊后的代碼:


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("答對了,恭喜\n");
                } else {
                    System.out.println("答錯了,加油\n");
                }
            }
            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 返回結果。

這為之后的工作提供了便利。

接下去就是另一個世界,你準備好了嗎?


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()