六角學院 - HexSchool
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 第一堂:從入門到進階:BMI 計算機 Clean Code 版本 ## 課程大綱 1. Clean Code 介紹 1. 1 個觀念- 5min 2. 變數 1. 3 個觀念 - 20min 3. 函式 1. 3 個觀念 - 20min 4. 中場休息 - 5min 5. BMI 計算機 Clean Code 版本 1. 2 份程式碼 - 30min ## Clean Code 介紹  [**3 Rs 程式架構**](https://github.com/ryanmcdermott/3rs-of-software-architecture) - 可讀性(Readability)- 讓別人好讀你的程式碼 - 可重用性(Reusability)- 減少重複的程式碼 - 可重構性(Refactorability) - 模組化 - 可維護性 - 可擴展性 ## 1. 變數 ### 1-1. 使用具有意義且可閱讀的名稱 **糟糕的程式碼範例:** 這段程式碼中使用了 **`yyyymmdstr`** 這樣的變數名稱,這樣的命名方式雖然包含了一些日期格式的資訊,但對於閱讀者來說不夠直觀,也難以理解其真正含義。 ```jsx const yyyymmdstr = moment().format('YYYY/MM/DD'); ``` **好的程式碼範例:** 這段程式碼改用了 **`currentDate`** 作為變數名稱,這樣的命名直接反映了這個變數儲存的是當前的日期,讓意圖更一目了然。 ```jsx const currentDate = moment().format('YYYY/MM/DD'); ``` ⭐️小練習:以此原則,請調整以下程式碼為更有意義且可閱讀的名稱 ```jsx 1) const data = ['red', 'white', 'black']; // 無法點擊的按鈕狀態 2) let status = 'disabled'; // 註冊時需填寫的資訊 3) let usr = { account: '', name: '', psd: '', } 4) let loading = false; // bmi 公式 5) const result = w / (h / 100) **2; ``` ### 1-2. 使用可搜尋的名稱 **糟糕的程式碼範例:** 在這個範例中,使用了一個不清楚的常數 **`1.07`**,代表了某種計算中的倍數或者增加的百分比,但這個數字本身並沒有清楚表達具體含義。 ```jsx function calculateTotal(price) { return price * 1.07; // 假設 1.07 代表含稅價格 } ``` **好的程式碼範例:** 在改進後的範例中,我們使用 `taxRate` 設定為 **`0.07`**,以表示稅率為 7%。然後在計算總金額時使用這個常數。這樣做可以讓後續閱讀程式碼的開發者更容易理解他的意圖和功能,並且在需要查找特定用途時更方便。 ```jsx const taxRate = 0.07; function calculateTotal(price) { return price * (1 + taxRate); // 使用常數使計算含稅價格更清楚 } ``` ⭐️小練習:以此原則,請調整以下程式碼為可搜尋的名稱 ```jsx // 0.5 秒後頁面重整 setTimeout(() => { window.location.reload(); }, 500); ``` ### 1-3. 使用可解釋的變數 **糟糕的程式碼範例:** 在這個例子中,雖然這段程式碼是正確的,但 **`x`** 和 **`y`** 並沒有提供足夠的上下文來解釋它們代表的具體含義,這使得理解真正意圖變得困難。 ```jsx const data = [{ x: 10, y: 20 }, { x: 20, y: 30 }]; let result = []; data.forEach(item => { result.push(item.x * item.y); }); ``` **好的程式碼範例:** 在改進後的例子中,我們通過更具描述性的變數名稱來明確每個屬性的含義 ```jsx const orders = [{ quantity: 10, unitPrice: 20 }, { quantity: 20, unitPrice: 30 }]; let totalCosts = []; orders.forEach(order => { totalCosts.push(order.quantity * order.unitPrice); }); ``` ⭐️小練習:以此原則,請調整以下程式碼為可解釋的名稱 ```jsx // 九九乘法表 const size = 9; for (let i = 1; i <= size; i++) { let row = ''; for (let j = 1; j <= size; j++) { row += `${i} * ${j} = ${i * j}\\t`; } console.log(row); } 英文單字提示:multiplier 表示乘數,multiplicand 表示被乘數,product 代表乘積 ``` ## 2. 函式 ### 2-1. 一個函式只做一件事情(單一性) **糟糕的程式碼範例:** 這個範例中`createUserProfile` 函式負責四件事: 1. 處理用戶名稱的空白 2. 檢查名稱長度 3. 解析和驗證年齡 4. 檢查電子郵件格式 這樣混合了太多責任的設計使得函式難以閱讀和維護,也增加了錯誤發生的機會。當一個函式做超過一件事情時,它會更難以被理解。 ```jsx // 建立用戶檔案,包含資料驗證和錯誤處理 function createUserProfile(userDetails) { const name = userDetails.name.trim(); // 正規化名字 if (name.length < 2) { console.log("錯誤:名稱至少需兩個字元長。"); return; } const age = parseInt(userDetails.age); // 轉換年齡 if (isNaN(age) || age < 18) { console.log("錯誤:您至少需要18歲。"); return; } if (!userDetails.email.includes('@')) { // 驗證電子郵件 console.log("錯誤:無效的電子郵件地址。"); return; } console.log("用戶檔案創建成功。"); // 成功訊息 } // 使用範例 createUserProfile({ name: "張三", age: "19", email: "zhangsan@example.com" }); ``` **好的程式碼範例:** 在這個重構的範例中,將功能拆分成更小的部分,每個函式專注於一個任務,提升了程式碼的清晰度和可維護性: ```jsx // 建立用戶檔案,包含資料驗證和錯誤處理 // 去除名稱中的前後空白 function trimName(name) { return name.trim(); } // 驗證名稱至少需要兩個字元 function isNameValid(name) { return name.length >= 2; } // 解析並驗證年齡,確認其為合法數字且至少為 18 歲 function parseAndValidateAge(age) { const parsedAge = parseInt(age); return !isNaN(parsedAge) && parsedAge >= 18; } // 確認電子郵件格式是否包含 '@' 符號 function isEmailValid(email) { return email.includes('@'); } // 處理創建用戶檔案的整個過程 function createUserProfile(name, age, email) { const normalizedName = trimName(name); if (!isNameValid(normalizedName)) { console.log("錯誤:名稱至少需兩個字元長。"); return; } if (!parseAndValidateAge(age)) { console.log("錯誤:您至少需要18歲。"); return; } if (!isEmailValid(email)) { console.log("錯誤:無效的電子郵件地址。"); return; } console.log("用戶檔案創建成功。"); } ``` ⭐️小練習:以此原則,請調整以下程式碼,保持函式的單一性 ```jsx function manageTodoList(todoList) { // 更新待辦事項清單 todoList.forEach((todo, index) => { if (!todo.completed) { todo.completed = true; } }); // 通知用戶 console.log('已完成的待辦事項清單:', todoList); } ``` ### 2-2. 函式名稱應該說明它做的內容 **糟糕的程式碼範例:** 在這個例子中,函式名稱 `updateInfo`,描述函式的功能不明確,update 會覺得是要更新某個資訊,但也無法得知要更新什麼資訊 ```jsx function updateInfo(info, data) { // 函式內容... } ``` **好的程式碼範例:** 在這個重構後的程式碼中,將函式名稱從`updateInfo`改為`mergeInfoWithNewData`,更清楚地表明了函式的功能,其實是要將現有資訊與新資料進行合併。這樣的命名方式使得程式碼更容易理解。 ```jsx function mergeInfoWithNewData(existingInfo, newData) { // 函式內容... } ``` 補充:建議函式命名以動詞開頭,有許多業界常用的字詞可以當作優先選擇 - get: 取得、取出 - set: 賦予、修改 - add: 增加 - remove: 排除 - is: 判定 - do: 執行邏輯 ### 2-3. 移除重複的程式碼 **糟糕的程式碼範例:** 兩個函式分別計算了電子產品和服裝的折扣價格,但計算邏輯大部分是一樣的,增加了程式碼的冗長度。如果未來需要增加更多類型的商品並計算其折扣價格,就需要再次複製貼上類似的函式,只會增加更多的重複性。 ```jsx function calculateElectronicsDiscount(price) { const discountPercentage = 0.1; // 電子產品的折扣率為10% return price * (1 - discountPercentage); } function calculateClothingDiscount(price) { const discountPercentage = 0.2; // 服裝的折扣率為20% return price * (1 - discountPercentage); } ``` **好的程式碼範例:** 將兩個函式中重複的部分抽取出來,並將可變的部分作為參數。 ```jsx function calculateDiscount(price, discountPercentage) { return price * (1 - discountPercentage); } const electronicsDiscountedPrice = calculateDiscount(price, 0.1); const clothingDiscountedPrice = calculateDiscount(price, 0.2); ``` ⭐️小練習:以此原則,請調整以下程式碼,提取重複內容管理 ```jsx function printInfo(person) { console.log(`Name: ${person.name}`); console.log(`Age: ${person.age}`); } function printStudentInfo(student) { console.log(`Name: ${student.name}`); console.log(`Age: ${student.age}`); console.log(`Major: ${student.major}`); } ``` ## **3. BMI 計算機 Clean Code 版本** ### [3-1. 洧杰老師 LV3 範例程式碼](https://codepen.io/hexschool/pen/VwmGZBd?editors=0010) **結合 Clean Code 規範,發展出更好的程式碼** **函式命名:對應 Clean Code 原則「函式名稱應該說明它做的內容」** `bmiStatesText`→ `printBmiResult` 並用動詞開頭 **判斷可以簡化:對應 Clean Code 原則「移除重複的程式碼」** `18.5 <= bmi && bmi < 24`→ `bmi < 24` **提出重複的程式碼:對應 Clean Code 原則「移除重複的程式碼」** `addData` 幾乎每個判斷都重寫一次,可以拿出來放在判斷結束的下方執行 **提取變數:對應 Clean Code 原則「使用具有意義且可閱讀的名稱」** `totalNum` → `totalRecord` 可以看出是記錄總量 `lastNum` → `lastRecord` 可以看出是最後一筆紀錄 ```jsx const totalRecord = bmiHistoryData.length; const lastRecord = totalRecord - 1; const lastState = bmiHistoryData[lastRecord].state; console.log(`您總共計算 ${totalRecord} 次 BMI 紀錄,最後一次 BMI 指數為 ${bmiHistoryData[lastRecord].bmi},體重${bmiStatesData[lastState].state}!健康指數為${bmiStatesData[lastState].color}!`); ``` ### [3-2. 常見學生範例程式碼](https://codepen.io/hexschool/pen/NWZgREV?editors=1011) 一開始自己寫完的 LV3,大概會長這樣: - 只會依照題目要求拆出 `printBmi` 和 `showHistoryData` 兩個函式 - `printBmi` 函式內容很長 ```jsx const bmiStatesData = { overThin: { state: "過輕", color: "藍色" }, normal: { state: "正常", color: "紅色" }, overWeight: { state: "過重", color: "澄色" }, mildFat: { state: "輕度肥胖", color: "黃色" }, moderateFat: { state: "中度肥胖", color: "黑色" }, severeFat: { state: "重度肥胖", color: "綠色" }, }; let records = []; function printBmi(height, weight) { const bmi = (weight / ((height / 100) * (height / 100))).toFixed(2); let state = ''; if (bmi < 18.5) { state = 'overThin'; } else if (bmi < 24) { state = 'normal'; } else if (bmi < 27) { state = 'overWeight'; } else if (bmi < 30) { state = 'mildFat'; } else if (bmi < 35) { state = 'moderateFat'; } else if (bmi >= 35) { state = 'severeFat'; } if (!state) { return '您的數值輸入錯誤,請重新輸入'; } records.push({ bmi: bmi, state: bmiStatesData[state].state, color: bmiStatesData[state].color }); return `您的體重${bmiStatesData[state].state},健康指數為${bmiStatesData[state].color}`; } function showHistoryData() { return `您總共計算 ${records.length} 次 BMI 紀錄,最後一次 BMI 指數為 ${records[records.length - 1].bmi},體重${records[records.length - 1].state}!健康指數為${records[records.length - 1].color}!`; } // 以下需一行一行執行 printBmi(178, 20); // 您的體重過輕,健康指數為藍色 printBmi(178, 70); // 您的體重正常,健康指數為紅色 printBmi(178, 85); // 您的體重過重,健康指數為澄色 showHistoryData(); // 您總共計算 3 次 BMI 紀錄,最後一次 BMI 指數為 26.83,體重過重!健康指數為澄色! ``` **為什麼這段程式碼還不夠好?** 多運用函式的優點,找出更合適的寫法: 1. ✅ (已做到)可重用性:因需要重複計算不同人的 BMI,因此會將 BMI 計算功能封裝在一個函式中重複利用,而不需要重複寫計算 BMI 邏輯,只要呼叫函式即可 2. 增加可讀性:函式因為模組化將功能獨立出來,讓整體結構更清晰,閱讀也會更容易理解和維護 3. 可重構性:讓程式碼方便維護,好擴展 **分析 BMI 功能,將程式碼做模組化並提高可讀性** ```jsx 第三階段:儲存每筆計算資料,多一個變數為 bmiHistoryData,並賦予空陣列來儲存計算物件資料,若數值輸入錯誤,則不儲存。 printBmi(178, 20) >> 印出 console.log 文字為「您的體重過輕,健康指數為藍色」 printBmi(178, 70) >> 印出 console.log 文字為「您的體重正常,健康指數為紅色」 printBmi(178, 85)>> 印出 console.log 文字為「您的體重過重,健康指數為澄色」 showHistoryData() >> 印出 console.log 文字為「您總共計算 3 次 BMI 紀錄,最後一次 BMI 指數為 26.83,體重過重!健康指數為澄色!」 ``` 如何規劃函式模組化?依照單一功能去發想: - 計算 BMI 值 → `calculateBMI` - 印出「您的體重 xx,健康指數為 xx」文字 → `printBmi` - 條件判斷 BMI 結果是在哪個狀態 → `getBMIState` - 將每次結果記錄下來 → `addRecord` - 印出歷史紀錄 → `showHistoryData` **實作功能拆分→ 對應 Clean Code 原則「一個函式只做一件事情」** ```jsx const bmiStatesData = { overThin: { state: "過輕", color: "藍色" }, normal: { state: "正常", color: "紅色" }, overWeight: { state: "過重", color: "澄色" }, mildFat: { state: "輕度肥胖", color: "黃色" }, moderateFat: { state: "中度肥胖", color: "黑色" }, severeFat: { state: "重度肥胖", color: "綠色" }, }; let records = []; function calculateBMI(height, weight) { const bmi = (weight / ((height / 100) * (height / 100))).toFixed(2); return bmi; } function getBMIState(bmi) { let state = ''; if (bmi < 18.5) { state = 'overThin'; } else if (bmi < 24) { state = 'normal'; } else if (bmi < 27) { state = 'overWeight'; } else if (bmi < 30) { state = 'mildFat'; } else if (bmi < 35) { state = 'moderateFat'; } else if (bmi >= 35) { state = 'severeFat'; } return state; } function addRecord(bmi, state) { const bmiColor = bmiStatesData[state].color; const bmiState = bmiStatesData[state].state; records.push({ bmi: bmi, state: bmiState, color: bmiColor }); } function printBmi(height, weight) { const bmi = calculateBMI(height, weight); const state = getBMIState(bmi); if (!state) { return '您的數值輸入錯誤,請重新輸入'; } addRecord(bmi, state); return `您的體重${bmiStatesData[state].state},健康指數為${bmiStatesData[state].color}`; } function showHistoryData() { return `您總共計算 ${records.length} 次 BMI 紀錄,最後一次 BMI 指數為 ${records[records.length -1].bmi},體重${records[records.length -1].state}!健康指數為${records[records.length -1].color}!`; } ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully