2014年05月21日

电源插座和软件设计

  2002年的冬天,我在联通出差。刚参加工作,有点小紧张,给电脑接电源,插三孔插座的时候错位60度,噗哧一下冒起了火花。没有酿成事故,但这件事情一直没能忘记。

  我们知道国标的三孔操作在设计规范上已经考虑了这种错误,三个插孔的开口方向是不一样的。问题出在插座,当时用的是万能接线板,除了国标,欧标和美标也能插进去,因此国标插头即便错开60度,用点力气也能插进去。此后到其他地区出差,特别留意他们的插座,发现最安全的是欧标,三相插孔的设计两阴一阳,根本没有错位的可能。

  欧标插座还有其他优点,比如最多调整90度就可以对准;插头作为整体嵌入插座,不用担心金属部分漏电;圆柱状金属头加球面前端设计,省力且牢固。这些暂且按下不表,今天谈的是软件设计中的”万能插座”。

  最近用 Python 开发过几个小工具,颇为趁手。其中一个原因是python这类动态语言支持duck typing,就是把类型检查延后到运行时进行。你可以把任何一个类型的对象赋值给变量,直到访问这个对象的时候运行时系统才会判断其类型是否正确。换句话说,实现了类级别的运行时多态(对比一下,C++/JAVA通过接口和虚函数支持函数级的运行时多态,而模板支持类和函数级别的编译时多态)。

  首先,不要求定义接口。可以边思考边写代码,不用在接口部分和实现部分之间来回切换。

  其次,不要求统一的对象界面。实现多态的时候不需要对象都是从统一接口上扩展出来的,对象可以拥有只在某些特定分支下会被调用的接口,其他对象,如果不会命中这些分支,就可以不用定义这些接口。对习惯了c++/java风格的我来说,这种设计简直就是脑残。但是考虑到快速开发,不得不承认真的很有效。想象一下,在静态语言的OOD中,如果原来设计的统一接口满足不了需求,通常意味着概念抽象出了问题。乐观情况下,要么赋予原来某个接口方法更加宽泛的职责;要么把无法被统一接口涵盖的部分抽取出来,增加一个多态方法。

  凡事必有两面,首先是正确性不易验证。类型错误只有程序运行时才能检测出来,好比通电以后才能检测出抽头插错了,以前用静态语言,同样的错误最晚编译时就能检查出来。

  其次是理解代码费力。阅读静态语言,看看数据结构、函数原型(C语言),或者类型/接口定义(C++/JAVA),对程序结构能了解个不离十。动态语言,非得把对象间的调用代码读完,才能把握程序的设计结构。

  JAVA可能是相反风格的代表,典型的“欧标插座”语言。不支持动态类型,更不支持内存这种“万能插座”。所以有人说,做一件事情,JAVA只给你一种方法。

  语言设计和程序设计要考虑两个认知因素,一是方便写,一是方便读。动态类型方便写,静态类型方便读。动态语言编写结构复杂,确定性要求高的程序还是面临挑战。语言没有内置“欧标插座”,必然要求语言的使用者自律,尤其是团队协作开发时必须遵循一致的设计策略。

  程序设计语言一直在往里面添加安全特性。比如ANSI C对函数原型的支持(最古老的C语言只关心函数名字,不检查返回值和参数类型,C的链接器现在还保留这样的设计),const和static修饰符;C++链接器的隐式函数类型检查,引用类型,类的可见性修饰;java对指针的弃用,package 等等。恰当使用这些特性,可以规避电火花。

  但会犯糊涂的地方,都是“欧标插座”的用武之地。比如只能单向打开的消防门,电脑机箱里的各种接线缆接头,优秀软件的人机界面等等。程序设计语言和API,本质上是抽象的人机界面。程序语言是人和计算机之间的界面,API是人和另外一堆代码的界面。记得读大学的时候,学校里有个工业心理学实验室,主任是现在阿里的王坚,他们的主要方向就是各种人机界面设计问题。