4.3.1 顶层设计

根据 calendar 程序的规格说明,很容易设计一个简单的 IPO 模式的算法:首先从用户处 获得年份输入 year,然后计算该年份 1 月 1 日是星期几,最后按特定格式输出年历。我们用 伪代码来表示该算法,如下:

输入 year
计算 year 年 1 月 1 日是星期几 输出年历

这个算法属于高层设计,其中第二、第三两个步骤都不是一目了然能直接编码实现的, 但我们不妨假设每个步骤都由一个函数实现,从而可以利用这些函数实现程序。

首先,尽管第一个步骤“输入 year”看上去很容易用 input 语句实现,但我们仍然先 用一个顶层模块——函数 getYear()来表示该步骤的实现。函数 getYear()负责从用户 处获得输入并返回给主程序使用,因此我们将函数的返回值赋值给主程序变量 year。至此, 我们的 calendar 程序取得了第一个进展:

def main():
    year = getYear()

其次,计算 year 年 1 月 1 日是星期几,这个步骤不是那么显然,但我们仍然假设函数firstDay()能够实现该步骤,这个函数以 year 作为输入,然后返回一个代表星期几的值(例如,用 0 表示星期天,用 1 到 6 分别表示星期一到星期六)。在主程序中添加一行调用firstDay()的语句,并将函数返回值赋值给主程序变量 w,这时程序就进展到如下形式: def main():

year = getYear() w = firstDay(year)

最后一步是输出年历,仍然假设函数 printCalendar()能够实现该步骤,此函数需要用到的信息包括 year 和 w,无需提供返回值。在 main 中添加相应的函数调用语句之后,得到 calendar 程序的完整结构如下:

def main():
    year = getYear()
    w = firstDay(year) 
    printCalendar(year,w)

至此,我们做出了 calendar 程序的顶层设计,将原始问题分解成了三个模块,当然各模块的细节尚不清楚。主程序虽然只有寥寥 3 行,看上去不过是上面的算法伪代码略加细化的 结果,但它确实满足程序规格说明的要求。此外,我们还为对应每个模块的函数声明了函数 名、参数和返回值,这些信息构成了函数的接口(interface)。在 main 这个层次,并不需要 关心 getYear()等函数的实现细节,只需要关注它们对于给定的参数能返回预定的数据。 亦即,只关心每个子程序“做什么”,而非“怎么做”。函数接口正是表达“做什么”信息的。

自顶向下设计中经常使用一种设计工具——结构图(或称模块层次图),其中用矩形表 示程序模块,用两个矩形之间的连线表示模块间的调用关系,在连线旁边用箭头和标注来指 明模块之间的界面信息。各模块分别处于不同层次,高层模块是调用模块(或控制模块), 低层模块是被调用模块(或受控模块)。结构图最顶层就主程序(总控模块)。例如,calendar 程序的顶层设计可以用如图 4.7 所示的结构图来表示。

图 4.7 calendar 程序的顶层结构图

在结构图中,越处于下层的模块,其细节程度就越高,即更加精化。