## 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("此為科學記號,請輸入十進制格式"); } } ```