转载: http://www.goldendoc.org/2012/07/somethings_i_ve_learnt_about_programming/
原文地址:Some things I’ve learnt about programming —- By John Graham-Cumming
我已经从事编程 30 年了,用过的机器包括从现在看来很差的(基于 Z80 和 6502)到最新的,用过的语言包括 BASIC,汇编语言,C,C++,Tcl,Perl,Lisp,ML,occam,arc,Ruby,Go等等。
下面是我学到的一些关于编程的事儿:
编程更接近于一门手艺而不是科学或工程。它是技能和经验的组合,它需要通过工具来表达出来。一个手艺人会选择特定的工具(有时候他们会自己去做)然后学习用它去创造。
在我看来这就是手艺。我认为最好的工程师更加接近于钟表匠而不是桥梁建筑师或者是物理学家。当然因为对逻辑和数学的应用,编程看起来像是科学或工程,但是本质上它就是你拿你手上的工具去做东西。
既然编程是一门手艺,那么我们就不难理解为什么在编程中经验,工具,直觉很重要。
编程的时候我们经常为了看看发生了什么或者让一个程序跑起来去试试一些代码,但是却并不真正地理解到底发生了什么。比如:你决定要调用一个 API,仅仅因为它神奇地让 Bug 消失了;再比如,在程序中插入一行 printf
,因为它让程序不再崩溃。
这些都是对自己不诚实的例子。你必须问自己:“我是否知道我的程序到底做了什么事情?为什么做这个事情?”。如果你不知道,你迟早会陷入麻烦。明白一个程序做了什么是程序员的责任,计算机只会精确地做它被告诉去做的事情,而不是你想要它做的事情。
诚实需要你对自己非常严格。你必须非常严格地确定你已经知道你的程序干什么,为什么这么干。
Tony Hoare 说过:
有两种方式构建软件设计:一种把软件做得非常简单以至于明显没有缺陷,另一种是把它做得非常复杂以至于找不到明显的缺陷。第一种方法要难得多。
简化,重构,删除。
我把 Hoare 的话改了一下:“在每一个大型复杂的程序里面,都是一些小而优雅的程序在把同样的事情做对。”
和这个相关的是“小块松散组合”哲学。通过一些相互通信的部分去构建一个程序,而不是构建一个单独的大程序。这也是 UNIX 如此成功的部分原因。
我几乎从来不用调试器。我的程序会打印日志,我知道我的程序到底干了什么。大部分时候我可以从日志中看出发生了什么而不用借助调试器。
我不用调试器的原因是因为我认为它会导致你懒于思考。当遇到一个 Bug 的时候,很多人都会去设置断点,然后检查内存或者变量的值。我们很容易被这样迷人的工具所吸引,因为思考看起来会花费更多的时间。如果你的程序非常复杂,你必须需要一个调试器,那么你或许应该回去看看第二点。
(题外话:尽管说了这么多,但是一个我非常尊重的程序员,John Ousterhout,似乎会把整天的时间都花在 Windows 调试器上)。
另一方面,如果你需要了解你的程序的性能,那么性能分析器是非常重要。你不会被一个性能分析器所吸引。
不要重复你自己。你的代码中的每一件事情应该只做一次。
这是第二点的一个特例。甚至是一点小小的代码重复都会在将来给你带来麻烦。
有些人对一门特定的语言很着迷,所有的东西都用它去做。这是一个错误。没有一门语言是适合所有的编程任务的。
关键是你要明白当你遇到问题的时候你要从你的工具箱里面拿什么语言去处理。工具箱中的工具当然是越多越好。试试不同的语言,试着用它做些东西。
如果你用下 Python 或者 ML,你会觉得 List Comprehension 很强大;或者你可以尝试下 Go,看看它是如何处理并发的;或者你可以用用 Perl,看看它处理字符串是多么地灵活;或者你可以用 PHP 去快速搭建一个动态网页。
我讨厌语言战争。失败者才会参与语言战争,因为这个争吵的东西本身就是个错误。比如说,在我的手上,PHP 就是个悲剧,但是在别人会用得很爽。对于 C++ 也是一样的道理。
这也和第二点有关。从一个小的部分出发,然后慢慢发展起来。如果你正在解决一个问题,那么从你要解决的问题的一个部分开始发展要比在问题的基础上设计一个庞大的架构容易。
当你从一开始就创建了一个庞大的架构,你就错了,你做了一个拜占庭式的复杂而死板的迷宫,你会发现自己很难去改变它。反过来,如果你从一些相互协作的小的部分出发,那么当你意识到从一开始你就把一个问题搞错了以后,重构起来将会非常容易。
这个的根本是你无法知道一个正确的架构到底长什么样子。这是因为你很难知道你的程序的外部输入会是什么样子。你也许认为你知道,比如,你的邮件服务器所需要处理的 TCP 流,或者是收件人的个数是多少,或者是垃圾邮件长什么样子。有些外部的数据会让你的假设失效。如果你的假设已经融合在一个大的,复杂的,难以改变的程序里,那么你真是麻烦大了。
我觉得对从 CPU 到你所用的语言有一个认识是非常重要的。理解计算机的每一个层次非常重要。
当你需要处理性能问题的时候这就非常有用。我想起一个事儿,这个事情我依然记得很清楚,一个客户给我们公司发了一个 Window 2000 的崩溃截图,图上有一些内存和寄存器的信息。当我们知道他所用的程序的版本后,我们就找到了是一个空指针的问题导致了这个现象。
我依然有很多的东西要去学。有一些语言我想要接触还没有怎么接触过(Erlang,Clojure);有些语言我有所涉猎但是并不精通(JavaScript);有一些知识点我根本就不懂(monads)。
PS:我还没有提到过测试。我应该加上这一点,因为我确实认为对任何不是马上就扔掉的代码,测试用例都是非常重要的。也许再写 30 年的代码我就可以对“单元测试提升了软件质量吗?”这个问题有了一个答案。我写代码有时候写单元测试,有时候不写,我不知道这个问题的答案,尽管我更倾向于写单元测试。