+_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 也不能使用這個變數。
為了好維護,可以試試看怎麼包。
```
#### 連名帶姓不能輸入

```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)

#### 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 就好

>
>取出來的 code 都會做成閉包

>
>Chris:啊我不喜歡我都改成這樣 ✌🏻

>這段直接好讀了起來
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
沒事有寫就好,現在最低需求就是寫一個就好。