本文摘譯自HtDP第三章旱函,原有語言DrRacket改寫為Python。本文還混有Composing Programs的內(nèi)容踪古。
設計程序當中很重要的一個環(huán)節(jié)是將問題轉(zhuǎn)化為程序。在這當中需要注意區(qū)分哪些對于描述問題至關重要拘泞,哪些無關緊要枕扫。此外,我們還應當確定目標程序的參數(shù)是什么诗鸭,輸出結(jié)果又是什么参滴,以及參數(shù)與輸出結(jié)果有什么樣的內(nèi)在聯(lián)系。我們還應該注意所選擇的編程語言以及相關庫是否對數(shù)據(jù)處理過程提供了基本的運算支持蝌箍。如果沒有暴心,那我們得自己編寫相關的輔助函數(shù)酷勺。最后,一旦程序完成甚亭,我們得檢驗一下程序是否按照預想的方案執(zhí)行計算過程击胜。
程序?qū)懞煤螅詈糜幸粋€簡短的說明暇唾,講述這個程序的功能辰斋,所需要的參數(shù),以及所產(chǎn)生的結(jié)果够挂。如能夠說明程序的正常運行狀態(tài)就再好不過了藕夫。最好的情況是,程序與求解問題的對應關系非常精確办悟。即如果求解問題狀態(tài)有小幅變動病蛉,程序也同樣能夠?qū)Υ俗鞒鲂》{(diào)整。
我們之所以作出上述要求是因為程序不止是滿足客戶要求能夠運行即可铡恕。我們要考慮到程序后期維護的需要:如果團隊人員發(fā)生變動探熔,新員工必須盡快讀懂程序。此外柬甥,提高程序的可讀性其垄,也會方便客戶隨時改動。
本書針對上述情況提出了一套系統(tǒng)地設計程序的流程臂外。
設計函數(shù)
信息與數(shù)據(jù)
信息是對這個世界的描述漏健。例如橘霎,三張電腦桌,價格分別為599元瓦盛,1299元外潜,2899元。
數(shù)據(jù)是對信息中與問題相關部分的抽象嘱吗。例如碧库,要求出三張桌子的平均價格,那么這三張桌子的品牌弄匕,材質(zhì)就無關緊要沽瞭,從而三張桌子可抽象為一個數(shù)組tables = [599, 1299, 2899]
驹溃。
從某種意義上講,程序?qū)嶋H上是對信息處理流程的描述亡哄。程序?qū)⒄鎸嵤澜绲男畔⑥D(zhuǎn)化為數(shù)據(jù)布疙,經(jīng)過計算處理后蚊惯,得出新的數(shù)據(jù),然后將其轉(zhuǎn)化為信息并輸出灵临。其信息的來源范圍實際上是這個真實世界的一部分截型,稱作程序的定義域(Domain)。
例如一個智能手機操作系統(tǒng)需要捕捉手指的點擊儒溉,將觸摸屏幕的電學信號轉(zhuǎn)化為屏幕坐標宦焦。正在運行的app接收到坐標后,根據(jù)預先編好的程序顿涣,發(fā)出指令要求屏幕上的圖片翻轉(zhuǎn)一次波闹。操作系統(tǒng)按照指令,控制屏幕的像素點的電學信號园骆,不斷的刷新屏幕,生成圖片翻轉(zhuǎn)動畫锌唾。
軟件工程界使用MVC(Model-View-Controller)模型來組織上述過程的代碼锄码。三個字母分別指代:數(shù)據(jù)處理過程、數(shù)據(jù)->信號過程晌涕、信號->數(shù)據(jù)過程滋捶。
撰寫函數(shù)說明
當明白了信息與數(shù)據(jù)的相互轉(zhuǎn)化過程后,就可以根據(jù)如下流程設計函數(shù):
-
使用注釋語句解釋一下如何使用數(shù)據(jù)描述信息余黎。例如重窟,
'We use sequence to represent the price of tables'
-
寫出函數(shù)簽名(Function Signature),目標陳述(Purpose Statement)惧财,函數(shù)存根(Function Header, 也叫stub)
函數(shù)簽名(Function Signature)描述了函數(shù)所需參數(shù)和產(chǎn)生的結(jié)果巡扇。例如扭仁,一個將整型數(shù)組轉(zhuǎn)化為整型數(shù)字的函數(shù)的簽名為
'Integer[] -> Integer'
目標陳述(Purpose Statement)描述了函數(shù)的作用:這個函數(shù)計算了什么。例如厅翔,一個計算三張桌子平均值的函數(shù)的目標陳述為
'Calculate the average price of tables'
函數(shù)存根(Function Header, 也叫stub)將函數(shù)的參數(shù)替換為具體的合法數(shù)據(jù)乖坠,并提供了輸出結(jié)果的具體示例
'100 = average_price([int, int, int])' # average_price為函數(shù)名
-
使用樣例來證明以上三項。
""" >>> average_price([50, 100, 150]) 100 >>> average_price([80, 160, 240, 320]) 200 """
編寫函數(shù)代碼
也就是完成對信號->數(shù)據(jù)過程刀闷、數(shù)據(jù)處理過程熊泵、數(shù)據(jù)->信號過程的描述。-
測試函數(shù)
Python中可以使用
run_docstring_examples
函數(shù)來完成這個過程甸昏。
def average_price(prices):
"""This function is to calculate the average price of tables.
We use sequence to represent the price of tables.
Integer[] -> Integer
100 = average_price([int, int, int])
>>> average_price([50, 100, 150])
100
>>> average_price([80, 160, 240, 320])
200
"""
total = 0
count = len(prices)
for price in prices:
total += price
average = total / count
return average
from doctest import run_docstring_examples
run_docstring_examples(average_price, globals(), True)
函數(shù)average_price()
在聲明時使用三個引號提供了一份簡單的說明顽分。在解釋器中執(zhí)行help()
函數(shù)皆可獲得這段說明(按Q退出)。同時施蜜,這份說明也指出了特定值下的輸出值卒蘸。利用run_docstring_examples()
函數(shù)可以自動完成檢驗,并輸出檢驗結(jié)果翻默。如下,
Trying:
average_price([50, 100, 150])
Expecting:
100
ok
Trying:
average_price([80, 160, 240, 320])
Expecting:
200
ok
其他須知
計算機程序都是在解決實際問題悬秉,因此編程人員應當對程序所應用的相關學科有一定的了解,例如數(shù)學冰蘑、音樂和泌、生物、土木工程等等祠肥。
此外武氓,還要對所使用的函數(shù)庫的API有所了解。例如仇箱,要用Python處理IP相關問題县恕,應當對ipaddress函數(shù)庫有所了解。
從函數(shù)到程序
復雜的程序不可能只有一個函數(shù)剂桥。大多程序都需要不少輔助函數(shù)忠烛,有些還需要定義很多常量。因此一定要使用好輔助函數(shù)和全局常量权逗,來系統(tǒng)的設計好每一個函數(shù)美尸。記得給函數(shù)和常變量起一個有意義的名字。
如果有必要斟薇,應該把預先定義的全局常量在函數(shù)說明里公示出來师坎,以提醒程序維護人員這些常量的存在。
通常堪滨,隨著程序編寫的深入胯陋,你會發(fā)現(xiàn)需要不斷添加新的輔助函數(shù)和常量。我們建議你在編寫程序的過程同時也維護一張“目標清單”,把需要添加的新的輔助函數(shù)和常量列進清單遏乔。一旦完成义矛,把它劃去。只要清單上還有項目盟萨,就繼續(xù)工作症革。如果你發(fā)現(xiàn)清單里的項目全部完成了,那編程的工作也就結(jié)束了鸯旁。
程序測試
Python還可以使用assert
聲明執(zhí)行成規(guī)模的自動化的函數(shù)測試。
# define a function to test
def fib(x):
if x==0:
return 0
elif x==1:
return 1
else:
return fib(x-1)+fib(x-2)
# define a test function
def fib_test():
assert fib(2) == 1, 'The 2nd Fibonacci number should be 1'
assert fib(3) == 1, 'The 3rd Fibonacci number should be 1'
assert fib(50) == 7778742049, 'Error at the 50th Fibonacci number'
# execute test
fib_test(2)