程式碼的品質攸關軟體專案的成敗,因此開發者必須掌握最佳實務與技巧。從測試優先的開發策略開始,確保程式碼的正確性是第一步。接著,透過簡化程式碼邏輯、避免魔術數字和重複程式碼,可以有效提升程式碼的可讀性和可維護性。同時,瞭解運算子優先順序、全域變數的使用限制以及 SESE 原則等,有助於避免常見的程式設計錯誤。此外,在追求程式碼效能的同時,也需注意避免過早最佳化,並保持程式碼的簡潔明瞭,才能在效能和可讀性之間取得平衡。最後,選擇適當的程式碼風格和排版,並遵循 DRY 原則,都有助於開發高品質、易維護的軟體專案。
寫測試優先(有時)
撰寫測試優先是軟體開發中的一個重要實踐,尤其是在處理複雜演算法時,如二元搜尋。一個好的測試策略不僅能確保程式碼的正確性,還能幫助開發者更深入地理解問題本身。
二元搜尋的例子
二元搜尋是一種在已排序陣列中尋找特定目標值的演算法。它的基本思想是透過比較中間元素與目標值來縮小搜尋範圍,直到找到目標值或確定目標值不存在。
原始的二元搜尋實作
def binarySearch(array, target):
left = 0
right = len(array) - 1
while left <= right:
mid = (left + right) // 2
if array[mid] < target:
left = mid + 1
elif array[mid] > target:
right = mid - 1
else:
return mid
return -1
解開糾纏的程式碼
def binarySearchUT(array, target):
left = 0
right = len(array) - 1
while left <= right:
mid = (left + right) // 2
if array[mid] < target: left = mid + 1
if array[mid] > target: right = mid - 1
if array[mid] == target: return mid
return -1
內容解密:
- 二元搜尋的基本邏輯:透過不斷計算中間索引
mid,並比較array[mid]與target的大小,來決定下一步的搜尋範圍。 - 程式碼簡化:將原始的
elif和else分支改為多個獨立的if陳述式,使程式碼更直觀易懂。 - 效能考量:雖然簡化後的程式碼在某些情況下可能稍慢,但對於大多數應用場景來說仍然足夠高效。
測試的重要性
測試是確保程式碼正確性的關鍵步驟。透過事先撰寫測試案例,可以在開發過程中及時發現並修復錯誤。
如何測試測試?
- 注入錯誤資料:故意傳入錯誤或邊界資料,驗證測試程式是否能正確捕捉到錯誤。
- 簡單程式碼的自我驗證:對於邏輯簡單的程式碼,測試程式與被測程式可以相互驗證。
專家建議
在軟體開發過程中,累積經驗和學習最佳實踐是非常重要的。以下是一些寶貴的建議:
- 快速失敗:在開發初期就應當盡快進行測試,以避免在錯誤的方向上浪費過多時間。
- 閱讀他人程式碼:學習優秀的開源程式碼,可以幫助我們提升自己的程式設計水平。
編寫高品質程式碼的專家建議
在軟體開發的過程中,編寫高品質的程式碼不僅能夠提升程式的穩定性和可維護性,還能減少除錯的時間和成本。以下是一些提升程式碼品質的實用建議。
垂直對齊的重要性
- 使用垂直對齊來強調變數之間的關係,使錯誤更容易被察覺,並且使查詢更加容易。這需要使用等寬字型,如 Courier。
M = [
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, -1, 1, 0, 0, 0],
[0, 0, 0, 1, -1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
]
程式碼解析:
- 上述範例展示瞭如何使用垂直對齊來呈現二維陣列
M。透過保持每個元素垂直對齊,可以更容易地觀察和理解資料的結構。 - 這種對齊方式特別適用於需要檢查或比較矩陣元素的情況。
字型與排版
在程式設計中,字型(font)和字型(typeface)經常被混用,但它們具有不同的含義。字型與字型的屬性相關,例如 Calibri Italic、Calibri Bold 或 Calibri Mono-spaced,都是 Calibri 字型的不同字型。
程式碼範例:使用不同字型呈現文字
for c in range(1, length-1):
ch = chr(32) # 空白字元
if L[c] == 1:
ch = chr(9607) # ■ 符號
if maxx == max1:
canvas.create_text(c*12 + 640-12*r, (r-1)*10,
text=ch,
fill='red', font=('Helvetica', 8, 'bold'))
程式碼解析:
- 這段程式碼根據
L[c]的值決定要顯示的字元,並將其呈現在畫布上。 - 使用不同的字型大小和顏色來強調特定的文字或符號,使視覺呈現更具層次感。
編寫強壯的程式碼
- 編寫強壯的程式碼,避免程式碼過於脆弱。強壯的程式碼應該能夠自我修復、允許使用者協助還原,或在必要時優雅地當機。
全域變數的使用
- 避免使用全域變數,因為大範圍的變數難以追蹤,特別是在偵測意外的變更時。不過,全域常數是可以接受的,因為它們的值不會被改變。
例外情況:
- 當全域變數不是程式碼的一部分時,例如用於計算遞迴函式的回溯次數。
- 用於記錄程式啟動時間,以便在不同函式中列印增量時間步長。
SESE 原則
- SESE(單一進入點和單一離開點)原則曾經被廣泛討論。雖然單一進入點有其合理性,但單一離開點可能過於嚴格。在某些情況下,直接傳回或中斷迴圈會更方便。
傳回多種資料型別的函式
- 避免編寫傳回不同資料型別的函式,因為這會增加使用的複雜度。例如,一個函式可能根據情況傳回字串或數字,這會導致呼叫時需要額外的判斷邏輯。
範例:二次方程求解
def quad(a, b, c):
from math import sqrt
disc = b*b - 4*a*c
if disc < 0:
return 'There are no real roots.'
x1 = (-b + sqrt(disc)) / (2*a)
x2 = (-b - sqrt(disc)) / (2*a)
if disc == 0:
return x1
return x1, x2
程式碼解析:
- 該函式根據判別式
disc的值傳回不同型別的結果,可能是一個字串、一個數字或一個元組。 - 這種實作方式需要呼叫者在處理傳回值時進行額外的型別檢查。
改進版二次方程求解函式
def quad(a, b, c):
# 調整係數以防止溢位
m = max(abs(a), abs(b), abs(c))
if m != 0:
a1 = a/m
b1 = b/m
c1 = c/m
# 特例處理
if a == 0 and b == 0 and c == 0:
return 'All real numbers are roots.'
if a == 0 and b != 0:
x1 = -c/b
if x1 == int(x1):
x1 = int(x1)
return x1
# 使用合理的演算法計算根
from math import sqrt
disc = b*b - 4*a*c
if disc < 0:
return 'There are no real roots.'
# 對某些情況進行特殊處理,例如當 b*b 比 4*a*c 大得多時
# ...
程式碼解析:
- 改進後的
quad函式處理了更多邊界情況,例如當a、b或c為零時的特例。 - 對係數進行縮放以避免運算中的溢位問題,並在必要時對結果進行合理的處理。
程式碼最佳化與可讀性:專家建議
在軟體開發過程中,編寫高效且可讀的程式碼是至關重要的。本文將探討如何最佳化程式碼以提高其效能和可維護性,同時參考專家建議來改善我們的程式設計實踐。
1. 瞭解運算子優先順序
在編寫程式碼時,瞭解運算子的優先順序至關重要。錯誤的運算子優先順序可能導致意想不到的結果。例如:
a = 6
b = 4
print(a + b/4) # 輸出:7.0
print(a + b >> 2) # 輸出:3
print(a + (b >> 2)) # 輸出:7
內容解密:
- 在第一個
print陳述式中,b/4的結果是1.0,然後加到a上,得到7.0。 - 在第二個
print陳述式中,b >> 2的結果是1,但由於運算子優先順序,a + b並未被正確評估,因此結果是3。 - 在第三個
print陳述式中,加上括號後,b >> 2正確地被評估為1,然後加到a上,得到正確結果7。
2. 通用程式碼與特定程式碼的權衡
編寫通用程式碼可以提高程式碼的復用性,但也可能增加複雜度和開發時間。以下是一個例子:
def dataInput():
s = '輸入一個整數:'
posLimit = float('inf')
negLimit = -float('inf')
while True:
try:
data = input(s)
num = int(data)
if not (negLimit < num < posLimit): raise Error
except:
s = '"' + str(data) + '" 不是一個整數!請再試一次。\n輸入一個整數:'
else:
print('輸入 = ', num)
return num
內容解密:
- 這個函式使用
try/except區塊來捕捉非整數輸入,並提示使用者重新輸入。 - 它檢查輸入是否在指定的範圍內,如果超出範圍則引發錯誤。
3. 可擴充套件性與複雜度的平衡
在設計程式碼時,需要考慮其可擴充套件性和複雜度。以下是一個 Sudoku 解題器的例子:
# 建構 9x9 網格的區塊列表
block = [[],[],[], [],[],[], [],[],[]]
block[0] = [matrix[0][0].value, matrix[0][1].value, matrix[0][2].value,
matrix[1][0].value, matrix[1][1].value, matrix[1][2].value,
matrix[2][0].value, matrix[2][1].value, matrix[2][2].value]
# ...
內容解密:
- 這段程式碼手動建構了一個 9x9 網格的區塊列表,用於 Sudoku 解題。
- 將其改寫為 n×n 網格的通用版本可能會更簡潔,但也可能更難除錯。
程式設計最佳實踐:避免重複程式碼與魔術數字
在軟體開發過程中,遵循特定的程式設計最佳實踐至關重要。本文將探討兩個重要的原則:避免使用魔術數字(Magic Numbers)以及不重複程式碼(DRY: Don’t Repeat Yourself)。這些原則有助於提高程式碼的可維護性、可讀性和可擴充套件性。
避免魔術數字
魔術數字是指在程式碼中直接使用的數字常數,它們的含義不明確。例如,在一個程式中,如果多次使用數字10作為陣列的長度,當需要將這個長度改為100時,就需要在整個程式中查詢並替換所有相關的10。這不僅麻煩,而且容易出錯。
# 不好的做法
array_length = 10
my_array = [0] * array_length
# 好的做法
MAX = 10
my_array = [0] * MAX
內容解密:
- 定義常數:將魔術數字定義為常數,如
MAX,這樣當需要更改時,只需在一個地方修改。 - 提高可讀性:使用有意義的常數名稱可以提高程式碼的可讀性,使其他開發者更容易理解程式碼的意圖。
- 例外情況:在某些情況下,如使用數學公式或眾所周知的常數(如π),直接使用數字或常見的數學表示可能是可以接受的。
不重複程式碼(DRY原則)
DRY原則強調避免在程式碼中重複相同的邏輯或模式。這有助於減少維護成本,因為當需要更改某個邏輯時,只需在一個地方進行修改。
示例:檢查井字遊戲(Tic-Tac-Toe)的勝負
# 第一版:重複程式碼
def result(board):
score = 'XXX'
B = board
if B[0] + B[1] + B[2] == score or B[3] + B[4] + B[5] == score or \
B[6] + B[7] + B[8] == score or B[0] + B[3] + B[6] == score or \
B[1] + B[4] + B[7] == score or B[2] + B[5] + B[8] == score or \
B[0] + B[4] + B[8] == score or B[2] + B[4] + B[6] == score:
return 'win'
score = 'OOO'
if B[0] + B[1] + B[2] == score or B[3] + B[4] + B[5] == score or \
B[6] + B[7] + B[8] == score or B[0] + B[3] + B[6] == score or \
B[1] + B[4] + B[7] == score or B[2] + B[5] + B[8] == score or \
B[0] + B[4] + B[8] == score or B[2] + B[4] + B[6] == score:
return 'win'
return 'unk'
# 第二版:遵循DRY原則
def result(board):
B = board
for score in ('XXX', 'OOO'):
if B[0] + B[1] + B[2] == score or B[3] + B[4] + B[5] == score or \
B[6] + B[7] + B[8] == score or B[0] + B[3] + B[6] == score or \
B[1] + B[4] + B[7] == score or B[2] + B[5] + B[8] == score or \
B[0] + B[4] + B[8] == score or B[2] + B[4] + B[6] == score:
return 'win'
return 'unk'
內容解密:
- 減少重複:第二版透過迴圈檢查兩種不同的分數(‘XXX’和’OOO’),避免了重複的條件檢查。
- 提高可維護性:如果需要更改勝利條件的檢查邏輯,只需在一個地方進行修改。
- 簡化邏輯:透過抽象出共同的邏輯,使程式碼更加簡潔和易於理解。
撰寫程式碼的智慧:避免常見的錯誤與陷阱
在軟體開發的過程中,程式設計師們常常面臨著各種挑戰與抉擇。從效能最佳化到程式碼的可讀性,每一個決策都可能對專案的成功產生深遠的影響。本篇文章將探討一些在程式設計中常見的錯誤與陷阱,並提供專家的建議來避免這些問題。
1. 避免過早最佳化
最佳化程式碼是許多開發者的直覺反應,但過早的最佳化卻可能導致程式碼變得複雜難懂。Ken Thompson,一位UNIX的創始人,曾經說過:“百分之九十九的時間裡,簡單和暴力的方法就足夠了。” Barbara Liskov,2008年圖靈獎得主,也指出:“我認為效能被過度高估了,因為你需要的效能是足夠好的效能,而不是最好的效能。”
程式碼範例:最佳化前的思考
import random
import math
def hill_climbing():
theta = 0
for _ in range(1000):
if random.random() < 0.8:
theta += 0.3
else:
theta -= 0.1
return theta
print(hill_climbing())
內容解密:
- 這段程式碼實作了一個簡單的爬山演算法,用於模擬某種隨機過程。
random.random()生成一個0到1之間的隨機浮點數,用於決定theta的更新方向。- 程式碼簡單直接,易於理解和維護。
2. 簡潔明瞭的程式碼優於聰明的技巧
“聰明的”程式碼往往會變成錯誤的溫床。簡單直接的程式碼更容易被理解和除錯。專家們一致認為,應當避免使用那些看似聰明但實際上難以理解的程式碼技巧。
程式碼範例:簡單 vs. 聰明
# 簡單的程式碼
if x > y:
z = z + 3
if x <= y:
z = z - 5
# 聰明的技巧(應避免)
z = z + 3*(x > y) - 5*(x <= y)
內容解密:
- 第一個例子使用簡單的條件判斷來更新
z的值,易於理解。 - 第二個例子使用布林值轉換為整數的技巧來簡化表示式,但降低了可讀性。
3. 模擬Switch陳述式的不同方法
在Python中沒有內建的switch陳述式,但可以透過多種方法來模擬它。下面展示了幾種常見的方法,包括使用字典和列表。
程式碼範例:模擬Switch陳述式
def fn0(): print(0)
def fn1(): print(1)
def fn2(): print(2)
def fn3(): print(3)
# 使用if-elif-else
x = 1
if x == 0: fn0()
elif x == 1: fn1()
elif x == 2: fn2()
else: fn3()
# 使用列表
doIt = [fn0, fn1, fn2]
doIt[1]() # 輸出:1
# 使用字典
dict = {0: fn0, 1: fn1, 2: fn2, 3: fn3}
dict[1]() # 輸出:1
dict.get(2, fn3)() # 輸出:2,使用預設值
內容解密:
if-elif-else結構是實作條件分支的最直接方法。- 使用列表可以根據索引呼叫不同的函式,但不支援預設值。
- 使用字典可以實作類別似
switch的功能,並支援透過get方法設定預設值。