# 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算法