软件设计随想录

时间:2022-06-04 15:30:00

有一段时间(大概是2011至2013年期间)有个习惯,喜欢在微博、微信上会随手记录些软件设计和编程语言的思考和随想。前几天翻看以前的记录,给意外刨了出来。当时的一些想法,时过境迁,可能也有了变化,这里就不做修改了,就把内容简单整理一下,放在这里做个原生态的记录。

软件设计

  1. 大型软件开发中最大的挑战是对复杂度的管理。 程序=数据+算法,数据的复杂度又是软件复杂度的主要来源。如何对数据复杂度进行管理,一直是语言进化演进的原始动力:过程式语言如C,借助于数据结构;对象式语言如C++,借助于类和多态;函数式语言如Erlang,借助于函数以及高阶函数。
  2. 数据的核心复杂度主要来自2个方面:1)数据的建模;2)数据的生命周期管理。不同语言提供了不同的建模范式,基本上各有千秋。但是高级语言的一个很大优势就是把人从第二个复杂度的深渊中解放出来。
  3. 软件设计想可扩展性强,需要解决2个问题:what & how。what要求的是业务知识,即软件需要哪些扩展点,这需要对问题领域的变化有较强的洞察能力;how要求的是技术知识,即如何对变化进行隔离。OO隔离最好的办法是抽象,在C里面这就是函数指针,在C++里面就是虚函数,在Java里面就是接口。这就是OO的核心设计法则:依赖倒置法则。
  4. 对软件设计来说,最佳的设计模式还是语言。如写操作系统驱动用C;写界面用C++,Python;写并发分布式用Erlang,这才是最理想的设计。如果业务没有合适的语言,这是创造个DSL的好时候。
  5. 好的模块化设计者具有强烈的自我约束意识,划清内外界限,把数据保护在内部,严格审查每一个外部接口。因为每多一个外部接口对使用者就多一份负担,对自身的演化就多一个障碍。
  6. Linux设备模型用device抽象设备,device_driver抽象驱动,用bus抽象device和driver的关系,用kobject抽象所有设备的公共属性和行为(如层次结构描述,生命周期管理,热插拔,用户态呈现等),用kset抽象设备组的公共行为(如热插拔事件),用ktype抽象设备组的公共属性(如sysfs属性)
  7. 面对对象的设计模式具有一定的局限性,特别是用在非OO语言上要特别小心。以前我也尝试在C、Python中应用OO的23种设计模式,但由于缺乏OO语义的支持,结果往往是结构膨胀,表达失真,噪点大。现在我觉得,做好C语言的软件设计,就2点:对内,用数据结构做好建模;对外,用接口做好隐藏。
  8. 不同语言对世界提供了不同的观察角度和描述方式。过程式、对象式、函数式、并发式语言的世界观,就是把世界抽象理解成一个个过程、对象、函数、进程。
  9. 软件要具有可扩展性,预留出多态扩展点是基本途径。在这需求下,面对对象语言提供多种语法支持:抽象类,接口,模板,mixin等等。从语言级别上可扩展性得到了较好的支持,但是这些功能往往是填一个坑出两个坑,总之永远盖不住语言的窟窿。
  10. 计算机是在抽象实现问题的基础上进行计算。不同的计算抽象模型(如图灵机,Lambda演算等)产生不同的硬件结构(如冯洛伊曼机,Lisp机),进而语言产生。以冯洛伊曼机来说,是一种过程计算模型,不同的过程描述角度和方式,孕育出个性迥异的语言,如汇编的过程描述单元是机器指令,C是命令,C++是对象。进而这又影响到语言的类型系统、数据结构和算法。
  11. 软件语言的设计过程:首先澄清问题领域及其边界,并从中提炼出关键需求点,围绕需求点建立模型和描述模型的核心语义,以此设计出编译器/解释器,建立语言系统解释模型。
  12. 模块解决多实例问题有2种方法: 以模块作为管理器;以模块作为类型。类可以看作一种具有继承机制的模块类型。


函数式

  1. lambda表达式做为函数式语言的最大特征,却无法简洁直观地实现递归。Y combinator的作用就是提供一个通用的方式来实现lambda的递归。
  2. curry的价值是函数具有部分求值的能力。
  3. 闭包(closure)是函数式编程语言中对控制流抽象的重要手段。 函数式语言由于原生支持currying,构造closure十分方便。C语言虽然没有这些特性,但是如果理解了闭包的原理,手动构造个闭包也是件简单的事情。
  4. 编程语言的 lazy evaluation 特性,是一个很妙的模块化设计手段,能有效地解除数据消费和数据生产的过程耦合,使封装这2个动作成为可能。这也是python的yield用意。