---
# System prepended metadata

title: F_Q1-Q5 code review
tags: [CodeReview]

---

## Titan
### Q2
命名用縮寫：例如rl？
建議封裝成function，可叫做"askQuestion"或是"getUserInput"之類的名稱

要去釐清意圖：主程式單純為了使用者輸入，我的主程式會看起來複雜

rl.question是callback，容易有callback hell，練習用promise >> 再用async await

驗證部份:
```javascript=
isEmpty(input);
isInteger(input);
isNonNegativeInteger(input);

//Titan建議 尚未理解...
function(input,[isEmpty,isInteger,isNonNegativeInteger])
```
isNonNegativeInteger(input)的命名，取名變成反向？

每一題驗證客製錯誤訊息(看個人)
可以有字串驗證器，數字驗證器

驗證器方向：
* 預設驗證器 >> 可以共用
* 客製化驗證器 >>用在每一題客製




```javascript!
export function compareRemainder([firstNumber, secondNumber]) {
  const divisor = 3;
  if (firstNumber % divisor === secondNumber % divisor) {
    return `餘數相同`;
  } else {
    return `餘數不同`;
  }
}

//建議餘數相同、餘數不同不要寫死，並用布林值方式傳出去給main
//divisor可以當作參數
```
[function signature函數簽名](https://developer.mozilla.org/zh-CN/docs/Glossary/Signature/Function)：定義函數的輸入輸出
我的函式會因為外部傳入的參數而配合改變



### Q5module
複用
例如：(n-2)*2重複，宣告變數存放，之後好維護

建議都用const宣告

## 季芳
#### Q2
```javascript!
//原本寫法
//在全域宣告一個answer的空陣列存放，容易被改動到
const array = [];
main();
function main() {
  rl.question(`輸入第1個整數數字：`, (inputNumber) => {
    try {
      isEmpty(inputNumber);
      isInteger(inputNumber);
      isZeroAndPositiveInteger(inputNumber);

      const integerOne = Number(inputNumber);
      array.push(integerOne);
      askSecondQuestion(integerOne);

      function askSecondQuestion(integerOne) {
        rl.question(`輸入第2個整數數字：`, (inputNumberTwo) => {
          try {
            isEmpty(inputNumberTwo);
            isInteger(inputNumberTwo);
            isZeroAndPositiveInteger(inputNumberTwo);

            const integerTwo = Number(inputNumberTwo);
            array.push(integerTwo);
            const result = compareRemainder(integerOne, integerTwo, 3);
            console.log(result);
            rl.close();
          } catch (error) {
            console.log(error.message);
            askSecondQuestion(integerOne);
          }
        });
      }
    } catch (error) {
      console.log(error.message);
      main();
    }
  });
}
```
```javascript!
//修改成把數字存放在askSecondQuestion，function的參數，不使用array
//把askSecondQuestion拉出來跟第一個rl.question同層

// const array = [];
main();
function main() {
  rl.question(`輸入第1個整數數字：`, (inputNumber) => {
    try {
      isEmpty(inputNumber);
      isInteger(inputNumber);
      isZeroAndPositiveInteger(inputNumber);

      const integerOne = Number(inputNumber);
      // array.push(integerOne);
      askSecondQuestion(integerOne);
    } catch (error) {
      console.log(error.message);
      main();
    }
  });
    
  function askSecondQuestion(integerOne) {
    rl.question(`輸入第2個整數數字：`, (inputNumberTwo) => {
      try {
        isEmpty(inputNumberTwo);
        isInteger(inputNumberTwo);
        isZeroAndPositiveInteger(inputNumberTwo);

        const integerTwo = Number(inputNumberTwo);
        // array.push(integerTwo);
        const result = compareRemainder(integerOne, integerTwo, 3);
        console.log(result);
        rl.close();
      } catch (error) {
        console.log(error.message);
        askSecondQuestion(integerOne);
      }
    });
  }
}
```
* 主程式main巢狀，建議改用Promise >> 再用async await
```javascript!
//answer.length<2，我的2寫死，也有另一種寫法Array.fill做出陣列
if (answer.length < 2) {
        main();
      } else {
        const result = compareRemainder(answer, divisor);
        console.log(result);
        rl.close();
      }

```
* 如果是+0、-0、科學記號，我的驗證都會過
* 都用const，有要修改再改成let


寫糙code >> 再來調整：
1. 重構 
2. 語法
3. 排版
4. 寫完看學長姐code，看看別人的邏輯，可以多學習


## Voss
程式是由上到下，會建議main()寫在function main之後
```javascript!
function main(){
 ...
}
main();
```
* 宣告的位置要注意，會考慮作用域、效能等
* function命名一般都是動詞開頭，例如：askQuestion、getPrice等
* 如要註解用git commit幫你紀錄
* let和const的選擇，會先選用const，別人看到你用let會想說是不是等等哪裡會更動到

#### 驗證包裝問題
```javascript!
//原本這些驗證不知道怎麼包入validator裡面，因為isMoreThanNumber有兩個參數
 isEmpty(userInput);
 isInteger(userInput);
 isPositiveNumber(userInput);
 isEven(userInput);
 isMoreThanNumber(userInput, 4);


//橘子修改
//使用柯里化方式，把原本isMoreThanNumber(input,number)兩個參數拆分，function return一個function
function isMoreThanNumber(number) {
  return function (input) {
    if (Number(input) < number) {
      throw new Error(`請輸入大於${number}的數字`);
    }
  };
}

//isMoreThanNumber就能帶一個參數跑驗證了
validator(userInput, [isInteger,isPositiveNumber,isEven,isMoreThanNumber(4)]);

//驗證器
function validator(input, otherValidator) {
  isEmpty(input); //共同，其他驗證用陣列跑forEach呼叫
  otherValidator.forEach((item) => {
    item(input);
  });
}
```
#### return
```javascript!
//return 盡量都不要再有計算，就傳出要的東西就好
//原本
function listFomula(n) {
  if (number === 4) {
    return String(number - 2) + "*" + String(number);
  } else {
    return (
      listFomula(number - 2) + "+" + String(number - 2) + "*" + String(number)
    );
  }
}

//修改成樣板字面值
function listFomula(n) {
  let list = "";
  if (n === 4) {
    list = `${n - 2}*${n}`;
    return list;
  } else {
    list = `${listFomula(n - 2)}+${n - 2}*${n}`;
    return list;
  }
}
```
for loop初始值，永遠都是從==0==開始

## Chris
### Q2
1. stdin(standard in) 一般是keyboard，stdout(standard out) 一般是screen
2. 如果有轉型的地方，用[匈牙利命名法](https://zh.wikipedia.org/zh-tw/%E5%8C%88%E7%89%99%E5%88%A9%E5%91%BD%E5%90%8D%E6%B3%95)，一眼就能看出變數型別，其他沒轉型的地方就不使用

```javascript!
//原本寫法
const inputNubmer = Number(userInput);

//修改
const numberUserInput = Number(strUserInput);
```
3. try...catch...finally，[finally](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch#the_finally_block)多加運用(控制流退出之前都會執行finally裡面的語法)

```javascript!
//Chris修改Q2
const answers = [];

function keyboardInput(question, valid, answers) {
  return new Promise((resolve, reject) => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    rl.question(question, (input) => {
      try {
        validator(input, valid);
        resolve([...answers, Number(input)]);
      } catch (error) {
        console.log(error.message);
        reject(answers);
      } finally {
        rl.close();
      }
    });
  });
}

function initial(answer) {
  return new Promise((resolve) => {
    resolve(answer);
  });
}

initial(answers)
//這邊是輸入
  .then(ask("請輸入第1個數字：", [isNumber, isInteger, isNonNegativeInteger]))
  .then(ask("請輸入第2個數字：", [isNumber, isInteger, isNonNegativeInteger]))

//這邊是邏輯-->要單元測試的地方
  .then((answers) => {
    // const array = compareRemainder(answers);
    // const result = isTheSameRemainder(array);
    // return resultToMessage(result);
    //驗證放來這邊 測試then裡面的邏輯
    return [compareRemainder, isTheSameRemainder, resultToMessage].reduce(
      (result, fn) => fn(result),
      answers
    );
  })
//.then裡面函式可以不用加參數，因為是為了註冊進去
//.then(console.log)
  // .then(compareRemainder)
  // .then(isTheSameRemainder)
  // .then(resultToMessage)
  
//這邊是輸出
  .then((result) => console.log(result));

function isTheSameRemainder([firstNumber, secondNumber]) {
  return firstNumber === secondNumber;
}

function resultToMessage(result) {
  return result ? `餘數相同` : `餘數不同`;
}

function ask(question, valid) {
  return (result) => {
    return keyboardInput(question, valid, result).catch(ask(question, valid));
  };
}

//原本寫法
function askQuestion(question, answers) {
  return new Promise((resolve) => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    rl.question(question, (input) => {
      try {
        validator(input, [isNumber, isInteger, isNonNegativeInteger]);
        const inputNumber = Number(input);

        answers.push(inputNumber);

        rl.close();
        resolve(answers);
      } catch (error) {
        console.log(error.message);

        rl.close(); //錯了先關掉，再開接口
        askQuestion(question, answers).then(resolve);
        // console.log(askQuestion((question, answers)));
      }
    });
  });
}

askQuestion("請輸入第1個數字：", answers)
  .then((result) => askQuestion("請輸入第2個數字：", result))
  .then((result) => (compareRemainder(result) ? `餘數相同` : `餘數不同`))
  .then((finalResult) => console.log(finalResult));
```
4. 使用[functional programming函數式程式設計(FP)](https://ken-chen.medium.com/functional-programming-%E6%98%AF%E4%BB%80%E9%BA%BC-%E6%88%91%E5%8F%AA%E8%81%BD%E9%81%8E-oop-ce222618bc05)：透過最小化增進程式碼可讀性
5. validtor可改成reduce跑
6. new Error的new一定要寫嗎？他自己是沒有寫
7. 變數轉型完，再放入function跑(不在驗證裡面轉型)
```javascript!
  //Chris寫法
  .then(resultToMessage)
  .then(console.log);

  //原本
  .then((result)=>resultToMessage(result))
  .then((result) => console.log(result));
```


### Q5
1. 數字太大要擋(會跳出max stack)
2. test要測正常和不正常
3. 使用new Function把數列字串直接計算值出來

```javascript!
//原本寫法
function umleven(n) {
  if (n === 4) {
    return (n - 2) * n;
  } else {
    return umleven(n - 2) + (n - 2) * n;
  }
}
//列數列
function listFomula(n) {
  if (n === 4) {
    return `${n - 2}*${n}`;
  } else {
    return `${listFomula(n - 2)}+${n - 2}*${n}`;
  }
}

//修改後
//雖然較少使用new Function，但在字串直接計算值很方便
function umleven(n) {
  return new Function(`return ${listFomula(n)}`)();
}
```

### validator.js
```javascript=
//檢查是否為數字
export function isNumber(input) {
  if (Number.isNaN(Number(input))) {
    throw new Error("請勿輸入文字");
  }
  if (input.startsWith("0b")) {
    throw new Error("此為二進制，請輸入十進制格式");
  }
  if (input.startsWith("0o")) {
    throw new Error("此為八進制，請輸入十進制格式");
  }
  if (input.startsWith("0x")) {
    throw new Error("此為十六進制，請輸入十進制格式");
  }
    
  //Chris不使用正規的寫法，來判斷科學記號
  if (
    input.includes("e") &&
    input.split("e").length === 2 &&
    input.split("e").every((item) => !Number.isNaN(Number(item)))
  ) {
    throw new Error("此為科學記號，請輸入十進制格式");
  }
}
```

