如何设计程序

本文摘译自HtDP第三章,原有语言DrRacket改写为Python。本文还混有Composing Programs的内容。

设计程序当中很重要的一个环节是将问题转化为程序。在这当中需要注意区分哪些对于描述问题至关重要,哪些无关紧要。此外,我们还应当确定目标程序的参数是什么,输出结果又是什么,以及参数与输出结果有什么样的内在联系。我们还应该注意所选择的编程语言以及相关库是否对数据处理过程提供了基本的运算支持。如果没有,那我们得自己编写相关的辅助函数。最后,一旦程序完成,我们得检验一下程序是否按照预想的方案执行计算过程。

程序写好后,最好有一个简短的说明,讲述这个程序的功能,所需要的参数,以及所产生的结果。如能够说明程序的正常运行状态就再好不过了。最好的情况是,程序与求解问题的对应关系非常精确。即如果求解问题状态有小幅变动,程序也同样能够对此作出小幅调整。

我们之所以作出上述要求是因为程序不止是满足客户要求能够运行即可。我们要考虑到程序后期维护的需要:如果团队人员发生变动,新员工必须尽快读懂程序。此外,提高程序的可读性,也会方便客户随时改动。

本书针对上述情况提出了一套系统地设计程序的流程。

设计函数

信息与数据

信息是对这个世界的描述。例如,三张电脑桌,价格分别为599元,1299元,2899元。
数据是对信息中与问题相关部分的抽象。例如,要求出三张桌子的平均价格,那么这三张桌子的品牌,材质就无关紧要,从而三张桌子可抽象为一个数组tables = [599, 1299, 2899]

从某种意义上讲,程序实际上是对信息处理流程的描述。程序将真实世界的信息转化为数据,经过计算处理后,得出新的数据,然后将其转化为信息并输出。其信息的来源范围实际上是这个真实世界的一部分,称作程序的定义域(Domain)

例如一个智能手机操作系统需要捕捉手指的点击,将触摸屏幕的电学信号转化为屏幕坐标。正在运行的app接收到坐标后,根据预先编好的程序,发出指令要求屏幕上的图片翻转一次。操作系统按照指令,控制屏幕的像素点的电学信号,不断的刷新屏幕,生成图片翻转动画。

软件工程界使用MVC(Model-View-Controller)模型来组织上述过程的代码。三个字母分别指代:数据处理过程、数据->信号过程、信号->数据过程。

MVC

撰写函数说明

当明白了信息与数据的相互转化过程后,就可以根据如下流程设计函数:

  1. 使用注释语句解释一下如何使用数据描述信息。例如,

    'We use sequence to represent the price of tables'
    
  2. 写出函数签名(Function Signature),目标陈述(Purpose Statement),函数存根(Function Header, 也叫stub)

    函数签名(Function Signature)描述了函数所需参数和产生的结果。例如,一个将整型数组转化为整型数字的函数的签名为

    'Integer[] -> Integer'
    

    目标陈述(Purpose Statement)描述了函数的作用:这个函数计算了什么。例如,一个计算三张桌子平均值的函数的目标陈述为

    'Calculate the average price of tables'
    

    函数存根(Function Header, 也叫stub)将函数的参数替换为具体的合法数据,并提供了输出结果的具体示例

    '100 = average_price([int, int, int])' # average_price为函数名
    
  3. 使用样例来证明以上三项。

    """
    >>> average_price([50, 100, 150])
    100
    >>> average_price([80, 160, 240, 320])
    200
    """
    
  4. 编写函数代码
    也就是完成对信号->数据过程数据处理过程数据->信号过程的描述。

  5. 测试函数

    Python中可以使用run_docstring_examples函数来完成这个过程。

      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)

函数average_price()在声明时使用三个引号提供了一份简单的说明。在解释器中执行help()函数皆可获得这段说明(按Q退出)。同时,这份说明也指出了特定值下的输出值。利用run_docstring_examples()函数可以自动完成检验,并输出检验结果。如下,

    Trying:
        average_price([50, 100, 150])
    Expecting:
        100
    ok
    
    Trying:
        average_price([80, 160, 240, 320])
    Expecting:
        200
    ok

其他须知

计算机程序都是在解决实际问题,因此编程人员应当对程序所应用的相关学科有一定的了解,例如数学、音乐、生物、土木工程等等。

此外,还要对所使用的函数库的API有所了解。例如,要用Python处理IP相关问题,应当对ipaddress函数库有所了解。

从函数到程序

复杂的程序不可能只有一个函数。大多程序都需要不少辅助函数,有些还需要定义很多常量。因此一定要使用好辅助函数和全局常量,来系统的设计好每一个函数。记得给函数和常变量起一个有意义的名字

如果有必要,应该把预先定义的全局常量在函数说明里公示出来,以提醒程序维护人员这些常量的存在。

通常,随着程序编写的深入,你会发现需要不断添加新的辅助函数和常量。我们建议你在编写程序的过程同时也维护一张“目标清单”,把需要添加的新的辅助函数和常量列进清单。一旦完成,把它划去。只要清单上还有项目,就继续工作。如果你发现清单里的项目全部完成了,那编程的工作也就结束了。

程序测试

Python还可以使用assert声明执行成规模的自动化的函数测试。

# 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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容