适合做程序员的人:像机器一样思考

本文的起源是因为思考一个问题:什么样的人适合做程序员?

我曾经苦苦思索这个问题,直到我在SICP上看到了答案。

说的白话一点就是,能像机器一样思考的人就适合做程序员。

那么“计算机”这台机器是怎么思考的呢?这里是我的答案:

我们所有的计算机,都可以用下面这个模型来表示,江湖人称“冯・诺伊曼体系”。

从这个模型上我们看到了什么吗?嗯,可能太多干扰了,看的不够清楚,我给你们再抽象一层:

现在清楚了吧?计算机在中间,两边是输入输出。所有的问题都从输入和输出的角度去思考,这就是“计算机”这台机器的思考方式。也就是说你能做到这样思考,你就会像机器一样思考了。 很简单吧?但是新的问题又产生了,处理自然要处理输入、产生输出了,输入、输出是些什么呢?这就要在微观层面理解机器是怎么思考的,这一部分叫:机器在加工什么?

SICP中又说了,“非形式的讲,我们只在处理两种东西,数据和过程,他们之间并没有明确的区分。”先不管是否有明确区分,我们回看模型,中间处理的部分其实就是过程,输入和输出则是数据。(在冯诺伊曼体系里,数据和过程被称为数据和指令)那说到数据,我们有一门学科叫做数据结构,它很好的解释了什么是数据。我们还有面向对象、类型系统之类的知识,他们都能帮助我们更好的定义数据。

各位看官估计心里犯嘀咕了,扯了这么多,还是无法想象怎么就算像机器一样思考了。不急,下面我们拿几个例子来学习一下。

我们来写一个加法函数,接受两个参数作为加数和被加数,返回一个和,这个太简单了,几乎任何一个程序员都可以在几秒钟内写完。拆成机器的思维是什么样呢?

加法函数

加法函数 
输入:
  a   
  b 
输出:   
  result

大概就长这样,输入是a和b,输出是一个结果,我们起名叫result。它到底表达了个啥样的代码呢?大概长这样(本文所有的代码都会采用javascript描述,但是不代表本文内容,只适合描述前端开发):

function add(a, b) {
    return a + b; 
}

咦?result哪去了?在你调用的地方可能会有一行代码:var result = add(1,2); 这个表达方式不仅仅可以用来描述函数定义,还可以用来描述表达式。比如,如果我们把前面的输入输出思维描述改为加法表达式。你会发现这段描述“编译”成代码大概长这样:

var result = a + b;

所以不仅仅可以用来描述函数定义,还可以描述代码块。

但是到这里就结束了吗?感觉好像对数据的表述不够细致啊。确实,我们忘了加类型了。不加类型这描述简直万灵丹啊,反正俩参数一个返回值的都能用,这不行,我们还得把类型加上看着才清楚点。加上类型就变成了这样:

加法函数 
输入:   
   a: Number  
   b: Number 
输出: 
   result: Number

这看着就好多了,是不是比刚才更加理解上文所讲的“像机器一样思考”了呢?好吧,你可能会说,“这玩意有啥用啊,我有分析的这个空,我代码都写完了啊。”不急,我们接着往后看。

刚才那个题目有点太简单了,我们做一个稍微复杂的。比如下面这个:写一个函数,可以选出一个由数字组成的集合当中所有的偶数的最大值。

这回一步做出来可能就有点难了,没关系,我们可以分成两步:

  1. 选出集合中的偶数
  2. 选出偶数中的最大值 这两步呢,按照我们之前的格式写一下,大概是下面这个样子:
    #1 选出集合中的偶数 
    输入: 
      inputArray
    输出: 
      evenArray
 
    #2 选出偶数中的最大值 
    输入: 
      evenArray 
    输出: 
      max:Number

哎呀,突然发觉不知道该怎么描述集合呢。Javascript里就用数组就好了,但是还是不知道怎么描述数组啊。这个其实很简单,这不是一个由数字组成的数组吗?我们只要写成[Number]就可以了。因为我们的一个好习惯是一个集合里不要放两种类型的元素,所以就这么写就好了。那么加上去的话,大概就长这样:

#1 选出集合中的偶数
输入: 
  inputArray:[Number] 
输出: 
  evenArray:[Number]
 
#2 选出偶数中的最大值
输入: 
  evenArray 
输出: 
  max:Number

咦,第二步的evenArray没有写类型。嗯,因为evenArray是第一步的输出,我就把它省了,相信大家也能看明白。 耐着性子看到这里,你估计已经发现了,我还是没有回答你“思维方式有什么用”这个问题。我很想忽悠着你再做一道题,不过估计你坚持不完就会转身离开了。那我们就这两道题试着讲一讲。

第一道题,我们只是展示了这个思维,第二道题,我们才开始发挥出它的威力。尽管这道题也不复杂,但是思考过程还是展示了:

  1. 分解问题;
  2. 找到子问题之间的关联(通过输入、输出关联起来);
  3. 找到问题的边界,明确假设与结果。

上述三点看着简单,却是思维清楚与否的关键。我们管这个能力叫Analytical Thinking。

思维清楚带来的收益是什么?这些步骤可以直接转化为工作的任务列表,而且可测试。这样分解出来的任务列表,完成效率是极高的。我们曾经做过实验,按这个思路分解过问题的人,比没有分解过的人效率要高3倍以上,而且前者只学了一周的编程。 一个完全不会写程序的人,只要学会了这个思维,就可以开始编程之旅了,而且威力非常巨大。

听起来好简单啊,有那么神吗?不是编程的人都应该会吗?然而并不是的,很多人思考编程这件事情是靠感觉的。 我前几天面了40多个外包公司外派来的人,只有5个人,可以按照输入输出来对问题进行分解。所以我觉得我还是有必要写点东西来讲讲这个。

除了对初学者有益之外,对Team Lead也是有益的。当你觉得你遇到的人没sense的时候,你可以试着让他们这么表达一下程序。一般就会发现一些问题。

题外话-1:

我们像机器一样思考,不就都变成机器了吗?嗯,其实不是的。所谓我们像机器一样思考,那机器这种思考方式又是从哪里来的呢?机器的思考模型是一个叫“图灵机”的计算模型,而图灵机则是图灵祖师爷模拟人思考而发明出来的。所以,其实不存在什么像机器一样思考,只不过是学会一种人类的思考方式而已。 考虑到图灵只能以自己和自己周围的天才科学家的作为人类的具体实例来抽象图灵机,所以我们学习的其实不是什么机器的思考方式,而是天才的思考方式,这篇文章其实应该叫《像天才一样思考》。

题外话-2:

这个不就是面向过程编程吗?如果你的思考仅仅停在这里,那就是面向过程编程了。如果我们接着想下去,当数据复杂到一定程度的时候,我们会自然的引入封装,于是面向对象诞生了。回到数据与过程不严格区分那半句,当我们试图模糊数据和过程的界限,将过程像数据一样纳入输入输出的范畴,我们就走上了函数式编程之路。

题外话-3:

有人觉得练习不够吗?请留言,如果感兴趣的人多,我就加紧写更多练习的解析。

本文文字及图片出自 insights.thoughtworkers.org

余下全文(1/3)
分享这篇文章:

请关注我们:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注