---
# System prepended metadata

title: +_q6~q10 code review
tags: [CodeReview]

---

+_q6~q10 code review
===
# Pam q6~q10_code review

### 驗證寫法
#### isNotEmpty
```javascript!
export function isNotEmpty(input) {
  if (!String(input).trim().length) {
    throw new Error('請勿輸入空白^^');
  }
}

(!String(input).trim().length)

// 可改為此寫法比較好讀，比較像人在講的話、不用再轉
(String(input).trim().length === 0)
```

#### isOnlyEnglish
```javascript!
export function isOnlyEnglish(input) {
  for (let i = 0; i < input.length; i++) {
    const charCode = input.charCodeAt(i);
    // 檢查是否是大寫字母或小寫字母的 Unicode 範圍
    if (!((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122))) {
      throw new Error('請輸入英文字^^');
    }
  }
}

如果 q6 用 isNot Empty(擋掉空白) 和 isOnlyEnglish(擋掉符號和數字)，確實是可以，但這隻叫 isOnlyEnglish，是 Empty 應該也要會擋掉，所以要走這個路線，建議在 isOnlyEnglish 優化成也可以擋掉空白。
```
#### -0 / +0
可以驗證特殊符號濾掉

#### 有正反兩種寫法
isNotEmpty >> 代表不是空的都給過
isEmpty >> 我要擋掉空的
 
```javascript!
可以一次只想一個擋掉的東西就好，不要做複合的。
例如：這題只能輸入英文，所以要讓使用者不能輸入中文，就寫一個專門擋掉中文的，若是要寫直接讓使用者只能輸入英文，那就是要用嚴謹的正則去寫。

命名會影響解讀，isNotEmpty 這個 Not 有點會遲疑要想一下要做什麼事。
```

### q6 
#### getNameArray 驗證可以拆出來
```javascript!
getNameArray(userInput, names);

export function getNameArray(name, array) {
  checkInputValidity(name);
  array.push(name);
}

// checkInputValidity 可以拆出來
```
#### array 可以取 arr
#### 全域變數
```javascript!
所有 function 都可以修改它，甚至會不小心被改到。

如果我將 names 宣告在 main() 裏面，那生命週期就是呼叫 main() 的時候，其他 funciton 也不能使用這個變數。

為了好維護，可以試試看怎麼包。
```
#### 連名帶姓不能輸入
![image](https://hackmd.io/_uploads/BJJACSIuA.png)
```javascript!
空白被視為不是英文所以擋住了，可以試試看如何判斷這個空白讓他過
```

### q7
#### 題目是字串，用字串做初始處理比較好，可以寫一個 funciton 轉為 array
#### 把事情都封裝成一個 function / 和 分開的差別，不同情境有不同作法
```javascript!
function main() {
  console.log(`【印出原本圖】\n${getOriginalHeartString()}`);
  console.log(`【印出翻轉 90° 後的圖】\n${getRotateHeartString()}`);
}

main();

// 包成一個 getRotateHeartString()
export function getRotateHeartString() {
  return arrayToString(rotateArray(originalHeartArray));
}

export function rotateArray(array) {
  return array[0].map((_, colIndex) =>
    array.map(row => row[colIndex]).reverse()
  );
}

export function arrayToString(array) {
  return array.map(row => row.join('')).join('\n');
}
```
>比較有善於使用者，喔我要做這件事我只要呼叫這個 funtion 就好了。
>但今天如果我是一個 developer，例如我這件事情有三步，如果未來我中間這步不要了，是不是我要改掉這個 function 裡面呼叫的事，但如果我一開始就拆成三步，我就只要 comment 掉我不需要的。
>但還是看應用場景。

#### 這題要操控的就是 index
```javascript!
1. 找規律
2. 要用什麼策略
3. 怎麼用語法實現
```

#### test：丟 array 進去吐 string 出來感覺有點不太一致
```
這題比較好的話是照題目的丟 string 進去 吐 string 出來
```


### q8 
#### rl.question 可以看看如何包起來
```
例如包起來時，就是吐回一個驗證過後的正確的 value，然後再繼續做計算
```
#### quotientFixedToThirdDecimal 這個變數可能不用
```javascript!
const quotientFixedToThirdDecimal = currentQuotient.toFixed(3);
  const secondDigit = quotientFixedToThirdDecimal[quotientFixedToThirdDecimal.length - 2];
```

#### 可考慮把 input 和驗證包成一個 moudule，這樣在 main 裏面邏輯就會跟 main 分開
#### 這題也可以用 while (不確定要跑幾次的時候)
#### 嘗試延伸和擴充
```javascript!
保有靈活性/彈性，但有時候是 over design 可以在這兩者之間取得平衡

例如這題題目是除以 3 是否可以做成其他數值也可以重用？
```
### q9
#### 可以用用看 reduce
### q10
#### 命名：list 有一種默認是 array 的感覺
#### findPrimeNumbersArray
```javascript!
forEach 的 (element, )
可以取成 number 比較直觀
```
```javascript!
切入點有點不太像程式，可能因為我這個 function 做太多事了。
1. 驗證
2. 將一段字串 push 進 質數的 array，讓 main 去印

// 原本寫法：基本上不會像這樣直接處理字串丟進去，比較不能重用。
primeNumbersArray.push(`${element} (索引值：${index})`);

// push 進去一個物件會比較好
primeNumbersArray.push({value:`{$element}`, index:`${index}`, isPrime: true });
```
#### primeList
```javascript!
const array = [3, 50, 0, 13, 2, 4, 11];
const primeNumbersArray = [];
const primeList = String(findPrimeNumbersArray(array, primeNumbersArray));

// 比較會寫成
const array = [3, 50, 0, 13, 2, 4, 11];
const primeNumbersArray = findPrimeNumbersArray(array);
```
#### 可以寫寫看 early return / 或 switch case
```javascript!
export function findPrimeNumbersArray(originalArray, primeNumbersArray) {
  originalArray.forEach((element, index) => {
    if (element === 1) {
      // 是 1 的話繼續跑下一個
      return;
    } else if (element === 2) {
      // 是 2 的話就是質數
      primeNumbersArray.push({value:`{$element}`, index:`${index}`});
    } else {
      let isPrime = true;

      // 這個數去開根號，看除以 2 ~ 自己的開根號 是否能整除，可以的話不是質數
      for (let i = 0; i <= Math.sqrt(element); i++) {
        if (i === 0 || i === 1) {
          continue;
        }

        if (element % i === 0) {
          isPrime = false;
          break;
        }
      }
      if (isPrime) {
        primeNumbersArray.push(`${element} (索引值：${index})`);
      }
    }
  });

  return primeNumbersArray;
}


// 改寫 early return(怪怪的先不要看)
function testPrime(arr) {
  arr.forEach(() => {
    if (element === 1) {
      // 是 1 的話繼續跑下一個
      return;
    }
    if (element === 2) {
      primeNumbersArray.push(`${element} (索引值：${index})`);
      return
    }
    let isPrime = true;

    // 這個數去開根號，看除以 2 ~ 自己的開根號 是否能整除，可以的話不是質數
    for (let i = 0; i <= Math.sqrt(element); i++) {
      if (i === 0 || i === 1) {
        continue;
      }

      if (element % i === 0) {
        isPrime = false;
        break;
      }
    }
  })
  return primeNumbersArray;
}

```
### 其他
#### 多多練習其他方法
```javascript!
現在還有餘裕的時候練習多嘗試作法，比接案和工作的時候有 deadline 再思考要用哪些做來得好XD
先準備好工具箱，懂得使用情境很重要。
```

#### 一個 function 做一件事情 / 怎樣認定是一件事情？
```javascript!
不同模式都可以寫看看，要切多細，寫到後面會慢慢有畫面，一個動作寫成一個 function

每個人看法都會不太一樣，慢慢累積經驗，會可以講出一番道理、說服他人，你會慢慢建立這個東西。
```

#### 何時開始有這種感覺的呢
```javascript!
之前 Pam 也是會重複看自己的 code 覺得會一直改，不太確定自己的想法和寫法，17 題寫完開始慢慢有一點感覺。

Chris 推薦：可以把你每次想要改一個寫法的"原因"記錄下來，之後你就用這個規則去繼續寫下一題，這樣就不會因為今天心情好就改、明天又改。

確定這是一個你相信的道理和準則之後就這樣做，遵循這些原因後，最後會發展出屬於自己的一套 design rule。
(這個 ending 蠻勵志的🥹)
```

# Wendy q6~q10_code review
### q6 
#### 盡量不使用全域變數，可能會被污染到/被改到
#### askQuestion() 放在 main 檔案的原因
```
跟 main 比較無關，可以拆出去，main 比較像是組裝的檔案
因為我其他也都是用 import 的
```
#### 假設輸入 10 個問題？可以試試看調整彈性
#### names.push 一定要 askQuestion 做嗎/關乎到全域變數
`如果不是在 askQuestion 做，可以想一下跟那個 names 的全域變數有沒有關係`
#### askQuestion 是否可以把 push 拆出去
```
這隻 function 可以做驗證和拿到正確答案就好
然後再傳入一個陣列，就會變成 -> 喔 askQuestion 拿到正確答案，然後做下一件事，拿到全部的答案
這樣感覺比較邏輯清楚
```
#### 也可以用 async await 做做看

#### getOddNameList
 ```return { oddListOfName, letterOfFirstName, letterOfThirdName };```
這個也是對於問十個問題時，會比較難擴充可以再想想看。
#### getOddNameList的 filter 可以想想看可以怎麼包起來因為都長一樣
### q7
#### 愛心字串少一個空格就爆掉了
```
因此可以想：可以先去算說這個字串最長是有幾個字，那我在其他行，沒有空格的話會自己補空格
```
### q9
#### addArray(arr)
arr 這個可以命名的清楚一點 才知道要傳進來的是二維陣列
#### 如果陣列長度不同的話ㄋ
可以用 generator 試試看

#### 需不需要把結果包成一個變數再處理？
```javascript!
export function stringToArray(str) {
  const originalHeartArray = str.split('\n').map((element) => {
    return element.split('')
  })

  return originalHeartArray;
}

可以省略成

export function stringToArray(str) {
  return str.split('\n').map(element => element.split(''));
}
```

#### 
可以想想看不用 map 把字串處理出來，可以在 console.log 這邊處理之類的
```javascript!
function main() {
  const array = [3, 50, 0, 13, 2, 4, 11];
  const primeList = createPrimeStatusArray(array).map((element) => {
    return `${element.value}（索引值 ${element.index}）`
  })
  console.log(`此陣列 [3, 50, 0, 13, 2, 4, 11] 中，包含的質數有：\n${primeList.join('\n')}`);
}
```

### q10 
#### checkPrime push可以用 map
#### checkPrime 這個函式不只做檢查
他丟出來是額外處理過的資料，因為看到 checkPrime 會預想他返回的是布林值
### 其他
#### 怎麼想拆檔案這件事
```
套件底層，為了寫測試，拆的蠻細的
是否有拆會關乎：好不好找檔案，一般要做到，看資料夾檔案放在哪/要做什麼事情

後來工作後才對這些事比較有感覺，資料夾下的東西結構必須清楚
```
#### 擴充
```
開發上會有問題，pm 跟你說突然要問 10 個問題，因此可以練習延伸
```
#### 驗證套件推薦
https://github.com/logaretm/vee-validate/tree/main/packages

可以看一下驗證的命名方式和規則
#### 可以看 code completed 變數 函式這兩章
#### 作品集準備
要做什麼：可以從生活中的痛點開始找。
也可以做做看 [F2E](https://2021.thef2e.com/) 的(活動有 UI/UX 所以有設計稿可以選)。

#### 接案和進公司的區別
進公司會學到：接到別人寫的東西然後接續維護、開發，會遇到比較多想像不到的問題
工作室的案子：都是自己做自己的部分，比較不會碰到維護部分

# Chris q6~q10_code review
### q6
#### 初始化 `const names = []` 為 Promise 排版比較簡潔
```javascript!
// 原本
const names = [];
  .then(askQuestionAndSaveNames('・請輸入第 1 個英文名字：'))(names)
  .then(askQuestionAndSaveNames('・請輸入第 2 個英文名字：'))
  .then(askQuestionAndSaveNames('・請輸入第 3 個英文名字：'))
  .then(askQuestionAndSaveNames('・請輸入第 4 個英文名字：'))

// 改為
initialNames()
  .then(askQuestionAndSaveNames('・請輸入第 1 個英文名字：'))
  .then(askQuestionAndSaveNames('・請輸入第 2 個英文名字：'))
  .then(askQuestionAndSaveNames('・請輸入第 3 個英文名字：'))
  .then(askQuestionAndSaveNames('・請輸入第 4 個英文名字：'))

function initialNames() {
  return new Promise((resolve, reject) => {
    const names = [];
    resolve(names);
  });
}
```
#### 除錯可以用這招 - 全部印出來
```javascript!
.then((...e) => {
  console.log(...e)
	return e;
})
```
#### 多重 rl 接口問題(14:43)
![image](https://hackmd.io/_uploads/rJdun_vKA.png)

#### rl.question 生命週期管控
避免重複接口問題(我覺得這個問題還是很玄乎)
```javascript!
export function askQuestionAndSaveNames(question) {
  return (answerList) => {
    return new Promise((resolve) => {
      const rl = createInterface({  ----------------> 開
        input: process.stdin,
        output: process.stdout
      });
      rl.question(question, (answer) => {  ---------> 用
        rl.close();  -------------------------------> 用完馬上關
        try {
          validator(answer, [isEmpty, isEnglish]);
          answerList.push(answer)
          resolve(answerList);
        } catch (error) {
          console.log(error.message);
          resolve(askQuestionAndSaveNames(question));
        }
      });
    });
  }
}
```

#### 讀錯誤訊息(22:55)
```javascript!
file:///Users/guojia/Jami/%E5%A5%BD%E6%83%B3%E5%B7%A5%E4%BD%9C%E5%AE%A4/Learning/03.%20JS/j1/q6/q6.js:42
    return element.split("").filter((_, index) => {
                   ^

TypeError: element.split is not a function
    at file:///Users/guojia/Jami/%E5%A5%BD%E6%83%B3%E5%B7%A5%E4%BD%9C%E5%AE%A4/Learning/03.%20JS/j1/q6/q6.js:42:20
    at Array.map (<anonymous>)
    at getOddLetterOfList (file:///Users/guojia/Jami/%E5%A5%BD%E6%83%B3%E5%B7%A5%E4%BD%9C%E5%AE%A4/Learning/03.%20JS/j1/q6/q6.js:41:14)
    at getOddLetters (file:///Users/guojia/Jami/%E5%A5%BD%E6%83%B3%E5%B7%A5%E4%BD%9C%E5%AE%A4/Learning/03.%20JS/j1/q6/q6.js:30:26)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Node.js v22.1.0
			
// 由下往上讀，就會知道呼叫的順序，是從哪到哪
// 搜尋找：q6/q6.js，ctrl+G找：42，就會到了
```

### functional programming / 一般寫法
```javascript!
export function getOddLetters(allNameList) {
  const oddNameList = getOddNameOfArray(allNameList);
  const oddLetterList = getOddLetterOfList(oddNameList);
    
  return {
    allNameList,
    oddLetterList
  };
}

// 把行為拆成 function 
function getOddNameOfArray(arr) {
  return arr.filter((_, index) => {
    return index % 2 === 0;
  });
}

function getOddLetterOfList(arr) {
  return arr.map((element) => {
    return element.split("").filter((_, index) => {
      return index % 2 === 0;
    })
  })
}
```
```javascript!
// 進化中
export function getOddLetters(allNameList) {
  allNameList
    .filter((_, index) => {
      return index % 2 === 0;
  })
    .map((element) => {
      return element.split("").filter((_, index) => {
        return index % 2 === 0;
    })
  })
    
  return {
    allNameList,
    oddLetterList
  };
}

// 然後就可以把 .filter 和 .map 裡面的 callback function 包成 function 呼叫！請看下一步
```
```javascript!
// functional programming style

export function getOddLetters(allNameList) {
const oddLetterList = allNameList
  .filter(odd)
  .map(oddLetters)

  return {
    allNameList,
    oddLetterList
  };
};

function odd(_, index)  {
  return index % 2 === 0;
}

function oddLetters(element) {
  return element.split("").filter(odd)
}
```
>Chris 都用這個去變成 function 就好
![image](https://hackmd.io/_uploads/SkoFHuuFR.png)
>
>取出來的 code 都會做成閉包
![image](https://hackmd.io/_uploads/BkRXU__FC.png)
>
>Chris：啊我不喜歡我都改成這樣 ✌🏻
![image](https://hackmd.io/_uploads/BkcHId_YR.png)

>這段直接好讀了起來

const oddLetterList = allNameList 
  .filter(odd) ---------> 過濾單數
  .map(oddLetters) -----> 轉成單數字母

>我的小結論：
現階段感受到的好處是：call back function 包出去直接當成參數呼叫、用 method 他原本的行為去順整個語意，與一開始用變數和函式命名傳過去的那種方式蠻有差別的！
#### 解構賦值
```javascript!
console.log(`第一個英文名字 ${allNameList[0]} 的單數個字母是：${oddLetterList[0]}\n第三個英文名字 ${allNameList[2]} 的單數個字母是：${oddLetterList[1]}`);

// Chris 說他不太喜歡有這個[]，allNameList[0]、allNameList[2]，所以解構賦值一下

const [ firstName, _,thirdName] = allNameList; -----> 這邊注意不用的要用'空白'或'_'之類的區隔

console.log(`第一個英文名字 ${firstName} 的單數個字母是：${oddLetterList[0]}\n第三個英文名字 ${thirdName} 的單數個字母是：${oddLetterList[1]}`);
```
### q7
```javascript!
如我一開始的愛心如果空格不對，就不會轉出我要的結果。

1. isRowsEqualLength(arr) 判斷文字是否每一行字串長度相等，不是就噴錯
// 這樣 ok，做測試的宿命就是，有想到才做，就先這樣
2. 或要讓他'自動補空格'嗎？
// Chris：不要，等一下補錯！
```
### q10
```javascript!
checkAndClassifyPrimes(arr) 這個function，實務上，會不會像這樣存入一個物件？再用物件去做事？

Chris：
首先，你創建了一個型別(物件)，那
(1) 它是什麼？
有時候它是一個臨時的型別，有時它是一個有意義的型別，這個意義，是你要尋找給它的
(2) 它可以解決什麼問題？
因為我們創造型別是為了解決抽象的問題。

例如：我創造了一個型別，是為了解決找質數的問題創造出來的。
找質數很久，所以我找過的不要再找了，並且把是不是質數的資訊都記下來，把運算能力花在沒找過的數字上，所以這抽象是為了解決特定問題。

```
#### checkAndClassifyPrimes 命名 改 signPrimes 
```javascript!
可以改為"標記"的命名，你就是給他們一張 run card，
```
#### getPrimeStatusList
```javascript!
// 原本寫法：將 質數 array 丟進去，拿到全部狀態的 array 然後 filter 掉是質數的
export function getPrimeStatusList(array) {
  return checkAndClassifyPrimes(array).filter(element => element.isPrime);
}

// Chris 改寫法
export function getPrimeStatusList(array) {
  return array
    .map(signPrimes)
    .filter(element => element.isPrime);
}
```
#### 恍然大悟 forEach
```javascript!
只要我為了要拿到一個一樣長的陣列，宣告了一個 [] 跑 forEach，就是 .map 🥰
```
#### 整理 code (1:10:47)
### 
```
應該如何去思考：我接下來要做什麼事，那我在這一步應該做什麼處理。

要返回布林值，還是返回處理過的資料，如何會讓下一步更好進行？這個設計的思路應該怎麼去順比較好。
```
要用推理去解決這種問題的話。
有一種是從頭，因為所以因為所以，然後到後面。
有一種是從尾巴，缺什麼找什麼。
寫到一半卡住就有可能是不是很明確知道後面是什麼就過不去。

例如我在接 API 要拿到一個 json 格式，有時候我就會先把那個格式先寫出來，往回逆推。就會比較容易跨過這個過程。


### 1:21:54 isPrime 順序？
### 整理 code 這件事
先做
因為現在無法用想像力

### 命名
很多天回來看，看到一個變數，心中冒出：這是什麼？
這個聲音你要記下來。
那就是你在寫的時候，不夠瞭解問題，或是只有給他一個代名詞(這個那個)的做法。詞窮就是你只覺得他是這個那個。
像是我們在寫練習的時候都用莫名的 abc 去命名，那就是沒有練習做到這件事。
有意識去做到先命名這件事，認識問題、更深入了解問題的思路，就會慢慢有了。
像這個 element 就很像沒有命名，現在不夠痛，你把命名改為 abc 你就知道了
```javascript!
function isPrime(element) {
  for (let i = 0; i <= Math.sqrt(element); i++) {
    if (i === 0 || i === 1) {
      continue;
    }

    if (element % i === 0) {
      return false;
    }
  }
  return true;
}
```

### 測試
要測不正常狀況
測試代表跟使用者告知，這些 case 以外的問題再來找我！

愛心這題，輸入寫死的是愛心，所以你要測愛心。

寫測試感覺會很痛苦嗎：不太知道測什麼、想到什麼測什麼
但至少有讓測試跑過，這很重要
測試至少可以讓程式不會到處都 console.log 或是 rl.question
沒事有寫就好，現在最低需求就是寫一個就好。