日常使用编程语言:Ruby(高抽象、语法糖), Rust(性能,宏) 工作=游戏开发中使用编程语言:Lua(热更新),C#/GDscript/js(引擎自身绑定语言), C/C++(引擎)
编程语言是编程过程中的首要之门,是编写软件过程中首要的选择项。如今编程语言遍地开花,选择项有多种,每种编程语言都有大量拥护者和狂热者。每种语言设计哲学都有区别,应对的领域也不相同,语言设计的性能、 开发效率也不相同,如何挑选适合自身的编程语言是一项技术活。
目标明确,选择才能清晰。
日常使用主要图开发效率,最好一句代码搞定大量内容,这需要极高的抽象,有简化代码编写工作的语法糖。
对于开发效率而言,富含其他附加的功能有:所见即所得/实时预览/热加载;尽早识别错误;大量现成的库能快速完成开发等。
其次是领域。区块链要求安全;云计算、机器学习、爬虫、大数据分析等追求速度;实时性命攸关系统,追求的是容错性。
然而,最主要的还是语言本身是可靠的!也就是说,语言本身不应出现 BUG。
针对游戏开发而言,依照现有库的选择标准,3D 引擎基本上都是 C++写成,选择显而易见。引擎有外扩脚本语言,用于编写逻辑和完成快速开发,每种引擎都有其官方的脚本绑定,官方的脚本语言的选择有官方自己的抉择标准,往往别无二选。对于需要热更新,引擎如有内置脚本引擎,则可实现热更新。
针对游戏日常开发而言,主要是写工具,简化外围工作,因此高开发效率的脚本语言成为不二之选。如果工具的性能是选择标准,那么选择只能限于编译式语言。
综上,选择编程语言的标准如下:
- 高效开发
- 丰富库,支持 FFI(使用现有库)
- 辅助工具:IDE(快速定位错误)
- 调试方式,调试工具(快速定位运行时错误)
- 快速解析,实时预览(快速运行预览结果)
- 性能,速度
- 并发
- 安全,容错性
各种编程语言,大同小异。选择优秀的编程语言标准,主要集中在:开发效率高,高性能,易维护,易扩展。
-
开发效率高:
- 语法简洁、清晰程度(快速编码,提高开发效率)
可以减少代码量(开发效率),同时,使代码变得更为清晰易懂,更能理解与接受(可读性)。
- 完善的字面量及类型推导
- 高阶函数
- 宏
- 模板
- 丰富的内置库。可以提高开发效率。
- C语言FFI。可以利用现有库,或直接与操作系统操作。
-
易维护:
-
能否在开发中快速发现潜在问题(IDE支持)
编码只是整个产品过程中的一部分,另外一部分是确保没有错误。快速发现问题也很重要。
-
静态类型,类型推导
-
测试驱动,自动生成
-
-
高性能
程序最终是要运行在计算机中,计算机资源是有限的。性能在很大程度上决定语言的应用范围。
- 并行运算。并发是在性能上锦上添花的手段,也是未来发展趋势。简单明了的并行运算支持,可以简单、快速开发并行运算程序。
- 惰性求值。也是作为性能的提升的手段。
-
易扩展
- 多态。可以快速进行扩展,并进行维护。
- /包/模块/命名空间。可以使用其他模块,迅速扩展。
优秀特性
-
丰富的内置类型:如bool,int,float,symbol,lambda;
-
丰富的内置容器:如Array/Tuple,List,HashMap/map
-
简洁、清晰的字面量表示。如0b10100、100_999、dead?、replace!,1..2,nil
-
Unicode编码。
-
高阶函数,lambda表达式。函数和数据是同等重要的,都是编程语言中的核心组成部分。
高阶函数可以以函数作为处理单元,简化代码量。
函数作为第一等公民,可以简化很多代码的编写。如C++和JAVA将函数作为第二等公民,为了回调函数,需要平白无故地创建一个类型,实属事倍功半,徒增麻烦。函数和数据都是同等重要的,厚此薄彼必定产生不必要的问题。
-
模块/包/命名空间/库管理
先进的包管理,拿来即用,可以提高生产力,无需因配置库环境而消耗大部分时间。
-
垃圾回收。(不适用于强实时应用)
内存自动管理,可以简化程序员的心智负担,无需关心内存何时释放与否,而造成内存泄漏。
-
自动作用域。
特别对于文件等操作,在操作完后,很多时候会忘记关闭文件,造成问题。简单的作用域范围使用,可以自动关闭文件。与内存自动管理类似。
-
-
惰性求值。
一个比较重要的例子是:惰性求值瞬间可以打开非常大的文件而无需等待,但常规的求值表达式在打开文件时,需要占用非常大的内存空间,而需要等待。
-
并发语句。
并发是当今提高运算速度的可行方案。
- async/await
- Channel
- yield,coroutine,thread,fiber
-
模式匹配
-
正则表达式
正则表达式在文本处理中非常简练,可以简化很多代码编写。
-
-
自动文档化注释
-
属性标记
align,depracate,等等。通过编译器辅助属性特征,帮助完成部分工作,而无需手动对这些属性做一些处理。
-
-
具名参数。参数名称往往能包含很多内容,一个好的参数名,可以省略多余、繁杂的注释。
- 默认参数值。
- 不定参数处理。
- 多返回值,及其解构。
-
多态。
多态在类型理论中,用于对多种不同类型实体,提供相同接口;或使用单个符号代表多个不同类型。可以以统一方式处理不同类型的数据。
多态的实现方式主要有以下几种:
- 特定同质法(Adhoc):应用多态函数到不同类型参数上。例如,加法可以应用在数值、数组、列表等
- 参数多态:类型不以名称,而以抽象符号表示任意类型。(模版)
- 子类型:通过共同超类,以不同名称表示不同类的实例。(继承)
- 鸭子类型:类似于子类型,但以结构化类型决定。允许使用包含一些相同属性的类型,但不丢失类型信息。
- 数据类型泛型(多型):它比多态更具一般性,对于多型函数,可提供不存在特殊组合的不同数据类型的固定几种特殊情况。
-
跨平台
编写一次代码,可在不同操作系统上顺利运行,而无需手动解决跨平台相关问题。
- 操作系统抽象层
- 与外部语言集成:C
-
宏/ 同相性
规则或模式:通过定义过程,指定输入序列如何映射到可替换的输出序列。
代码即数据,数据即代码,可以非常便利地实现DSL。可以以较少的代码,自动生成复杂的代码块,或处理复杂的任务。简化代码量。
-
尾调用优化
当函数尾部调用另一函数时,只保存内层函数调用记录,从而优化掉不必要的一些中间变量存储状态,简化内存空间使用。通常在递归函数使用中,会大大增加内存开销,尾调用优化可以减少递归函数的内存空间开销。
-
错误处理
- 异常:运行时错误
- 断言:开发时错误
静态语言
- 类型推导。可以简化对于类型声明的代码量。
- 泛型。简化函数逻辑所能应用的类型。
- 宏。可以简化代码量。
- 类型别名
- 函数重载
- 基本的类型组合手段:struct/class/record等
OOP
-
接口,triat,鸭子类型
类型只要响应对应的消息,皆可适用需求。
-
类型扩展,开放类型
类型可以附加新的操作,以适应于灵活的需求。
-
操作符重载
简化代码意义,节约命名。
-
单继承,Mixin
-
反射,自省
-
异常处理
可以简化错误处理流程。
不利的特性
-
副作用
副作用表示在函数处理过程中,改变了传入的值或其他外部的某个状态。
-
状态
状态意味着随时可能被更改,在并行环境下,状态的改变通常难以跟踪而引发问题。
最佳实践
-
契约式编程
遵循契约,则可正确执行,不遵循契约,则拒绝执行,直接报告错误。
-
单元测试,测试驱动的开发过程
每个大型的功能,需要确保有特定的测试用例。这样在迭代开发时,如果不满足测试用例,则可快速定位错误的地方。
-
RAII。
资源在获得时,先初始化。
-
无副作用编程。
函数不改变传入的参数,也不改变外部的状态。即不依赖外部状态。无副作用的函数无论何时以何种方式调用,所得到的结果相同。
-
可并行代码。
对于耗时的操作,采用并行原语进行处理。
-
100个函数操作一个数据结构比10个函数操作10个数据结构要优。
函数是第一等公民,数据结构通常而言:列表、元组等即可满足常用。
-
减少使用静态、全局变量
-
边界检测
对于边界检测,做好断言。
-
一致性。
相同名称的函数和类型,请保持一致的操作。
怎样写程序
使用编程语言写好程序是有技巧的。
编程风格
编程风格是编程的细节,比如变量名的选择方法,函数的写法等。
算法
算法是解决问题的方法。现实中各种算法都已广为人知,所以编程时的算法也是对这些技巧的具体应用。
数据结构
很多算法光靠自己想是很难想出来的。比如数组的排序,就有多种算法。如果对这些算法根本就不了解,那么像做出高速排序就很难。算法和特定的数据结构关系很大。所以:程序=算法+数据结构。
设计模式
设计模式是指设计软件时,根据以前的设计经验对设计方法进行分类。算法和数据结构从广义上说也是设计模式的一种分类。
开发方法
指开发程序时的设计方法,包括管理在内的整个开发工程。在大的软件项目中,随着开发人员的增加,整个软件工程的开发方法就很重要。
面向对象
面向对象编程是对结构化编程的延伸。面向对象的核心是:对功能扩展是开放的;保持接口稳定,在修改内部实现细节时,不会改变其他部分。是否数据和函数一体化,是否封装不充分等等,都不重要,因为面向对象只是抽象化的工具。
多态/动态绑定:最重要且必需的功能
多种形态,即不同种类的东西当做相同的东西来处理。如有3个箱子,一个木箱子,一个带锁的箱子,一个银行里层层加密的箱子。当发出“打开箱子”的命令时,每个箱子的打开方式都不一样,但它们“都是箱子,可以打开”。这就是多态的本质。“打开箱子”的命令,我们称之为消息,而打开不同箱子的具体操作,我们称之为方法。
多态的好处:
- 各种数据可以统一处理。多态性可以让程序只关注要处理什么(what),而不是怎么去处理(how)。
- 根据对象的不同自动选择最合适的方法,而程序内部不会发生冲突。不管打开什么箱子,它们都能自动处理,不用担心调用中会发生错误,这样就会减轻程序员的负担。
- 如果有新数据需要对应处理,通过简单追加就可实现,而无需改动以前的程序,这就让程序具备了扩展性。
数据抽象/信息隐藏/封装:简化理解力的利器
人的记忆和理解能力是有限的,但计算机无论多少数据、运算多久都可以完成。在早期结构化程序思想中,人们把程序执行顺序限制为顺序、分支和循环这3种,把共通的处理归结为例程。结构化编程的限制和抽象,是人类处理复杂软件的非常有效的方法。通过限制大大降低了程序的自由度,减少了各种组合,使得程序不至于太过复杂。但如果由于降低了额自由度而导致程序的实现能力低下,这是我们不愿看到的。结构化的顺序、分支和循环可以实现一切算法,虽然降低了程序的复杂性和灵活性,但程序实现能力并没有降低。
抽象化的目的是,我们只需要知道过程的名字,而不需要指导过程的内部细节,因此它也被称为“黑盒化”。我们只需要知道“黑盒子”的输入与输出,而过程的细节是隐藏的。
如果把黑盒的内部处理也考虑上,整个系统的复杂性并没有改变。但是如果不考虑黑盒内部的处理,系统复杂性就可以降低到人类的可控范围内。此外,黑盒内部的处理无论如何变化,如果输入和输出不发生变化,那么对外部就没有影响,所以这种扩展特性是我们非常希望获得的。
结构化编程采用限制和抽象化解决程序控制流复杂问题,结果证明它是非常成功的。所以现在几乎所有编程语言都支持结构化编程。
但是程序里不仅包括控制结构,还包括要处理的数据。结构化编程降低了程序流程的复杂性,但随着处理数据的增加,程序的复杂性也会上升。
数据抽象是数据和处理方法的结合。对数据内容的处理和操作,必须通过预定义好的方法来进行。数据和处理方法结合起来成为了黑盒子。如对栈进行操作,只有push和pop两种操作方法。通过简单的两种方法就能控制许多内部细节,但这些细节我们并不需要太过关注。利于变化是抽象数据的巨大优点。
有了数据抽象,程序处理的数据就不再是单纯的数值或文字这些概念性的东西,而变成人脑容易想象的具体事物,更易于理解和接受。
重复的程序是冗余的,暴露太多细节而难以修改,从而降低了程序的可靠性,且人们解读程序、理解程序意图的成本也会增加。对计算机而言,无论重复与不重复都没关系,但对人而言,却非常重要。重复冗长的程序会降低人的生产力。
对于同样对象大量存在时,为了避免重复,可采用两种方法:
- 原型。用原始对象的副本来做作为新的相同的对象。
- 模板。比如浇注东西时,往模板里注入材料就能出来相同的东西。这种模板在OOP语言中称为类(class)。操作方法和属性可以共享。类与对象是有区别的,如同蛋糕模具和蛋糕是有区别的。因此,类是模具,对象是实例。
继承:避免重复
随着软件规模的扩大,用到的类的数量也会增加,其中会有很多性质相似的类。重复这些相似性质会降低生产力,如果将这些相似的性质汇总在一起就好了。继承即是这种方式。
在动态语言中,允许调用没有继承关系的方法。但静态语言中,只能调用有继承关系的方法。
多重继承
单一继承的特点:
- 继承关系单纯。但不能通过继承关系共享程序代码,导致需要复制程序。
- 类的优先顺序明确了然
多重继承的特点:
- 很容易扩展单一继承
- 可以集成多个类的功能。但类关系变得复杂。
- 结构复杂化,继承类的关系很复杂
- 多重继承中的优先顺序模糊。
- 功能冲突。如果不同基类中有相同方法时,就会产生冲突。
继承作为抽象化的手段,是需要实现多重继承功能的。在抽取类的共通功能时,如果一个类只允许抽出一个功能,那限制就太多了。既想利用多重继承的优点,又要回避它可能带来的问题,就需要找出解决方法:受限制的多重继承。
实现继承和规格继承(接口)
继承包含两种含义:1. 类都有哪些方法,即类都支持什么操作,即规格的继承。2. 类中都用了什么数据结构和算法,即实现的继承,指继承功能的实现方法。
在静态语言中,两者的区别很重要:类是用来指定对象实现的,而接口只是指定对象的规格(都有哪些方法)。接口对实现没有任何限制,即接口可以由跟实现的继承没有任何关系的类来实现,也就是实现这一接口的类可以继承任何其他类。在一些静态语言中,如JAVA或C#明确区分两种继承,实现的继承只能是单继承,却可以多重继承接口。
但接口并非解决多重继承完美的办法。Java推荐解决共享实现问题的方案是,在单一继承的前提下,使用组合模式来调用别的类实现的共通功能。本来只是为了跨越继承层次来共享代码,现在却需要另外一个独立对象,而且每次方法调用都要委派给那个对象,这实在是不太合理,而且执行的效率不高。
动态语言本来就没有规格的继承这种概念。动态语言需要解决的就是实现的多重继承。
mix-in:受限的多重继承
mix-in是降低多重继承复杂性的技术。它按以下规则来限制多重继承:
- 通常的继承用单一继承。
- 第二个以及两个以上的父类,必须是mix-in的抽象类。
mix-in类是具有以下特征的抽象类:
- 不能单独生成实例。
- 不能继承普通类(没有父类)。
以此,类层次单一,且可实现功能共享,避免复制代码。(mix-in类通常以-able结尾)
元编程
元编程是对程序进行编程。
元编程的应用范围:
- 动态生成方法。在静态语言中,通常使用宏来实现。
- 反射:在程序执行时取出程序的信息(自省)或改变程序的信息。在运行时,按需修改功能。
- DSL:主要利用宏或块的功能。
闭包
闭包是可以捕获外部环境的变量,用于在函数之间共享信息。
闭包通常用于高阶函数。如map,reduce,each等。高阶函数把函数作为数据来处理。
闭包也能保证程序的后处理。如文件打开后,如果出现异常,不会关闭。利用块则很容易处理。在闭包中,捕获的参数在结尾会自动清除。
闭包也能用于回调函数。
设计模式
设计模式是反复出现、使用的模式。模式与库不同,模式不能以库的形式来共享。设计模式是软件抽象化的新工具。
把共通处理抽象化,产生了例程(函数)。把数据构造也包括在一起进行抽象化,产生了抽象数据类型;把抽象数据类型的共通部分再抽象,产生了继承(派生)。软件设计总是在不断导入新工具,以实现更高程度的抽象化。
- Singleton:单个实例保证
- Proxy:为某个对象提供代理对象。通常用于惰性操作(需要的时候,才真正出现)。假设有个生成代价非常大的对象。如果不知道是否真正需要该对象的时候就事先生成,可能会带来很大浪费。但如果不生成,什么也做不了,此时代理对象就有用武之地了。Proxy通常使用delegate来实现。
- Iterator:顺序访问集合对象中的各个元素。
- Prototype:重复使用既存对象。核心思想:复制并衍化(进化)。在面向对象中,类并非必不可少的。类最初只是用于模子(雏形),然后从类生成实际对象。对于原型来说,复制既存对象,给复制的对象增加方法或实例变量等功能。在静态的面向对象语言中,类的继承其实本质也是Clone+扩展。生成新的实例时,也是进行着类似的操作:复制雏形。在很多原型为基础的语言中,实例是可以自定方法的。
- Template Method:编写抽象算法。父类定义算法框架,步骤的具体实现留给子类实现。它不涉及各种数据结构细节,而是只在抽象的水平上编写算法。此模式通常与继承成对讨论。
- Observer:避免高度依赖性。当某个对象的状态发生改变时,依存于该状态的全部对象都自动得到通知,而且为了让它们都得到更新,定义了对象间一对多的依存关系。这是控制类与类之间依存关系的一种模式。通常用于MVC框架。观察者模式有两个对象:观察者(接受变更通知)和被观察者(发出变更通知)。通常在使用中,由被观察者添加观察者,被观察者在改变时,通知观察者。观察者模式是事件系统的核心。
- Composite: 组合,用树结构来构成对象。
- Facade:隐藏
子系统
的详细内容,提供统一的接口。 - Decorator:给对象动态增加新的功能。
- Flyweight:以共享的方式提高大量小对象的实现效率。
- Visitor:对集合的元素进行操作。
- Strategy:封装算法,使之具有可变换性
- State: 把对象的内部状态独立出来,封装状态变化。
- Mediator:封装对象之间的相互作用。
- Adapter:变幻对象的接口
- Bridge:分离类之间的实现
- Builder:分离复杂对象的生成过程
- Decorator:给对象动态增加新的功能
- Factory Method:基类只定义生成对象的接口,具体的生成过程由派生类实现
- Memento:记录对象的内部状态
- 职责链:用多个对象来处理请求。
MVC框架
一般采用Observer模式和轮询两种方法。
- 模型:内容(信息)的对象。模型只是信息,不能包含如何来显示这些信息的信息。
- 视图:将模型中包含的信息在窗口中进行表示的对象。视图指导要表示的模型的信息,而模型一般不知道要表示自己的视图的信息。
- 控制:从用户端接受输入,对视图和模型进行操作的对象。
通常对应的文件夹为:model,view,cntrl,helper.
但对于多数GUI工具箱来说,包括组件、事件和回调。在典型的GUI应用中,控制和视图紧密协作,共同实现功能。在此密切相关的情况下,对类进行分割的做法并不明智。所以多数GUI都是视图和控制相结合,称为组件(Component/Widget)。在GUI中,模型部分没有提供相当的东西,作为替代,提供了访问模型的入口:回调。回调可以将完成一定操作的程序块作为对象调出来。当用户输入特定事件时,GUI组件通过回调调出功能程序块。回调一半时较小的操作功能,发挥“启动”模型对象的功能。回调的功能就像时联结“视图+控制”组成的组件和模型之间的“胶水”。即:
框架部分 | GUI中对应的概念 |
---|---|
View+Controller | GUI Component(组件) |
Model | Callback(回调) |
虽然GUI工具箱(Toolkits)是以此构建,但把整体应用构成MVC模式,是很容易维护的。
杂牌军/猴子补丁:
开放类,不改变原先的代码,替换方法(打补丁)称为猴子补丁。有以下功能:
- 功能追加:追加标准库或已有代码中不存在的功能。
- 功能变更:替换方法让已有的类发挥完全不同的功能。
- 修正程序错误:重新定义了有程序错误或有副作用的方法,不用修改原来的代码即可解决问题。这也是猴子补丁本来的目的。
- 钩子:可在方法调用的
同时
增加一些其他处理。这些处理是原本调用此方法时没有的功能。 - 缓存:在计算量很大,而且一次计算后,结果可反复使用的情况下,在一次计算完成后,对方法进行替换可提高处理速度。
性能与并发:
不要做优化。先不要做优化。过早的优化是万恶之源。
对于性能问题,通常执行时间都耗费在程序中不足4%的部分。
通常在优化之前,使用Profiler对性能剖析,有依可循,不可盲目猜测。
常用优化手段:
- 削减对象。复杂对象的生成会占用一定的时间。
- 减少方法调用。多态方法的调用,会占用一定的时间。
- 利用立即值。对于对象不使用引用,不实际分配内存的值,称为立即值。它不生成对象,因此不担心对象生成成本。
- 采用合适的数据结构。
- 以空间换时间。
并行是以硬件为辅助,解决性能问题。在计算机,多个程序运行时,操作系统以进程为单位同时运行。进程之间相互通信,通过管道、套接字或共享内存等机制。进程之间的内存空间是相互独立的,用于保护进程不被其他进程干扰。
线程可在一个进程中同时运行多个处理,线程可以看作轻量级进程。与进程不同,同一进程的线程可以共享内存空间。
线程的执行状态:
- run:执行。线程将处理细分为一段一段,交替执行。
- stop:停止的理由有IO等待、时间等待和join等待。IO等待等待外部输入的状态;时间等待指到达指定时间为止,停止的状态(sleep等的结果)。join等待是指等待别的线程终止。
- to_kill:终止处理中。线程被强制终止,完全终止之前,正在处理后处理程序等状态。
- killed:已终止。
并行处理的问题:
- 数据完整性的丧失。数据共享,可能无意间同时访问了同一变量或对象。例如银行账户,可能A和B同时访问同一账户。在并行处理中,访问一个变量时,其他处理不可中途插入,这样不可分割的处理称为原子操作。在原子操作中,需要将数据保护起来,不可由外部操作。
- 死锁。多个线程互相争夺资源,互不相让,造成线程停止。这通常发生在某一瞬间。
对于并行,同时访问资源就会成为程序的陷阱。规避资源的同时访问,有几种方法,其中最简单的就是独占使用中的资源。为此,引入了锁(Mutex互斥锁)的手段。由锁锁起来的代码块,是别的线程不能侵入的领域(Critical Section)。对上锁的资源,在使用之前,需要检测是否有锁。
但很多问题并非简单锁可以解决,如数据库中的问题:
- 可以同时引用
- 禁止同时更新
- 禁止更新中引用
- 禁止引用中更新
这就需要使用二级锁。
对于并行同步,锁只是一种机制。问题的根源在于多线程访问同一资源。所以解决这个问题就可以解决并行同步问题。
无共享
给每个资源准备一个管理用的线程,各线程间只能交换某些限定的信息。县城建信息交换的方法有很多种,有代表性的有:消息存储、信道(Channel)、队列(Queue)。
在无共享队列中,一个线程制作数据,另一线程通过队列获得数据,然后处理。即生产者和消费者模型。
队列也可用于解决资源的竞争。准备一个用于管理资源的线程,对资源的请求(Request)通过队列发送给该线程,这样,直接操作资源的只有管理线程。
### 锁模型和队列模型比较:
锁模型:如果竞争足够少,多数情况下能保持高性能。但,对于资源竞争,不能忘记加锁,难以保证完美无缺。
队列模型:竞争少时,性能不及锁模型,但更容易 贯彻。
在Erlang中,使用与队列模型本质上等价的消息存储。
Actor:
Actor是仅通过消息进行通信的实体。对于普通方法调用,需要等待结果。但对Actor发送消息,不必等待返回结果。Actor通过信息交换而非直接共享空间,性能不及线程高。Actor没有消息以外的信息传递手段,所以不用担心资源竞争。多个消息同时到达时的竞争,已经由内联到系统中的排除机制处理了。
Actor更安全、易懂。Actor根据消息进行处理,必要的话会向其他Actor传递消息,或者向源Actor返回消息。
Erlang采用Actor模型。
Actor模型 底层可线程或进程等。
并发与并行
并发是同时应对多件事情的能力;并行是同一时间动手做多件事情的能力。
并发的目的,不仅仅是为了让程序并行运行从而发挥多核的优势。若正确使用并发,程序还将获得以下优点:及时响应、高效、容错、简单。
并发是系统及时响应的关键。比如文件下载可以在后台进行时,用户不必一直盯着鼠标沙漏而烦心。软件在非同步运行的多台计算机上分布式地运行,其本质是并发。
并发代码的关键是:独立性和故障检测。独立性是一个故障不会影响到故障任务以外的其他任务。故障检测指当一个任务失败时,需要通知负责故障处理的其他任务来处理。
Unicode:
Unicode(16位)的两大问题:
- 字节顺序。Intel普遍采用小端,网络上普遍采用大端。因此在Unicode中,采用BOM(ByteOrerMark,0xfeff.如果头两个字节读取是0xff,0xfe则是小端)标志判别文本字节序。
- NUL文字问题。传统C语言以’\0’(NUL)判断结束,但Unicode中有很多NUL字符。传统的C处理函数:strcmp,strcpy则无法使用。
随着文字被发现的越来越多,16位已经到达上限。现在Unicode采用21位表示一个文字。
现在表示Unicode文字主要推崇:
- UTF-8: 可变长,与ASCII兼容。不含NUL文字。兼容C语言文字处理。
文字在显示时,需要使用对应的字体。如果字体不支持,则会发生乱码或豆腐块。
负数与小数的表示:
负数:-1:会经过以下变化:2的补数方式:将正数的每一位反转,然后加1所得。
- 数字1 : 00000001
- 位反转:11111110
- 加1: 11111111
浮点数:是真实数的近似。浮点数和整数永远不会绝对相等。如1.0与1只是近似相等,二进制也不相同。1/3后得到的值,连加3次却不是1。在计算机中1/10,结果值连加10次,却也并非1。
浮点数比较特殊的值:
- Inf 无限。当浮点数运算溢出时,使用此值。
- 0,正0和负0是有区别的。
- NaN:非数。如0/0,表示未定义的结果的错误。
软件漏洞
- DOS攻击:拒绝服务(DenialOfService),妨碍软件正常运行的攻击手段。能够引发软件异常终止的程序错误。
- 信息泄漏。
- 权限夺取。
- 权限升级。
主要的程序错误的攻击:
- 缓冲区溢出
- 整数溢出
- 跨站点脚本攻击(XSS)
- SQL注入
- 跨站点伪造请求(CSRF)
对于程序中的错误,有些严重错误需要中断执行,有些错误只需要记录日志,恢复执行即可。对于软件使用者而言,没有必要因为非严重错误而中断正在运行的工作。
函数式编程
函数式编程语言的特点:
- 函数本身也作为数据来处理
- 以函数为参数的高阶函数
- 函数相同即可保证结果相同的引用透明性
- 为实现引用透明性,禁止产生副作用的处理。
学习语言的步骤
- 作为兴趣来学习。了解此语言及其背后的思想。
- 作为个人工具使用,日常生活中使用此语言作为工具。
- 工作中作为辅助开发工具。
自动代码生成
应用范围主要有:
- 数据库访问。从数据结构定义自动生成数据库访问例程(包括SQL)。手工编写大量的文件当然不是聪明人应该做的事。代码生成可以帮我们解决这个问题。
- 用户接口。
- 单元测试。
- 客户界面
- 文档化
代码生成的作用:
- 改进质量
- 确保一致性
- 集中知识
- 增加用于设计的时间
- 独立于程序实现的设计判断。
对语言的正确理解
争论语言孰好孰坏没有任何意义。每种语言都有其诞生的环境和应用范围,在语言特性的选择中,各有取舍。世界上没有完美的事物,总是有一些这样或那样的问题或缺陷。选用适合自己的、最满足自己需求的语言,从一而终即可。当此语言没有好用的特性时,自己动手添加或给创造者建议,对语言进行进化。
语言选定之后,就学着去适应它,使用它,掌握它。
想知道软件是如何执行的,就去阅读源代码。软件有错误的时候,就自己来修改。如果觉得自己行动受到限制的话,就阅读源代码,利用编程技巧排除这一限制。这些都是程序员的本能。
推荐的语言
- System Language 系统语言 :C,Rust
- 手动管理内存;高性能;能调用C库(调用系统API);能编译成C库; 能编写汇编(能编写驱动);
- Server/Service Language 服务器(游戏、Web)语言:Go,Elixir
- 并发;库多;简洁;快速;错误处理;较高性能
- Scripting language 脚本(游戏、工具、自动化)语言:Python
- 简单;快速;库多;解释型
- Object-oriented Language 面向对象语言: Ruby
- 面向对象编程特征;
-
Functional Language 函数式语言: Lisp-Scheme-Clojure
- 最佳语言拍档:
- 返璞归真,皈依太极:C+LISP
- 游戏开发: C++ + 脚本语言(Lua/Python/Ruby/Lisp)
- Web与网络容器: Go
- 大数据与机器学习: C++(TensorFlow) + Python(PyTorch)
- 人工智能: C++ + Lisp