+_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 沒事有寫就好,現在最低需求就是寫一個就好。