在學習程式設計的過程中,你可能會遇到一個重要的問題:什麼時候該使用函式?
函式(Function)就像是程式中的小工具,能幫助你整理重複的程式碼、提高可讀性,甚至在未來應對更多變化。
本文將以一個簡單的農場數字列印案例為例,帶你逐步體驗函式設計的思考過程——從寫死的程式碼,到結構更合理、可重複利用的函式。
為什麼需要函式?
在寫程式的時候,你一定會慢慢發現:光靠一行一行寫,很快就會亂掉。
程式一多、功能一複雜,就會開始覺得「這段邏輯好像重複好幾次」或「這裡應該有個專門的功能才對」。
這時候,函式就派上用場了。它就像是幫你整理房間的小抽屜,可以把一段邏輯收起來,想用的時候再拿出來。
下面這兩種情況最常讓人自然想到「我應該寫個函式」。
發現自己寫了好多重複的程式碼
🔍 什麼意思?
這種情況超常見:你在不同地方,不斷重複做一模一樣的事情。
比如說:
- 在數字前補 0
像是列印發票或農場動物數量時,7要補成007、11要補成011。 - 計算價格加稅
每次結帳時都要算一次總金額,可能好幾個頁面都用到同樣的公式。 - 格式化日期
把2025/7/21轉成2025-07-21,結果這段轉換程式碼在五個地方都複製過。
一開始專案小的時候你可能覺得沒關係,「複製貼上最快」。
但當專案變大、程式碼到處散落,你會開始覺得頭痛,因為只要邏輯一有改動,你得在好幾個地方重複修正。
⚠️ 為什麼不好?
- 改起來超麻煩
只要有一天,你想把補 0 的邏輯從「補到三位數」改成「補到四位數」,
你得去翻找所有有用到這段程式碼的地方,一個一個改。如果漏掉其中一個地方,結果就會不一致,可能還會產生奇怪的 bug。 - 超難維護
半年後你回來維護專案,可能完全忘記「哪一段是最新版本」。
假設你有三個地方都在計算價格加稅,但其中一個是舊公式,另外兩個是新公式,你會被自己搞瘋。 - 增加理解負擔
其他人(甚至未來的你自己)要看懂這段程式碼,就得每次都重新讀一遍整段邏輯,浪費時間。
✅ 把它包進函式
最好的做法就是把這種重複邏輯「收進一個小盒子裡」,也就是寫成一個函式。
比如說,你可以寫一個 zeroPad(number, width) 函式,專門負責補 0。
以後只要需要補 0,不用再重複寫一堆 while 迴圈,直接呼叫它就好。
console.log(zeroPad(7, 3)); // 007
console.log(zeroPad(11, 3)); // 011好處是什麼?
- 改邏輯只要改一個地方
- 其他人一看函式名稱就知道用途
- 讓主要程式碼看起來更乾淨、易讀
想到一個功能,但還沒寫出來
🔍 什麼意思?
有時候你還沒開始寫程式,就已經知道「這裡應該要有個功能」。
你可以先「取一個有意義的名字」放著,就像先插一個旗子,告訴自己「這裡之後要補這段邏輯」。
例子:
- 「我要列印農場的動物數量」→ 先寫一個
printFarmInventory()放著,之後再決定裡面要怎麼印。 - 「我要計算購物車總價」→ 先寫
calculateTotalPrice(),就算現在還沒想好怎麼寫,也不會打亂主要流程。
✅ 為什麼要先取名字?
- 幫助思考、理清流程
你可以先把整個流程寫出來,不用卡在細節上。就像先畫一張地圖,細節以後慢慢補。 - 讓程式更好讀
就算你今天只寫好主流程,其他人(還有未來的你自己)一看函式名稱,就能大概猜到它的作用。 - 避免「寫到一半忘了自己在幹嘛」
當你一次專注在主要結構,再慢慢回頭寫細節,寫程式會更有條理。
小結
總結一句話:
- 遇到重複的程式碼 → 把它包成函式,省事又安全。
- 腦中已經想好功能 → 先取個好名字,保持流程乾淨。
函式就像幫你整理邏輯的小工具,越早習慣這種思維,程式就會越好維護。
第一個版本:寫死的簡單函式
目標
我們要做一件很簡單的事:
列印農場動物的數量,並且在數字前補 0,確保數字永遠是三位數。
最終想要得到的輸出是這樣:
007 Cows
011 Chickens也就是說:
- 7 頭牛 → 變成
007 - 11 隻雞 → 變成
011
實作:第一次嘗試
來看看最直接的做法:
function printFarmInventory(cows, chickens) {
let cowString = String(cows);
while (cowString.length < 3) {
cowString = "0" + cowString;
}
console.log(`${cowString} Cows`);
let chickenString = String(chickens);
while (chickenString.length < 3) {
chickenString = "0" + chickenString;
}
console.log(`${chickenString} Chickens`);
}
printFarmInventory(7, 11);程式做了什麼?
這段程式其實很簡單,但如果你是剛開始學程式,可能會好奇「為什麼要這樣寫」。讓我們一步一步拆開來看。
把數字轉成字串
let cowString = String(cows);- 這行做的事是:把原本的數字(
7)轉成字串("7")。 - 為什麼要這樣做?
因為我們要用.length來檢查字串長度,而數字是沒有.length這個屬性的。 - 換句話說,我們是為了後面方便處理「字串長度」才轉型的。
💡 初學者常見問題:
很多人會問「為什麼不直接操作數字?」
原因是數字要補 0 很麻煩,你得用數學方式計算,還得考慮進位。但字串就簡單多了,只要在前面一直加 "0" 就好。
用 while 不斷補 0
while (cowString.length < 3) {
cowString = "0" + cowString;
}- 這段邏輯的意思是:
只要字串長度還沒到 3,就一直在最前面加一個"0"。 - 例如:
- 第一次檢查:
"7"→"07" - 第二次檢查:
"07"→"007" - 第三次檢查:長度已經是 3,跳出迴圈。
為什麼要用 while?
因為我們不知道最一開始的數字有幾位數,可能是 7(1 位)、11(2 位)、甚至以後可能是 123(3 位)。
while 能幫我們重複動作,直到長度符合需求。
💡 初學者常見問題:
有人會問「那為什麼不用 if?」
因為 if 只能判斷一次,遇到 7 這種需要補兩次 0 的情況就不行了,while 才能確保補到剛剛好。
印出結果
console.log(`${cowString} Cows`);- 最後用模板字串(
`${變數} 文字`)列印結果。 - 輸出的效果:
007 Cows- 模板字串的好處:比起寫
console.log(cowString + " Cows"),看起來更直覺。
成果驗收:程式能跑起來
執行後,得到我們想要的結果:
007 Cows
011 Chickens到這一步,一切看起來都沒問題,目標達成!
但這樣有什麼問題?
雖然現在功能正常,但這種寫法只適合「小玩具專案」。
當需求變複雜,你會很快被自己的程式碼拖垮。
每新增一種動物,就得複製一大段程式碼
現在只有牛和雞還好,但老闆如果再養豬、鴨、羊,你就得一直複製貼上這幾行「轉字串 + while 補 0 + 列印」。
重複這麼多次,不僅麻煩,還會讓程式碼越來越長。
規則改動要改好幾個地方
假設老闆說:「我想把動物數量改成四位數,例如 0007。」
你得去每個動物的 while 迴圈裡把 < 3 改成 < 4,一不小心漏改一個就出錯。
補 0 的邏輯寫了好幾次,很亂也容易搞錯
這種邏輯最好只有一個版本。如果寫在多個地方,哪天你改壞其中一個,結果輸出就可能不一致。
小結
所以,雖然這是最簡單、最直觀的寫法,但它不適合長期維護。
接下來我們就要思考:
👉 能不能把「補 0」這段邏輯抽出來,寫成一個獨立的小工具函式?
👉 這樣新增動物或改規則時,就不用到處修改程式了。
改善重複程式碼:第一步優化
重新思考:可以把「補 0」的邏輯抽出來嗎?
在上一個版本,我們為了列印每種動物的數量,重複寫了很多相似的程式碼:
- 把數字轉字串
- 用
while補 0 console.log印出結果
這樣雖然能用,但問題很明顯:
每次要新增一種動物,就得複製貼上一大段幾乎相同的程式碼。
👉 所以我們開始重新思考:
「這段重複的邏輯能不能抽出來?能不能寫一次就到處用?」
答案是:可以,把它封裝成函式。
改寫後的程式
function printZeroPaddedWithLabel(number, label) {
let numberString = String(number);
while (numberString.length < 3) {
numberString = "0" + numberString;
}
console.log(`${numberString} ${label}`);
}
function printFarmInventory(cows, chickens, pigs) {
printZeroPaddedWithLabel(cows, "Cows");
printZeroPaddedWithLabel(chickens, "Chickens");
printZeroPaddedWithLabel(pigs, "Pigs");
}
printFarmInventory(7, 11, 3);🔍 改進版的執行流程
以 printFarmInventory(7, 11, 3) 為例,程式其實做了這些事:
- 呼叫
printZeroPaddedWithLabel(cows, "Cows")
- 把數字
7丟進去 - 函式幫你自動轉成
"007" - 印出:
007 Cows
- 呼叫
printZeroPaddedWithLabel(chickens, "Chickens")
- 把
11丟進去 - 自動轉成
"011" - 印出:
011 Chickens
- 呼叫
printZeroPaddedWithLabel(pigs, "Pigs")
- 把
3丟進去 - 自動轉成
"003" - 印出:
003 Pigs
最後得到這樣的結果:
007 Cows
011 Chickens
003 Pigs這樣做有什麼好處?
重複的邏輯被「收進一個盒子」裡了
以前要寫好幾行才能「補 0 + 印出」,現在只要呼叫一次 printZeroPaddedWithLabel 就能搞定。
這就像是把工具收進工具箱裡,用到時直接拿出來,不用每次都從頭造工具。
增加新動物超方便
要新增一個動物,只要再呼叫一次函式就好,例如:
printZeroPaddedWithLabel(sheep, "Sheep");完全不需要再寫一次補 0 的邏輯。
換句話說,你現在只要專心處理資料,不用再去想「我要怎麼補 0」。
主要程式碼看起來更乾淨
printFarmInventory 現在變得超簡單,看起來就像一份「動物清單」:
printZeroPaddedWithLabel(cows, "Cows");
printZeroPaddedWithLabel(chickens, "Chickens");
printZeroPaddedWithLabel(pigs, "Pigs");很直覺、很好讀,你一眼就能看懂「這裡在做什麼」。
不足的地方(為什麼還能再優化?)
雖然這樣已經好很多,但還有兩個小問題:
- 函式名稱太長
printZeroPaddedWithLabel這名字很精確,但又臭又長,打起來很煩。 - 一個函式同時做兩件事
- 幫數字補 0
- 直接印出結果
這樣會有一個問題:如果未來我只想補 0,但不想印出來,這個函式就不能直接用。
所以我們下一步會再把「補 0」拆成一個獨立的小函式。
進一步精煉:單一職責的函式
到這裡,我們的程式已經比一開始好很多:
- 重複邏輯被抽出來
- 增加新動物只要多呼叫一個函式
但仍有一個問題:
👉 我們的 printZeroPaddedWithLabel 仍然在做兩件事:「補 0」+「印出結果」。
🔍 為什麼要讓函式只做一件事?
在程式設計中,有一個非常重要的好習慣:
「一個函式最好只負責一件事」(這也被稱為「單一職責原則」)。
原因很簡單:
- 更靈活:
如果補 0 的功能被獨立出來,我們可以在其他地方(不只是列印動物數量)直接重用它。 - 更好維護:
以後要改補 0 的規則(例如補到 5 位數),只要改一個地方。 - 更好理解:
一個函式名稱就代表一個明確的功能,讀起來就像在讀一個清單,而不是一大段混雜邏輯。
所以,我們要把「補 0」獨立成自己的函式,並且讓它只專注於回傳補好 0 的字串。
終極版本程式
function zeroPad(number, width) {
let string = String(number);
while (string.length < width) {
string = "0" + string;
}
return string;
}
function printFarmInventory(cows, chickens, pigs) {
console.log(`${zeroPad(cows, 3)} Cows`);
console.log(`${zeroPad(chickens, 3)} Chickens`);
console.log(`${zeroPad(pigs, 3)} Pigs`);
}
printFarmInventory(7, 16, 3);執行結果:
007 Cows
016 Chickens
003 Pigs終極版本做了什麼?(一步步拆解思路)
這個最終版本的核心,就是把程式分工做得更清楚,每個函式只專心做好一件事。
我們來細拆每一步。
zeroPad(number, width):專心補 0
這個函式就像一個「小工具」,專門幫你把數字格式化成你想要的樣子。
👉 這個函式到底做了什麼?
String(number)→ 把數字轉成字串
- 例如
7→"7",16→"16"。 - 為什麼要這樣做?
因為字串才有.length屬性,我們才能用.length來檢查目前的位數。
while (string.length < width)→ 持續補 0
- 只要字串長度還沒達到我們指定的寬度(
width),就不斷在前面加"0"。 - 例子:
7→"7"→"07"→"007"16→"16"→"016"(補一次就夠了)
- 為什麼用
while而不是if?if只能判斷一次,如果數字只有 1 位(需要補 2 個 0),if只會補一次,結果會錯。while能確保「補到剛剛好」。
return string→ 回傳結果
- 最後這個函式只會回傳處理好的字串,不做其他事。
- 例子:
zeroPad(7, 3)→"007"zeroPad(16, 3)→"016"
printFarmInventory:專心列印清單
在這個版本裡,printFarmInventory 不再關心補 0 的細節,它唯一的工作就是「把數字和標籤一起印出來」。
👉 它現在的樣子:
console.log(`${zeroPad(cows, 3)} Cows`);
console.log(`${zeroPad(chickens, 3)} Chickens`);
console.log(`${zeroPad(pigs, 3)} Pigs`);這樣一眼看過去,就像在讀一個簡單的「動物清單」,而不是一大堆重複的補 0 邏輯。
✅ 好處是什麼?
- 你不用再關心「補 0 要寫幾行、邏輯怎麼寫」。
- 你只要相信
zeroPad會給你正確的結果。
這樣做有什麼好處?
zeroPad 是萬用小工具
因為它只專心處理字串格式,所以它不限定在動物數量,任何需要補 0 的地方都能直接拿來用:
- 產生編號(
001,002,003) - 格式化日期(例如把
7→"07",方便印成2025-07-21) - 製作整齊的報表數字(對齊效果更好看)
維護更輕鬆
如果哪天需求變了,老闆說「我要把數字補到 5 位數(例如 00007)」——
只要修改 zeroPad 一行邏輯,所有用到它的地方都會自動更新。
不用再像第一版那樣跑去每個地方逐一修改。
程式可讀性大幅提升
printFarmInventory 現在變得非常乾淨,看起來就像一段「流程描述」:
console.log(`${zeroPad(cows, 3)} Cows`);
console.log(`${zeroPad(chickens, 3)} Chickens`);
console.log(`${zeroPad(pigs, 3)} Pigs`);即使是第一次看這段程式碼的人,也能馬上猜到:
「哦,這裡是印出動物數量,而且數字會被格式化成 3 位數。」
函式責任分工更明確
zeroPad→ 只負責處理數字格式(像是專門的「工具人」)printFarmInventory→ 只負責輸出結果(像是「記錄員」)
這種分工結構,就像在搭積木:
每塊積木功能單純、好組合,以後還能重複利用。
小結:這就是「好程式」的樣子
從最初的「把所有東西擠在一起」,到最後的「每個函式各司其職」,你應該能感受到:
✅ 程式更容易理解
✅ 新增功能或改需求更簡單
✅ 函式能重複使用,真正變成工具
這就是為什麼「單一職責的函式」是程式設計中非常重要的一個好習慣。
函式設計思維:該多抽象?該多聰明?
當你開始會寫函式後,很容易陷入一個誤區:是不是能抽出來的邏輯,就一定要抽?
事實上,並不是所有程式碼都需要被封裝成函式。函式的目的是讓程式更好維護,而不是為了「顯得自己很專業」。
接下來我們來看看在設計函式時,應該怎麼拿捏這個「抽象程度」。
適度聰明:別為了「炫技」過度抽象
zeroPad 是一個很好的例子,它簡單、明確、而且用途很廣,所以非常值得封裝成函式。
但有些初學者一學會寫函式,就會過度抽象,像這樣:
function addOneAndPrintHello() {
console.log("Hello");
return 1 + 1;
}這種函式就完全沒必要,因為:
- 它只會用到一次
- 功能非常簡單,一眼就能看懂
- 別人看到這個函式名稱,還得打開來看它到底在幹嘛,反而增加理解成本
💡 一句話總結:函式應該讓程式「更簡單」,而不是「更複雜」。
判斷原則:什麼時候該封裝成函式?
你可以用以下兩個問題來幫自己判斷:
- 這段邏輯會被重複使用嗎?
- ✅ 會 → 封裝成函式
- ❌ 不會 → 留在當前程式碼就好
**例子:**補 0、計算稅額、格式化日期 → 很常用,所以值得封裝。
- 這段邏輯有「明確意圖」嗎?
- ✅ 有 → 封裝成函式,名稱就像「標籤」一樣,幫助理解
- ❌ 沒有 → 抽出來只會讓程式更難讀
例子:printFarmInventory→ 一看就知道「列印農場動物數量」。
💡 一個小技巧:
當你可以用一句話清楚表達這段邏輯是「做什麼事」時,就適合把它封裝成函式,並用這句話當作它的名稱。
找到平衡點:什麼該抽?什麼不該抽?
✅ 該抽出來的:
- 通用邏輯 → 多次使用、到處都會用到
例如:zeroPad()、formatDate()、calculateTax() - 具有明確意圖的功能 → 即使只用一次,但能讓程式更好讀
例如:printFarmInventory()(一看就知道它是「列印農場清單」的功能)
❌ 不該抽出來的:
- 只用一次、邏輯非常簡單
例如:let total = a + b;不需要寫一個addTwoNumbers(a, b) - 抽象到過於零碎
例如把「印一行結果」單獨抽出來成printLine(),除非它有更多附加功能,否則這種封裝只會增加負擔。
結語
從這個簡單的例子中,你應該能感受到函式的力量。
- 第一步: 解決當下問題
- 第二步: 減少重複
- 第三步: 抽出可重用的邏輯,讓程式更靈活
當你未來寫程式時,記得隨時觀察自己的程式碼是否出現重複,並思考:
「這段邏輯會在其他地方用到嗎?能否抽成一個好名字的函式?」