发现式编程

发现式编程,起这么大的一个题目你可以理解为我的一种发散性思维。其实我想 说的只是一种编程工具。

不访把时钟往回拨得远一点,我还在上大学的时候,大家都在学MFC编程,我不知 道别人是怎么学的,我的方法是把MSDN打开,一页一页的去看。后来开始学 Linux编程,同宿舍的兄弟(大龙)告诉我,学好Linux就是要把Man手册一页一页 的看,info手册一册一册的看。好吧,我的确按他说的那样做了,并且我认为这 个绝对是有必要的,没有这样做过的人几乎不可能成为编程高手,应该赶紧打开 man/info手册,吃饭也看,拉屎也看。很有趣的一点是看MSDN可能看十年也不可 能成为MFC/Windows编程的高手,看Man/Info手册的话,半年应该足够让你成为一 个Linux系统管理员了,然后会开始写一些脚本,然后会明白哇靠这不也是一种编 程吗!实战非常重要,所以Linux更适合初学编程,因为他能提供各种各样的实战 机会,各种脚本是最方便入门的。曾经我认为我要成为一个黑客的最快的方法是 学会ed命令的用法,然后看了好久的 man ed ,看完了忘,忘了再看,至少有 一年以上我会时不时的打开ed手册来看一看,年轻真好啊,有大把的时间可以浪 费,完全不需要去动脑子:-)

但总有一天,你看得应该够了,不需要再一页一页的去看这些手册了,这时候你 要开始大规模的编程了,你会发现你还是时不时的需要回去翻手册页。很久以前, 我为可以在VC6里把光标放在 printf 上按F1可以直接跳到MSDN里printf的帮助 页面而欢呼雀跃。毫无疑问,这是一个非常有用的功能,尤其是在阅读(别人写 的)源代码的时候非常有用,看到一个不熟悉的函数可以直接跳过去看一下帮助 (如果能精确的跳回来就更好了)。

但是,这不是我要讲的发现。在写代码的时候,这个功能就不是那么有用了——一 个你不熟悉的函数,你可能还不能清楚地回忆起它的名字,比如snprintf系列的 函数,它的家族里还有一个vsnprintf,这种名字真的不好记,你第一次用的话怎 么把它打出来还存在困难呢,上哪儿去按F1查它的定义去啊?

这时候就要讲到这篇文章的题目了,发现式编程,意思是你应该有一种非常好的 方法让你可以 发现 系统里都有什么函数,然后你可以灵活方便的选择你所需 要的函数。就像搭积木一样,同样有一百块积木,一种情况是把一百块积木一下 子全堆在你面前,随便你怎么发挥,想必你胡乱就能搭出一个挺像那么回事儿的 东西;一种是把一百块积木全部藏起来,不让你一下子全看见随便挑,非得让你 一样一样的问:“有没有三角形的?”“有没有大一点的三角形的?”你乐意用哪种 呢?

私以为,用第二种不仅更费劲,而且会影响我的想象力。

这就是为什么我喜欢用Emacs编程,为什么我喜欢用Perl,Python等语言,以及一 位大师曾经说过,C语言我每天可以用到它95%的特性,C++可能我一辈子也用不到 那么多。让我来告诉你Emacs的最爽的地方吧,它能让你发现式的编程,你系统里 的每一个Emacs-Lisp函数,只要你打C-h f,它会全部给你列出来,然后你可以在 里面过滤出你想要的。Linux呢,你系统里的每一个可执行的命令,你都可以写一 小段脚本把它列出来,然后对它进行过滤。你系统里的所有man手册。Perl的 perldoc,里面所有的perl函数,perl模块。Python/Java等的Reflect机制,给定 一个python的模块、类、对象,你可以用dir()函数知道它有什么成员。所有以上 这些,都让你能够很轻易地“发现”,有了这个更容易发现的可能性之后,你不再 会抱怨:“啊为什么我没有早一点发现这个”然后感慨相见恨晚——这种抱怨大概是 因为你使用的系统不支持这种容易发现的特性。

ESR的Unix编程艺术里有一些让我觉得很有道理的编程哲学,比如,要多用纯文本, 不要上来就自己咔咔定义二进制数据格式,为什么?因为纯文本人更容易发现, 用肉眼看,用grep搜。比如,在早期的时候,可以用文件系统来实现数据库,为 什么,原因之一还是因为这样的数据库更容易“发现”,用ls/find等Unix系统原始 命令可以查询,用rm/touch可以删除/创建数据,用git/svn可以把数据库版本管 理起来!

最后要切入到我想说的主题了,Java编程和C#编程。最近在C#上的一个小实验让 我决定动手写这篇博客。众所周知,Java编程基本上是神器的天下,Eclipse有人 说它是神器,Idea有人说它是神器。这些神器,拿到一个Jar包,然后就去“发现”里 面有什么package啊,有什么类啊,每个类有什么成员变量、成员方法啊。然后给 你补齐。这个我在我的Ajoke里已经都实现了,呵呵。

下面要告诉大家的是,给我一个C#写的dll/exe,我可以一样地发现它有什么类啊, 有什么成员方法啊,这个其实是Mono项目的monodis命令提供了,然后我用脚本封 装了一下,把monodis的输出放到GNU Global的数据库里,这样在Emacs里就可以 用tags工具去查询了,所有System命名空间里有什么玩意儿都可以搜索哦!

比如下面这个图,我在查C#里它应该不会没有判断文件存不存在的函数吧,然后 我试了一下Exists这个名字,一下就搜出来勒这个System.IO.File.Exists:

find-exists.png

我自认为这个功能可能是Eclipse、Idea等神器不具备的,“发现”。如果我自己知 道了System.IO.File的话,还需要你来告诉我它有一个叫Exists的方法啊?在我 什么都不知道的情况下,瞎猜一个Exists,然后把我真的想要的整个找到,这, 才叫“发现”(不好意思,接触C#编程只有Beagrep这个项目,所以连 System.IO.File这么常用的类都真的不知道啦,不骗你)。

神器们的功能,我也能很轻易地做到,比如我打了System.IO.File.之后,想要补 齐的话,我就搜一下这个,然后就知道可以在后面补比如 GetAccessControl :

find-system-io-file.png

我已经为我的发现式的C#的Emacs编程工具想好了名字,ACoke,英文含义很丰富 哟嘻~ 接下来就看哪天有那个闲功夫把它做出来,耶!

(由于我们之前一页一页的看过MSDN、Man手册,Info手册,有了一定的词汇量的 积累,现在应该到了不怎么需要再看printf、Exists、GetAccessControl帮助的 时候了,有时候知道这个名字,大概就知道它是干什么的了;知道 这个名 字,大概就知道我们能干什么了,否则就得开动编程马达,自己去造轮子:-))

(编程,有时候就是我猜我猜我猜猜猜,我猜系统应该提供 .*exist.* 函数来 让我判断文件是否存在,然后去 发现 这个函数真正的名字,发现过一次以后, 如果一段时间内你不停地用到这个函数,你不需要每次都发现,因为你的短期记 忆应该不会那么烂吧:-) 如果你很长时间又不用这个函数了,那你可以把它安全 地忘记,放空自己的脑袋去想一些更重要的事,比如晚饭应该吃什么——因为你已 经知道了下回怎么去再 发现 一次:-))

(为什么我说一页一页的看这些手册是有必要的,虽然这看上去很傻。看,是为 了有一天你可以不用再看。因为看得多了熟了之后,你可以猜(猜完了之后可以 发现)。如果猜错的话怎么办?好吧,那就是“惊喜”了。Ruby作者Matz说过, Ruby设计的原则之一是要减少“惊喜”(surprise,惊呆了:-)),这种惊喜不是说 让一个学C++/Perl的程序员在学Ruby的时候可以减少惊喜,而是说把Ruby学得比 较熟了之后,再用新的功能,不熟悉的功能,可以发现跟以前学了的Ruby是很和 谐的,甚至可以本能的猜出来,而不会感到不必要的“惊喜”。Matz说,他以前学 C++,学得不可谓不滚瓜烂熟了,却还是时不时地会感到惊喜。也就是本能地猜了 一下,结果发现猜错,这是让人很挫折的,尤其是如果造成一个很难发现的bug的 话:-()

发现,搜索,探索,问题,答案,你,发现了吗?