RSS

Author Archives: Wu Shaobo

About Wu Shaobo

@ThoughtWorks

中秋月圆

在把家庭生活看得越来越淡、对待邻里关系若有实无的如今,中秋节不算个什么重要时节了,甚至过年也变得只是走走过场而已。我本打算埋头宅在家啃书,独自抵御阴雨的寒冷,却意外地度过了温暖的三天假期。

妻只休第一天,让她好好睡一个奢侈的懒觉吧~ 她却仍然被无情的电话吵醒,无奈地赶去单位处理事情。中午接到她,说想吃肉丸呼啦汤,立刻满足。饭后决定带她散散心,哪怕雨大天阴,驱车奔浐灞而去。停车,沿浐河边信步而行,雨声沙沙,河床树顶的白鹭却不因阴雨而烦闷,或嬉戏、或悠悠然~ 忽然发现垃圾箱边一只小笨狗注视着我们,在雨中颤巍巍的,饿么?不能让这小家伙即离了家人又没有饭吃,中秋的月饼送上。不够吗?这就买肉去!借这石板搭一个雨棚吧,虽然不忍离去。后赶往高新,接老妈去奶奶家,团圆的晚饭其乐融融。愿长辈健康、大家开心~

次日一早醒来,窝在沙发里拽本书看。眼光扫扫家里,未扔的垃圾袋、半箱腐坏的水果、洗衣机上一堆的衣物、斑点的地板、冰箱里的剩食、歪斜的沙发衬垫,不能再忍了。平时早出晚归,我俩人确实不够勤快,现今是收拾的时候了,等妻下班了就是一惊喜。心中既怀此念,动手时更加有劲,挨个清除,逐一料理,整顿完毕已是困意连连……被电话叫醒时,我才记起和三位发小儿早有约。晚餐时对炖羊肉一番大快朵颐,把酒谈论最近的境遇。饭后怀念起从前一起游戏的日子,遂往网吧去,早已不熟悉的网吧中,《生死四人组》的世界里我们团结配合、相互帮扶,真是只有最知心的朋友们才能达到的境地啊,当游戏完毕,我们更加惺惺相惜~ 朋友们,加油!在自己的人生长跑里,可以吊吊车尾、可以呼呼喘气,但一定要努力下去,大步流星的终究还会是我们!

最后一天假期,天似乎晴了,被窝里坐着看书挺惬意。老妈的电话里问我有什么安排,我听得出来,其实是想让我开车载她们出去散散心,老爸也终于休假了,这半天的假期让我为他们再拆分吧。于是立即答应,来吧,把小舅也叫上一起,我带你们去呼吸雨后的新鲜空气。车过灞河桥,这宽阔的水面、暗流里翻出的浊浪,却是平日里瞧不见的光景,多亏这一阵雨。沿河东行驶,一片环绿的湖水吸引了我们,以及先到的若干钓鱼爱好者。湖水宁静,丝毫不似河水的昏黄;草地、小丘天然生成,全是清洗过的碧绿。长辈们哄然称好,这确是个十个农家鱼塘也不换的宝地。雨后的阳光倾泻着,我们尽兴之后也不得不返回了。送回父母,我还要赶去我另一对父母那里,中秋佳节,他们也想女儿、女婿。说说时事,聊聊家常,互相问各种“缺不缺”、“好不好”,话里绝不是寒暄,心里却有各种温馨~ 末了出门,回我们的小家去。

车在立交上,看云里透出的淡淡圆月,心中温暖畅快,无须言语~

 

Tags: ,

Mock,有人爱它我偏恨

一次又一次地在项目中用Mock写测试,每次都是无奈和不情愿。

对话一

Me: 为什么要用Mock呢?
Dev: 因为客户要求只有Model中的代码才能touch数据库。
Me: 构造数据做其他层次的测试而不是mock,这不可以吗?
Dev: …测试数据构造很麻烦。我们用Mock分离其依赖层次的代码,这样就可以专心写当前方法的测试了。
Me: 依赖的低层次方法全都Mock了理想返回值,接下来直接断言结果就好了?高层次的测试过了但低层的反而没过!
Dev: ……Mock不是保证了各层依赖的方法真的被调用了嘛。
Me: 产品代码里我能看见谁被调用,可代码集成后结果到底对不对呢?集成测试在哪呢,怎么知道产品代码放在一起真能work?
Dev: ………Cucumber测试……
Me: Holy crap!

你应懂得:

  • Mock应该作为分离模块和模块之间的手段,以降低测试的难度,例如Mock数据查询,返回自己构造的测试数据而不是访问模块之外的资源。
  • 模块内的层次之间的关联在测试中未必非要由Mock打断,在一定数据的基础之上,为一个原子功能做跨越一二个类的单元测试,更能从集成的角度证明代码的正确性。
  • 比证明“某些依赖确实被调用到”更有意义的是证明“对给定的输入数据能得出期待的结果”。不要把基于结果断言的测试变成用Mock描述实现过程。
  • 如果已经知道是delegate方法到其他方法上,那就省了Mock的测试吧,就像拿着标准答案答考题,过家家一样毫无意义,除非你不幸要完成变态且愚昧的“100%测试覆盖率”。
  • 如果对“某层次代码能否间接访问到数据库”没有苛刻要求,那么有必要的话,可为每个开发机器配置独立数据库,并区分开test数据库和development数据库,让test数据库只跑测试。这样在写访问数据相关的测试时,就可大胆预置测试数据,之后清除也方便,让测试能够“所见即所得”从而更加真实。

对话二

Me: Mock的代码这么多,还对应着产品代码里的实现细节,这不容易写更不容易看懂啊!
Dev: 这也不算多,构造测试数据才麻烦呢。被测方法里调的其他对象方法你不得不Mock啊,不然会报错。
Me: 但是我要用测试来驱动开发啊,这中间的细节我需要到真正写出产品代码时才能确定,这样的测试也太难写了吧?
Dev: …是有点麻烦,不然你先写好断言部分,产品代码需要时再补上Mock吧。
Me: 这是先开发后补测试啊!
Dev: ……一点一点写的…最终反正是有效的测试啦。
Me: 如果被Mock的方法被更改了实现呢,你能及时发现吗?测试全过了可应用启动却废了,这事发生过吧?
Dev: ………改代码时细心一点,多搜索下检查检查,启动应用看一看……
Me: Holy crap!

你应懂得:

  • Mock在测试中必然会构建假设,通常如“某类的某方法在某条件下调用时应返回某结果”。应该将其控制到所测功能的数据源头处,而不是任其泛滥成为实现过程在测试中的描述。
  • Mock的假设在描述时必然会牵扯到所测方法的实现细节,这在一定程度上为先写测试后写代码增加了难度。
  • 过多地使用Mock会降低测试代码的可读性,更重要的是其可维护性。错误的假设不易分辨,因为真实改变时假设还保持不变,无法通过测试的失败而被发现,而代码检查、集成测试以及人工测试成为了代价昂贵的补偿方法。
 
Comments Off on Mock,有人爱它我偏恨

Posted by in Uncategorized

 

Sequel

This is the slides for my introduction of Sequel.

 
Comments Off on Sequel

Posted by in Uncategorized

 

Tags: , , ,

新人的coach与被coach

项目在更替,人员在变动,总会有加入一个项目成为新人的时候。什么能让你更快上手?

技术经验?领域知识?适应能力?勤奋程度?聪明与否?

都没有错。技术经验让你更早能开始写程序;领域知识帮助你更快理解业务与需求;适应能力帮你更快调整工作方式,与同事更协调地合作;勤奋学习让你尽快补上缺少的东西;聪明与否让学习和接受新东西变得更容易。

可惜的是,项目和项目是不同的,个人因素的作用不是故事的全部,总有你不知道的业务、没听过的规则。所以刚进入项目时你只能是一个“新人”,你需要coach帮你度过这个时期。

今年内我第二次半途加入项目的经历让我明白,如何coach新人以及新人如何配合coach,是非常重要却常被忽视的事。

找到知识范围——新人的第一件事

一个新人加入项目,他需要知道什么?

项目背景、业务逻辑、数据关系、涉及的技术、架构与运作、代码结构、工具选择?交付计划、沟通方式、项目组的各种习惯以及约定、客户/合作者情况、工作的技巧?

我多希望这个被coach的新人能和coach他的团队成员共同来想一想,不过可惜的是,不常见到。

理想状况,coach的人能把这些的信息以合适的进度全部传递给新人,新人也能将听到的知识全部归纳吸收。但这常常不可能,人的思路就像画画,怎么期望他一笔下去画到完?何况coach并非专职的,他也有手头的工作要做。

于是就要求新人上心,推动关于知识范围的讨论。

帮助新人跨越认知边界

人的认知范围是有边界的,常常有你不知道的认知边界之外的事,它们不像不知答案的问题,而是根本不知这有个问题。做coach的人,你不仅有义务让被coach的人听到问题的正解,你也有义务为被coach的人指出他所不知的问题。

在这方面最难的要算理解客户的业务需求,这些抽象的规则如果没人告诉新人,新人很难知道;而困难也存在于coach一方,他们不容易意识到有多少业务要讲给新人听,常常是遇到了才说起来。同时两方还有一个共同的想法:新人不用知道太多历史,目前的已经够多怕吃不消。这样的环境里,新人加入一月也不一定了解所有的业务,就像我的上个项目,在项目的最后两个月加入,可直至roll off都仍有盲点没清除。不过让人稍感欣慰的是,从工作的角度谈,那些历史没有让我的日子太难过,因为遇不到它(遇到的都不得不了解了)。总之,积极让新人面对他不熟悉的项目部分会帮他扩展认知范围,他成长得就更快。

话很多与话很少——互动很重要

不是想在这儿谈论人的性格,而是想说互动很重要。努力做一个话多的人,在coach和新人之间确实是对的。话多话少分场合,当你踏踏实实看文章练技术的时候,闭嘴才是应该的。

做为coach,应多问问新人做着什么、怎么想的;多讲讲情况,谈谈窍门,问问遗漏了什么。同时,新人在所有交流中都应坚持这样的积极方式:常问常重复,把知道了的讲出来,请coach听;把自己没听清的问出来,两人一起弄清;把coach不小心略过的问题拾起来,记下之后去问;把联想到的可能说出来,更正你的过程就是辨析正确的事的过程。

那么新人本身话少怎么办?我深信,只要老员工主动说话了,他就会回答;只要给新人一个放松的说话氛围,他就会慢慢打开话匣,一来二去话自然就能多起来。所以大家也可想一想,在评论身边的同事话少的时候,你是否意识你可以做一些事去改变他?

高效而有深度地做coach

古人云:“知无不言,言无不尽”,把这个要求提给做coach的人正合适,但还要加上一个词:敏感。

我发现从coach中收获最大的新人,并不是被灌输大量知识的人,也不是最快问到问题答案的人,而是他的coach对他的处境最敏感的人。对新人的处境敏感,就是操心着他能否理解要点,是否满足他的问题背后想知道的知识,是否把道理和窍门完整地呈现在他的面前,关心着他是否正确运用了学到的去解决类似的问题。把这几点放在心上吧,对被coach的新人的处境敏感起来。

接着,还要再对自己的讲解敏感一点。耐心安排完全面对被coach人的时间;控制自己不要跑题过远,牵扯太多;不停地反思有没有把关键点都讲到,有没有把他的问题答完整;亲近一点,把他的反馈看在眼里。我觉得好的coach应该做得到。

我脑海里浮现出两个例子。

一个正面的例子是,当初入职两个月的我拿自己的笔记给新入职的同事,讲Hg的基本使用和在项目里提交代码的流程,因为把规则有条理地讲给了新人,并分析了可能遇见的问题及应对方法,新人虽然慌张但一天内就基本掌握了。

作为对比的,我上周刚加入的这个项目组里闹了个小笑话,一位新人同事在git的push和commit命令上(都能用“提交”这个词讲)和coach他的同事出现了误会,coach他的人希望他“频繁提交”,本意是常push一下。但新人同事git经验尚浅,对“提交”的直接反应就是commit,于是每次被问及“有没有提交”时,他都回答说“提交好了”。在我看来这挺郁闷的,虽然我们都把这件事当笑话提起。如果coach新人的同事能在头一次就把整个流程对着命令讲清,并主动的看一次新人自己操作,那么问题可能会被避免;如果这位新人同事能主动去讨论他对提交规则的理解,或者把命令本身讲出来核实一下,那么也不至于误会。

 

 

关于类继承与Liskov替换原则

续《When I click “object-oriented”…

派生类重写父类方法可能违反Liskov替换原则?

如果父类仅仅声明了抽象方法,而各派生类分别实现了该方法,那么就如同实现接口一样,可达到多态的目的;

如果父类实现了某方法,那么它对外所描述的行为也就确定了,派生类如果重写这个方法,那么就修改了这个行为,当派生类实例被父类型的引用使用时,表现出的行为与父类本身的实例不相符,即违反了Liskov substitution principle。

举个例子

现实中,正方形是矩形的一种特殊形式。

现在先有Rectangle作为父类,具有height和width两个property(有邪恶的getter/setter);再有Square继承Rectangle做子类,由于Square长与高相等,所以重写height和width的set方法为同时设置长与高。至此似乎是设计与现实完全相符,接着有人这样调用了:

void  tryAreaCalculation(Rectangle rectangle) {

     rectangle.setHeight(4);
     rectangle.setWidth(5);

     assertEquals(20, rectangle.area());
}

....

tryAreaCalculation(new Square());

结果assertion失败,“expect: 20, but was 25.” 也许我们会说,明知道传入的是Square对象还写那样的assertion是不可能出现的,但不清楚程序的人如果只看tryAreaCalculation方法,就会认为“矩形面积=长×高=4×5=20“是理所当然的。这显然不是我们想看到的事。

当然,这个例子并不是Liskov替换原则所针对问题的典型例子,在以“不择手段地复用代码”为目的的继承例子中(class Students extends List<Student>),大家更能明白Liskov替换原则的意义,以及对组合和继承的选择应首先去考虑面向对象的模型。

不要让OO的眼光被具体编程语言遮挡

是不是觉得我把类继承妖魔化了呢?有没有因为担心人类被官员类继承就不敢给人类添加“说话”的方法呢?不必这样。我们在用面向对象编程时要清楚,什么是从面向对象建模角度认为对的事,什么是所使用的编程语言能够做到的事。

如果你手上的是Java语言,发现官员会说话,人也会说话,官员又是人的一种,那么就在人类中实现“说话”这个方法,再让官员类继承人类(即便在未来可能会在官员类中重写说话方法,加入说空话的语句),这一切已是你能做到的合理的事了。但请心里清楚,这并不是使用OOP对待此模型的最正确方案,因为Java语言的限制。

如果编程语言有了如Mixin的特性(例如Ruby中的Mixin、Scala中的trait,下面以Ruby为例),那么你会发现在这个问题上你能有更好的解决方案。为了官员、教授等也是人的事实,我们依旧让这些类继承人类,但除了固有属性如身高、性别,以及固有行为如睡觉,不继承任何可变的因素;把可能共用的因素都单另放在Module中,之后在需要的类中Mixin进来,如为程序员、司机、中学生等类Mixin普通“说话”方法所在的Module,而在官员类中定义含说空话逻辑的“说话”方法。

总结下经验

  • 设计时的考虑,应首先尊重使用面向对象思想的建模,其次参照具体编程语言的特性,第三考虑与已有设计的配合和约定,必要时坚持正确的事去争取。
  • 用接口描述行为,各类通过实现接口来定义行为的具体内容;留意接口隔离(ISP)。
  • 用基类抽象固有属性和行为,减少public方法,合理使用Template method模式;子类(尽量)不去重写继承自基类的行为,只添加新属性和行为(LSP, OCP)。
  • 用组合描述以借用其他对象的行为为目的的设计。
 
Comments Off on 关于类继承与Liskov替换原则

Posted by in Uncategorized

 

When I click “object-oriented” …

它是一种实践

面向对象不是编程模型而是一种人们总结出的实践,它优于过程化或模块化编程方式。

哲学

它的哲学是拉近现实物体或概念与程序实体之间的距离,不仅将程序中的参与单元看作对象并将对象归类,而且在对现实物体以对象的方式建模中,将各对象作为属性的数据纳入各自结构,并与其相关行为组织在一起;另外,还通过继承或组合等方式描述实体间的存在的数据或行为的关系。

形式

面向对象编程语言有两种形式,基于类型(class-based)和基于原型(prototype-based)。

  • 基于类型的形式自C++时代就确立了在静态语言中的绝对地位,它明确提出类型的概念,认为对象都是类型的实例化;类型通过继承的方式重用其他类型的属性和方法。可以用“is-a”来解释上述两关系:我们可以说对象是一个类型的具体化,也可以说继承自类型B的类型A是类型B的一个派生。基于类型的形式在重用其他类型的行为方面的还有另一种方式叫组合,它将类型B的对象作为类型A的属性,以便直接使用。可以用”has-a”来解释组合:类型A中有一个类型B的对象做属性。
  • 基于原型的形式也可被称为类型无关的或基于实例的编程形式,它最出名的实现语言莫过于javascript。除了直接创建自身对象之外,它以其他对象作为原型通过克隆(clone)的方式重用其他对象的行为。通过对象得到新型对象,并在被调用时追溯原型对象中的方法,支持这种编程方式的语言特性是委托(delegation)。

OOP核心

  • 封装(encapsulation),它是能称为面向对象编程的最重要的特点,它将对象自有的属性和行为封在类型中,限制内外的访问接口,在不同种类的类型之间划清界线,明确了模型,保证了数据安全。
  • 继承(inheritance),它实现了类型级别的抽象和代码重用。
  • 多态(polymorphism),它为面向对象设计提供了充足的自由度。它能通过接口级别的抽象,利用类型匹配和晚绑定,实现从多种表现形式中为对象或方法做出选择。

OOD原则

  • 单一职责原则(single responsibility principle)。封装的目的是将对象自身的行为限制在自身类型中,而该原则进一步强调类型应当专注,只因一个变数而变化,所有的行为集合体只在扮演一个角色,承担单一的职责。引申的,其中所有的方法也只各做一件事。
  • 开放封闭原则(open-close principle)。对扩展开放,对修改封闭。经常考虑的是现在如何设计才能在未来做扩展的同时,能尽量不修改现有系统的代码,或能够将修改降低到少数的几个地方。
  • Liskov替换原则(Liskov substitution principle)。它要求所有子类的对象在代替其父类被使用时都不会得到错误的结果,这是在保证以多态方式使用时不会出错;若该原则被打破,那么开放封闭原则多半也同时被打破了。这是一个比较苛刻的原则,子类重写父类方法似乎变得不可能了,可是仔细想想会发现,似乎当初对父类抽象得还不够彻底。
  • 依赖倒置原则(Dependency inversion principle)。高层模块需要调用低层模块的方法,但不能直接依赖,而是应让两者都依赖于接口。这是该原则的第一层意思,它的初衷是强调要面向接口编程,而不是面向实现,达到降低耦合的目的。这基本是对的,不过我觉得接口被过分强调了,应该在未来可能会扩展的地方考虑接口的使用,不要过度设计徒增代码。该原则还有第二层意思,抽象不应依赖于细节,细节应该依赖于抽象,这才是“倒置”二字的本意。因为我们往往是由简单到复杂,由单一类型到多种类型地在扩展程序,所以为有共同特点的类做抽象常是在已有实现类之后决定的,这时要考虑抽象是否彻底、是否有对实现的依赖。不过我觉得这种随机应变的抽象手法问题不大,大可不必一开始就死磕接口,那才是过度设计了,拥抱变化不是白说的。可以实现分析下问题,选择合适的设计模式是个好主意。
  • 接口隔离原则(Interface segregation principle)。这个原则比较简单,它针对的是过多的接口集中在一起的问题。就像为类划分单一的职责一样,也应为接口考虑较单一的分类,不要吧所有接口方法都堆在一个接口中。好处是客户可以有选择地实现特定的接口,而不实现无关的,实现解耦合。
———————-
后面一篇文章中对类继承和Liskov替换原则有更多讨论
 
Comments Off on When I click “object-oriented” …

Posted by in Uncategorized

 

利用Mailbox为日志型web应用服务

很多时候,我们都以类似日志的方式做记录,而回顾历史时,我们想看到的不只是一条条的历史记录,更多的是期望能以某种方式对记录信息做聚集,并有组织地、最好是华丽丽地展现出来。为这类需求去开发web应用,如果仍采用典型的Browser <–> Web server <–> Database 的设计(如古老的S.S.H.常做的那样)是不好的。

  • 原因一,数据库大材小用。单纯的数据内容加上更加单纯的关系,如果用一关系型数据库,那真是浪费;
  • 原因二,Web form提交记录的方式死板。日志型的信息何必要以登录后提交页面的方式记录,太过正式了吧,为什么不是发一条微博或者一封邮件就顺带着记录了呢?

如果考虑到微博的私密性不够,那么就邮件吧:让你的邮箱为你忠实记录下这些日志型的记录——用约定的字眼为邮件打上标签后发送,让设计的web应用从你指定的收件箱中抓取此类邮件,分析内容获得历史记录,适当地做信息聚集,然后将你的记录信息华丽地展现在页面上(比如这样)。

应用的设计框架画成下图。

可以看到设计中很重要的一点是周期性地将内存维护的数据与mailbox同步,这一步的工作应该是交给独立的后台线程去做的。不采用实时访问mailbox取数据的原因是显而易见的——慢,并发就更难了;同时,考虑到记录本身的数据量不大,实时性也无苛求,所以维护内存数据库在这里十分划得来。

这种设计的好处还有:

  • 多个用户的记录都通过邮件发送到同一个邮箱,邮件本身除了标题内容之外还记录了发件人、时间等,我们的web应用可以直接利用它们;
  • 通常web应用都不免被基于数据库的CRUD困住脱不开身,而上图的设计却没有为我们这个需求简单的应用添麻烦,开发人员可把精力更多地放在提取信息和创建丰富的页面展现上。

最后回归到代码。使用“mail.jar”访问邮件的代码可以参考下面的demo. 不要错引或多引关于mail的jar包哦,因为疏忽曾引入了两个不同的但都关于mail的jar包,以致跑起的程序取不到邮件却又不报错误,本人吃过这个亏了,所以希望读者小心。

import com.sun.security.sasl.Provider
import java.security.Security
import java.util.Properties
import javax.mail._

class MailFetcher {

  def connect(username: String, password: String) : Store = {
    Security.addProvider(new Provider)

    val SSL_FACTORY: String = "javax.net.ssl.SSLSocketFactory"
    val pop3Props: Properties = new Properties

    pop3Props.setProperty("mail.pop3.socketFactory.class", SSL_FACTORY)
    pop3Props.setProperty("mail.pop3.socketFactory.fallback", "false")
    pop3Props.setProperty("mail.pop3.port", "995")
    pop3Props.setProperty("mail.pop3.socketFactory.port", "995")

    val session = Session.getInstance(pop3Props, null)
    val store = session.getStore("pop3")
    store.connect("pop.gmail.com", 995, username, password)

    store
  }

  def fetchMailsFromFolder(folderName: String, store: Store) : Array[Message] = {
    val folder = store.getFolder(folderName)
    folder.open(Folder.READ_ONLY)
    folder.getMessages();
  }

  def printMessagesInfo(messages: Array[Message]) {
    printTotalCount(messages.length)
    printSubjects(messages)
  }

  private def printTotalCount(count: Int) {
    println("Total count of emails: " + count)
  }
  private def printSubjects(messages: Array[Message]): Unit = {
    messages.foreach(msg => println(msg.getSubject))
  }

}

object MailFetcher {
  def main(args: Array[String]) {

    val mailFetcher = new MailFetcher

    val store = mailFetcher.connect("your.account@gmail.com", "your.password")

    val messages = mailFetcher.fetchMailsFromFolder("INBOX", store)

    mailFetcher.printMessagesInfo(messages)
  }
}
 
Comments Off on 利用Mailbox为日志型web应用服务

Posted by in Uncategorized

 

Scala 初学乍练

《Programming in Scala》确实不是本新书,而且它700+页的厚度也挺吓人,但对以初学一门语言为目的的我来说,它的思路和讲解却已足够清楚。这本书或许不能痛快地直奔scala核心思想,但从语言层面开始学习和实践的角度,有了它确实挺好。

类型推断
    记得java中一次一次地写对象引用的类型声明吗?对象自实例化之初就明确了它自己的类型,为什么还非要指定其引用的类型呢?即便是如Fruit fruit = new Apple();使用抽象类型或接口,在更新语言看来也是多此一举。如C#3.0起引入var作为为引用声明的类型的placeholder,为编译服务,var fruit = new Apple(); Scala自然也看这种类型显示指定不顺眼,于是直接省略掉,辅助以类型推断来理解省略的类型信息, val fruit = new Apple. val为不可变变量的标志。

val 、var声明
    val array = Array(1,2,3)  中,array这个引用不能改为引用其他对象,而Array对象本身可更改。
    for(param <- params)  …  句中,param是val的,即不能通过此for循环直接遍历params数组并逐一改变元素值。

operator
    没有operator,所有如+,-,*,/的操作都是方法,1+2 实际上是 (1).+(2),即1上调用+方法且参数为2
    规则:如果一个方法被用作操作符标注,如 a * b,那么方法被左操作数调用,就像 a.*(b)——除非方法名以冒号结尾。这种情况下,方法被右操作数 调用。因此,1 :: twoThree里,::方法被twoThree调用,传入1,像这样:twoThree.::(1)。

tuple 现代怪胎
    使用(element1,element2,….)的方式创建的不可变多元组,元素类型不统一,使用诡异tupleObject._N方式访问各元素,其中N从1开始而非起自0的典型方式。源自Haskell和ML语言从1开始计索引的现代怪胎。

绕人的mutable和immutable
    先不要提特质(trait)了,只看immutable.Set 和 mutable.Set到底差多少?前者的元素内容不可被更改,后者可以,且其set对象是可扩充的。以含有 += 操作的如下代码为例
————————————————————————————————————————————————–
    var immutableSet = Set(1, 2)    // 不特别import的情况下,scala.collection.immutable.Set是默认Set类型
    val mutableSet = scala.collection.mutable.Set(a, b)

    immutableSet += 3                    // 等同于 immutableSet = immutableSet + 3
    mutableSet += c                        // 调用mutable Set的方法,等同于mutableSet.+=(3)
                                                    // 因为是val声明,所以保证mutableSet仍是原对象

    immutableSet.foreach(print)     // 输出:123
    mutableSet.foreach(print)         // 输出:abc
————————————————————————————————————————————————–

函数式编程方式
    指令式的编程方式也是scala语法的一部分,但却常常闻不到scala的美味。
    哲学
        把每个方法当作是创建返回值的表达式。鼓励你分解大的方法,制造多个小方法。
    设计/重构的目标
        使用val而非var
        使用immutable对象
        编写易于测试的且职责单一的方法
    程序风格
        方法最后一个表达式的计算结果即是返回值,显示调用return是备用方案
        单行方法体合并到方法声明同一行
        用过程(procedure)风格编写无结果类型(Unit型)的方法
        [编译原因] 中缀操作符放在行尾而不是行首,如拼接字符串时,放在行首是单独的语句

伴生对象(companion object)
    val list = List(1,2,3)        ==>    val list = List.apply(1,2,3)    其中apply方法是List的伴生对象的方法,而非java中的static方法。scala没有静态成员。
    当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象(companion object)。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类(companion class)。类和它的伴生对象可以互相访问其私有成员。

 
Comments Off on Scala 初学乍练

Posted by in Uncategorized

 

请及时sign off用户故事吧

敏捷项目以迭代划分增量式工作周期,以用户故事划分可被独立开发并交付的任务,以点数衡量交付工作量的大小。

Showcase不能取代客户sign off用户故事

迭代中的用户故事会经历“分析-开发-QA认可-用户认可”的pipeline,交付团队的目标就是按期计划并完成迭代内的故事。我们常常强调的是迭代计划、迭代内按优先级完成开发、及时的QA测试、及时地沟通问题和完美的showcase,但似乎最后那点睛一笔没有落在让全部故事得到用户sign off上。我想原因有几。

  1. 迭代内任务到QA测试通过就为止了,之后的工作是用户的,时间他们看着安排;
  2. 我们完成了故事的QA测试,才能轮到用户开始signing off这个故事,常常这时已经到当前迭代的尾声了,交付团队把重心移向了下一迭代的内容;
  3. 交付团队常常在迭代结束会做详细的showcase,展示完成的故事给用户来看,用户的称赞给了交付团队定心丸,我们也就不急着让客户sign off那些故事了。

在敏捷项目中,showcase可以作为向客户展示新开发的功能和周期性交流的手段,作为对这段时间完成工作的汇报,但它不能代替用户sign off,因为后者才是用户对完成故事的正式肯定。用户在看到showcase时的点头满意和用户自己详细测试之后sign off了故事的满意是不同的。sign off是故事在pipeline上的收尾,它里程碑式地标志着故事所描述的功能被如意完成并认可,未来任何时候返回头来看,这白纸黑字都是不会变的;而showcase或平时言语的讨论,说难听了都算不上是最终定音啊。

不及时sign off故事会直接影响交付

即便是双方信任度很高的团队,不能及时由用户sign off前面迭代的用户故事都是带来未来风险的因素。迭代式开发不同于瀑布模型的开发方式,前者籍以达到及时交付用户价值的优点,在于不断得到要求与反馈并及时交付部分的用户需求;而后者则试图在最开始就通过深思熟虑谈妥用户需求,并在最终一并交付它们。可见如果不在迭代结束就及时请客户承认需求的交付,拖到后来地新需求被实现时,旧的需求就将被历史无情地掩埋,而那时开发的故事在今看来则失去了意义。好在目前为止碰到的客户都很诚信,并信赖我们的交付工作,所以一切正常地前进着。

不过,在项目即将结束时,那些没有及时被sign off的用户故事带来了新一轮问题。在即将停止迭代、进入UAT阶段前,我们发现竟然有大量故事仍没有被客户sign off,交付项目眼看就到了开发结束但无法交出手的地步,因为客户不仅要sign off这些剩余的故事,还要进行全面的UAT,这样一来测试将很花时间。而我们当初只为UAT阶段预留了一周时间,也就是说,我们做回归测试之余只有等待用户的测试结果,而这个结果客户在这有限的一周内是很难拿得出来了;即便拿了出来,我们修defect的时间也所剩无几。这就是各迭代中没有及时催促客户sign off故事的恶果,我们自己将品尝它!

不幸如此了,有何良策

当客户不得不在最后一并signing off大量历史迭代的故事时,问题又来了,如何测试?要知道,最终的feature都是随各个迭代增量式开发出来的,历史过程中完成的故事所描述的feature已经很难从当前版本的应用中看到了全貌了,难道要一个个版本回滚去测试一个个悠久的故事么?那不行,也不值得。最终交付的是当前版本,岂有回滚到从前去测试之理?最终交付的是当前版本的feature,何必回到以前验证已经被更改或移除的东西?既然已经到了如今的境地,我们要向背负着大量未sign off故事的客户提建议,及时地,建议他们按feature来测试当前版本的各个功能模块,我们提供给他们最新版feature对应的故事列表以及收集起来的feature表述和测试用例。这样为客户省下处理历史问题的时间,并确保他们将注意力集中在最终交付的那些需求上,客户当然更愿意接受。

总结一下

  • 用户sign off用户故事是对交付团队工作的正式肯定,showcase不能取代它;
  • 及时sign off故事能及时确认交付的需求,增加交付团队迎接新需求的信心;
  • 及时sign off故事能节省项目收尾时验收的工作量和难度,为UAT腾出时间;
  • 在迭代结束时应及时地催促客户去sign off该迭代完成的故事,不惜有迫切感地push客户;
  • 当大量未sign off的故事遗留到项目收尾阶段时,向客户建议以feature为粒度来sign off,集中注意力到当前版本同时也避免浪费时间。

 

 
Comments Off on 请及时sign off用户故事吧

Posted by in Uncategorized

 

Command pattern 应用特点

命令模式是一个很有活力的设计模式,它的几项特点具有很好的利用价值,我们可以通过演变该模式为许多问题找到解决方案。

先教科书式地摆出命令模式的UML图,并有请模式中各角色登场。看过之后请一定记住,不能教科书式地背诵它们或使用它们!不能拘泥于教科书式的命令模式表述,发挥其特点和思路才是正途。设计模式提炼自问题又用来交流并重复解决问题,所以为具体的问题去理解并打造它吧。

1、批量执行命令

通过实现Command接口,各实现类实例可聚合在一起并被Invoker遍历,依次执行各命令的Execute方法,达到批量执行命令的目的。如果有需要的话,每个命令执行的返回结果都可在Invoker处收集到。

这里举参数检查的例子。如筛选面试者信息时,需要一一检查各关键参数是否满足要求:email要验证格式,年龄段要符合限制,各项评分达到相应要求等等。

  • 普通写法什么样儿?若干含有“ValidatingXXX”方法的if语句形成丑陋的排比句,凡是返回值出现false,一律终止之后的检查并取消后续的业务逻辑执行。
  • 命令模式带给你清净。对着图,Client把典型验证逻辑包含在若干ConcreteCommand对象当中,形成命令集合交给Invoker,再将面试者信息对象填入,由Invoker执行各命令的Execute方法分别去验证输入对象的参数,Invoker可随时观察到返回false的命令执行结果并终止后续逻辑。Receiver在上述过程中可以不必出现,因为还没有出现需要让其他某各对象特地做点什么的时候。

2、命令对象的存在感

狭义地讲,命令就是希望完成的意图。调用方法是达到意图的过程,可惜这个过程从期望执行到开始执行是瞬间启动的,方法执行完毕命令的效果也就结束了,烟消云散。当我们将命令寄居在对象上的时候,这个意图的生命周期可见了。

  • 从发布命令到命令被执行,对象可以等待,于是时间上的耦合被解开了。我们可以为创建的命令对象设置延时,让需要深夜执行的程序在白天就发布给命令执行引擎。
  • 从参数的接收、验证到命令执行,工作可以分派,所以对象实体上的耦合被解开了。我们可以为ConcreteCommand类对象指定多个Receiver,让它们分别完成参数的验证和意图的执行。

毫无疑问,面向对象的世界里,方法也可被视为对象。Function Object被多种编程语言支持着且不说,就连我所学的第一门编程语言——C语言,也能以函数指针的方式将意图指定并保留下来,之后执行。如果情况适合的话,也可以试用它们完成命令模式的思想,不过一个命令实现类还是在提供辅助方法以及实现统一接口上有更大的操作空间。

3、命令的执行对发布者透明

命令模式最基本的模型,只有commander类和若干command实现类,对象之间是1对N的关系。其中,commander对象负责在各种情形下发出各种命令,即同步或异步地安排command对象执行Execute方法;command对象在得到执行许可后,全权接管具体执行过程。

好处是显而易见的。就像掌控全局的指挥官,了解了各种命令能达到的意图,按需发出命令就是了,不需要知道是谁又是如何让这条命令落到实处。是不是想到了手握遥控器那一刻的自己呢?

命令模式对“事件-响应”模型有良好的支持。我们常使用dispatcher将响应函数与事件特征相关联,如MVC中由router将Request映射到Controller某方法上,将对响应函数的调用换作命令的发出,就换到了命令模式的思维上。综合上一节对命令对象的阐述,

  • 我们可以在命令发出时立即调用其Execute方法,这与直接调用响应函数等效;我们也可利用命令对象的长命,以异步方式推迟执行命令的方法;
  • 我们不需要在dispatcher上就知道要调用哪个对象的哪种行为作为响应,只需要知道发出那类命令。
  • 我们可以利用命令对象对外暴露接口的一致性,借用命令对象作为Wrapper包装个性不一的响应函数,dispatcher更轻量和整洁。

4、命令的追踪和undo

命令模式以对象为粒度管理命令,命令对象创建后并未立即执行,命令执行完毕后对象却仍未销毁。我们可将未执行的命令入队变成序列,将已执行的命令保存为历史,这样来追踪命令。

命令模式中命令被实现类所定义,为其关联除Execute接口方法之外的方法很方便,其中很有意义的一个思路是为可逆命令添加undo方法。前面说到可追踪已执行的命令,那么对一个全部实现了undo方法的命令序列,将每条命令执行后入栈,即可随时通过出栈并执行undo的方式实现回滚。(如果没有undo方法,那么实现回滚就意味着要从一个起点开始将之后到记录中某点之前的所有命令重新执行。)

用两个应用实例来说明它的优势。

  • 事务。如果将事务中的执行步骤切分为若干连续的命令,那么事务的开始就是这一命令序列的执行开始,事务成功完成的标志是最后一个命令成功执行,而事务失败后回滚则是从失败点开始将前面的命令按序列倒序执行undo方法直至开始。
  • 增量型操作的redo与undo。如使用Photoshop对图片做处理,每步操作都是一个Command,由对应的工具作为其Receiver在上一操作完成后的图像上做出修改,并如实记录该Command及相关参数在历史列表中。我们可以使用undo回滚当前一步的修改,或再使用redo应用它;当点选历史列表中的某项时,该项之后的历史操作被依次undo,即回滚到指定位置。

5、命令队列与Active Object模式

命令模式满足Active Object模式的六要素:

  1. 供外部访问的代理,能构造请求并入队——Client;
  2. 所有Active Object实现了统一的接口——Command::Execute;
  3. 待处理的请求序列——CommandQueue;
  4. 维护请求序列并安排处理队首请求的引擎——ActiveObjectEngine;
  5. Active Object中处理请求的实现者或实现方法——Receiver;
  6. 一种将结果向外传达的结果返回或函数回调方式——Receiver;

实现的核心思想是:在执行中的队首命令有能力将新命令加至队列尾;等待事件或资源(如时间片)的方式是以持续查询的方式不断将仍不满足执行条件的队首命令添加至队尾。还有,CPU时钟与实时时钟是不同步的,利用延时检查作为调度条件会出现一定不确定性,这种性质正是多线程系统的特点。

Active Object模式是实现多线程调度的一种典型方式。利用延时等待线程的时间片,在时间片轮转到时开始对应线程的临界资源检查,满足运行条件时唤醒线程。延时长短的设置可反映线程的非抢占式优先级。

Active Object模式可用来以单线程模拟多线程系统。这里对模拟的线程是有要求的,需要RTC(run-to-completion)线程。这里利用到了命令一经执行就一定得完成的性质,可以看到,多个命令其实是有同步完成执行的关系。这样只需单一的工作者线程,在不同时间段执行不同的任务,避免了为各个任务分别分配各自的运行时堆栈,这在内存受限系统中是一个不错的节约。