18.9 大纲式编程

    分离文档和代码的想法可以看成是人为分离。历史上,我们在代码外部编写文档是因为编程语言相对来说不透明并且偏重于高效的编译,编程语言本身并不注重清晰地阐述。人们用了很多技术试图减少工作代码和关于代码的文档间的距离。例如,嵌入更完善的注释一直是传统做法。Python在这样的做法上更进一步,它在包、模块、类和函数中包含了正式的文档注释。

    软件开发中的大纲式编程方法由Don Knuth首先提出。这种编程方式的想法是一个单一的源文档可以生成高效的代码和美观的文档。对于面向机器的汇编语言和类似于C的语言,从源代码的语言(强调翻译的标记)换到一个强调清晰阐释的文档还有一个额外的好处。另外,一些大纲式编程语言作为高层编程语言,这种做法可能适合C或者Pascal,但是对于Python没有直接的帮助。

    大纲式编程鼓励更深入地理解代码。在Python的例子中,源代码一开始的可读性非常好,可以让一个Python程序容易理解不需要复杂的大纲式编程。事实上,对于Python而言,大纲式编程的主要好处是让更深层次的设计和用例信息能够以一种比简单的Unicode文本更易读的方式来表达。

    更多关于这方面的信息,可以查看http://en.wikipedia.org/wiki/Software_

    qualityhttp://xml. coverpages.org/xmlLitProg.html。DonaldKnuth写的Literate Programming中有关于这个主题的完整描述。

    18.9.1 大纲式编程用例

    当创建大纲式程序时,有两个基本目标。

    • 一个工作程序:这是从源文档中提取的代码,这些代码是为编译器或者解释器准备的。
    • 易读的文档:这是为演示提供的阐释、代码和其他任何对我们有帮助的标记。这个文档是随时可以查看的 HTML。或者它基于 RST,然后我们可以用docutils的rst2html.py将它转换为HTML格式。或者它基于LaTex,然后我们通过一个LaTex处理器来执行它并创建一个PDF文档。

    工作程序目标意味着我们的大纲式编程文档将会覆盖所有的源代码文件。尽管这看起来有点吓人,但是我们必须记住组织良好的代码片段不需要太多复杂的手动工作,在Python中,代码本身就可以很清晰并且是有意义的。

    易读的文档目标意味着我们希望生成包含一些其他样式而不只是字体的文档。尽管大多数的代码是以等宽字体表示,但是这种字体并不是最易读的。基本的Unicode字符集没有包括有用的字体变种,例如粗体或者斜体。这些额外的显示细节(字体变更、字号变更和样式变更)必须保持演变来让文档更加可读。

    在许多情况下,我们的PythonIDE会给Python的代码加上颜色,这对我们也是有帮助的。书面交流的历史包括很多可以增强可读性的功能,它们中没有哪个可以用在只使用一种字体的简单Python源代码中。

    另外,一份文档应该围绕问题和解决方案来组织。在许多编程语言中,代码自身无法遵循一种明确的组织方法,因为它会限制于语法的技术考虑和编译顺序。

    我们的两个目标可以归结为两个技术用例。

    • 将一份原始的文本转换为代码。
    • 将一份原始的代码文本转换为最终文档。

    我们可以在某种程度上用一些深刻的方法重构这两个用例。例如,我们可以从代码中提取文档。这是pydoc模块的功能,但是这个模块无法正确地处理标记。

    我们可以将代码和最终文档这两个版本编写为同型的。这是PyList项目采取的方法。最终文档可以通过文档注释和#注释完全嵌入Python代码中。代码可以通过::文本块完全嵌入RST文档中。

    18.9.2 使用大纲式编程工具

    有许多大纲式编程(Literate Programming,LP)工具可以使用。根据工具的不同,基本的要素也不同,这些要素是用于将解释从代码中分离的高层标记语言。

    我们编写的源文件将包含下面3种元素。

    • 作为解释和描述的标记文本。
    • 代码。
    • 用于从代码中分离文本(带有标记)的高层标记。

    由于XML的灵活性,它可以作为大纲式编程的高层标记。但是,编写XML不简单。有一些工具基于原始的Web(和新的CWeb)的工具可以处理类似LaTex的标记。有一些工具将RST作为高层标记。

    然后,选择一个工具的基本步骤是仔细研究这个工具所使用的高层标记。如果我们发现标记很容易书写,就可以使用它来生成源文档。

    Python带来了一种有趣的挑战。由于我们有了基于RST的工具,例如Sphinx,我们可以创建非常大纲式的文档注释。这让我们可以创建两层文档。

    • 大纲式编程的注释和代码以外的背景。这应该作为通用的背景材料,而不是只关注代码本身。
    • 嵌入文档注释中的引用和API文档。

    这为我们带来了一个愉快的、不断进化的大纲式编程方法。

    • 首先,我们可以通过将RST标记嵌入文档注释中作为开始,这样Sphinx生成的文档就会拥有很好的外观并且为实现方法的选择提供了合理的解释。
    • 我们可以跳过信息集中的文档注释,创建背景文档。这个文档可能会包含关于设计决策、架构、需求和用户故事的信息。尤其是,不属于代码的非功能性质量需求描述。
    • 一旦我们开始标准化这个高层的设计文档,可以更容易地使用LP工具。然后,这个工具会规定我们将文档和代码合并到一个单一的全局文档结构中的方式。我们可以使用LP工具提取代码并生成文档,一些LP工具也可以被用于运行测试套件。

    我们的目标是创建不但设计合理而且值得信赖的软件。正如前面介绍的,我们有很多建立信任的方式,包括提供一个整洁、清晰的解释来说明为什么我们的设计是合理的。

    如果我们使用类似PyLit这样的工具,可能会创建类似下面这样的RST文件。

    #############
    Combinations
    #############

    .. contents::

    Definition
    ==========

    For some deeper statistical calculations,
    we need the number of combinations of n things
    taken k at a time, :math:'\binom{n}{k}'.

    .. math::

      \binom{n}{k} = \dfrac{n!}{k!(n-k)!}

    The function will use an internal "fact()" function because
    we don't need factorial anywhere else in the application.

    We'll rely on a simplistic factorial function without memoization.

    Test Case
    =========

    Here are two simple unit tests for this function provided
    as doctest examples.

    >>> from combo import combinations
    >>> combinations(4,2)
    6
    >>> combinations(8,4)
    70
    Implementation
    ===============
    Here's the essential function definition, with docstring:
    ::

      def combinations( n, k ):
        """Compute :math:'\binom{n}{k}', the number of
        combinations of n things taken k at a time.

        :param n: integer size of population
        :param k: groups within the population
        :returns: :math:'\binom{n}{k}'
        """

    An important consideration here is that someone hasn't confused
    the two argument values.
    ::

        assert k <= n

    Here's the embedded factorial function. It's recursive. The Python
    stack limit is a limitation on the size of numbers we can use.
    ::

      def fact(a):
        if a == 0: return 1
        return afact(a-1)

    Here's the final calculation. Note that we're using integer division.
    Otherwise, we'd get an unexpected conversion to float.
    ::

      return fact(n)//( fact(k)
    fact(n-k) )

    这是完全用RST标记编写的文件。它包含一些解释文本,一些正式的数学表达式,甚至还有一些测试用例。这些元素为我们提供了额外的细节来支持相关的代码块。考虑到PyLit的工作方式,我们将文件命名为combo.py.txt。我们可以利用这个文件做以下3件事情。

    • 可以用PyLit以下面的方式从这个文件中提取代码。
    python3.3 -m pylit combo.py.txt

    这行命令从combo.py.txt创建combo.py。这是一个可以直接使用的Python模块。

    • 也可以使用docutils将这个RST格式化为HTML页面,这个HTML页面包含了比原始的单字体文本更易读的文档和代码。
    rst2html.py combo.py.txt combo.py.html

    这行命令创建了可以查看的combo.py.htmldocutils会使用mathajax包来排版文本中数学相关的部分,生成外观很好的输出。

    • 另外,还可以用PyLit运行doctest并且确认这个程序确实在正常工作。
    python3.3 -m pylit —doctest combo.py.txt

    这行命令会从代码中提取doctest块,并且通过doctest工具运行它们。我们会看到所有测试(一个导入和两个函数计算,共计3个)都会生成所预期的结果。

    最后生成的网页看起来可能类似下面的截图。

    空标题文档 - 图1

    我们的目标是创建值得相信的软件。一份整洁、清晰的文档阐述为什么我们的设计是合理的,对于建立这种信任非常重要。通过在一个单一的源文本中并排地编写软件和文档,可以确保文档是完整的并且为设计决策和全局的软件质量提供一份合理的审核。一个简单的工具就可以将工作代码和文档从一个单一的文件中提取出来,这样我们就可以很容易地创建软件和文档。