程序员思维

-回复 -浏览
楼主 2019-08-20 06:28:39
举报 只看此人 收藏本贴 楼主



点击上方"奥远科技"关注我们



起因

    首先简单说一下,为什么我会想到这个话题。主要有这么几方面的原因。


    当我试图回过头去总结大学在计算机专业所学习的一些理论和知识的时候。发现,在学校里面学习的一些东西,走了两个极端。 一个极端是偏向了细节。比如我们学习的那些《***程序设计》的课程。看这几门课的名称的我们能够很明显的看出,***是一个形容词定语,用来修饰主题“程序设计”。但是,你却非常意外的意识到《C++面向对象程序设计》和面向对象程序设计貌似关系不大,整门课程主要讲了一个更好用的C,比C好用的地方是在于人家有对象。学习了这一系列《***程序设计》的课程之后的结果是:你知道了汇编的语法,知道了C的语法,知道了C++的语法;但是用他们能做些什么却不知道。 另外一个极端是偏向了宏观。在我们对工程是个什么东西,项目是个什么东西,软件又是个什么东东还没有构建起最基本概念的时候。我们上了《软件工程》这样的课程。劈头盖脸的理论砸下来了,没有消化也没有吸收。你甚至找不到,学这些课程对于你的编程实践有什么样的版主。等你写代码的时候,你真真切切的发现软件工程这个东西对于我这个函数怎么命名,模块怎么划分真的帮助不是很大啊。 其实,随着时间一点点的流逝,工作年限的增加。你能够发现,这两个极端的东西还是有作用的。只不过,学校里面的课程少了一些能够把它们融会贯通,串联起来的东西。而这个中间起到粘连作用的东西就是我所思考的。

 

    另外一个原因是,当与其他同学或同事去写同一个程序的时候。他们有些时候看到你的代码,然后就会评论:你这代码耦合性太紧,内聚性太差,不符合高内聚低耦合的概念啊。你当然,有点气不过啊。你凭什么说我代码没高内聚低耦合啊。于是你就问他为什么这么说。结果绝大多数时候,对方只是说”我感觉“。即使有些时候他们根据他们多年的编程经验说出了一些什么东西。但是你还是感觉说服不了你。你心里会暗自嘀咕,不就是你感觉嘛。你感觉的东西也不一定对。 同样的问题,也会发生在当你去评论别人的代码质量差的时候。要想让别人认识到问题所在是异常困难的。果然世界上最难的事情有两个:把别人的钱放进自己的口袋里面,把自己的思想放进别人的脑袋里面。 于是,你就会去思考,有没有一种理论或者评价的方法,是大家都认可的。而且的确能够衡量出一个设计耦合性和内聚性的强弱来呢?

 

    还有一个原因。和上面的原因有点类似,在编程中接触到的很多同事。或者同样是编程的人。我发现他们的能力参差不齐。而且,有些时候这种能力的参差不齐不止是因为经验造成的。当然,一个有十年多编程经验的人肯定比一个刚刚入门的小孩写的程序靠谱。但是,你会惊奇的发现有些人写了四五年程序后,程序的质量甚至比十几年经验的人要好很多。与他们聊天的时候,就发现导致这种现象的原因在于他们思维方式的不同。有很多十几年编程经验的同事,很多时候只是在用感觉编程。在长期编程时间中形成的某些思维定势帮助他们能够快速的完成编码工作,但是仅限于此。而那些年少却有能力的同事,虽然年轻但是能够编写高质量的代码。因为他们不是通过经验积累获得编码能力;而是通过理论学习,并且快速的消化掌握了和十年多经验同事一样的思维定势。不一样的是,他们还能通过理论触类旁通。写出更高质量的代码。 以前有个同事(他不是干程序员的,而是产品经理),他和其他同龄人最大的不同就是思维方式。他总喜欢干一件事情就是找规律。后来,他想自己做点东西,一时又没有拉到程序员一起干,就自己操刀学习JS。你看他的代码,才一两个月的时间,代码质量和以JS为职业的人已经有的一拼了。每当想到这,我就想:我们还能说对于编程这件事情经验是最重要的吗?

 

    在我们的编程实践中,我们需要找到一种思维框架来帮助我们设计和解释我们的程序。这个东西就是要讨论的程序员思维。


从哪里来,是什么

    先讲一个笑话,说是一个外国的哲学家来中国做客。中途去一个小区找一个朋友,然后就发现原来中国的小区保安都是哲学家。他进门的时候,保安问了哲学家三个问题: 你从哪里来? 你是谁? 你到哪里去? 哲学家就感慨这是终极问题啊,自己穷其一生也没能解答,没想到这么个小地方还有人关心哲学的终极问题。其实人家保安就是想搞明白这个“哲学家”嘛。因为你要弄明白一个概念,只要能够回答好这三个问题,基本上就比较OK了。同样我们要弄明白“程序员思维”,也要问三个问题?


1. 程序员思维是什么?

2. 程序员思维从哪里来?

3. 程序员思维到哪里去?

    好吧,这是三个终极的问题。没有标准答案,也没有什么不标准的答案。我只是试图给出自己的一个思考的总结。


软件历史

    计算机软件技术发展很快。50年前,计算机只能被高素质的专家使用,今天,计算机的使用非常普遍,甚至没有上学的小孩都可以灵活操作;40年前,文件不能方便地在两台计算机之间进行交换,甚至在同一台计算机的两个不同的应用程序之间进行交换也很困难,今天,网络在两个平台和应用程序之间提供了无损的文件传输;30年前,多个应用程序不能方便地共享相同的数据,今天,数据库技术使得多个用户、多个应用程序可以互相覆盖地共享数据。了解计算机软件的进化过程,对理解计算机软件在计算机系统中的作用至关重要。


第一代软件(1946-1953)

    第一代软件是用机器语言编写的,机器语言是内置在计算机电路中的指令,由0和1组成。例如计算2+6在某种计算机上的机器语言指令如下: 10110000 00000110 00000100 00000010 10100010 01010000 第一条指令表示将“6”送到寄存器AL中,第二条指令表示将“2”与寄存器AL中的内容相加,结果仍在寄存器AL中,第三条指令表示将AL中的内容送到地址为5的单元中。 不同的计算机使用不同的机器语言,程序员必须记住每条及其语言指令的二进制数字组合,因此,只有少数专业人员能够为计算机编写程序,这就大大限制了计算机的推广和使用。用机器语言进行程序设计不仅枯燥费时,而且容易出错。想一想如何在一页全是0和1的纸上找一个打错的字符! 在这个时代的末期出现了汇编语言,它使用助记符(一种辅助记忆方法,采用字母的缩写来表示指令)表示每条机器语言指令,例如ADD表示加,SUB表示减,MOV表示移动数据。相对于机器语言,用汇编语言编写程序就容易多了。例如计算2+6的汇编语言指令如下: MOV AL,6 ADD AL,2 MOV #5,AL 由于程序最终在计算机上执行时采用的都是机器语言,所以需要用一种称为汇编器的翻译程序,把用汇编语言编写的程序翻译成机器代码。编写汇编器的程序员简化了他人的程序设计,是最初的系统程序员。


第二代软件(1954-1964)

    当硬件变得更强大时,就需要更强大的软件工具使计算机得到更有效地使用。汇编语言向正确的方向前进了一大步,但是程序员还是必须记住很多汇编指令。第二代软件开始使用高级程序设计语言(简称高级语言,相应地,机器语言和汇编语言称为低级语言)编写,高级语言的指令形式类似于自然语言和数学语言(例如计算2+6的高级语言指令就是2+6),不仅容易学习,方便编程,也提高了程序的可读性。 IBM公司从1954年开始研制高级语言,同年发明了第一个用于科学与工程计算的FORTRAN语言。1958年,麻省理工学院的麦卡锡(John Macarthy)发明了第一个用于人工智能的LISP语言。1959年,宾州大学的霍普(Grace Hopper)发明了第一个用于商业应用程序设计的COBOL语言。1964年达特茅斯学院的凯梅尼(John Kemeny)和卡茨(Thomas Kurtz)发明了BASIC语言。 高级语言的出现产生了在多台计算机上运行同一个程序的模式,每种高级语言都有配套的翻译程序(称为编译器),编译器可以把高级语言编写的语句翻译成等价的机器指令。系统程序员的角色变得更加明显,系统程序员编写诸如编译器这样的辅助工具,使用这些工具编写应用程序的人,称为应用程序员。随着包围硬件的软件变得越来越复杂,应用程序员离计算机硬件越来越远了。那些仅仅使用高级语言编程的人不需要懂得机器语言和汇编语言,这就降低了对应用程序员在硬件及机器指令方面的要求。因此,这个时期有更多的计算机应用领域的人员参与程序设计。 由于高级语言程序需要转换为机器语言程序来执行,因此,高级语言对软硬件资源的消耗就更多,运行效率也较低。由于汇编语言和机器语言可以利用计算机的所有硬件特性并直接控制硬件,同时,汇编语言和机器语言的运行效率较高,因此,在实时控制、实时检测等领域的许多应用程序仍然使用汇编语言和机器语言来编写。 在第一代和第二代软件时期,计算机软件实际上就是规模较小的程序,程序的编写者和使用者往往是同一个(或同一组)人。由于程序规模小,程序编写起来比较容易,也没有什么系统化的方法,对软件的开发过程更没有进行任何管理。这种个体化的软件开发环境使得软件设计往往只是在人们头脑中隐含进行的一个模糊过程,除了程序清单之外,没有其他文档资料。


第三代软件(1965-1970)

    在这个时期,由于用集成电路取代了晶体管,处理器的运算速度得到了大幅度的提高,处理器在等待运算器准备下一个作业时,无所事事。因此需要编写一种程序,使所有计算机资源处于计算机的控制中,这种程序就是操作系统。 用作输入/输出设备的计算机终端的出现,使用户能够直接访问计算机,而不断发展的系统软件则使计算机运转得更快。但是,从键盘和屏幕输入输出数据是个很慢的过程,比在内存中执行指令慢得多,这就导致了如何利用机器越来越强大的能力和速度的问题。解决方法就是分时,即许多用户用各自的终端同时与一台计算机进行通信。控制这一进程的是分时操作系统,它负责组织和安排各个作业。 1967年,塞缪尔(A.L.Samuel)发明了第一个下棋程序,开始了人工智能的研究。1968年荷兰计算机科学家狄杰斯特拉(Edsgar W.Dijkstra)发表了论文《GOTO语句的害处》,指出调试和修改程序的困难与程序中包含GOTO语句的数量成正比,从此,各种结构化程序设计理念逐渐确立起来。 20世纪60年代以来,计算机用于管理的数据规模更为庞大,应用越来越广泛,同时,多种应用、多种语言互相覆盖地共享数据集合的要求越来越强烈。为解决多用户、多应用共享数据的需求,使数据为尽可能多的应用程序服务,出现了数据库技术,以及统一管理数据的软件系统——数据库管理系统DBMS。 随着计算机应用的日益普及,软件数量急剧膨胀,在计算机软件的开发和维护过程中出现了一系列严重问题,例如:在程序运行时发现的问题必须设法改正;用户有了新的需求必须相应地修改程序;硬件或操作系统更新时,通常需要修改程序以适应新的环境。上述种种软件维护工作,以令人吃惊的比例消耗资源,更严重的是,许多程序的个体化特性使得他们最终成为不可维护的,“软件危机”就这样开始出现了。1968年,北大西洋公约组织的计算机科学家在联邦德国召开国际会议,讨论软件危机问题,在这次会议上正式提出并使用了“软件工程”这个名词。


第四代软件(1971-1989)

    20世纪70年代出现了结构化程序设计技术,Pascal语言和Modula-2语言都是采用结构化程序设计规则制定的,Basic这种为第三代计算机设计的语言也被升级为具有结构化的版本,此外,还出现了灵活且功能强大的C语言。 更好用、更强大的操作系统被开发了出来。为IBM PC开发的PC-DOS和为兼容机开发的MS-DOS都成了微型计算机的标准操作系统,Macintosh机的操作系统引入了鼠标的概念和点击式的图形界面,彻底改变了人机交互的方式。 20世纪80年代,随着微电子和数字化声像技术的发展,在计算机应用程序中开始使用图像、声音等多媒体信息,出现了多媒体计算机。多媒体技术的发展使计算机的应用进入了一个新阶段。 这个时期出现了多用途的应用程序,这些应用程序面向没有任何计算机经验的用户。典型的应用程序是电子制表软件、文字处理软件和数据库管理软件。Lotus1-2-3是第一个商用电子制表软件,WordPerfect是第一个商用文字处理软件,dBase III是第一个实用的数据库管理软件。


第五代软件(1990-今)

    第五代软件中有三个著名事件:在计算机软件业具有主导地位的Microsoft公司的崛起、面向对象的程序设计方法的出现以及万维网(World Wide Web)的普及。 在这个时期,Microsoft公司的Windows操作系统在PC机市场占有显著优势,尽管WordPerfect仍在继续改进,但Microsoft公司的Word成了最常用的文字处理软件。20世纪90年代中期,Microsoft公司将文字处理软件Word、电子制表软件Excel、数据库管理软件Access和其他应用程序绑定在一个程序包中,称为办公自动化软件。 面向对象的程序设计方法最早是在20世纪70年代开始使用的,当时主要是用在Smalltalk语言中。20世纪90年代,面向对象的程序设计逐步代替了结构化程序设计,成为目前最流行的程序设计技术。面向对象程序设计尤其适用于规模较大、具有高度交互性、反映现实世界中动态内容的应用程序。Java、C++、C#等都是面向对象程序设计语言。 1990年,英国研究员提姆·柏纳李(Tim Berners-Lee)创建了一个全球Internet文档中心,并创建了一套技术规则和创建格式化文档的HTML语言,以及能让用户访问全世界站点上信息的浏览器,此时的浏览器还很不成熟,只能显示文本。 软件体系结构从集中式的主机模式转变为分布式的客户机/服务器模式(C/S)或浏览器/服务器模式(B/S),专家系统和人工智能软件从实验室走出来进入了实际应用,完善的系统软件、丰富的系统开发工具和商品化的应用程序的大量出现,以及通信技术和计算机网络的飞速发展,使得计算机进入了一个大发展的阶段。 在计算机软件的发展史上,需要注意“计算机用户”这个概念的变化。起初,计算机用户和程序员是一体的,程序员编写程序来解决自己或他人的问题,程序的编写者和使用者是同一个(或同一组)人;在第一代软件末期,编写汇编器等辅助工具的程序员的出现带来了系统程序员和应用程序员的区分,但是,计算机用户仍然是程序员;20世纪70年代早期,应用程序员使用复杂的软件开发工具编写应用程序,这些应用程序由没有计算机背景的从业人员使用,计算机用户不仅是程序员,还包括使用这些应用软件的非专业人员;随着微型计算机、计算机游戏、教育软件以及各种界面友好的软件包的出现,许多人成为计算机用户;万维网的出现,使网上冲浪成为一种娱乐方式,更多的人成为计算机的用户。今天,计算机用户可以是在学习阅读的学龄前儿童,可以是在下载音乐的青少年,可以是在准备毕业论文的大学生,可以是在制定预算的家庭主妇,可以是在安度晚年的退休人员,所有使用计算机的人都是计算机用户。


计算机语言发展历史

(PS:摘自WIKI)


1950与1960年代

    有三个现代编程语言于1950年代被设计出来,这三者所衍生的语言直到今日仍旧广泛地被采用:

· Fortran (1955),名称取自”FORmula TRANslator”(公式翻译器),由约翰·巴科斯等人所发明;

· LISP,名称取自”LISt Processor”(列举处理器),由约翰·麦卡锡等人所发明;

· COBOL,名称取自”COmmon Business Oriented Language”(通用商业导向语言),由被葛丽丝·霍普深刻影响的Short Range Committee所发明。


    另一个1950年代晚期的里程碑是由美国与欧洲计算机学者针对”算法的新语言”所组成的委员会出版的ALGOL 60报告(名称取自”ALGOrithmic Language”(算法语言))。这份报告强化了当时许多关于计算的想法,并提出了两个语言上的创新功能:


· 巢状区块结构:可以将有意义的程式码片段群组成一个区块(block),而非转成分散且特定命名的程序。也就是我们所熟悉的模块化设计。

· 词汇范围(lexical scoping):区块可以有区块外部无法透过名称存取,属于区块本身的变量、程序以及函式。就是我们所熟悉的作用域。


    另一个创新则是关于语言的描述方式:一种名为巴科斯-诺尔范式 (BNF)的数学化精确符号被用于描述语言的语法。之后的编程语言几乎全部都采用类似BNF的方式来描述程式语法中上下文无关的部份。BNF主要使用在了Algol 60的设计上面。而Algol 60对之后语言的设计上带来了特殊的影响,在其他部分的语言设计中这种设计思想很快的就被广泛采用。并且后续为了开发Algol的扩充子集合,设计了一个名为Burroughs(en:Burroughs large systems)的大型系统。而延续Algol的关键构想所产生的成果就是ALGOL 68:


· 语法跟语意变的更加正交(orthogonal)

· 采用匿名的历程(routines)

· 采用高阶(higher-order)功能的递回式输入(typing)系统等等。


    整个语言及语意的部分都透过为了描述语言而特别设计的Van Wijngaarden grammar来进行正式的定义,而不仅止于上下文无关的部份。Algol 68一些较少被使用到的语言功能(如同步与并列区块)、语法捷径的复杂系统,以及型态自动强制转换(coercions),使得实作者兴趣缺缺,也让Algol 68获得了很难用(diffcult)的名声。尼克劳斯·维尔特就干脆离开该设计委员会,另外在开发出更简单的Pascal语言。


 在这段期间被开发出来的重要语言包括有:

· 1951 – Regional Assembly Language

· 1952 – Autocode

· 1954 – FORTRAN

· 1954 – IPL (LISP的先驱)

· 1955 – FLOW-MATIC (COBOL的先驱)

· 1957 – COMTRAN (COBOL的先驱)

· 1958 – LISP

· 1958 – ALGOL 58

· 1959 – FACT (COBOL的先驱)

· 1959 – COBOL

· 1962 – APL

· 1962 – Simula

· 1962 – SNOBOL

· 1963 – CPL (C的先驱)

· 1964 – BASIC

· 1964 – PL/I

· 1967 – BCPL (C的先驱)


1967-1978:确立了基础范式

    1960年代晚期至1970年代晚期的期间中,编程语言的发展也有了重大的成果。大多数现在所使用的主要语言范式都是在这段期间中发明的:

· Simula,于1960年代晚期由奈加特与Dahl以Algol 60超集合的方式发展,同时也是第一个设计支援面向对象进行开发的编程语言。

· C,于1969至1973年间由贝尔实验室的研究人员丹尼斯·里奇与肯·汤普逊所开发,是一种早期的系统程式设计(en:system programming)语言。

· Smalltalk,于1970年代中期所开发,是一个完全从零开始(ground-up)设计的面向对象编程语言。

· Prolog,于1972年由Colmerauer、Roussel,以及Kowalski所设计,是第一个逻辑程式语言。

· ML,于1973年由罗宾·米尔纳所发明,是一个基于Lisp所建构的多型(polymorphic)型态系统,同时也是静态型别函数编程语言的先驱。


    这些语言都各自演展出自己的家族分支,现今多数现代编程语言的祖先都可以追朔他们其中至少一个以上。 在1960年代以及1970年代中结构化程式设计的优点也带来许多的争议,特别是在程式开发的过程中完全不使用GOTO。这项争议跟语言本身的设计非常有关系:某些语言并没有包含GOTO,这也强迫程式设计师必须结构化地编写程式。尽管这个争议在当时吵翻了天,但几乎所有的程式设计师都同意就算语言本身有提供GOTO的功能,在除了少数罕见的情况下去使用GOTO是种不良的程序风格。结果是之后世代的编程语言设计者发觉到结构化编程语言的争议实在既乏味又令人眼花撩乱。


 在这段期间被开发出来的重要语言包括有:

· 1968 – Logo

· 1970 – Pascal

· 1970 – Forth

· 1972 – C语言

· 1972 – Smalltalk

· 1972 – Prolog

· 1973 – ML

· 1975 – Scheme

· 1978 – SQL (起先只是一种查询语言,扩充之后也具备了程式结构)


1980年代:增强、模组、效能

    1980年代的编程语言与之前相较显得更为强大。C++合并了面向对象以及系统程式设计。美国政府标准化一种名为Ada的系统编程语言并提供给国防承包商使用。日本以及其他地方运用了大量的资金对采用逻辑编程语言结构的第五代语言进行研究。函数编程语言社群则把焦点转移到标准化ML及Lisp身上。这些活动都不是在开发新的范式,而是在将上个世代发明的构想进一步发扬光大。 然而,在语言设计上有个重大的新趋势,就是研究运用模组或大型组织化的程式单元来进行大型系统的开发。Modula、Ada,以及ML都在1980年代发展出值得注意的模组化系统。模组化系统常拘泥于采用泛型程式设计结构:


· 泛型存在(generics being)

· 本质(essence)

· 参数化模组(parameterized modules)

    尽管没有出现新的主要编程语言范式,许多研究人员仍就扩充之前语言的构想并将它们运用到新的内容上。举例来说,Argus以及Emerald系统的语言配合面向对象语言运用到分散式系统上。 1980年代的编程语言实际情况也有所进展。计算机系统结构中RISC假定硬件应当为编译器设计,而并非为人类设计。借由中央处理器速度增快的帮助,编译技术也越来越进展神速,RISC的进展对高阶语言编译技术发展来不小的贡献。 在这段期间被开发出来的重要语言包括有:


· 1980 – Ada

· 1983 – C++ (就像有类别的C)

· 1984 – Common Lisp

· 1985 – Eiffel

· 1986 – Erlang

· 1987 – Perl

· 1988 – Tcl

· 1989 – FL (Backus)


1990年代:互联网时代

    1990年代未见到有什么重大的创新,大多都是以前构想的重组或变化。这段期间主要在推动的哲学思想是提升程式设计师的生产力。许多”快速应用程式开发” (RAD) 语言也应运而生,这些语言大多都有相应的集成开发环境、垃圾回收等机制,且大多是先前语言的衍生语言。这类型的语言也大多是面向对象的编程语言,包含有Object Pascal、Visual Basic,以及C#。Java则是更加保守的语言,也具备垃圾回收机制。与其他类似语言相比,也受到更多的观注。新的脚本语言则比RAD语言更新更好。这种语言并非直接从其他语言衍生,而且新的语法更加开放地(liberal)与功能契合。虽然脚本语言比RAD语言来的更有生产力,但大多会有因为小程式较为简单,但是大型程式则难以使用脚本语言撰写并维护的顾虑[来源请求]。尽管如此,脚本语言还是网络层面的应用上大放异彩。 在这段期间被开发出来的重要语言包括有:


· 1990 – Haskell

· 1991 – Python

· 1991 – Visual Basic

· 1993 – Ruby

· 1993 – Lua

· 1994 – CLOS (part of ANSI Common Lisp)

· 1995 – Java

· 1995 – Delphi (Object Pascal)

· 1995 – JavaScript

· 1995 – PHP

· 1997 – REBOL

· 1999 – D


现今的趋势

编程语言持续在学术及企业两个层面中发展进化,目前的一些趋势包含有:

· 在语言中增加安全性与可靠性验证机制:额外的堆栈检查、资讯流(information flow)控制,以及静态执行绪安全。

· 提供模组化的替代机制:混入(en:mixin)、委派(en:delegates),以及观点导向。

· 元件导向(component-oriented)软件开发

· 元编程、反射或是存取抽象语法树(en:Abstract syntax tree)

· 更重视分散式及移动式的应用。

· 与数据库的整合,包含XML及关联式数据库。

· 支援使用Unicode编写程式,所以源代码不会受到ASCII字符集的限制,而可以使用像是非拉丁语系的脚本或延伸标点符号。

· 图形化使用者接口所使用的XML(XUL、XAML)。

在这段期间被开发出来的重要语言包括有:

· 2001 – C#

· 2001 – Visual Basic .NET

· 2002 – F#

· 2003 – Scala

· 2003 – Factor

· 2006 – Windows PowerShell

· 2007 – Clojure

· 2009 – Go


需求

    在回顾这些历史的时候发现,我们无论是创造程序语言还是计算机,或者软件也好,最终的目的都是为了两个字——需求。我们遵循着工具理性的框架,追寻着完成需求的目标。而在计算机发展的过程的过程中,主要的需求有哪些?


1. 创造工具来满足人们现实生活中的需求,比如金融工具、QQ、微信

2. 不断创造更加好用的硬件基础。并且创造响应的软件来适应更快的硬件。

3. 随着硬件规模和软件规模的不断扩大,发展相应的理论去控制规模扩大带来的影响,即控制复杂性。


    当然,我们必须先解决的第一个需求就是我们创造计算机的原始需求:创造工具来满足人们现实生活中的需求。但是像绝大部分工具一样,一旦我们穿凿了它,它本省也会衍生出来很多需求。工具本身也需要演化。而工具本身的需求就是后两条。 满足第一条需求的方式,千奇百怪!基本上会涉及到人类现已掌握的知识的各个层面。比如一个图书分享网站,最起码要涉及:管理学、心理学、营销、产品设计、美术设计、交互设计、图书馆管理等等。而这些知识通过软件设计物化在了网站这个东西上面。然后我们就能通过网站这个工具,来满足我们图书分享的需求。 但是,我们能够发现,其实程序设计(程序员思维的最主要的展现形式)虽然与第一条需求有关,但又关系不是很大。的确,我们是通过软件设计这件事情,物化了我们的知识。但是在这个过程中,软件设计并不关系人们的需求具体是什么,是图书网站,还是聊天软件和软件设计并没有直接的关系。相对来说,软件设计比较关心的是后面两个需求:来自工具本身的需求。用一句话说就是:Make it work, Keep it simple。 首先你必须让这个工具能够工作,其次你必须让这个工具能够持续稳定的工具的工作(不会因为规模扩大,复杂性增长而招致灾难)。而这就是程序员思维中在工具理性下面最为核心的两个具体的概念:Make it work, keep it simple。 明白了这一点,我们再回过头去看一下刚刚所述的软件和程序设计的历史。刚开始人们需要有一个机器替代人进行计算于是有了差分机和ENIAC,有了硬件之后,自然就需要一种驱动机制能够让这些机器能够运转起来,于是我们发明了程序语言和软件。而随着硬件的不断发展和软件规模的不断扩大,人们发现最原始的计算机语言(机器语言)不使用于快速开发。于是就有了汇编这样的低级语言。后来低级语言也被证明在开发速度上存在缺陷,也不太适合快速开发,于是我们有了高级语言,比如Lisp、C、fortran。刚开始的时候这些高级语言,的确能够满足快速开发的需求。但是,随着软件规模的不断扩大。我们开始发现:靠,我们创造了软件却控制不了软件。计算机和软件这个工具的规模已经超出了人类能够认知的规模,它的复杂性已经开始变得不可控了。这怎么能行呢,于是我们开始创造了面向对象和软件工程等理论工具来帮助我们控制这种软件复杂性。 而到了今天,我们看一下我们日常Coding中常见的那些理论的方法,基本上也都是围绕着Make it work, keep simple展开的。其实这两个问题是不能够割裂开阐述的,为了理解上方面,我们就先单独说吧,不过中间会有概念的穿插。


Make it work(编程范式,程序语言的世界观)

    如何让计算机按照我们制定的方式工作,如何让软件能够按照我们假象的方式运行?我们把完成这两个需求的过程叫做程序设计。就是我们在前文中所说:程序设计就是利用计算机将知识物化,并利用知识的可复现性来对现实产生作用。但是,这只是一个概念啊。如何才能把它落实到实践上呢?这就要说一下编程范式了,我们对于程序语言是什么的世界观。 编程范型或编程范式(英语:Programming paradigm),(范即模范之意,范式即模式、方法),是一类典型的编程风格,是指从事软件工程的一类典型的风格(可以对照方法学)。如:函数式编程、程序编程、面向对象编程、指令式编程等等为不同的编程范型。 编程范型提供了(同时决定了)程序员对程序执行的看法。例如,在面向对象编程中,程序员认为程序是一系列相互作用的对象,而在函数式编程中一个程序会被看作是一个无状态的函数计算的串行。编程范式就是我们程序设计的世界观,他决定了程序在我们眼中是个什么样,而我们又怎样去操作或者使用程序语言。 正如软件工程中不同的群体会提倡不同的“方法学”一样,不同的编程语言也会提倡不同的“编程范型”。一些语言是专门为某个特定的范型设计的(如Smalltalk和Java支持面向对象编程,而Haskell和Scheme则支持函数式编程),同时还有另一些语言支持多种范型(如Ruby、Common Lisp、Python和Oz)。 很多编程范型已经被熟知他们会禁止使用哪些技术,同时又允许使用哪些技术。 例如,纯粹的函数式编程不允许有副作用;结构化编程不允许使用goto。可能是因为这个原因,新的范型常常被那些惯于较早的风格的人认为是教条主义或过分严格。然而,这样避免某些技术反而更加证明了关于程序正确性——或仅仅是理解它的行为——的法则,而不用限制程序语言的一般性。 编程范型和编程语言之间的关系可能十分复杂,由于一个编程语言可以支持多种范型。例如,C++设计时,支持过程化编程、面向对象编程以及泛型编程。然而,设计师和程序员们要考虑如何使用这些范型元素来构建一个程序。一个人可以用C++写出一个完全过程化的程序,另一个人也可以用C++写出一个纯粹的面向对象程序,甚至还有人可以写出杂揉了两种范型的程序。 下面是我们比较常见的几种“程序语言的世界观”:

    为了能够深入的理解每一种编程范式对我们编程实践的影响,我们来看一下用上面提到的不同的编程范式的代表语言去写一个我们常见的快速排序算法:


    同样是一个快速排序算法,每一种编程范式下他们实现的思路真的是千差万别。最主要的是各有千秋。我们很难说某一种编程范式比另外一种编程范式优秀,也很难说某一种编程范式比另外一种要坏。我们只能说某一种编程范式在某些特殊的情境下或者需求下要比其他的合适。因为他们就是为这些需求而生的。而其他的编程范式是为另外的编程范式。如果剪子砸不了核桃,不是剪刀的问题,而是你没有使用对工具的问题。


    而在我们看了这么多的编程范式,接受了这么多的对于程序的世界观之后。就会问,这些编程范式之间有没有一些内在的联系呢?这个问题一个明显的答案是:他们都是为了解决问题而生的。但是这个答案似乎有点欠妥。因为,我们是说的内在联系。而不是问他们的目标。 Pascal之父尼克劳斯·沃思曾经给出过程序的一个定义:算法+数据结构=程序,后来他又对算法进行了定义:逻辑+控制=算法,最后一个完整的表述是:程序=(逻辑+控制)+数据结构。虽然他提出这个概念的时候是针对命令式或者过程式的程序的。但是我们在很多编程范式中依稀能够看到:逻辑+控制+数据结构=程序的影子。 我们能够看到无论是在哪一种编程范式指导下的语言。我们都能够看到相同的“控制”结构——顺序结构、分支结构、循环结构。我们也都能够看到是与非的逻辑判断。数据结构更不用说。没有任何语言不去操作数据的吧。所以命令式是一种基础范式。同样为基础范式的还有函数式和逻辑式。而比较有意思的是,这几种基础编程范式各有所重。命令式编程着重控制和数据结构,函数式编程着重逻辑、控制和数据结构,逻辑式着重逻辑和数据结构。这几种编程范式很好的构建起了,程序的基础组成部分的概念。命令式可以告诉你控制这个东西是怎么回事,怎么去用。逻辑式,像数学思维一样的精巧告诉你逻辑的魅力。函数式,告诉你怎样组合起来使用逻辑和控制。而他们又共同在讲着数据结构的概念。其实,逻辑+控制+数据结构的世界观来自于我们对于计算机这个机械器件的认知。 其他的编程范式我们称之为高级范式,因为他们在基础范式概念基础之上增加了自己对于程序的额外的一些认知。这里着重说一下,面向对象编程范式。面向对象认为程序就是信息交互。他把数据结构放到了一个非常重要的位置。突破了数据域代码隔离的限制。让数据能够与逻辑和控制紧密的结合在一起。这种思维方式,非常符合人类的认知模式。面向对象,通过人类思维中常用的手段——抽象,提供了一种新的组织程序的方式。而这种方式,极大的提高了软件的易用性和重用性,同时非常好的控制了整个软件的复杂性。 如果想要了解更多关于编程范式的东西,推荐看一下《冒号学堂》。


Keep it simple(复杂性控制)

    首先我们尝试先构建起复杂性的概念。什么是复杂性? 复杂,字面意思理解,就是一个东西极其庞大承载的东西太多而我们理解或者认知起来已经很困难的时候,我们就说这个东西很复杂。而《现代汉语词典》对于复杂的解释也符合我们的直觉:

1

2

<code>(事物的种类、头绪等)多而杂:颜色~ㄧ~的问题ㄧ~的人际关系

</code>

    复杂性,就是在把复杂作为一种特性了而已。就是说一件事物具有了一种“多而杂”的特性。而现代的复杂性科学认为复杂性可分为:情景复杂性(事物固有属性)和认知复杂性,而且两者互为因果。 那么软件为什么会有复杂性呢?或者说程序设计为什么会带来复杂性呢?这和程序设计本身有关系。我们回顾一下前面我们对于程序设计的定义:程序设计就是利用计算机将知识物化,并利用知识的可复现性来对现实产生作用。 那么说,程序设计就是在为整个软件系统增加知识。在增加整个系统的信息总量。通过信息论我们知道,当一个系统中的信息越多的时候,其信息熵值就越大。熵值越大,我们理解难度就越大,势必会带来复杂性增加。于是,复杂性一个比较浅显的数学定义就是信息熵。

    上述公式是香农信息熵的公式,现在也可以理解成软件复杂性的公式。我们容易发现,软件设计这个事情,本事就是在增加复杂性的。他在不断的往整个软件系统中,增加信息,信息越多,我们越难去理解这个软件。这个软件就越复杂,当这种复杂到了一定的量级的时候,就爆发了软件危机。于是我们需要扩充一下我们对于程序设计的定义:程序设计在完成思维工具物化的同时,还需要尽可能的控制信息熵的增长,来降低系统的复杂性。 但是,人们总是聪明的,有了问题,我们就去发明新的工具啊。于是我们就有了一系列复杂性控制的理论。这里我们不去探讨更普遍层面的复杂性,我们先只看一下,程序设计,这一个我们比较关心的实物的复杂性控制的问题(要想对复杂性有一个浅显的认识可以看一下《探索复杂性》和《信息简史》)。 在程序设计层面上,我们没有很好理论化的东西去控制复杂性。但是我们创造了一系列的方法来控制复杂性。我们把这一些列的方法叫做“最佳实践“。如果你去看一些软件工程或者面向对象设计的书的时候,这个词可能经常遇到。而我们的最佳实践有哪些呢? 结构化程序设计、面向对象程序设计、软件工程、敏捷开发、UML、领域建模…….不一而足。 关于“最佳实现”的具体细节的东西,有很多写的非常好的书中都有描述我们这里就不再赘述了。我们着重关心一点,如何去评价一个设计或者软件的复杂性呢?因为你不知道,如何评价或者衡量自己设计的复杂性的话,势必也不知道如何改进。 而评价的方式比较靠谱的有两个:

1. 高内聚低耦合

2. 是否满足设计模式的六大基本原则


高内聚低耦合

    这个是我们刚开始提出的问题之一。内聚和耦合是一对相对的概念。相辅相成。两者虽然有联系,但不是必然联系。高内聚的设计不一定低耦合,低耦合的东西也不一定高内聚。那么内聚性和耦合性是什么,如何去评价呢?


内聚性

    在程序设计中,内聚性是指机能相关的程序组合成一模块的程度。应用在面向对象程序设计中,若服务特定类型的方法在许多方面都很类似,则此类型即有高内聚性。在一个高内聚性的系统中,代码可读性及复用的可能性都会提高,程序虽然复杂,但可被管理。 以下的情形会降低程序的内聚性:

· 许多机能封装在一类型内,可以借由方法供外界使用,但机能彼此类似之处不多。

· 在方法中进行许多不同的机能,使用的是相关性低或不相关的数据。

低内聚性的缺点如下:

· 增加理解模块的困难度。

· 增加维护系统的困难度,因为一个逻辑修改会影响许多模块,而一个模块的修改会使得一些相关模块也要修改。

· 增加模块复用困难度,因为大部份的应用程序无法复用一个由许多不一定相关的机能组成的模块。


内聚性的衡量

    内聚性是一种非量化的量测,可利用评量规准来确认待确认源代码的内聚性的分类。内聚性的分类如下,由低到高排列:

1. 偶然内聚性(Coincidental cohesion,最低)偶然内聚性是指模块中的机能只是刚好放在一起,模块中各机能之间唯一的关系是其位在同一个模块中(例如:“工具”模块)。

2. 逻辑内聚性(Logical cohesion)逻辑内聚性是只要机能只要在逻辑上分为同一类,不论各机能的本质是否有很大差异,就将这些机能放在同一模块中(例如将所有的鼠标和键盘都放在输入处理副程序中)。

3. 时间性内聚性(Temporal cohesion)时间性内聚性是指将相近时间点运行的程序,放在同一个模块中(例如在捕捉到一个异常后调用一函数,在函数中关闭已打开的文件、产生错误日志、并告知用户)。

4. 程序内聚性(Procedural cohesion)程序内聚性是指依一组会依照固定顺序运行的程序放在同一个模块中(例如一个函数检查文件的权限,之后打开文件)。

5. 联络内聚性(Communicational cohesion)联络内聚性是指模块中的机能因为处理相同的数据,因此放在同一个模块中(例如一个模块中的许多机能都访问同一个记录)。

6. 依序内聚性(Sequential cohesion)依序内聚性是指模块中的各机能彼此的输入及输出数据相关,一模块的输出数据是另一个模块的输入,类似工厂的生产线(例如一个模块先读取文件中的数据,之后再处理数据)。

7. 功能内聚性(Functional cohesion,最高)功能内聚性是指模块中的各机能是因为它们都对模块中单一明确定义的任务有贡献(例如XML字符串的词法分析)。

    由赖瑞·康斯坦丁、爱德华·尤登及史蒂夫·麦康奈尔等人的研究都提出偶然内聚性和逻辑内聚性是不好的,联络内聚性和依序内聚性是好的,而功能内聚性是最理想的状态。 当我们拿到一个设计的时候,完全可以通过上述的几个层面去衡量这个设计的内聚性。


耦合性

    内聚性是一个和耦合性相对的概念,一般而言低耦合性代表高内聚性,反之亦然。耦合性和内聚性都是由提出结构化设计概念的赖瑞·康斯坦丁所提出[1]。低耦合性是结构良好程序的特性,低耦合性程序的可读性及可维护性会比较好。 耦合性可以是低耦合性(或称为松散耦合),也可以是高耦合性(或称为紧密耦合)。以下列出一些耦合性的分类,从高到低依序排列:

1. 内容耦合(content coupling,耦合度最高)也称为病态耦合(pathological coupling)是指一个模块依赖另一个模块的内部作业(例如,访问另一个模块的局域变量),因此修改第二个模块处理的数据(位置、形态、时序)也就影响了第一个模块。

2. 共用耦合(common coupling)也称为全局耦合(global coupling.) 是指二个模块分享同一个全局变量,因此修改这个共享的资源也就要更动所有用到此资源的模块。

3. 外部耦合(external coupling)发生在二个模块共用一个外加的数据格式、通信协议或是设备界面,基本上和模块和外部工具及设备的沟通有关。

4. 控制耦合(control coupling)是指一个模块借由传递“要做什么”的信息,控制另一个模块的流程(例如传递相关的旗标)。

5. 特征耦合(stamp coupling)也称为数据结构耦合,是指几个模块共享一个复杂的数据结构,而每个模块只用其中的一部份,甚至各模块用到的部份不同(例如传递一笔记录给一个函数,而函数只需要其中的一个字段。

6. 数据耦合(data coupling)是指模块借由传入值共享数据,每一个数据都是最基本的数据,而且只分享这些数据(例如传递一个整数给计算平方根的函数)。

7. 信息耦合(message coupling,是无耦合之外,耦合度最低的耦合)可以借由以下二个方式达成:状态的去中心化(例如在对象中),组件间利用传入值或信息传递 (计算机科学)来通信。

8. 无耦合模块完全不和其他模块交换信息。

    耦合性和内聚性二个名词常一起出现,用来表示一个理想模块需要有的特点,也就是低耦合性及高内聚性。耦合性着重于不同模块之间的相依性,而内聚性着重于一模块中不同功能之间的关系性。低内聚性表示一个模块中的各机能之间没什么关系,当模块扩充时常常会出现问题。 相对于内聚性而言耦合性是一个相对比较好度量的概念。已经有比较好的数学公式来比较精准的衡量耦合性。 以下是一种计算模块耦合性的方法[: 对于数据和控制流的耦合:

· di: 输入数据参数的个数

· ci: 输入控制参数的个数

· do: 输出数据参数的个数

· co: 输出控制参数的个数

全局耦合:

· gd: 用来存储数据的全局变量

· gc: 用来控制的全局变量

环境耦合:

· w: 此模块调用的模块个数(扇出)

· r: 调用此模块的模块个数(扇入)

    若Coupling(C)数值越大,表示模块耦合的情形越严重,数值一般会界于0.67(低度耦合)到1.0(高度耦合)之间。 到此,我们已经构建起了高内聚和低耦合的概念。并且知道了如何去使用这两个概念去控制复杂性。


设计模式的六大原则

    设计模式这个东西就不细说了,大家都知道。但是如何去评价一个设计模式呢?或者是什么知道我们设计“设计模式”的,那就是六大原则:

1. 单一职责原则

2. 里氏替换原则

3. 依赖倒置原则

4. 接口隔离原则

5. 迪米特法则

6. 开闭原则


    这里偷点懒不详细赘述了,可以参考六大原则


总结

    在上面的叙述中,我们讲了工具理性,之后从工具理性衍生出了程序员思维的定义。通过回顾历史我们定义了程序设计,并且指出了程序设计中的两个主要的问题:Make it work, keep it simple。之后我们针对这两个问题,分别阐述了不同的方法论。以编程范式为主的make it work,和以复杂性控制为主的Keep it simple。同时提到了,在这个方法论之下的一些最佳实践。至此我们构建起了一个程序员的思维框架。

PS:个人见解,仅供参考。


最后用一幅图来总结上面我们所述:

推荐阅读

我要推荐
转发到