王辉的博客

是什么让我对未知世界始终充满热情?

俗话说,一颗老鼠屎坏了一锅汤。在持续集成中那些随机通过的测试,便是那可恶的老鼠屎。把它们称作随机通过的测试,是乐观的说法,其实它们是一些随机失败的测试。之所以说它们坏了一锅汤,是由于它们飘忽不定的特性,使得程序员对持续集成的结果难以做出准确的判断。

为什么这么说呢?假如,你提交了一段代码,结果测试报错了。如果说你的测试是说一不二的测试,那么你提交的代码肯定是破坏了程序的某些功能。唯一的做法,就是亡羊补牢,提交一个纠正的代码,再运行一次持续集成把所有的测试通过了。可如果这些测试不三不四,它们报错了,有时候,什么都不改,重新运行一遍,就又通过了。可有的时候,是真的错了,必需进行改正。这样一来,这持续集成就成了一个鸡肋,食之无味,弃之可惜。

我是一个比较激进的人,遇到这样的情况,对这些老鼠屎绝不姑息,斩钢截铁的把它们从持续集成的任务中剔除出去。只把那些讲信用的测试留下来,这样一来,虽然测试少了,但至少它们可靠。

可那些随即通过的测试,也不能丢掷一旁,不闻不理,要抓紧时间,把它们纠正过来,把它们从新争取成可以依赖的战斗伙伴。

末了,看看Martin用什么手段来解决这些不确定的测试的。

 

代码审查的时候,我们总会有些争论。痛苦的不是别人的代码写的不好,而是明明知道写的不恰当,却不能有力的说服他,采纳更好的解决方案。

想要说服别人,一定要先说服自己。这是最最重要的一部分。不能过早的下结论。说得太多,却说得不对,久而久之,说出的话,倒出的词,就没什么分量了。所以一定要充足的把握,才可以开始说服别人。

说服别人,也讲究一定的方法方式,不能靠权势,或武力把见解强加与人。

程序,对于一个程序员来说,是他的劳动成果,是他智慧的结晶。如果受到他人的指责,一个程序员的本能反应便是去反驳他人的观点,保护自己的代码。因此,审查别人代码的时候,一定要提建设性的意见,肯定他已写代码的优点,真诚的指出可以改进的地方。相信稍微有点理想的程序员,都希望看到更美的代码。

谈说服力的同时,我觉得谈谈被说服的能力也很重要,一个可以被说服的人,是一个能理性的看待自己劳动成果的人,是一个拿得起放得下的人,是一个开放上进的人。所以受到别人说服的时候,要耐心的听取别人的意见,要有自我否定的魄力。毕竟,代码审查是一个相互的过程,今天审了别人的,明天会被别人审。

 

两顶帽子,说的是开发软件的时候,对重构和添加新功能这两种不同活动的区分。戴上不同的帽子就意味着要专心致志的做帽子所代表的事情。

重构是在不改变软件功能的情况下对软件内部结构的优化。为了保证重构只是在优化而不会引进新的BUG,重构的先决条件就是:所被优化的模块一定要有测试的保护。重构完毕以后换上另外一顶帽子,增加新功能,如果新的功能导致原有的测试失败,那我们可以确定这是由于所添加的新功能导致的。就像唱歌跳舞需要找到节奏一样,两顶帽子的灵活搭配,才能把代码写的又快又好。

可有很多时候,迫于时间的压力,很多人都不喜欢戴重构的帽子,对代码的重复,架构的死板,能忍则忍,忍了再忍。以为这样可以更快的完成任务,其实不然。一旦有重复代码的出现,就意味着,程序员犯错误的可能性会增加。首先,是因为每次做改动的时候,都要保证所有的重复处都要做出改动。其次,重复的要多,多以后优化的阻力就越大,如此一来,就进入了一个恶性循环,技术负债越来越多。

戴上重构的帽子,有利于改变这个状况。可有的时候,你给客户说,“别急,在增加新功能之前,让我先重构一下”。客户指定不买单,他付钱是为了要新功能的,而不是要你打磨老代码的。这时候唯一可以说服他们的论据,就是什么都不说,给结果就行了,他们指定喜欢又好又快的。所以,重构到底有没有用,一定得能在实际操作中表现出来。

戴好这两顶帽子,BUG会越做要少,新功能会越添越快,代码质量会越来越好。何乐而不为呢,偷着乐吧。

2018年6月更新 已离职,再见,Murex!
2017年4月更新 Murex程序员的学习氛围

11年毕的业,后来凭着对计算机的热情和执着,进了Murex,当了一名软件工程师,到如今,工作一年有余了。一直以来,都有学弟学妹或朋友向我询问关于Murex面试的种种事宜,我很高兴能帮助他们,一方面是能为他们顺利的找到自己喜欢的工作尽一点微薄之力,一方面也是为了多交些志同道合的朋友。搁这说说当时面试的心得,留点文字作为回忆的同时,也希望能给大家一点小小的启发。

暂且不说具体面试是怎么个经过,先说说当时的心态吧。

找工作之前,在一投行干了一半年的实习,对多线程,设计模式,稍有了解,经验不多,但热情很大,爱追根问底,倒腾代码。当时找工作的方向很清楚,就是要到金融业里做一个不懂金融的程序员。原因有二,一是在金融业里,搞编程,有挑战性,什么海量数据了,低延迟了,可以点起我战斗的热情,二是,赚钱多。另外,当时我的职业规划也很清楚,坚定不移的走技术路线,一是我好这一口,好到可以日有所思,夜有所梦,梦的都是代码,二是,走这条路,也能有肉吃,并且,也不少瘦肉。

之所以说完心态再说面试,是觉得,我当时的这个心态,对我最后取得Murex的肯定是有决定性关系的。

再来说面试。第一轮笔试,面过的普遍反映有点难,其实也是,和大部分公司要求做选择题不一样,Murex的题都是开放性的,给你些代码,让你说哪里好,哪里坏,坏的地方如何提升改进。不过说来说去,考的都是平日的积累。你说难吗?其实也不是特难,题出的也不偏,考的都是些基本的概念,比如面向对象的思想,多线程的使用。可正是如此,可以看出考生对基本概念理解的深浅。

所以第一轮面试,之前的准备是有必要的,但主要是要出于温故知新的目的。过与不过,要看的更理性一点。过了,说明工作适合自己。没过,可能是平日的积累没有达到要求,但也可能,工作本身不适合自己,毕竟每个人和每个人的情况不一样。把它看做一种测试,不仅是对知识的测试,也是一种匹配度的测试。

说到这,具体题是什么题,就没那么重要了,发挥出真实的水平就行,对别人负责,也对自己负责。

在接下来的和人力的面试,我觉的是些软能力的测试。比如,语言表达能力,另外最重要的就是自己对自己的了解,自己对自己的规划,自己的动机。人家就问为什么。问为什么要做程序员,为什么是金融业,为什么留在法国,为什么选的是Murex?所以,要多思考,思考自己的生活,思考自己的职业规划,思考自己的兴趣,思考自己为什么要下这一步棋,而不是另外一步,思考下一步棋的下一步该怎么走。所有的这些问题,我是学生的时候就在想,并且现在也在想,以后还要想,因为一切都是变化的,人在变,物在变。什么是动机,动机是内在的东西,推着自己往前走的那个东西,是主动的。而不是,人云亦云,随波逐流被动的。

第三面,我当时的印象,就是聊天,聊得高兴,聊得两情相悦,就被收了。但是,也有最后问问题的,写代码的,看情况。

今天写了这么些个情不自禁流出来的文字,其实就想说,在任何情况下,做自己,足矣。只有这样才能以不变应万变。另外把功夫用在平时,做到无怨无悔,就够了。Murex选你,其实你也在选它。

公变和反变,又称协变和逆变,要深入理解它们,我建议从它们所解决的问题出发,然后再从技术层面看它们是怎么实现的。

用一句话概括,协变和逆变为了更好的写通用算法。优秀的程序员的都是懒的,能一下做完的事情绝对不重复。

咱们举个例子,说有一个豆子交易员,他牵线搭桥,把豆子从生产者交易到消费者手里,然后从中抽一成的佣金。这个算法是通用的,一方面它既适用于普通的豆子生产者也能处理生产特别豆子的,另一方面它既支持豆子消费者又能交易给所有农产品通吃的更广大消费者。

function exchange(producer producer, consumer consumer) {
val bean = producer.getBean();
getMoney(bean.getBeanPrice() * 0.1);
consumer.consume(bean);
}
我们接下来具体看看在什么情况下能用这个算法。现在我们有三个类,分别是父类,农产品(product),中间的豆子 (bean),和子类绿豆 (greenBean)。

先看消费端,对于这个交易员来说,如果来的消费者是农产品通吃,那么给交易员一个consumer,交易可通过。但如果消费者只吃绿豆,那么交易员就做不了这单生意了,因为生产者提供的可能只是普通的豆子。

再看生产端,对于交易员来说,如果来的生产者是产绿豆的,那么给交易员一个producer,交易可通过。但如果生产者给的是一个任意的农产品,交易员就不做这个生意,万一是一个大西瓜,只抽一成的佣金,运来运去赔本不说,也不一定卖的出去,因为一些挑剔的消费者是只吃豆子的。

消费端,看逆变,农产品是豆子的父类,而农产品的消费者确是豆子消费者的子类,consumer可以被当做consumer传给通用的交易算法。生产端,看协变,绿豆是豆子的子类,绿豆的生产者也是豆子生产者的子类,producer可以被当做producer传给通用算法。

这一逆一协中的奥秘就是,要想写出来通用的算法,当你面对的是生产方的时候,条件要严,而当面对的是消费者的时候,条件要松。交易员把好严生产松消费的关,生意就好做,算法写一个就完事。

最后,不同的编程语言有不同的实现方式,不管是java中的super/extends,还是c#中的in, out,还是scala中的+/-,说的都是一个事。如果你还不满足,想挖的更深,这里有一个从范畴论的角度开讲的,http://tomasp.net/blog/variance-explained.aspx/。

软件测试从本质上讲,和其它领域的测试一样,都是为了验证该能用的功能都能用。拿造自行车来讲,出厂之前得试一下吧,保证脚蹬刹车等都好用。软件也一样,发布之前通过测试来完成产品验收。可不一样的是,自行车出厂之后,是什么就是什么了,很少有人会回来要求给自行车添加新的功能。可对于软件来说,这却是家常便饭。不变和变之间,虽然只有一字之差,却意味着思维方式上本质上的区别。

对于自行车的测试来说,它们也是一成不变的,就是为了在车子出厂之前,利用他们给车子打下个合格证。而软件呢,是变化的,不断成长的,主要体现在新功能的引入。可限于软件这种产品的复杂性,新功能的引入是有风险的,它们可能破坏既有的功能。所以在面对这些风险的时候,软件中的原有测试所扮演的角色,就是保护这些既有的功能在新事物的入侵下安然无恙。

所以说要用发展的眼光来看待软件测试,同一个测试,随着时间的推移,虽然说在本质上讲是不变的,但它们在不同的生命阶段扮演的角色的侧重点是不一样的。

如果一个接口后面,只有一个实现它的类,那么这个接口,还真正有必要吗?

我觉得百分之九十的人提出这个问题时的情形是应该这样的:他没有用接口的习惯,在能少则少的指导思想下,只要是只有一个类,他绝对不用接口。突然有一天,他看到他的一个同事,在写具体的类之前,先把接口写好了并且写了相应的文档解释这个接口应该提供的服务,可之后只实现了一个类。他就问了,这个接口有必要吗?这个问题暂且先放下不说,看完另外一种情形,在加讨论。

试想我们有一段写好的代码,用了接口,接口后面,有两个具体实现此接口的类,突然有一天,我们发现,其中有一个类,以后不会再被用了,于是乎就把这个类给删了,这时只剩下了一个类,那么敢问,是不是把接口也给删了呢?

以上两个问题,其实都是在对同一件事发问,但是出发点不一样,但我觉得这两个问题结合在一起回答,会使答案更加清晰。

先分析第一种情况,有意思的是,他那个同事为什么在实现类之前,先把接口给定义了? 先实话告诉你,我就是那个喜欢用接口的人。所以我就都交代了先。我之所以先写了个接口,倒不是因为,一下就想到了,接口后面会有多少具体实现的类,而是为了想先搞定依赖于此服务的另一模块。打个比方吧,我正在写的这个模块需要一个缓存服务,但缓存不是它的核心业务,所以说它根本不关心缓存具体是怎么实现的。所以我就写了个抽象的接口先,并清楚的描述此接口应该提供的服务,至于如何实现,一字没提。更加给力的是,在写那个具体的缓存类之前,依赖于它的这个模块的单元测试我也写好了。欢乐吧!最后,这个缓存怎么搞,我可以选择自己搞,也可以给外包出去,反正,协议都已经写好了。

好了,先小结一下,在这里接口的用处很明显,它定义了一个服务是什么,而不是说,服务是如何实现的。如此看来,写接口有利于专注在核心业务上,有利于软件的模块化,有利于增加代码的文档解释率,有利于提高模块的可测试性,并且还可以通过分布式工作加快开发进程。当然,一切的前提,是我们可以抽象出来我们所需要的服务。否则,都是扯淡。

现在再看第二种情形,我们需要把接口给删了吗?恐怕,想删都没那么容易了,为什么?因为,这个接口在测试里被用了,这个接口上有完善的文档。当然,估计也没多少人会提出要不要删它的这个问题。

得嘞,在看看开头的问题,是不是显得很幼稚?

0%