编程基本功触类旁通


Spark 是用scala写的,storm是clojure开发的,docker采用了go, 各种编程语言层出不穷,但其实都是马甲,作为一个程序员,我们需要理解那些相通的本质,做到触类旁通.
 
程序中的基本逻辑控制——跳转,在汇编语言中早就有满足条件后跳转的命令了。所谓跳转,就是告诉机器到那去干活. 就像过去的接线员那样,因而C语言中有个goto语句,它是跳转的最直接的解释.
 
如果程序中goto多了,恐怕没谁能看懂了. 为了避免goto语句的不便理解,C语言中的if...else语句就变得更让人欢迎了。满足条件时反复执行某区间的代码,goto语句都能做到,而while和break语句所作的事情不是增加了新功能,而是提高了程序的易读性和易写性。for 语句只是让数值渐增的while语句更简洁而已,而foreach语句是为了方便编写对某对象内的所有元素进行某种处理的代码。

函数是为了程序的便于理解和重复使用,把代码的一部分视作有机的整体,然后切分出来并为之命名,这是一种程序设计机制。函数催生了return语句,能够使函数调用后回到程序执行的原来内存地址,使用函数给操作命名的办法来表示操作开始时内存的位置。栈是一个存储有多个值的数据结构,实现后进先出,是管理函数操作的有力工具。了解栈,就可以解释运行时的函数行为了。

递归调用是函数使用的一种方式,是指函数内部再次调用当前函数的过程。当多重嵌套的数据结构无法用for语句实现时,就需要使用递归调用了。

一个程序中尤其在一个函数中,错误处理的传达方法主要有两种:
1)通过函数的返回值来传达出错的信息,调用者通过返回值来判断进行相应的错误处理
2)预先设定好错误处理的代码,错误发生时能跳转到相应的错误处理代码。
方法一存在两个问题,分别是错误遗漏和错误处理导致代码的可读性下降;对于方法二中可追加错误类型和可自主出发出错,这两种功能被现代的异常处理机制所继承。

异常处理逐渐成为了错误处理的主要传达机制. try 是一个为了方便理解的修饰符,触发异常的表述为抛出异常,异常处理在except中,出错也要执行的关键字为finally。而c++采用了资源获取即初始化技术,通过自动调用析构函数来实现类似的机制。一般地,函数调用参数不足时,数组越界等情况,都要抛出异常。Fail First,即异常处理优先, 但是,检查型异常是比较麻烦的.

关于变量,整个程序共用一个参照表,即全局变量的使用范围,作用域是指名字的有效范围。动态作用域是把变量原来的值事先保存在函数入口处,在出口处写回变量中的方式,静态作用域按函数区分各自的对照表,避免全局污染的原因就在于此。一般地,一个程序都会有整体对照表(内置),一张文件级对照表(全局),和一张函数级的对照表(局部)。

千万别轻易认为自己了解了数据类型,类型是个复杂的概念.类型是人们给数据附加的一种追加数据,最初是为了加入数值的类型信息,告诉编译器如何处理,而后,用户自定义类型和面向对象同样是类型,从而产生了作为功能的类型,进而发展为接口,出现了泛型和模板等。把类型的信息和数值看做整体的方式叫做动态类型,在内存上使用了同等类型对待的设计方法,支持类型推断。例如python,都是PyObject。 从内存和何时使用的角度来看待类型会更清晰。

从存储的形式看变量, 存放多个元素的东西可以理解为容器,容器中的数据存在内存中,存储的方式不同容器的体现也不同。数组是顺序存放的,链表同时存放了相对地址,因而对元素多插入频繁的情况,链表更适合。为了简洁地表达计算时间和数据量之间的关系,一般采用O表示法。O(n)是n的数量级,O(1)是常数数量级,树(平衡)中元素的读取时间为O(log n)。

字符串博大精深,是非常重要的元素. 北邮门口的摩尔斯码,到后来各种的字符集编码,程序中多用注释来指明编码方式,unicode带来了统一。一般语言的字符串都带有自身的长度,c中的字符串不知道自身的长度,是最为原始的字符串,NUL即\0是表示字符串终止的特殊字符。Unicode是16bit还是32bit是在编译时通过选项指定的。

并行处理越来越被关注, 单cpu的并行处理实际上是分时交替处理,交替方式有:
1)协作式多任务——在合适的节点交替,基于信任,所有处理都会在适当的间隔后交替处理
2)抢占式多任务——一定时间后交替,不需要终止程序的协助就可以单方面终止它
抢占式多任务存在竞态条件(race condition),或者称为线程安全,竞态条件成立的三个条件:
1)两个处理共享变量
2)至少一个处理会对变量进行修改
3)一个处理未完成前另一个处理会介入进来
只要三个条件有一个不具备,就可以写线程安全的程序了。
规避一,没有共享内存,就不存在竞态条件了,例如利用独立进程和actor模型。
规避二,比如C++中的const,scala中的val,Java中的immutable
规避三,不介入,使用协调模式的线程如coroutine等,也可以使用表示不便介入的标识——锁、mutex、semaphore,实际上是使用中的状态牌。锁的使用问题包括死锁和无法组合,只能寄托于事务内存来奢望解决了。

关于面向对象的解释, 至少有两种截然不同的含义:
1)class是一种创建用户自定义类型的功能,面向对象是使用用户自定义类型和继承的程序设计
2)通过不同状态的对象互相传送消息来通信的程序设计就是面向对象
归集变量和函数建立模型的方法有:
1)模块,将相互关联的函数集中在一起,将初始化处理也放入包中即构造函数,例如perl
2)将函数和变量放入散列中,例如javascript
3)闭包,通过命名空间来归集,是创建具有对象性质的的事务的一种技术。一个包含了自由变量的开发表达式,和该自由变量的约束环境组合在一起后,实现了一种封闭的状态。

对象是现实世界的模型,类在不同语言里的意义不同,没有一个通用的解释,除了Java外,类不是不可或缺的。
类的存在只是为了更方便书写程序而约定的一种规则,无他。class最初有分类的含义,还表示类型,还有功能说明的作用,概念复杂。C++和Java的类主要有以下作用:
1 ) 整体的生成器
2)可行操作的功能说明
3)代码再利用的单位

类继承的三种实现策略:
1)一般化与专门化:子类是父类的专门化
2)共享部分提取:在多个类中提取共享部分作为父类
3)差异实现:把继承作为实现再利用的途径
为了保证理解的简易性,要防止继承树的层级过多。一般采用里氏置换法则:对于类T的对象一定成立的条件,对于类T的子类S的对象也必须成立。

多继承问题的解决方案主要有:
1)Java语言中禁止了多重继承,取而代之的是委托和接口
2)按顺序搜索,例如python中使用了C3线性化
3)混入式处理
4)trait认为类同时具有的作为再利用代码单元和实例生成器,这两种作用是相反的,把再利用单元的作用特别化,设定一些更小的结构(特性=方法组合)。

本文永久更新链接地址:

相关内容