什么代码才是好代码?这真是个老得能拔掉牙齿的话题。好吧,那让我们再在这刮沙尘暴的无聊时光里重复一次。好的代码要是易读的代码、要做到职责分离、要做到单一职责、要有高的执行效率....
等等,等等,这才抽象了,太书面化了。我只是一个菜鸟,刚写代码几年,也没念过什么书,能不能说得通俗易懂一些?
好吧,我停下来,想,这真是个难缠的家伙。我说,这样吧,我推荐几本书你去看吧,《重构》熊节最近再版了,建议你去买一本。恩,等等,有个省钱的招,去图灵俱乐部讨论组注册下,蹭赠书也很爽,哈哈。
可是,你还没告诉我什么代码才是好代码呢?知道你也没什么好答案,我自己来说好了。
省略掉此时我内心花花的汗水,下面是菜鸟的叙述:
1.一致
我发现自己有轻微的强迫症,当我碰到以下代码时,我就会冲动。
冲动前代码:
def imgName
if(XXX){
imgName="meigui"
}else{
imgName=""
}
冲动后代码:
def imgName=XXX?"meigui":""
尽管两段代码功能一致,但一旦我发现出现冲动前代码时,我就会感到不舒服,感到难受,就好像看到阅兵正步走不齐一样。方法名也是一样:
冲动前:
def testXXX(){}
冲动后:
def should_XXX_when_XXX(){}
变量亦是如此:
冲动前:
def imgNode=resouce.adoptTo(Node)
冲动后:
def node=resouce.adoptTo(Node)
总之,我不愿意看到同一个事情有两种实现方式,如果功能类似,那么不管是逻辑还是变量、方法名,我会强迫一致,整齐划一。
关于一致,从调试代码的角度看,零星的不一致比大量的不一致更加糟糕,因为这时大部分地方的一致性会令人麻痹大意。在实现查询分页功能时,我们有这样一行代码:
nodeIterator.size
这行代码的意思是获取查询结果的总数,大部分情况下它工作良好,但是在一种特殊情况下它返回了-1。这对我当时几乎是灾难性的,因为调试过程中我们始终相信这行代码的行为一致,结果是花费了一个下午才找到这个问题。
2.简洁
我喜欢短的代码,对我而言,短的程序总是比更长一些的代码容易理解,小学时学课文就已经这样了,一看到大段的段落我总是会晕过去(特别是文言文,首先我就
对自己理解这段文字失去了信心)。这里要提到注释,即是这些注释明确是为了提高代码的可读性,也会增加我阅读代码的困难,所以我不会在方法里的任何位置添
加注释,撑死在个别方法声明前添加,并且这种情况也尽量避免,如果这个类确实包含了重要的不易理解的算法,我也只会在类声明前添加注释。
关于自然语言,有一个基于经验的结论被称为Zipf定律,即:自然语言中最常用到的单词,其长度会趋于最短。
我写代码的时候,能够简写尽量简写,例如,变量名,imageNode,我一定会写成imgNode;方法名procedureXXX,我一定会写成
procXXX,和讨厌大段代码一样,我非常讨厌命名很长的方法名和变量名,尽管这些名称这么长是为了更好的增加可读性,但可读性不是这样增加的。
在我的第一份代码工作里,我们使用拼音来命名方法和变量(还好,没有包括类名),我讨厌这种命名方式的原因并不是因为我的语文老师不好以至于我前后鼻音不分,而是这种写法根本排除了简写的可能性,甚至,为了避免歧义,有时不得不变得更长。
3.联觉和顺序
关于记忆,人类有两种重要的记忆能力:联觉和顺序记忆。
关于联觉,一个例子是:你总可以一眼记住一个人的脸,比如范冰冰,尽管我到现在也不清楚她到底是单眼皮还是双眼皮,也不清楚她到底是厚嘴唇还是薄嘴唇。
那么,在代码里,这里的表现就是局部,即一个功能的所有相关代码都集中在一个地方。我最讨厌的代码是这样的:最开始我打开一个文件,在阅读的过程中,我发
现一个不清楚的方法,于是我按下ctrl并点击鼠标,于是我跳到另外一个文件;接下来,在阅读另外一个方法里,我再次发现了一个不清楚的方法,于是我再次
按下ctrl并点击鼠标,哇哈,新的文件打开了....如此反复,终于当我打开最后一个文件时,我发现IDE的文件条里已经密密麻麻的排满了好几排文件,
于是,我移动鼠标,右键,弹出一个关闭菜单,我选择了close others,瞬间,哦米拖佛,整个世界清静了,但是,等等,我最初是打算干嘛来着?
所以,请把所有相关联的代码都集中在一个地方,求您了。哦,对了,能不用接口请不要用接口,总会碰到这样的情况,打开好几排的文件,接口文件占了一半,我靠,少几个接口会死啊。对了,这可能是您的一致性心理在作怪,对不起,对不起。
关于局部,一个范例同样与调试有关,在很久之前的一次调试中,我们始终找不到一个变量错误的原因,因为在这段代码里,根本找不到任何错误,很久以后,终于
发现,这个变量竟然是个全局变量,嘿嘿,告诉你吧,这个变量在servlet里,04年的时候,网上很火的一篇文章,标题就是:不要在servlet里使
用全局变量!
关于顺序,最典型的例子出现在高中化学里,我总也不能瞬间说出第12、13个化学元素是什么,我通常会这样记忆:氢氦锂铍硼碳氮氧氟氖钠镁铝硅磷,啊哈,第12个元素是镁,第13个元素时铝,合起来就是--美女!
所以,在代码里,请将互相调用的方法按顺序摆放,方法1先调用了方法2,那么请将方法2紧放在方法1后边。我讨厌这样的配置:打开方法1,发现其调用了方
法2,点击方法2,编辑器里的滚动条瞬间从最上端滚到最下端,紧接着,滚动条又从最下端滚动到中间,再接着,又是最下端,接着,归零到最上端....人生
经不起这样的大起大落,真的,那得要多么大的心脏啊,麦蒂才有过那么一次,13秒....
还有,知道为什么goto为什么那么臭名昭著了吧。
4.自然
使得代码具有轻松的表达方式,同时把错误率降到最低,一种最重要的方法就是代码变得“自然”,即向自然语言靠拢。因为代码并不仅仅是与机器交流的,更重要的是,需要在人之间交流。
机器语言到高级语言,面向过程语言到面向对象语言,jdbc到hibernate,java到动态语言,这些都促使代码变得更加自然。
Ruby里有个不起眼的特性,就是方法调用不用再写括号,这一特性是如此的微不足道但是却被很多人津津乐道,原因就是它更加自然,更加贴近我们的自然语言。于是,我看到,我的同事晓娜,在groovy里,一遍遍的将她力所能及的括号去掉。
此外,程序语言和自然语言是有区别的,除了不能在代码里利用感情词抒发情感之外(我想,如果可以,一定会看到很多的冯特),程序语言没有口语。很少看到程
序员之间这样交流,来吧,我们来说段代码(当然也有,徐昊就可以,哈哈),他们更多的会使用白板和笔或者直接是编辑器。所以,结束招聘时是否需要笔试的争
论吧,我真为那些不经过笔试就直接招人的公司感到羞愧,因为他们根本就不懂程序语言。
此处省略华丽的分割线。
此文谢谢我们项目组WGSN的激烈讨论,谢谢讨论中徐昊的精彩点评。
posted @
2010-03-21 22:27 ronghao 阅读(2126) |
评论 (2) |
编辑 收藏
唐僧与
QA MM
在一个典型的项目团队里,包括了以下几种角色(帽子):
PM(项目经理)、
BA(业务分析师)、
DEV(程序开发者)和
QA(质量保证人员),整个团队的目标是向客户交付价值。
那么,有一天,
QA MM来找我,我是开发人员。
MM说,一张图片没有正常显示,我想知道原因,同时想知道你能否修复。我的第一想法是,这不可能,一定是环境的原因。我说,好的,稍等。接下来,我张大嘴巴看到了
MM给我重现的
BUG:本该显示图片的位置一片空白,就像此时我合不上的嘴。这怎么可能呢?我想,这个功能完成的如此之得意,以至于测试用例里的数据都是以我的名字命名的。
几分钟后,或者更长,我叫来
MM,说,找到原因了。
我打开编辑器,光标在源程序的某一行闪烁,我说,最根本的原因在这里。我看到
MM的眼中闪过一丝迷茫。接下来,我却换到另外一个源文件,光标继续闪烁,我说,这里的程序因此受到影响。看得出,
MM有点发晕。终于,当我打开第
N个源文件并试图继续讲解时,
MM昏过去了。
当
MM苏醒过来时,我在她清澈的双眼中看到了一只清晰的唐僧。
MM肯定感到了不好意思,因为我的讲解中包含了比喻、类推、排比等我力所能及的各种语文知识,看得出,我很努力,我的语文老师也很努力,所以她委婉地说,能不能简单一点?
我想了想,说,测试驱动时测试数据不全导致程序少考虑一种情况。
MM说,能修复吗?
我说,可以。于是故事结束。
就
是这样,当我们执行一项任务时,围绕这项任务必然会产生许许多多的信息,这些信息对于该任务的执行者是必须的,但是对于其他人则不是,有效的沟通往往来自
于简练的表达即只表达对方需要和可以理解的内容,浩瀚的细节只会将真正想表达的内容淹没。其实这里还有这样一层意思:我之所以用这么多的细节信息来淹没
QA,实际上是不太情愿承认程序里有
BUG。
QA想要的结果很简单,是否是程序
BUG,能否修复。而开发人员则往往把自己的程序与自己关联在了一起,认为程序是自己的扩展,程序有
BUG则意味着自己有缺陷。这一关系明显是矛盾的,可是一些团队里开发人员和
QA能够和平相处,而有些团队却势如水火。
那么,对于单个任务而言,需要定义自己的变量,这些变量数据只与该任务相关,只在该任务里可见。典型的工作流应用于任务执行期间的中间数据存储。在文档处理中,一个重要的功能就是需要提供版本管理,在单个任务实例里,办理者能够管理自己处理过的文档版本。
描述
任务能够定义变量,在一个流程实例里,该变量只能被其任务实例所使用。
图
6-2任务级别的数据可见性
如图
6-2所示,我们在任务
B上定义了一个变量
M,此时,在一个流程实例里,只有任务
B的实例才能使用该变量。
实现
存在两种实现方式,一种是如图
6-1所示的,在任务节点定义中声明变量,运行期初始化任务实例的同时初始化该变量并使用;
另一种是在流程定义级别统一声明变量,但是各个任务实例都独立初始化并存储该变量。第二种实现方式在各个任务都需要使用同一语义的变量时很常见,例如各个任务实例都会有参与者,我们在流程定义时声明一个名为
userid的变量,在流程实际执行时,各个任务实例都会独自保存有自己的
userid数据。
posted @
2010-03-16 22:05 ronghao 阅读(1624) |
评论 (0) |
编辑 收藏
和前面的章节一样,我们先从一个故事开始,这个故事和晚饭有关。在我家,周一至周五,老婆做饭,我洗碗。每天做完
饭,老婆会叫我到厨房,说,看,这个盘要洗一下,另外,灶台脏了,也要擦。如果放在以前,我会说,好,明白了。但是现在,程序员的生活让我意识到,沟通永
远不是一件简单的事情,我说,好,知道了。
等等,这个故事和本章的主题-数
据模式有一毛钱的关系?这只是一个关于沟通的故事。是的,让我们稍微映射一下:这里,晚饭这个流程包含了两个基本的任务,分别是做饭和洗碗,在做饭这个任
务完成时,任务的执行者(老婆)向下一个任务的执行者(我)传递了数据(要洗哪些东西),正如语言是人之间的沟通方式一样,数据是IT系统之间的沟通方式,语言之间的沟通总是最有效的,数据交互却未必,因为IT系统里的数据交互除了让计算机理解外重要的是还需要人理解,IT系统是对现实生活的映射,也正因为如此,现在数据之间的沟通也在向语言靠拢即语义化(REST/语义网)。
好,言归正传。
在
前两章里,我们分别讨论了工作流的控制模式和资源模式,控制模式关注于如何合理调配业务流程里的任务,从而获得理想的执行效率和收益;资源模式则关注于如
何合理调配可用的资源来执行业务流程。本章将介绍工作流系统里的数据模式,从数据的角度分析工作流系统对数据的处理。数据模式共计39种,在下面的介绍中,我们将这些模式分为了四部分,分别是数据可见性、数据交互、数据传输和基于数据的路由。
本章先会概要重复一下与数据模式相关的一些基本概念,例如流程定义、流程实例、原子任务、块任务等。接下来会对具体的39种数据模式进行讨论,讨论的模式按照应用、描述和实现展开,分别对应着实际场景对模式的映射、模式的介绍和工作流系统对该模式的实现支持。最后是小结。
一、基本概念
1、工作流系统里的流程结构
在正式介绍数据模式之前,让我们先简单回顾一下工作流系统里流程的基本结构。
图 6-1 工作流系统里的流程结构
流程定义:对业务流程的建模和描述,其具有足够的细节信息,能够直接被工作流系统所执行。典型的,流程定义由一系列的任务组成,这些任务以图形的形式展现并被连接起来。
流程实例:流程定义的一个执行实例被称为流程实例。一个流程定义可以存在多个同时执行的流程实例。这些流程实例互相独立执行。
任务:一个任务对应着流程定义里的一个单一工作单元。存在四种不同类型的任务:原子任务、块任务、多实例任务和多实例块任务。
原子任务包含简单且独立的任务定义,当其初始化时只会产生一个可执行的任务实例。
块任务是一系列任务的组合,典型的,工作流系统里存在的子流程任务(节点)即是块任务,当一个块任务开始执行时,其将流程控制权传递给与之对应子流程的第一个任务,当子流程完成执行后则将控制权返回给块任务。如图6-1所示,块任务C对应着一个由任务X、任务Y和任务Z组成的子流程,实际执行时,任务C会触发任务X的执行,任务Z执行完毕即子流程执行完成后则会触发任务C执行完成。
多实例任务在实际执行时会产生多个并行执行的任务实例,这些任务实例一般互相独立执行。当一定数量的实例执行完毕后即会触发后续任务的执行。
多实例块任务结合了块任务和多实例任务的定义,其在实际执行时产生多个任务实例,每个任务实例对应着一个子流程实例。
任务实例:任务的一个执行实例。
2、数据相关约定
我们使用def var ${变量名}定义数据元素,同时def var ${变量名}的声明位置决定了变量的作用范围。如图6-1所示,我们在任务C上定义了一个名为M的数据变量,其的作用范围为任务C,任务级别。
我们使用use(${变量名})表明对变量的使用;使用pass(${变量名})表明数据变量的传递。在图6-1里,变量M从任务C传递至任务E。
对于变量的数据类型,典型的有String、integer、float、boolean、date、time等,很多工作流系统使用序列化和反序列化支持存储任意类型的数据类型,如数组、集合、对象。
3、类比的约定
在后续对各个模式的介绍里,我会围绕一个项目团队的故事进行映射,我们如此约定:
流程定义:我们认为所有成熟的软件公司都会建立起其完整并适用的一套软件开发流程,我们将这套流程看作是这里的流程定义。
流程实例:围绕着软件开发流程,我们会使用这套流程来开始我们实际的软件开发项目,我们将所有的软件开发项目都看作是开发流程的执行实例,即流程实例。
任务:项目开发过程中的各项任务。
数据:我们将团队成员之间的信息交流看作是数据交互。
posted @
2010-03-14 21:14 ronghao 阅读(2264) |
评论 (0) |
编辑 收藏
我们从一个最简单的登录例子开始。
最开始我们需要验证在用户名和密码都正确的情况下,能够正常登录系统,我们这样编写测试代码(以下都是伪代码,使用TestNG和Selenium):
@Test
def should_login_success_with_exist_username_and_correct_password(){
LoginPage page = user.open(LoginPage,"/login.html")
user.perform("login",['user1','1234'],on(page))
assert page.successLogin
}
恩,很不错,运行一下,出现红条。为什么呢?原来测试数据库里没有用户名为user1的用户,好吧,写个数据库数据初始化脚本。再运行,OK,绿条!
那么,接下来我们再增加一个测试,需要覆盖密码错误时不能登录系统的情况,很快测试就完成了:
@Test
def should_login_success_with_exist_username_and_incorrect_password(){
LoginPage page = user.open(LoginPage,"/login.html")
user.perform("login",['user1','4321'],on(page))
assert page.successLogin,false
}
再运行一下测试,绿条。好啦,现在可以看下这段代码,恩,有些重复,重构一下:
@Test
def should_login_success_with_exist_username_and_correct_password(){
assert login('user1','1234')
}
@Test
def should_login_success_with_exist_username_and_incorrect_password(){
assert login('user1','4321'),false
}
def login(username,password){
LoginPage page = user.open(LoginPage,"/login.html")
user.perform("login",[username,password],on(page))
return page.successLogin
}
重构完成,可以看到,我们的测试方法里现在没有了任何行为,仅仅是数据!这样让我感觉有点怪,不管了,先用TestNG提供的@dataProvider整理一下:
@Test(dataProvider="testdata")
def testLogin(username,password,expected){
LoginPage page = user.open(LoginPage,"/login.html")
user.perform("login",[username,password],on(page))
assert page.successLogin,expected
}
@DataProvider(name="testdata")
def Object[][] dataForLogin(){
def data=new Object[2][]
data[0]=['user1','1234',true] as Object[]
data[1]=['user1','4321',false] as Object[]
}
测试方法只剩下了一个!如果要测试不存在的用户不能登录系统呢?很简单,增加数据即可!
@DataProvider(name="testdata")
def Object[][] dataForLogin(){
def data=new Object[2][]
data[0]=['user1','1234',true] as Object[]
data[1]=['user1','4321',false] as Object[]
data[1]=['inexistuser','1234',false] as Object[]
}
在我们的测试方法里,测试数据和测试的行为进行了完全的分离。从系统的功能来说,功能一旦实现,那么就是一个黑盒,我们只要提供数据即可进行测试,这个数据包括两部分:输入和期待的输出。我开始暗自嘀咕:难道我以前那么多的洋洋得意测试方法很多都是不需要的吗?这些方式为什么会存在呢?恩,想起来了,这些方法是为了覆盖功能的各个路径的,是提高测试覆盖率的。那么为什么会产生这么多的测试方法呢?哦,在这些测试方法里,测试数据和测试行为是耦合在一起的!
我伸了个懒腰,突然想,这下好了,我已经封装好了功能行为,QA想增加测试用例只需要自己增加数据就可以了,嘿嘿,爽啊。说曹操,QA到。QA mm说,你的某个功能实现有问题。我瞅了一眼,说,不可能啊(这是dev面对bug的第一反应),俺有测试的,持续集成一直是绿条的。QA mm说,在你的开发环境下测试是没有问题的,但是在QA环境,因为数据库变了,数据变了,应用服务器变了,所以会有些问题。我极不情愿的登录到QA环境,一测试,还真是,郁闷。
怎么办?修复完BUG,第一反应就是自动化测试能不能跑在QA环境呢?一般情况下,这些测试需要干净的测试环境,我们会制造很多的测试数据,可是在QA环境下,QA有她自己的测试数据,这些数据都不存在了哈。恩,看看刚才的测试代码,哈,就用QA的数据也可以啊,心情愉悦的改下:
@DataProvider(name="testdata")
def Object[][] dataForLogin(){
def data=new Object[2][]
data[0]=['hrong','1234',true] as Object[]
data[1]=['hrong','4321',false] as Object[]
data[1]=['rhao','1234',false] as Object[]
}
OK,完成!为了该测试既能在开发测试环境运行又能在QA环境下运行,我们可以引入一个环境变量,将测试数据扔到文件里,通过环境变量来加载不同的测试数据(测试文件)。
好吧,喝点东西(甲流很厉害,喝板蓝根好了),总结一下:
数据驱动测试:
测试数据与测试行为分离,通过数据来驱动测试。
好处:
在对测试行为封装好的情况下,QA mm能够自己通过数据修改自动化测试;
自动化测试能够运行在多个环境下(开发环境、QA环境、产品环境)
测试的可读性
测试方法大量压缩
适用范围:
功能测试(selenium测试)
通过环境准备测试数据(非测试用例自己准备数据)
可能存在的问题:
比一般的测试编写困难,特别是在静态语言里
最后:该文章的思考来自于徐昊在团队内部的相应Session.
posted @
2010-01-17 12:08 ronghao 阅读(2565) |
评论 (0) |
编辑 收藏
今天下午1时20分,百度首席产品设计师孙云丰在自己的博客中撰文关于谷歌退出中国,直指Google退出中国的姿态证明自己是市侩分子,对此感到恶心。
他的博客全文如下:
google宣称要退出中国,所证明的,恰恰不是市面上的那些g粉所宣称的那样,google是个人权斗士,而刚好反了过来,正好证明google是个市侩分子。
google
的首席法律顾问的调调让我感到恶心。因经济利益退出,就直白白的说好了,把自己涂脂抹粉一番,还煞有介事的提到google被中国人攻击,中国异议分子的
Gmail信箱被攻击,把这些事情作为退出中国的铺垫,这种论调是侮辱中国普通老百姓的智商,但还真有可能迎合那帮目空一切,但从未到过中国、对中国没有
丝毫了解,却又喜欢对中国说三道四的西方人的假想。
只提一个假设,如果谷歌占据了中国80%的搜索市场份额,google的高管,还会这么高调的宣称要do no evil,从中国退出吗?
整个事情给我的唯一感受,就是恶心。
————–
以上是作为一个曾经的忠实google用户而说的,和百度无关。市面上沾沾自喜于了解一点google的产品技术细节将google奉为道德楷模而自封G粉的兄弟,请勿跟帖瞎喷,你们根本不懂什么叫搜索引擎,什么叫自由人权。
立此存照的原因在于,原帖 http://news.csdn.net/a/20100113/216459.html 被百度的人要求删掉,所以本着对历史负责的态度,保存与此,欢迎转帖。
posted @
2010-01-14 13:29 ronghao 阅读(414) |
评论 (0) |
编辑 收藏