我几乎不在这个blog上转载别处的文章,今天偶然看到蔡学镛的这篇《KPI心理学》,忍不住转过来,毕竟我也是看过《爪哇夜未眠》的人。
—————————-以下是转载内容—————————-
原文地址: http://www.jerry-tsai.com/2010/06/kpi.html
阿里巴巴集团大部分的员工,每季或每半年都要接受一次的KPI考核,看看他绩效如何。关于用KPI来打考核,许多员工其实都有一些负面的看法,而管理层也知道采用KPI有时候会有负面效果,但是没有更好的方法之前,我们还是仰赖KPI。
我已经到阿里巴巴的支付宝上班一年多了,对于KPI,我有四阶段的心理变化,值得描述一下。
刚进公司时,我对KPI的重视程度是70%。大多数的时间,我做的事都是KPI设定的任务,有些事情,虽然不是KPI关注的任务,但只要对公司有利,我依然会去做。这是第一阶段。
后来,我对KPI的重视程度降低到30%。大多数的时间,我做的事都是对公司有益处的事,至于是不是KPI的重点我就比较不在乎了。这是第二阶段。这是对公司最好的阶段。
接著,我发现做正确的事会导致自己的KPI不好,无法升迁,于是我开始变成100% KPI导向。只要不是KPI的内容,我就不愿意做。这是第三阶段。公司把一个员工逼到这个阶段,是很可悲的,对公司也是一个伤害。
第三个阶段不会持续太久,会立刻变成第四个阶段:对KPI重视程度为0%。这表示对于自己在这家公司的前途已经不在乎,准备开始找工作了。我现在正在第四阶段,至于会不会有第五阶段,我就不知道了。
70% -> 30% -> 100% -> 0%,你在哪一个阶段呢?或者,你有不一样的折线图呢?
—————————————————-
两点感受:
1. 蔡学镛竟然在支付宝工作过,看来我真是孤陋寡闻…
2. 发现自己有很长时间处于第二阶段,最近半年正急速逼近第三阶段…
这两天抽空把《重来》从头看了一遍,这本书的分类是“企业管理”,副标题是“更为简单有效的商业思维”,对这类带有所谓“管理”、“商业”等字眼的书,我本来是兴趣不大的,不过先前看过一篇这本书的书评,遂打算一读。
这本短短两百来页的书(去掉插图,实际不到一百页),包含了近百条企业运作的行动准则,读完的感受可以用“酣畅淋漓”来形容。书中所道出的观点都是我们想说却说不出来也无法说出的秘而不宣的事实。
这些观点不是纸上谈兵、不是空洞的说教,而是来源于“实战打拼的经验”,富于智慧的真知灼见。著述这本书的37signals公司“在长达10年的时间里,经历过两次经济衰退、一次经济泡沫破裂、数次商业模式转变…始终保持盈利”,“公司很小,开支甚少,盈利颇丰”。
而他们的秘诀似乎就是“刻意把自己做成一家小公司”,然而特立独行都是需要内在的强大来支撑的,看看37signals的理由吧:“何必壮大?”、“你的需求没有想象中的那么多”、“轻装上阵”。
“现实世界”中,小公司都希望变大,小团队也都希望扩张,然而扩大一定是好事吗?看看:“条件受限是好事”、“甘于低微”、“鸡尾酒会上的陌生人”。
如果公司或团队正处在良性发展中,适当扩张并不坏。如果盲目扩张,局面很快会失控,于是彼得定律开始发挥作用,进而就有了:
无数繁冗的规章制度流程——“员工不止13岁”、“不要听风就是雨”、“发出你的心声”;
无休无止的会议——“打岔是效率的敌人”、“会议有毒”;
中看不中用的长期计划——“计划即瞎猜”、“预估的都是垃圾”、“速战速决”;
不断增加的预算开支——“在问题上少投入点精力”、“给竞争力做减法”。
此外,书中还有很多和“现实世界”反其道而行之的惊世骇俗的观点,如:“哪来的从错误中学习”,“工作狂”,“立马就上线”,“不必逐字记下客户需求”,“人人都得干活”,“越快越好是毒药”等等,令人警醒,发人深思,值得一读。
这是个老生常谈的话题,多数程序员对此应该都并不陌生,不过从我不太长的从业经历来看,具有良好编码风格的程序员实在是凤毛麟角。个人感受:越优秀的程序员,越具有良好的编码风格。或者换个角度说,有良好编码风格的程序员,其它方面也差不到哪儿去。正所谓:见微知著,细节决定成败,一屋不扫何以扫天下。
关于编码风格,有两本很不错的书:一本是林锐博士的《高质量C/C++编程指南》,一本是bob大叔的代码整洁之道》)。前者适合C/C++程序员,后者适合Java程序员。在我初学编程时,看了《高质量C/C++编程指南》的电子版,有意识地实践,基本上奠定了此后的编码风格。《代码整洁之道》是去年有次部门购书时报的书单之一,由于里面的很多原则早已耳熟能详,我花了两三个晚上快速扫了一遍,然后推荐给组内其它成员看。之所以谈起这个话题,并不是想表明自己的编码风格有多好,只是最近有新人入职,我首先与其强调编码风格,因此有必要将一些心得、想法加以整理,贴到博客上。
代码风格是很多编码习惯的统称,例如:命名、注释、空格/空行、缩进、对齐、括号的使用、函数/方法的设计、概念的组织、抽象层级,等等。好的风格在于一致性,拿命名来说,骆驼命名法、下划线命名法、匈牙利命名法等各有其适用范围,如果你采用某种命名法,就当在你的程序中以一贯之地使用,假如混用就是不良风格了。
在一个多人项目团队里,统一的、整洁的代码风格有很多好处:
易于阅读、理解和调试,从而减少维护成本、提高生产率;
每个人可以快速切入和接手其它人的工作,在人员更替时减少交接成本;
提高团队成员之间的舒适感和信任感,大家写出的代码如出一辙,相互更容易悦纳对方,并且增强信任感;
高质量的代码,加上前述的好处,将提升产品的稳定性和团队的执行力。
计算机教育要从娃娃抓起,编码风格要从新人抓起
不少人认为编码风格无关紧要(特别是项目经验、工程经验较少的人),不注意培养编码风格,编码完全意识流,写哪算哪,只要运行结果正确就完事,一旦定型,很难改变,随着你的工作年限和资历的增长,不会有人监督你,不经意间,造成很高的维护成本,影响自身成长,于人于已带来诸多不便。良好的编码风格,需要一开始有意识地训练一段时间,可能是几周几月甚至更长时间,但最终会从中受益,良好的编码风格有助于提高效率,从而将时间精力解放出来,去做更大范围的或更有意义的事情。
信任
所有人都知道:信任,是团队合作的基础。信任是什么?我想用两句经典台词来说明:“你办事,我放心”,“跟着你,有肉吃”。——这就是信任。信任的原则“用人不疑、疑人不用”,在一个相互信任的团队中,每一个人都尽力做好自己的工作,也充分相信他人能够搞定各自的部分;一旦遇到意料之外的困难,每个人都将之视为自己职责的一部分,并且愿意提供协助。信任的基础是什么?信任感当然离不开长期合作过程中所形成的彼此之间的相互了解,但更重要的是团队个体成员的自信。相信自己能搞定每一件事情,相信所有的困难都能迎刃而解,相信每一个问题都能找到完美的解决方案——这不是盲目自信,而是一种胸有沟壑、举重若轻的淡定和从容。一个人足够自信,才有可能去信任他人。是很难想像一个人对某件事件没有把握,却相信别人能够做好它。团队为了某个目标而在一起合作,可能是因为兴趣、利益或荣誉,但离开了信任,余者无从谈起。
协作
团队协作的重要性已经被现代企业强调到几乎过了头的程度,这一方面是由于大规模的工业化生产要求很多人共同参与,另一方面是由于人们试图逃脱个体孤独和渺小的愿望——在自给自足的小农经济和手工业经济时代,不需要太多的团队协作;由于个体能力有限,人们希望在团队中得到慰藉和助力,并分享团队成功时所带来的满足感和成就感。我并非要抨击团队协作,而是想讨论团队协作的方式。有两种协作方式:一种是所有成员都具有很强的自我驱动力,每个人都能够充分发挥主观能动性,并与他人协作把工作做好;另一种是团队围绕少数超人,每个人都是好士兵,令行禁止,通过超人的驱动来完成目标。哪种方式好呢?可能很多人从理性上会选择前者,事实上大多数团队属于后者,恕我直言,多数团队主导者也更喜欢后者(扪心自问,假若是我也难逃窠臼)。团队中有超人是幸事亦是不幸,原因不再赘述。我相信很多人都认为超人驱动型的团队不好,因为“超人”与“糙人”同音(此处没有恶意)。至于团队到底采用哪种协作方式,这得看团队组建者(决策者)是需要能做事的员工还是听话的员工。
公平
古人云:不患贫而患不安,不串寡而患不均。每个人心里都有一个天平来衡量周遭的一切,且对自身尤其关切。我想说的是;公平不是平均主义,公平不是人人平等,锄强扶弱不是公平,劫富济贫也不是公平。我认为的公平是:让所得与付出相称,让职权与能力相称;公平在于机会均等,而不是平均分配。举个例子,每个人都公平的享有受教育的机会(如你所知,教育机会实际上也不公平),但并非将来公平地从事相同的工作,获得相同的收入。团队中的公平体现在,每个人都可以公平地获取信息,公平地得到锻炼和成长的机会,而不是职责等同,甚至以削有余补不足的方式达到平等。要做到机会均等确实很难,但职责等同只会更糟。有很多为领导者喜欢故意在下属间维护某种平衡,让他们互相牵制,从而保持自己的地位。
设计
很少有什么工作不可以通过团队合作的方式来完成,设计是其中的一种。我是写程序的,这里说的设计,不光是指程序的设计,往大了说,包括人类一切的设计活动,例如建筑的设计,桥梁的设计,城市交通的设计,宇宙飞船的设计,室内设计,绘画、雕塑、诗歌等艺术作品的设计;往小了说,包括团队组织结构的设计,工作流程的设计,搜索引擎系统的设计,程序模块的设计,用户体验的设计。所有伟大的设计通常都只是由一个人或紧密合作的两个人设计出来的,当然,最终成品可能是由很多人按照设计意图或设计方案来共同实现的。众所周知,苹果系产品出于乔布斯,莱特兄弟设计出了现代飞机的原型,而W3C委员会设计的XHTML规范却走到了尽头(被HTML5取代)。不得不说,出色的设计师确实很少,而设计出色的作品,往往需要天时地利人和。设计并非是很高深莫测的东西,实际上每个人每天都自觉不自觉的进行各种设计,写程序就不说了,你写一封邮件,得设计都包含哪些内容以及如何组织这些内容吧;你做一个演讲,得设计每个部分的详略及起承转合吧。设计分为很多不同的层面,每一个层面的设计都是分别进行的,事实上也不得不分别进行——一个设者,不可能既设计出企业组织结构,沟通协作流程,还设计出要生产的产品,并且设计每个系统及每个模块。每个层面的设计者单独负责相应的设计,而高层的设计往往会决定低层的设计。设计不是一劳永逸的,但初始的设计往往会对后续的再设计产生决定性的影响。设计由谁来进行?当然是各个层面上的决策者或设计者。决策者如果由于专业技能或精力所限不能进行设计,那么应当找到一个设计者来履行设计职能——这本身乃是他设计层面的一部分。
人性
很多组织标榜自己人性化,比如:免费零食,20%的工作时间可用于和工作无关的事情,诸如此类给员工提供的一些小恩小惠。似乎人性化仅止于此。当我们说“人性”的时候,应该意识到,人性之中,有善的一面也有不善的一面,有积极的一面也有消极的一面,有阳光的一面也有阴暗的一面。但在我们的成长环境和生活环境中,一味地宣扬好的一面,似乎不好的一面压根不存在一样。久而久之,我们也便习以为常。但这不能真正解决问题,人性中不好的一面时刻会占上风,干扰我们的判断力,影响我们的决策,妨碍我们的行动。人是自利的——这不同于自私,人也是需要激励的——物质上或精神上,人对周围人的崛起会本能的感到紧张、抵触甚至敌视。只有正视人性中不好的一面,通过外部措施或自身警醒及时察觉和疏导,才可以防患于未然。
由于google笔记本服务寿终正寝,我不得不寻找替代的产品。
对比了麦库、有道笔记和为知三家在线笔记工具,由于我对网页版的使用度最高,因此主要对这三家在线笔记服务的网页版做一个比较。
打开各家在线笔记网站主页,满屏都是各个版本的客户下载,但是网页版入口却放在小小的角落里面。不知道设计者是怎么想的,似乎新老用户,打开你的站点主页,都是为了来下载客户端似的。
照我看,打开网站主页,应该是直接进入网页版,对新用户来说,让他直接来体验网页版的功能;对老用户,很可能是来整理笔记,当然是直奔主题最好。
可以把客户端下载链接放在网页版页脚或侧栏或其它不太显眼但用户能找到的某个位置,而不是相反。但是三家的主页竟然是如此不约而同地相似,不是互相抄袭是什么。
再来看注册,有道笔记的注册,是注册网易通行证。也许N年前我注册过,但是基本不使用网易的服务,早忘了。
记得我刚开始上网的日子(那时应该叫web1.0时代吧),通行证满天飞,那时要在某个网站上注册一个通行证,可是个技术活。你得填写一大堆的表单,什么账号、口令、姓名、性别、住址、籍贯、OICQ号、邮箱、密保问题1、答案1、密保问题2、答案2。。。密保问题N、答案N,验证码等等,往往等你把表单填完,点提交按钮,会话已经超时了,你先前填写的那些信息全部消失,不崩溃才怪。所以你得拿捏好节奏,在会话有效时限内,填完表单并提交。
所以一看到这种冗长的注册表单我就直反胃,所以有道笔记直接cancel了。
相比较而言,麦库和为知,都可以通过填写一个邮箱和密码来完成注册。
网页版界面上,为知的UI和功能,比麦库逊色不少,为知的页面三列结构,视觉效果也不太好。
两家都没有导入/导出功能。
笔记分组,为知是通过文件夹来区分的,“新建文件夹”功能较为隐蔽。麦库是通过笔记分类来区分,且预设了好几个分类。预设分类可能比较适合懒人,但未必适合所有用户。
对麦库来说,查看和编辑笔记的视图是分开的,在笔记列表视图,点不同按钮以查看内容页或打开编辑区。
这两家笔记,在新建笔记时,都需要保存当前正在编辑的笔记,并且要点一个“新建笔记”按钮,操作体验不是很好。
不像google笔记本,列表、查看内容和编辑笔记都在同一个视图中,你可以随时随心地添加新笔记,随时随心地去编辑每个笔记,随时随心地删除笔记,用户体验很流畅。
麦库和为知,每个笔记都有标题和内容两项,这个也不是很好。因为笔记可以是随时随地记下来的一段话,没必要那么正式得分标题和内容。麦库的笔记犹如博客。
对于标签功能,为知,要给笔记添加新标签,可不是很好找。
而在麦库的“添加标签”弹出菜单中,完全可以把“新建标签”按钮去掉。让用户随意添加标签,程序在后台判断是否是新标签,若是新标签,由后台程序自己新建。
另外,有个问题,我在linux/firefox下使用时,麦库和为知的编辑区右侧都超出屏幕很多,如果不输回车,一行文字太长就看不见了。
最后是客户端,PC客户端对我来说就是网页版,主要说便携式设备客户端,为了叙述方便,就手机客户端吧。这种在线笔记工具,手机客户端的作用,我觉得主要就是随时随地的记录和编辑,并且保存到云端。至于搜索、查看、整理、归档等功能还是其次。因为记笔记,可能大多是即兴的,离散的,事后有大块时间再来反刍,那个时候可能主要是在PC上进行的。
我下载了麦库手机客户端,对于记笔记功能来说,不足之处,还是不要分标题、内容为好。就弄一个编辑区,写完了给我保存就OK。另外,手机客户端不支持富文本格式,要是想编辑先前在网页中记录的笔记,每次都会弹出一个选项——询问是追加还是先转换格式,这点也很烦人——要么就支持富文件,让用户有一致的体验,要不就只在第一次让用户选择,以后别再询问。手机界面小巧,UI设计上,怎么操作简单怎么来。
在Thrift的Java库中,读写字符串的编码固定为UTF-8,要想以其它编码(如GBK)来传输字符串不是很灵活(在C/C++中不存在这个问题,因为C/C++中的字符串无非是字节序列)。例如,java版的TCompactProtocol中的readString和writeString代码如下:
public String readString() throws TException {
int length = readVarint32();
if (length == 0) {
return "";
}
try {
if (trans_.getBytesRemainingInBuffer() >= length) {
String str = new String(trans_.getBuffer(), trans_.getBufferPosition(), length, "UTF-8");
trans_.consumeBuffer(length);
return str;
} else {
return new String(readBinary(length), "UTF-8");
}
} catch (UnsupportedEncodingException e) {
throw new TException("UTF-8 not supported!");
}
}
public void writeString(String str) throws TException {
try {
byte[] bytes = str.getBytes("UTF-8");
writeBinary(bytes, 0, bytes.length);
} catch (UnsupportedEncodingException e) {
throw new TException("UTF-8 not supported!");
}
}
以非UTF-8编码传输字符串,看似一个很普遍的需求,但在其自带库中竟然没有支持,在它的JIRA上,我找到了一个有人很早提交的Issue:Support non-UTF-8 in Java and C#,状态是: won’t fix。
提出这个问题的人建议如下:
Thrift的Java和C#类库都假定所有字符串都是UTF-8编码,而下面的方案或许会更好一些:
* 给TProtocol中的readString和writeString方法传一个encoding参数,默认值为UTF-8。
* 生成的struct reader和writer在读写字符串时将encoding参数传递给TProtocol对象。
* 使用一个默认的类型注解覆盖默认的UTF-8编码。
* 除Python3之外,其它所有语言都忽略这个注解。
这个提议非常符合我们的需求,可惜,由于Thrfit的开发者和参与评论的人大多都不认同将编码方案放在Thrift之中,而应当由应用程序自行处理,因此提议被否决了。
老外明显不懂中国国情嘛,众所周知,一个汉字以GBK编码是两个字节,以UTF-8编码是三个字节。这就意味着,在网络上传输中文字符串,GBK编码可以比UTF-8节省1/3的带宽(但是我们通过网络传输的数据不全是中文字符串,也有英文数字,以及数值类型,所以实际节省的没有这么多)。我们可以自己来实现一个TProtocol来支持GBK编码。
由于我们只是要修改一下读写字符串的编码,除了readString和writeString之外,当前所用的TProtocol实现类的其它接口都不变,所以应尽可能复用已有实现。遗憾的是我们使用的TCompactProtocol是final的,因此不能用继承,只好用委托(delegate)。方法是:定义一个GBKCompactProtocol类,保持和TCompactProtocol一致的接口,继承自TProtocol,内部包一个TCompactProtocol实例,除了readString和writeString方法需要重写外,其它方法调用都是直接委托给TCompactProtocol实例。代码如下:
public class GBKCompactProtocol extends TProtocol {
public static class Factory implements TProtocolFactory {
public Factory() {}
public TProtocol getProtocol(TTransport trans) {
return new GBKCompactProtocol(trans);
}
}
private TCompactProtocol prot;
public GBKCompactProtocol(TTransport trans) {
super(null);
this.prot = new TCompactProtocol(trans);
}
public String readString() throws TException {
int length = readVarint32();
if (length == 0) {
return "";
}
try {
if(prot.getTransport().getBytesRemainingInBuffer() >= length){
String str = new String(prot.getTransport().getBuffer(), prot.getTransport().getBufferPosition(), length, "GBK");
prot.getTransport().consumeBuffer(length);
return str;
} else {
if (length == 0) {
return "";
}
byte[] buf = new byte[length];
prot.getTransport().readAll(buf, 0, length);
return new String(buf, "GBK");
}
} catch (UnsupportedEncodingException e) {
throw new TException("GBK not supported!");
}
}
public void writeString(String str) throws TException {
try {
byte[] bytes = str.getBytes("GBK");
writeVarint32(bytes.length);
prot.getTransport().write(bytes, 0, bytes.length);
} catch (UnsupportedEncodingException e) {
throw new TException("GBK not supported!");
}
}
private int readVarint32() throws TException {
int result = 0;
int shift = 0;
if (prot.getTransport().getBytesRemainingInBuffer() >= 5) {
byte[] buf = prot.getTransport().getBuffer();
int pos = prot.getTransport().getBufferPosition();
int off = 0;
while (true) {
byte b = buf[pos+off];
result |= (int) (b & 0x7f) << shift;
if ((b & 0x80) != 0x80) break;
shift += 7;
off++;
}
prot.getTransport().consumeBuffer(off+1);
} else {
while (true) {
byte b = readByte();
result |= (int) (b & 0x7f) << shift;
if ((b & 0x80) != 0x80) break;
shift += 7;
}
}
return result;
}
byte[] i32buf = new byte[5];
private void writeVarint32(int n) throws TException {
int idx = 0;
while (true) {
if ((n & ~0x7F) == 0) {
i32buf[idx++] = (byte)n;
break;
} else {
i32buf[idx++] = (byte)((n & 0x7F) | 0x80);
n >>>= 7;
}
}
prot.getTransport().write(i32buf, 0, idx);
}
//其它方法都是直接委托给prot的对应方法,省略
}
由于TCompactProtocol中不少方法都是private的,所以这里把readVarint32和writeVarint32的代码拷了过来,代码显得比较冗长。
我平时会将自己看书、上网阅读到的有价值的信息,或者突然涌现出的某些有意义的想法,记录到google笔记本上。虽然google早就不再维护这个应用了,但我依然觉得这是所有在线笔记工具中最好用的一个。
今天打开google笔记本,发现有条醒目的提示信息:
笔记本即将关闭。自 2011年11月11日起,我们会自动将您的笔记本导出到 Google 文档。如果您有任何疑问,请访问我们的常见问题解答页面。

google选择在光棍节关闭笔记本服务,不知有何用意。不过,关闭笔记本服务,应该是早晚的事情,或许是没有什么商业价值吧。这种在线笔记服务,是比较小众的,不像微博、SNS那般火爆。在这个年代,还有写日记、记笔记习惯的人恐怕不多了吧(当然工作性质使然的就不说了)。
鉴于Google Docs长期被封,我是不准备使用了,只能再寻找其它类似的笔记工具。我不喜欢客户端,而对这种SaaS类型的在线服务情有独钟,在云时代,单机版的工具也显得有些落伍了,我们需要随时随地的Accessibility。已经记录的笔记全部导出备份了,找到好用且免费的替代服务就迁移过去,要是实在不行的话,我就利用blog空间自己整一个自己用。
近期部门在做新系统的开发,子系统和子系统之间免不了需要约定各种各样的接口;我们负责的子系统内部,在进行系统设计和模块划分时,也需要定义各个模块之间的接口。由于各个子系统固有的异构性,以及参与人员的个人背景和对问题理解等方面的不同,对接口的约定难免有分歧,因此,经常会翻来覆去的讨论和修改接口。
这里有一篇关于API设计的文档,API是应用编程接口,虽然我们平时所约定的接口并不全是公开的API,但是关于API设计的原则和方法,对于普通的接口设计,同样具有很好的借鉴意义。我摘录其中的主要思想,结合在别处所见的关于接口设计的原则,并加以整理,记录于此(原文中出现的“API”,这里大多换成了“接口”,替换之后某些地方的意思可能不大通顺)。
—————————分割线—————————
好接口的几个特征
1.易于学习和记忆
2.产生易读的代码
3.难以被误用
4.易于扩展
5.完整性
注意:“最小化”和“一致性”没有出现在这个列表之中,它们已被其它特征所涵盖了。因为,臃肿的、不一致的接口,很难被学习和记忆,并且难以扩展。
“最小化”是指尽可能让模块对外只公开最少的部分。这里的“模块”可以是一个类、一个包、一个组件或一个子系统等。类应尽可能拥有最少的public方法或成员;包尽可能拥有最少的公开类;组件尽可能拥有最少的配置参数;子系统对外提供尽可能少的公开接口,等等。
“一致性”等同于“概念完整性”,要让一个复杂系统保持架构师愿景中的条理清晰、前后一贯的设计,必须遵循“概念完整性”原则。《人月神话》及《设计原本》的作者Frederick Brooks曾写道:“概念完整性是最重要的系统设计原则。一个省略了某些不协调的功能特性、但保持一组合理设计意图的系统,要远胜于一个包含很多初衷良好、但相互独立且不协调的意图的系统”。
1.易于学习和记忆
易于学习的接口应当具有一致的命名规范和模式,为相同的概念使用相同的名字,为不同的概念使用不同的名字。
最小化的接口容易记忆,因为需要记住的东西也少。一致性的接口容易记忆,因为你可以在一个地方应用先前在其它地方已经学会的规则。
接口并不仅仅是类和方法的名字,而且还包含它们的语义。接口的语义应尽可能的简单、清晰,并且遵循最小惊奇原则(the principle of least surprise),让常见的任务简单易行;不常见的工作可行,但不会让用户过分关注;解决特殊问题时,不要让解决方案不必要的过度通用。
2.产生易读的代码
程序代码一经写就,会被不同的开发者读(并且除错和修改)多次。易读的代码可能需要花点时间来写,但是可以节省产品周期中的其他时间。易读的代码容易文档化和维护,而且不大可能包含bug,因为易读性让bug无处遁形。
易读的代码可能简洁也可能啰嗦,不管哪种情况,它总是处在恰当的抽象层级上——既不隐藏重要的事情,也不强制程序员指定不相关的信息。
3.难以被误用
经过良好设计的接口,更容易编写出正确的代码,而且会促进好的编程实践。它不会强制使用者按照严格的顺序调用方法、注意隐含的副作用或奇异的语义。
4.易于扩展
接口会随时间变化,有可能向已有模块添加新类、向已有类添加新的方法、向已有方法中添加新的参数、增加新的枚举值等,设计接口时应当将此谨记于心。接口越复杂,在新概念和已有概念之间越有可能发生冲突。此外,在接口初始设计阶段就应该考虑二进制兼容性。
5.完整性
理想情况下,接口应当是完整的,允许使用者完成任何他们想做的事情。完整的接口并不是要提供所有的功能,但至少可以允许用户扩展或自定义现有接口。
完整性会随时间而变化,随着时间的推移,可能会有新功能逐步添加到已有接口之中。
接口设计的流程
1.仔细研究需求
咨询他人,看看别人想要什么样的功能。
2.设计之前先写用例
一个很常见的错误——编写组件库时,先实现功能,再设计接口,然后发布。这种方式设计的接口,会反映出底层代码的结构,而不是调用者愿意看到的样子。
3.看看有没有现存的相似接口
这样有可能从现有相似接口的设计中学习有用的东西,避免重蹈覆辙,复用已有的概念,并保持一致性。
4.先设计,后实现
首先定义接口和它的语义,不要担心实现的复杂度。一个接口直观但实现复杂且布满trick的库,要优于一个实现简洁但接口复杂的库。
5.找人帮你评测接口
寻求反馈,特别是负面的。这些意见更能帮助你改进设计。
6.使用你的接口编写示例
这有助于你发现潜在的问题,而且可以帮助别人了解如何使用你的接口。
7.做好扩展的准备
这要求你了解现实的场景,并思考可能的情况。
8.内部API没评测之前不要发布
有些API一开始是内部使用的,后来大家觉得很有用,才公开发布。一个常见的错误就是发布之前没有进行完整的测试。
9.宁缺毋滥
如果对接口的功能不是很确定,先不要发布。
接口设计原则
1.命名
名字要能解释自己,要遵从英语语法。千万别一会英文单词,一会汉语拼音。
参数的命名也要清楚明白。尽量少用bool类型的参数,这样的代码不好读。
命名要统一。不要混用类似widget和control这类词语,这会让用户乱猜。参数的顺序也要一致。
避免二义性。为相近的事物选取无歧义的名字。一个名字要严格对应一个概念。假如你有两种事件传递机制,一个是同步的,一个是异步的,分别叫做sendEventNow()和 sendEventLater()就不错。
注意完整性。接口设计如同写作,要注意对称和前后照应。格式尽量一样,过程尽量一样,这样使用者能更容易了解你的意图。比如所有的set函数都用set开头,这样用户更容易习惯。
避免用缩写。但也有一些常见的例外,如min,max,dir,rect,prev。但是要注意一致性,不能有的用有的不用。
尽量用专名,不要用通用的名字。
不要太过迁就下层接口的命名。如果你要在一些接口外面再包装一层接口,不要被内部接口的命名方式所支配。按照你自己的命名规则统一命名方式。你设计的目的是让用户使用方便高效,而不是迁就下层的库。
2.语义
选择合适的缺省值。
不要聪明过度。接口应该简单清楚,尽量少让用户产生惊讶的感觉。如果过于自作聪明把API弄得不易用,就远离了API的本来目的。尽量贴近你用户的习惯而不是试图教他们怎么做,否则你就等着写文档去吧。
注意边界值。对于类库来说,边界值的处理相当重要,认为边界值发生概率很小就不加注意是很幼稚的。边界值造成的问题会在使用这个类的其他类中得到扩散和放大。
小心定义虚接口。虚接口一般更难定义,并且很容易在新版本发布时出错。设计接口时,必须全盘考虑,逐个过滤来决定哪些接口应该是虚的,哪些不应该是,在文档里应该详细说明你的类如何使用这些虚方法。
3.结构
采用基于属性的接口。很多接口要求使用者指定一大堆的参数。太多参数对于使用者来说是个噩梦。一种更好的方式就是基于属性的设计。用户创建一个参数对象,把需要指定的参数作为属性设置进去,其它取默认值。这样做有许多优点:
* 看起来比较简单
* 不用记住参数的顺序
* 可读性强,不需要特别说明注释
* 属性可以有缺省值,不是必须指定所有的属性
* 随时可以更改属性
* 可以随时取得属性,便于除错
* 方便进行可视化图形化设计
做开发,不管哪种开发——无论是组件库的开发还是整个系统的开发,无论是前端开发还是后端开发,无论是底层开发还是上层应用的开发——所做出来的功能特性都是要给别人使用的,使用者可能是最终用户,也可能是其它开发者,怎么给使用者提供一个好用的接口,是每一个开发者都应当努力思考的事情,而不是仅仅把功能实现就完事。面向最终用户的接口设计——UI/UE,随着互联网的兴起,已经形成了一个专门的领域。面向开发者的接口设计,需要每一个开发者为之努力,因为这是关乎我们自身的事情。
Java5.0引入的enum类型可以和switch语句连用,由于平时编码中使用switch语句的几率很小,switch和enum连用的几率就更小了,今天第一次写了switch中使用枚举的代码,就遇到了问题。
代码如下:
public enum Syntax { URL, SITE, FILETYPE }
Syntax s = ...;
switch (s) {
case Syntax.URL:
//url specific code
break;
case Syntax.SITE:
//site specific code
break;
case Syntax.FILETYPE:
//filetype specific code
break;
default:
break;
}
switch语句这段看似平淡无奇的代码报出了编译错误,eclipse中的错误提示如下:
The qualified case label Syntax.URL must be replaced with the unqualified enum constant URL.
照着错误提示修改之后就可以编译运行了:
switch (s) {
case URL:
//url specific code
break;
......
}
不过这种用法多少有些怪异,试看下面的代码:
enum Color { RED, GREEN, BLUE }
enum RGB { RED, GREEN, BLUE }
Color c = ...;
RGB r = ...;
switch (c) {
case RED:
//red code
break;
case GREEN:
//green code
break;
case BLUE:
//blue code
break;
default:
break;
}
switch (r) {
case RED:
//red code
break;
case GREEN:
//green code
break;
case BLUE:
//blue code
break;
default:
break;
}
两个switch语句的case标签完全一样,乍看起来似乎有问题,然而这两个switch语句竟然可以并行不悖地运行!
最新评论