驳『On literate programming』

噜吧 叽喳:

     很讨厌把 literate programming 翻译成 文学编程 , 尼玛这个文学有毛钱关系啊,  文式编程 这个说法很不错...

几年前,王垠写了一篇批判文式编程的文章『On literate programming』(http://yinwang0.wordpress.com/2011/05/18/literate-programming ),之所以现在才拎出来驳一下,是因为以前没看到。他在 2014 年写的一篇文章『我和权威的故事』(http://www.yinwang.org/blog-cn/2014/01/04/authority ) 中还是坚持着原有的观点。

文式编程不会弄丢大图景

王垠认为,文式编程会导致程序的大图景(Big Picture)的丢失。他认为,程序像电路或汽车,存在很多相互连接的零部件,如果将这些零部件分开来讲,会割裂它们之间的联系。

《冰与火之歌》这部书塑造的也是一个很复杂的系统,存在很多相互联系的角色,但是作者却可以游刃有余的通过 POV 的方式将这个系统刻画了出来。

人类所设计的系统,无论它有多么复杂,都具备可分解性,否则这个系统就很难被人类理解。即使这种系统难以单纯用文字直白的描述出来,依然可以通过图形示意的方式辅助描述。如果汽车系统能用教科书的形式描述出来,那么它的具体设计及制造过程自然也就能用书的形式记录下来。

文式编程不会搞丢大图景,除非写程序的人本来也没啥大图景。一篇论文的大图景在『引言』部分。一本书的大图景在『绪论』里。

将程序分割为文本的方式并非只有一种

王垠认为,文式编程工具不具备解析程序代码的能力,只是将代码分割为一段一段的文本,会导致变量作用域混乱,引入潜在的 bug。

还是以《冰与火之歌》为例,它那么长篇的书,也没有出现哪个角色在这一章里是死的而在另一章里还在说话的事。写程序这事,归根结底也是得看匠心的。有的人不用文式编程,也会犯下变量作用域混乱的错误。

将程序分割为文本也不一定就必然会导致变量作用域混乱。例如,用 C 写程序,一个 C 函数可能包含了上百行代码,但是这并不意味着你在编写这个函数的过程中就用文式编程。先将这个函数实现出来,验证无误后再将它拆分为更容易理解的代码段,融合文字说明或图形示意。如果你觉得这个函数的功能很简单,没必要解释,那就不用解释。

例如,下面这段代码,我可以先把它们写出来,然后再考虑如何给出注解。

PmPoint *
pm_point_new(void *x1, ...) {
        int n = 0;
        va_list ap;
        va_start(ap, x1);
        void *xi = x1;
        while (xi) {
                n++;
                xi = va_arg(ap, void *);
        };    
        PmPoint *ret = malloc(sizeof(PmPoint));
        ret->data = malloc((n + 1) * sizeof(void *));
        void **p = ret->data;
        va_start(ap, x1);
        xi = x1;
        while (xi) {
                *(p++) = xi;
                xi = va_arg(ap, void *);
        }
        *p = NULL;
        return ret;
}

这段代码所做的工作,只不过是在已知一个 n 点的各维坐标分量的前提下构建这个 n 维点对象。写这段代码的我,不觉得有什么难理解之处,感觉它是自明的,然而被动着去看这段代码的他人,未必觉得它是自明的——琐碎的 C 代码很容易掩盖那些自明的东西。

由于上述代码已经写完了,它的正确性也经过了测试。在这种情况下可以将它打碎:

不仅代码得到了注解,而且也不会出现变量作用域混乱的问题。

文式编程并非是用人类的语言取代编程语言

王垠认为,人类不是至高无上的,人类的语言是不精确的,程序语言却是在很多方面高于人类语言的,它不应该受到人类语言里的糟粕的影响……程序代码不应该迎合个别人的认知习惯,它应该符合它的建模的概念本质(The nature of the concept it models)。

即使 Knuth 本人,作为文式编程的倡导者,也没有说是要用人类的语言取代或影响编程语言,他只是说程序代码要按照人类的逻辑而不是按照编译器的逻辑来安排。这没啥好批判的,也许只有 PL 或编译器专家之类的职业病患才喜欢按照编译器的逻辑来理解程序代码。文式编程里,人类的语言虽然不精确,但是它可以很好的诠释代码片段的含义,而更精确的理解,可从文字附近的代码片段中获得。

如果直接丢给你一段代码,让你分析一下它实现的是什么功能,这种事其实就是逆向工程——通过思维的输出去反推思维本身。想想人类破译甲骨文的工作方式及其难度吧。如果代码中夹杂一些不精确的人类语言注释,那么思维反推的难度便会被显著降低。论证这个,很无聊的,因为大家基本上都会在自己的代码中写注释。

有些人自己所研究的领域里的各种形式化模型非常熟悉,训练有素,他们能够对领域问题相关的程序代码有着直觉上的认识。倘若代码交流只限于他们与同行之间,那么非形式化语言通常是不必要的。然而,初学者总是有的,否则知识无法承继。有着良好注解的代码,可以降低门槛,这就是文式编程的存在意义。

上一节所给出的 pm_point_new 函数所构建的 PmPoint 实例,其数据结构的定义如下:

typedef struct {
        void **data;
} PmPoint;

被动去看代码的人,可能连 PmPoint 这个结构为什么要这样设计都不是很清楚,而在文式编程中,可以给出要多详细就有多详细的注解:

文式编程未必会导致代码浏览困难

王垠认为,Knuth 的书让他看起来很费劲,不如直接用 IDE 看代码更有效率。

Knuth 的 WEB 以及后来的 CWEB,年代太早,生成的电子文档中没有超级链接,而且也不能输出 HTML 这样的文档。阅读文档时,仅靠页码索引来阅读代码的确是不太方便。现在要解决这个问题很简单,无论是 PDF 还是 HTML,都支持超级链接,更重要的是,它们还都支持目录。

之前说过,我在寒假时用自己写的文式编程工具写了个 k 均值聚类程序,它的文档中的代码片段就是有超级链接的(详见https://segmentfault.com/a/1190000004669095),没觉得代码浏览有什么不便之处。

总结

Knuth 本人在他的文式编程论文里强调过:『I must warn the reader to discount much of what I shall say as the ravings of a fanatic who thinks he has just seen a great light』。在我看来,文式编程只不过是注解程序的一种比较另类的方式——用写篇文章或写本书的方式来注解一个程序,并非是说程序代码必须像人类语言一样。这只是个编程习惯而已,它并非拯救软件工程的银弹,可能连弹都算不上……

有些人怀疑我是在推销、传教抑或布道文式编程,持此观点者不妨看一下去年我写过的一篇有关文式编程的文章https://segmentfault.com/a/1190000004011686 ,然后再下结论也不迟。

得到王垠回应后的后记 :)

万万没想到,垠神对此文有回应……受宠若惊。他说我的论据其实不但没能驳倒他的文章,反倒支持了它的论点。其实这话我也可以说。『On literate progamming』非但没能驳倒文式编程,反倒支持了它的观点……开个玩笑,『On literate progamming』本身就是用人类语言写的,里面一行代码都没有。

至少到现在为止,人类的语言依然是人类彼此理解的最基本的工具。脱离了人类语言,给你 E = mc^2 这个非常简单的程序,你未必知道它在说什么,以及这个程序可以用于计算哪些问题。未承载物理解释的形式化描述是没有任何意义的,无论它有多么准确。既然数学公式本质上就是程序,而有些人说他写的程序代码是自明的,事实上他将代码中的变量及函数名设成了人类语言的词汇,这恰恰可以证明,程序语言无法脱离人类语言。即使 E = mc^2 中出现的字母也都是人类语言词汇的首字母。

文式编程是在承认非形式化的人类语言与形式化的编程语言存在对立性的前提下尝试对它们进行统一,并非是用前者来干扰后者。

文章出处: segmentfault
文章地址: https://segmentfault.com/a/1190000004858370
本文地址: http://www.webrube.com/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80-web_rube/7404
本文由 噜吧 整理,转载请保留以上信息; 如有侵犯您的版权, 请联系 webrube.com@gmail.com 。

本文收录于 2016-04-22 02:37   ,   原文segmentfault 发布于 2016-04-03

发表评论