---
# System prepended metadata

title: J_q11~q17 code review
tags: [CodeReview]

---

# q11~q17 code review

## code review by Mango
### common
* `askQuestion()`設計上要pure一些：
```javascript=
//原寫法：
import readline from "node:readline/promises";

// input setting {question, strValidators, numValidators}, validates and return userInput.
export async function askQuestion(setting) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });

    const { question, strValidators, numValidators } = setting;
    const userInput = await rl.question(question);

    rl.close();
    try {
        validator(strValidators, userInput);
        if (numValidators.length === 0) { //如果驗數字陣列為空
            return userInput; //傳出字串
        } else { //如果驗數字陣列不為空
            const number = Number(userInput);
            validator(numValidators, number);
            return number; //傳出數字
        }
    } catch (e) {
        console.log(e.message);
        return askQuestion(setting);
    }
}

function validator(validators, input) {
    validators.forEach((currentValid) => currentValid(input));
}
```
>我設計的`askQuestion()`得到`input`後判斷了「驗證字串」的陣列跟「驗證數字」的陣列，如果驗證數字的陣列是空的，傳出的結果會是「字串」，如果驗證數字陣列不為空，則傳出「數字」。

>但pure一點應該是都先傳出字串比較好，轉型別應該是得到結果以後再另外做。
* ~~validators可以嘗試使用`class`來做~~
>~~用想像的可能會是在每一題用`Validators`建立新的`validator`物件，然後從`validator`去呼叫裡面的`method`，ex.`validator.isNotEmpty()`、`validator.isNumber`...~~
~~還沒想好所以暫時這樣想~~
> Chris認為over design
* 設計上可以參考Titan、Serry、Angela、Vic、Ajay
>Titan：對於模組設計，Titan設計了很多，不一定要全懂，其中有些概念看懂且覺得很棒就理解完後做做看自己的版本。
Sherry、Angela、Vic、Ajay：對於解題的方法邏輯，Mango比較喜歡看這些人的。
Tim：本來說Tim也可以看，解果去看他的q17發現思維很「特殊」，所以先不看
* 對於迴圈的選擇
>討論了之前`do...while`、`for`...等之間迴圈的選擇，結論還是看情境，我跟他說我覺得`for`是用在確定次數的迴圈；`while`用在不確定次數的時機，Mango也這麼認為，所以他認為我用的`do...while`沒什麼問題，（我想大概問題就是在如果你用了但你==不知道為何而用==）
* 討論`return`出去的內容
>對於`return`出去的結果，分成「變數派」跟「運算派」
Mango認為多令一個變數來存運算結果通常是因為運算複雜，可能會無法快速看出這段運算到底做什麼，才會令一個名字更清楚的變數來存結果再傳出去，所以這段運算本身就很清楚他覺得就不需要
* 對於寫一段程式碼如何開始
>我自己是理解完題目後，先將題目會用到的邏輯寫出來，可能一個可能很多個，然後測試沒問題再將每個邏輯串起來，最後再將主體`main()`包好，Mango自己一開始也是這樣，他說像Chris或其他邏輯想法很完善的，一開始可能就設計架構跟大方向，考慮很多面向，但我們這個階段沒辦法也沒關係。
* 對於邏輯中需不需要驗證
>我跟她闡述了Chris的驗證garbage in一說，她也覺得OK，Mango覺得眾多學長姐中，Chris的寫測試經驗比較多，所以他對測試的想法會比較完整，Mango自己跟他看其他人在開發好像也比較少在寫測試。
話題回到邏輯中要不要驗證，當然說得通就好（Chris說的），而且==測試跟邏輯是分開的==，所以你在邏輯中加入驗證不會影響邏輯就無所謂。
### q17
跟Mango討論出題目的目的，他認為是如何將看到的圖表用程式來解讀與實現（我的寫法很明顯沒有練到）：
```
//題目
17. 運用 Borda Count。算出下面的候選人分數。
(第一順位得4分、第二順位得3分、第三順位得2分、第四順位得1分)
    | 51 | 5 | 23 | 21
1st | 1  | 3 | 2  | 4
2nd | 3  | 2 | 3  | 3
3rd | 2  | 4 | 4  | 2
4th | 4  | 1 | 1  | 1
```
```javascript=
//我用人工將它加總
const candidateOne = new Candidate("No.1", 51, 0, 0, 5 + 23 + 21);
const candidateTwo = new Candidate("No.2", 23, 5, 51 + 21, 0);
const candidateThree = new Candidate("No.3", 5, 51 + 23 + 21, 0, 0);
const candidateFour = new Candidate("No.4", 21, 0, 5 + 23, 51);
```
所以參考了Angela、Vic、Sherry的，都是表格用不同方式切開處理。

## Code Review by Wendy
### common 
* Wendy傾向一個function做一件事，裡面在做驗證他個人不喜歡
* 對於如何寫好code，多次提到了『Code Complete 2』這本書
* 什麼樣的code才叫好，他認為接手的人的觀感很重要

### q11
* magic number：只一些沒有經過解釋莫名其妙出現的數字，例如我的第一期會費打79折，乘上了0.79，但我沒有對這個數字多另一個變數或多做解釋，就會看不懂，而且這個0.79也可能改動，所以盡量不要有magic number的出現

### q15
* 我將器材跟重量都分開做出個別的陣列，比較散落，可以用物件包起來
* 命名可以更仔細，例如我的器材陣列叫`itemName`，`item`通常會用在array method這類的地方裡面
* code complete 2中提到function的參數不要超過7個，如果超過應該要想如何包裝分類，包成物件或陣列

### q17
* `getMaxNumber(array){}`的array命名問題，我自己發現裡面很亂，變數命名要重新設計
* 變數`eachDistrictVotes`命名也要修正

### validator
* 驗證可看看[source code](https://github.com/logaretm/vee-validate/tree/main/packages/rules/src)
裡面將所有驗證器都個別以檔案分開，最後在`import`進`index.ts`裡
**Chris說over design哈哈哈哈**
* `isNumber`這個驗證偏複雜，可以在裡面多一些註解
* 我設計`setting`這個物件給`getAnswer()`用，裡面有`strValidators`跟`numValidators`，但有時候不需要驗證數字`numValidators`這個陣列就會是空的，Wendy不習慣可能有空的陣列出現，可能可以設計成同一個驗證器陣列
* 裡面寫了`dotCode = 46`，但可以寫成`dotCode = ".".charCodeAt(0)`去存CharCode，就不用再去找也不怕寫錯代碼。
* 通常要被比較的會寫在左邊例如：`charCode < zeroCode`
* 驗證function的命名也可以參考上面的[source code](https://github.com/logaretm/vee-validate/tree/main/packages/rules/src)

# Code Review by Chris
## common
* 設計問題：function的參數哪些是必要哪些不是，如果非必要是不是就可以給預設值，如果使用者沒給設定就用預設值去跑。
* 使用新語法要知道版本支援度，我只記得最積極的做法就是要找出整個專案的版本最低限度，node在哪個版本以上才確定一切正常，然後寫在專案的聲明之類的地方（（忘記哪裡我再去問問
> 我最後一版的`getAnswer()`使用了`readline/promise`版本，至少要知道最低要求是`^18.13.0 of node`
* 只要用到async/await或是promise，都要練到兩者無痛切換，因為兩者是同一個東西，一定可以切換，、以每次寫到就盡量兩種都練熟。
* 對於class概念他寫了：
```javascript
const S = new N(); //class的組成，可從英文文法發想
S.V(O); //class是主詞，method是動詞
```
> `S`是`new`一個`class N`的結果，然後S就是主詞，N裡面的method是動詞，接收的參數是受詞（可有可無）。
他認為物件導向這樣設計比較合理
## getAnswer()
* 前面提到過的使用新語法要知道版本支援度
* 我的參數`setting`是一個物件，包含`question`、`strValidators`、`numValidators`，但Chris認為只有`question`是必要的，所以另外兩個驗證器可以給預設值（後來預設是空陣列），讓使用者不需要有驗證器也能得到問題的答案：
```javascript=
//原寫法
async function getAnswer(setting) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });

    const { question, strValidators, numValidators} = setting;

    const strInput = await rl.question(question);

    rl.close();
    try { 

        validator(strValidators, strInput);
        
        if (numValidators !== undefined) {
            const numInput = Number(strInput);
            validator(numValidators, numInput);
        }
        
        return strInput;
    } catch (e) {
        console.log(e.message);
        return getAnswer(setting);
    }
}
```
```javascript=
//Chris建議
async function getAnswer(question, setting = {}) { //預設setting為空物件
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });

    const { strValidators = [], numValidators = [] } = setting; //這邊預設兩個驗證器是空陣列

    const strInput = await rl.question(question);

    rl.close();
    try { 

        validator(strValidators, strInput);
        const numInput = Number(strInput);
        validator(numValidators, numInput);

        return strInput;
    } catch (e) {
        console.log(e.message);
        return getAnswer(setting);
    }
}
```
然後Chris說try的範圍要包整個，我只包取得答案以後，所以改成：
```javascript=
async function getAnswer(question, setting = {}) {
    try {
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout,
        });

        const { question, strValidators = [], numValidators = [] } = setting;
        const strInput = await rl.question(question); //promise兩種都練到

        rl.close();
        validator(strValidators, strInput);
        const numInput = Number(strInput);
        validator(numValidators, numInput);

        return strInput;
    } catch (e) {
        console.log(e.message);
        return getAnswer(question, setting);
    }
}
```
## q11
* 還是變數命名問題，用敘述的方式說明變數意義（（取名子好難，誰能救我
例如這題的每期500元，要完整說明用什麼來計算價格：
```javascript
const eachTermsPrice = 500;
```
用“期數”來計算價格，每期價格 = 500。

---

折抵用負數：
```javascript
const eachFiveTermsReturn = -200;
```
但這裡我覺得不通順，我名字都叫退回多少錢了，還退回-200，那豈不是負負得正。
```javascript=
function getTotalPriceByTerms(terms) {
    //計算完總價return總價
}

function messageOfPriceForMember(total_price) {
    return `總會費為：${total_price}`; //將總價印成字串
}

function countPriceAndPrint(terms) {
    const total_price = getTotalPriceByTerms(terms);
    return messageOfPriceForMember(total_price);
}
```
Chris覺得這樣閱讀起來比較通順，每個function名稱也要清楚說明，本來我叫`getTotalPrice()`，但他是根據什麼來計算總價，如果沒有參數名字就看不出來，所以改成`getTotalPriceByTerms()`。
## q13
這題就不寫哪裡要改，他直接現場展示他的想法：
```javascript=
const speed_meter_per_second = 20;
const back_step = -1;
const back_per_second = 5;
let total_step = 0;

export class Rabbit {
    constructor() {}
    goOneSecond() { //動作：走一秒
        total_step += speed_meter_per_second;
    }
    useProvocative(current_second) { //動作：使用挑釁
        if (current_second % back_per_second) total_step += back_step;
    }
    getTotalStep() { //取得當下位置（公尺）
        return total_step;
    }
}

let current_second = 0;
const finish_line = 1000;
const rabbit = new Rabbit();
const turtle = new Turtle(); //這還沒寫，先假裝有這個class

const contestants = [rabbit, turtle];

do { //背景設定好開始講故事
    rabbit.useProvocative(current_second);
    contestants.forEach((animal) => animal.goOneSecond());
    // rabbit.goOneSecond()

    current_second++;
} while (!(rabbit.getTotalStep() >= finish_line));
contestants.some((animal) => animal.getTotalStep() >= finish_line);

```
這邊Chris在class這個檔案中寫變數，就沒辦法從外面取得，他很自豪他超越JS語言實現真正的Private變數，因為他覺得使用者不需要知道任何動物的時速讓步等等資訊。
但實際怎麼樣等我測試後再說：
```javascript=
const speed_meter_per_second = 20;
const back_step = -1;
const back_per_second = 5;
let total_step = 0;
```
class的上方宣告了這幾個變數，這些變數理論上就只有在這個檔案才能取用，也就是其中的class。

---

然後這題他不考慮後退的時間，直接設定兔子5秒往後瞬移1公尺

`do`開始後：
```javascript
rabbit.useProvocative(current_second); //兔子先使用挑釁，但只有當秒數為5的倍數才會成功使出
contestants.forEach((animal) => animal.goOneSecond()); //接著每隻動物都跑一秒
```
Chris說這裡`contestants.forEach(...)`是『替代』，他可能想說借代修辭吧，就是本來平鋪直敘的敘述他用更簡單的方式代換，原本應該是：
```javascript
rabbit.goOneSecond();
turtle.goOneSecond();
//然後如果有其他種動物就接下去
```
然後寫完就會發現能將它們做成array然後用forEach替代

---
最後只要有任何一種動物通過終點就停止之類的：
```javascript
while(contestants.some((animal) => animal.getTotalStep() >= finish_line)){...}
```
以上程式碼都是想像後寫出來，還沒有實測過，所以主要只是分享概念，等我之後成功做出來再跟大家分享
## q14
寫得太簡短不想給他看結果不小心被看到，一樣是magic number的問題，20分鐘成長兩倍的20跟2沒有說明清楚，如果是Chris他就會註解寫『橫空出世的20跟2』，或者『20跟2是三小拉』，這樣就會知道沒有解釋的數究竟有多可怕。
## q17
思考一下建構class物件的時候，需要去思考哪些是必要參數，才需要放在constructor內，其他不必要參數就設定預設值，直到使用者想設定再覆蓋過去，例如：weights、orderTable這兩個參數，就台灣的選舉法，不會有這兩個變因，一人一票沒有順位問題，所以也沒有權重，所以預設就是沒有這兩個也能跑，直到使用者想跑Borda Count時，才需要這兩個要素，這時候再提醒使用者要設定後才能使用就好。
```javascript=
//原寫法
import { Election } from "./classElection.js";

export function useBordaCountAndPrintResult(
    numberOfCandidates,
    eachDistrictVotes,
    weights,
    orderTable
) {
    const vote = new Election(
        numberOfCandidates,
        eachDistrictVotes,
        weights,
        orderTable
    );
    
    const votesResultMessage = vote
        .useBordaCount()
        .map((currentPoints, index) => {
            return `${index + 1} 號候選人得票數：${currentPoints}`;
        })
        .join("\n");
    const winnerInfo = vote.getBordaCountWinner();

    const winnerMessage = `${winnerInfo.index
        .map((element) => element + 1)
        .join(", ")} 號獲勝，得票數：${winnerInfo.maxNumber}`; //也可做成method

    return { votesResultMessage, winnerMessage };
}

```

---
以下是我想像，還沒實測過：
```javascript=
//Chris建議
import { Election } from "./classElection.js";

export function useBordaCountAndPrintResult(vote) { //參數直接丟vote這個繼承Election類別的物件
    vote.setWeights(weights); //權重這個參數必要嗎？如果非必要是不是在這邊設定就好，不用在建構的時候就列進去，看不懂來問我
    vote.setOrderTable(orderTable); //順位選票表格也是，沒有必要就不放參數，在這邊設定就好

    vote.useBordaCount(); //用Borda Count算
    const votesResultMessage = vote.getMessageOfBordaCountResult(); //印出結果字串
    
    vote.getBordaCountWinner(); //判斷贏家
    const winnerMessage = vote.getWinnerMessage(); //印出贏家字串

    return { votesResultMessage, winnerMessage };
}
```

```javascript=
//將印出結果這個行為也做成method
class Election {
    constructor(numberOfCandidates, eachDistrictVotes) {
        this.numberOfCandidates = numberOfCandidates;
        this.eachDistrictVotes = eachDistrictVotes;
        //...
    }
    
    getMessageOfBordaCountResult() {
        this.bordaCountResult.map((currentPoints, index) => {
            return `${index + 1} 號候選人得票數：${currentPoints}`;
        })
        .join("\n");
    }
    
    getWinnerMessage() {
        return `${this.winnerInfo.index
        .map((element) => element + 1)
        .join(", ")} 號獲勝，得票數：${this.winnerInfo.maxNumber}`
    }
}
```

再來就是測試，改太多就來不及寫報錯的測試（garbage in）...
Chris說從第一題寫到十七題還是沒有寫（（笑
你越笑我越慌
但我有寫報錯拉，只是忘了寫到測試裡（（汗

這邊的測試可能要寫的內容有：
* 如果參選者數量跟順位數量不同要報錯
* 如果orderTable表格跟各選區票數對不起來也報錯
* 順位數量跟各權重數量不同報錯
* 還沒給weights跟orderTable就想執行useBordaCount()報錯
* 再來可能要測都沒有順位（台灣投票算法）
* 最後當然就側Borda Count算法