程式碼可讀性不佳會增加軟體維護成本與團隊協作難度。本文以 Python 洗牌程式為例,闡述如何透過函式拆分、變數命名、常數設定、斷言使用及清晰的輸出設計,提升程式碼可讀性。同時也探討了單元測試、程式碼評審和團隊規範的重要性,說明這些實務經驗如何幫助團隊有效合作、提升程式碼品質,避免潛在錯誤。良好的程式碼檔案規範與輸出設計能提升程式碼易用性,有助於使用者理解程式執行結果和效能。文章也分享了老程式設計師的故事,提醒讀者持續學習和精進技術的重要性,才能在瞬息萬變的職場中保持競爭力。
程式碼可讀性與檔案規範的重要性
在軟體開發領域,程式碼的可讀性和檔案規範是確保程式碼品質的關鍵因素。本文將透過一個洗牌問題的範例程式,探討如何透過良好的程式碼結構、命名、註解和輸出設計來提升程式碼的可讀性和維護性。
洗牌問題範例程式解析
以下是一個用Python撰寫的洗牌問題範例程式,用於計算一副52張牌在洗牌後至少有一張牌保持原位不動的機率。
程式碼結構
########################<START OF PROGRAM>#####################
def print_heading():
print(' A SHUFFLING PROBLEM')
print(' (currently calculating)')
def shuffle_arrays():
total_arrays = 0 # Arrays with at least one unmoved element after shuffling.
for trial in range(TRIAL_RUNS):
array = list(range(LIST_SIZE))
shuffle(array)
for num in range(LIST_SIZE):
if array[num] == num:
total_arrays += 1
break
probability = round(total_arrays / TRIAL_RUNS, 2)
return probability
def print_result(probability):
print(' Result:', probability, 'is the probability of an array having at')
print(' least one unmoved element after shuffling. This is based')
print(' on a computer simulation with an array size =', LIST_SIZE, 'and')
print(' ', TRIAL_RUNS, 'trial runs.')
from random import shuffle
TRIAL_RUNS = 1000000
LIST_SIZE = 52
assert LIST_SIZE > 1, 'LIST_SIZE must be greater than 1.'
def main():
print_heading()
probability = shuffle_arrays()
print_result(probability)
if __name__ == '__main__':
from time import clock; START_TIME = clock(); main()
print('\n ' + '- ' * 12)
print(' PROGRAM RUN TIME:%6.2f' % (clock() - START_TIME), 'seconds.')
##################<END OF PROGRAM>#############################
#### 內容解密:
- 程式碼被組織成多個函式,每個函式都有明確的職責,如
print_heading、shuffle_arrays和print_result,這使得程式碼更容易理解和維護。 - 使用有意義的變數名稱,如
total_arrays和probability,能夠清晰地表達變數的用途。 - 全域常數
TRIAL_RUNS和LIST_SIZE被用於控制模擬的次數和陣列的大小,這些常數被定義在程式的頂部,使得修改引數變得容易。 - 程式中使用了斷言(
assert)來檢查LIST_SIZE是否大於1,確保程式執行的正確性。
檔案規範與輸出設計
良好的檔案規範和輸出設計對於提升程式碼的可讀性和使用者經驗至關重要。範例程式中包含了詳細的輸出說明和執行時間統計,這些資訊對於理解程式的執行結果和效能評估非常有幫助。
輸出結果
A SHUFFLING PROBLEM
(currently calculating)
Result: 0.63 is the probability of an array having at
least one unmoved element after shuffling. This is based
on a computer simulation with an array size = 52 and
1000000 trial runs.
- - - - - - - - - - - -
PROGRAM RUN TIME: 43 seconds.
#### 內容解密:
- 輸出結果清晰地標示了模擬的結果和基礎引數,使用者可以很容易地理解輸出的意義。
- 程式執行時間被列印出來,這對於評估程式的效能非常有幫助。
程式設計的幻想
從前,有一位才華橫溢的年輕程式設計師,他身處一個無法獲得更多教育資源的環境中。他有一份沒有前途的工作,這份工作永遠不會給他帶來晉升的機會。此外,他的家庭無法幫助他,而他居住的社群正在衰敗且不安全。我們的程式設計師有四位朋友,他們也具備相似的程式設計技能,並且同樣感到機會有限。他們都有些沮喪,並擔憂自己的未來。
突然,這五位程式設計師發現了一個令人驚嘆的機會。如果他們能夠團結合作,編寫一個特定的電腦應用程式,那麼他們將獲得的關注將立即為他們開啟通往更好工作的機會大門。當然,在這種情況下,任何人都想嘗試編寫這個應用程式。但事情並不那麼簡單。以前,他們每個人所寫的最具挑戰性的程式都需要三個星期的時間,每天花費1-2小時。大多數時間都花在除錯上。其中一些錯誤非常難以追蹤,以至於他們曾兩次放棄了自己的程式,只是出於好奇心才又回來繼續完成。事實上,這三個星期的時間實際上是在六週內分散完成的。他們都有相同的經歷。
在回顧這個新專案的工作時,似乎這項工作自然可以分成五個相等的部分。問題是,每個部分的工作量是他們任何一個人以前所做過的工作的五倍。他們有40週的時間來完成這個專案。理論上,如果所有人都能保持專注,那麼這段時間足以完成專案。但在實際操作中,這個專案的複雜程度超出了任何人所能應付的範圍。誘人的獎賞也是失敗的邀請。短暫地,每個人都覺得他們目前所過的安靜無奇的生活可能比接下來40週的痛苦更可取,而這40週幾乎肯定會以失敗告終。誰需要那樣?也許會有其他機會出現。最終,在交談中,這五位朋友意識到,這種失敗主義的思維是人們無法擺脫貧困生活的一個常見原因。然而,就像他們目前對這個專案的理解一樣,它太困難了,他們無法完成。如果他們能夠增加成功的可能性,那麼這可能是值得一試的。那麼,該怎麼做呢?
首先,這五位程式設計師必須接受一個事實,那就是他們必須把自己變成程式設計機器。他們日常生活中的許多樂趣都必須被數小時的程式設計所取代。這需要改變他們的生活習慣和觀點。他們能做到嗎?擺在他們面前的獎賞可能就足夠了。
真正的問題是除錯。儘管所有的程式碼看起來都相當合理,但由於程式碼數量眾多,除錯問題將會大量出現。他們看不到任何一個人能夠成功的方法。然後,有人提出了一個解決方案:對於幾乎每個編寫的關鍵功能,都可以編寫一個配套的功能來測試該功能。在每次程式設計結束後,將執行這些測試功能。另一個程式將匯入大多數重要的功能,並透過每個功能執行幾組資料。這些資料將測試,例如,函式中的幾乎每個if陳述式。
這意味著,如果發生重新設計,受影響的功能將立即被標記出來。為每個需要的功能編寫兩個功能將是額外的工作,但測試功能將很簡單,並且大多彼此相似。這種方案,被稱為單元測試,似乎給予了希望。
另一位成員建議,這個團隊每週聚會一次,互相閱讀對方的程式碼,討論問題,並提出來自新鮮眼光的解決方案。在這些程式碼評審中,他們將分享問題和來之不易的解決方案。另一個建議是,用英文描述(docstrings)記錄幾乎每個關鍵功能,以便其他成員可以更容易地理解程式碼。另一個建議是,他們應該偶爾嘗試成對工作(配對程式設計):一個人輸入,另一個人思考輸入的內容。
團隊認為,他們成功的唯一機會是採用這些慣例。其中一名成員後來把與這些慣例一起工作描述為穿著約束衣寫程式碼。
強化團隊合作與規範
在程式設計開始後不久,成員們注意到進展雖然緩慢,但卻很穩定。不可避免的重新設計,通常根據被忽略的特殊情況和選擇不佳的資料結構,幾乎總是會產生連鎖反應,導致其他變化。這些變化都被單元測試迅速發現和定位。
成員們也開始討論程式碼風格的小差異——例如,應該寫成
if (x and y) == True: print(x)
還是
if x and y: print(x)
內容解密:
上述兩種寫法都是用來檢查x和y是否皆為True,進而決定是否列印x。第一種寫法明確比較表示式(x and y)與True,但較冗餘。第二種寫法則直接利用Python的真值測試特性,若x和y皆為真,則執行列印x的操作。這種簡潔寫法更符合Python的程式設計風格。
由於意見不同,他們決定就團隊風格進行投票,並堅持團隊的決定。最終,他們的規範雖然往往是任意的,但開始看起來是正確的,而任何不同的規範看起來都是錯誤的。因為每個人都使用相同的風格,他們在閱讀以團隊風格寫成的程式碼時都變得高效。
簡而言之,他們透過犧牲、承諾和在寫程式碼方面的良好決策,使他們能夠完成專案並贏得更好的生活。最終,他們被尋求專家程式設計師的僱主所僱用。
為何團隊成功
他們的新僱主欣賞這個團隊成員有幾個原因。首先,這些程式設計師在寫程式碼上投入了大量的生命。他們透過共同合作、單元測試、程式碼評審和配對程式設計等方法,不僅提高了程式碼品質,也提升了團隊合作效率。這些實踐使他們在業界脫穎而出。
啟示
- 單元測試的重要性:透過為每個功能編寫測試函式,可以及時發現和修復錯誤,大大提高開發效率。
- 程式碼評審的價值:定期進行程式碼評審,可以分享知識、發現潛在問題,並促進團隊成員之間的相互學習。
- 統一的程式碼風格:採用統一的程式碼風格,可以提高程式碼的可讀性和維護性,使團隊合作更加高效。
這個故事提醒我們,透過採用良好的程式設計習慣和最佳實踐,即使面對複雜的挑戰,也能夠成功地完成專案並實作職業上的突破。
程式設計的幻想:從職場到教學現場
在程式設計的世界裡,優秀的程式設計師總是能夠寫出高效、易讀且靈活的程式碼。他們不僅能夠快速完成任務,還能確保程式碼的可讀性和可維護性。在某些公司,即使面臨裁員,這些優秀的程式設計師也總是能夠保住自己的工作職位。僱主們對他們的評價是:「他們總是超出預期,誰會捨得讓這樣的人離開?」
多年後,這些程式設計師紛紛退休,但其中一位年輕的程式設計師在退休後感到有些無聊。他懷念寫程式的日子,於是在配偶的建議下,他開始在鄰近的高中擔任兼職程式設計老師。
這位老師延續了前任老師的教學方法,強調學生對不同演算法的理解和程式設計能力的培養。在教學過程中,他意識到,雖然許多在業界成功的經驗不一定適用於學生編寫的小型程式,但編寫可讀性高的程式碼仍然是一項重要的技能。因此,他提出了以下指導方針:
給程式設計新手的建議
- 限制函式功能:每個函式應該只負責單一任務或高度相關的任務,以提高內聚力並降低耦合度。
- 標籤和對齊輸出:確保輸出內容清晰易讀。
- 檔案頭部註解:在程式頂部註明姓名、日期、班級、課程和程式描述,注意拼寫、語法和標點符號。
- 使用行號和縮排:編寫程式碼時使用行號,並至少縮排三個空格。
- 垂直對齊:在程式碼中使用垂直對齊來強調重要的關係。
- 避免使用保留字:不要使用Python語言的保留字或內建名稱作為識別符或檔案名稱。
- 重構程式碼:在程式運作正常後進行重構,以提高可讀性。
- 逐步細化:使用函式呼叫來概述程式的工作流程,將
main()函式限制為呼叫其他函式。 - 撰寫自檔案化程式碼:使用描述性的識別符和動詞-物件函式名稱,減少註解的使用。
- 列印執行時間:在每個程式中列印執行時間和其他相關統計資料。
- 避免魔術數字:除非能夠簡化程式碼,否則避免使用魔術數字。
- 避免全域變數:儘量避免使用全域變數,但可以使用全域常數。
內容解密:
上述指導方針強調了編寫可讀性高、易於維護的程式碼的重要性。其中,第1點和第8點強調了函式設計的重要性,第3點和第9點則關注於程式碼的註解和命名規範。第7點提到的重構是提高程式碼品質的重要步驟。
def calculate_area(radius):
"""
計算圓形面積。
:param radius: 圓形的半徑
:return: 圓形的面積
"""
pi = 3.14159 # 使用區域性變數而非魔術數字
return pi * (radius ** 2)
# 使用描述性的變數名稱
circle_radius = 5
area = calculate_area(circle_radius)
print(f"The area of the circle is {area:.2f}")
內容解密:
在這個範例中,我們定義了一個calculate_area函式來計算圓形的面積。我們使用了描述性的變數名稱和函式名稱,並在函式內部定義了pi的值以避免使用魔術數字。輸出的結果使用了格式化字串,以提高可讀性。
- 避免聰明的程式碼:不要寫出難以理解的程式碼,簡單明瞭的程式碼才是更好的選擇。
- 優先考慮可讀性:在最佳化速度和記憶體使用之前,先考慮程式碼的可讀性。
- 預期錯誤:使用斷言、錯誤捕捉、
try/except區塊和中間列印來預期和處理錯誤。 - 測試每個關鍵函式:在完成每個關鍵函式後進行測試,將未經測試的程式碼視為有問題的程式碼。
內容解密:
第13點和第14點強調了可讀性在程式設計中的重要性。第15點和第16點則關注於錯誤處理和測試,以確保程式碼的穩定性和可靠性。
def divide(a, b):
"""
將兩個數字相除。
:param a: 被除數
:param b: 除數
:return: 商
:raises ZeroDivisionError: 如果除數為零
"""
try:
return a / b
except ZeroDivisionError:
print("錯誤:除數不能為零。")
raise
# 測試函式
try:
result = divide(10, 2)
print(f"結果:{result}")
except ZeroDivisionError:
print("捕捉到除以零的錯誤。")
內容解密:
這個範例展示瞭如何使用try/except區塊來處理潛在的錯誤。在divide函式中,我們捕捉了ZeroDivisionError並列印了錯誤訊息,同時重新引發了該錯誤以便呼叫者可以進一步處理。
一位老程式設計師的故事:警惕平庸的危險
在程式設計的世界裡,學生們常常會遇到各種挑戰和困惑。他們會抱怨老師的要求太嚴格,程式碼必須遵循一定的規範和風格。然而,老程式設計師的故事卻揭示了一個殘酷的現實:平庸是成功的最大敵人。
從熱情到失望
起初,學生們對程式設計充滿了熱情,但隨著課程的進行,他們開始對老師的嚴格要求感到不滿。“為什麼要在程式碼中加入錯誤處理?”“我的程式碼在我的輸入下可以正常工作,但他的輸入卻總是出錯。”這些抱怨反映了學生們對老師要求的困惑和不理解。
老程式設計師察覺到課堂氛圍的變化,從最初的熱情逐漸變成了不滿。於是,他改變了教學策略。他開始減少對程式碼風格的檢查,轉而接受只要程式能夠運作的作業。作業變得越來越短,也越來越容易。他開始在課堂上播放有趣的YouTube影片,並允許學生們進行熱烈的討論,即使這會佔用一些練習時間。
一個令人驚訝的故事
在學年即將結束時,老程式設計師反思了整個教學過程。他意識到,自己試圖傳授給學生的想法、習慣和視野對於他們來說太過超前。他們能夠快速掌握細節,但缺乏成熟度和動力去理解更大的圖景。多年前的自己和朋友們曾經被迫透過艱難的經歷來改變自己,而這種元層次的思考方式無法單純透過講述來傳授,必須透過親身經歷才能被真正理解。
就在這時,老程式設計師給學生們講了一個故事,這個故事將成為他們未來的警示。
未來的警示
在故事中,老程式設計師聲稱自己在夢中與上帝交談,上帝向他揭示了學生們未來的命運。學生們將會順利完成學業,找到一份不錯的工作,並擁有幸福的家庭生活。然而,在45歲左右,他們將面臨被解僱的命運,因為企業不斷變化,僱用市場處於不斷的流動中。
由於他們在職業生涯中並未表現出眾,因此被解僱。他們試圖找到新的工作,但年輕的程式設計師因其薪水要求較低而更受青睞。最終,他們不得不接受低薪的工作,甚至從事清潔工來維持生計。家庭關係變得緊張,最終導致離婚。
程式碼解析:理解老程式設計師的故事
def future_warning(age):
if age >= 45:
return "被解僱的風險增加"
else:
return "仍有發展機會"
print(future_warning(40)) # 輸出:仍有發展機會
print(future_warning(50)) # 輸出:被解僱的風險增加
內容解密:
def future_warning(age):定義了一個名為future_warning的函式,該函式接受一個引數age,代表個人的年齡。if age >= 45:檢查傳入的年齡是否大於或等於45歲。根據故事內容,這個年齡段的人面臨被解僱的風險增加。return "被解僱的風險增加"如果條件成立,函式傳回一個字串,警告該年齡段的人面臨的就業風險。else:如果年齡小於45歲,則執行這一部分的程式碼。return "仍有發展機會"傳回一個字串,表示該年齡段的人仍有發展的機會。
這個簡單的函式模擬了老程式設計師故事中的未來警示,提醒人們在45歲左右可能面臨的職業挑戰。