编程的美学标准诌议

编程的美学标准诌议

做了几年的程序员,虽然自己写的代码还远远没有达到要求,但在日复一日的实践过程中,我逐渐开始信奉一条标准:在实现功能的前提下,简单即是美。

其实,编程的过程就好比是一个建模的过程。设计就是将一个现实问题抽象成逻辑模型。而编码则是将逻辑模型进一步表达成程序模型(如果我可以这么叫的话)。建模是数学和物理中的很重要的方法,而数学和物理是强调“简单”的。不知通过这样的论证来给编程引入简单美是否令人信服。

我喜欢从“成本”的角度看程序的简单与否。有三个方面:

实现的便捷(实现成本)

              学习的便捷(学习成本)

              运用的便捷(运用成本)

 

注重实现的便捷就是追求自己写代码时的省力。要实现起来简单,关键应该是平衡好程序的灵活度和复杂度,不能为杀鸡造牛刀。我刚开始写代码的时候犯过这样的毛病,把需求考虑得过于复杂,甚至把以后的复用,扩展都考虑进去了,同时由于经验积累的不足,不知道利用已有的成果――搞得造房子从挖土烧砖开始,结果十天半个月都没完成,而要求实现的功能其实很简单。

 

关心学习和运用的便捷,则是为了让用户省力。这里所说的用户(user)是广义的,不单单指人。一个大型的软件也好,小的程序也好,甚至是由几个类构成的模块,都会提供供用户操控的抓手,就是接口了,想要让用户学着简单,用得方便,关键是要定义好它。

以模块的接口为例,首先注意的是接口函数的规模。有多少个可被用户调用的函数;是使用一次调用一次,还是一旦初始化了就一直起作用等等。接口的规模是和具体问题相关的,不是每个模块都能设计成只有一个接口函数的,但是往简单了去规划,却是不错的。

接口的设计还要注意“语境”。首先是具体业务逻辑的语境,电子商务有电子商务的一套概念体系,电信有电信的,银行金融有银行金融的,这个自不待说,对软件、程序和模块都适用。但对任何一个模块而言,最终都是要用具体的编程语言实现的,所以我们的模块也要注意编程语音的“语境”。比如如果是用C/C++,我们就要考虑指针。如果是用Java,同样也有讲究。比如包、类、函数、变量的命名按习惯来,甚至模仿jkd里的来。如果要把用户定义的对象放在散列(hashcode)容器里,就要求用户定义对象类重载实现Object#equals()hashCode()。如果要排序,就要求实现Comparable或提供Comparator。要复制对象就定义拷贝构造函数或重载Object#clone()方法。总之,跟着大家约定俗成的概念、思路来表述问题,不自创一套,就是为了学习和运用的方便。

接口的灵活性和简洁性是一对矛盾,要学用方便,就要平衡这对矛盾,坚持一个“够用”的原则,不能给用户太多的选择。否则,当你告诉他可以这样用,也可以那样用,反而容易让人糊涂。小时候,我爷爷给我讲三国的故事,说周瑜其实比诸葛亮聪明,周瑜是一步三计,诸葛亮则是三步一计。但结果周瑜还是让诸葛亮气死了,就是因为前者计策太多,不知道用哪个好了。这虽然是虚构的故事,但其中的寓意却让我受益至今。

 

三个“便捷”也有相互矛盾的时候。尤其是实现成本会和学用成本矛盾,个人认为前者应该服务于后者。如果能让用户在调用时少写一行代码,实现因此多一百行代码也是值得的。虽然我在设计用户界面方面经验很有限,但我觉得界面上窗口的布局,菜单和按钮的排布,以及色彩与色调以及它们的搭配,都是很有讲究很有学问的,并不能因为我不懂而轻视它。

实现和学用的矛盾在模块开发中更为突出。因为对一个模块而言,调用者是另一个模块,学习运用者是该模块的开发人员,地位上和你以及你所开发的模块是完全平等的。这时的接口定义就好像是两个帮派在划分势力范围,于是两个模块开发组可能就会在多做些还是少做些的考量中互相踢皮球。这种类似的经历让我至今想起来都仍然感到不愉快。当然在这种局面出现之前,系统工程师或架构师已然缺位或失职,是根本原因。

至于学习和运用,大部分情况下是一致的,但偶尔也会出现矛盾。比方说对同一个功能有两种实现方法(决定了对外接口的差别),第一种实现是符合人们常识的,所以易上手。而第二种则另辟蹊径,在性能或其他方面有很大提高,但要向人解释清楚则颇费一番周章。这种情况下,我还是支持采用第二种方法,如果有好的使用效果,学习上的成本提升一些也是值得的。

 

总之,我觉得一个好的程序模块(我是一个程序员,不敢说的太大,还是以模块来说事儿吧),应该像一台同时支持专业摄影功能的傻瓜照相机。如果你想要它工作,只需摁一两个按钮,一张还过得去的照片就出来了。否则,捣鼓半天成不出个像来,用户多半会因为挫败感而放弃使用。另一方面,“简单”放低了使用的门槛,只有在使用的过程中,才能加深体验,才会不满足于简单功能,从而有动力去追求更复杂更灵活的配置,到了那个时候,原来一直支持的那些专业摄影功能就派上用场了。同时寓学于用,更有感性的认识。

我想用GNU C中提供的socket编程APIJava中提供的socket编程java.net包作比较来说明。首先要承认这和实现语言的不同有关系,并且GNU C的支持更灵活。但从易用这个角度,我无疑会支持Java的方案。在GNU C中,socket地址结构体因为不同地址类型的所有变数,以及其中的参数(AFPF,地址、端口字节顺序),还有在建立套接字时协议参数、套接字数据类型等的取值,统统都暴露给我们用户了,大大提高了学用成本。而这些在Java提供的socket编程接口中就得到了很好的封装,同时不失可扩展性。让我们一般的使用者免于关注这些细节,而专注于你到底是要一个UDP还是TCP的连接。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值