王辉的博客

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

我想结合两个概念来谈职业规划,一是服务精神,另一个是技术。我想说明的结论是,技术需要服务引领,服务需要技术驱动。一旦理解他们之间的联系,职业规划就很容易谈了。

为什么要结合两者谈?首先,作为一个热情洋溢的程序员,我工作已有六年,主攻低延迟,高性能的金融市场数据系统。对技术的追求是执着的,除了白天敲代码,业余还参加各种算法比赛,上公开课,写技术博客。因此,不可能避技术不谈。其次,我开始意识到服务精神的重要性,我体会到培养服务精神,有利于超越自我,有利于搭建反馈循环,让别人喜欢用你做的产品,给你带来快乐。

技术的困境

技术,对于一个程序员来说,是一件美好,令人着迷的东西。你看看知乎上,有人问,有没有一段代码,让你觉得人类的智慧也可以璀璨无比?, 还有人问你认为最优美的数据结构是什么?。就像欣赏一件艺术品一样,你可能深深的折服于某个拥有对数复杂度的数据结构,你可能会享受微妙级低延期给你带来的快感,你可能自豪于你所向无敌的智能战斗机器人。

技术是一个有魔力的东西,可以给你带来快乐,但它也有困境。当你倾注了你的青春开发出的产品没人用的时候;当你写了上万行代码却未得到一个赞美之词的时候;当你面对着层出不穷的技术怎么学也学不完的时候;你会无助,困惑,感叹什么时候是个头。

这是技术的无力的一面,技术的困境。为什么?因为我们过分的看重技术,以为事情的成败,取决于技术的优劣。以为只要我学的越多,我就能立于不败之地。

服务精神

深爱技术的人,往往特别单纯。为了技术信仰,可以挣的面红耳赤。我曾经就有过类似的经历,当时以为是不善沟通,并且还参加了一个非暴力沟通的培训,其实是忽略了一个更深层次的原因,就是缺乏服务精神。

所谓服务精神,在我看来是一种意识,一种指导思想。就是当你做事情的时候,是不是先想到你服务的对象,是不是想着如何让它们满意。在服务行业里,这很容易辨别,不管是在餐馆,理发厅,还是水果店,你都知道你服务的对象是谁,因为他们会出现在那里,并且你能从他们的是否有笑容中判断他们满意了吗。

而在软件行业里,很多程序员接触不到最终客户,因此往往易缺乏这些意识,更糟糕的是,有些程序员认为他们的服务对象是技术不是人,体现就是他们不善解人意。

技术和服务

最近对我感触比较大的一件事是云栖大会上宣布成立的达摩院。马云说,阿里巴巴要有社会责任感,要把阿里的技术,阿里发展带来的红利更好的服务于社会,服务于人民。在别人看来,好像在说大话。可这些话却深深的触动了我。对于一个在服务精神上开始觉醒的人,这无疑是一颗定心丸。当一个人,一个企业,都要富可敌国的时候,仍然想着要服务别人,对于普通的我呢。

从另外一个角度来看,达摩院的第一批员工有谁?他们都是清一色的技术人员,都是在各自领域出类拔萃的人才。为什么?因为他们要解决的问题,是全社会,全人类都关注的问题。我们服务的对象越高,所要求的技术水平也就越高。

在说一下第三点,就是达摩院的盈利模式。他们不是漫步目的的搞科研,他们要解决实际问题,解决实际问题,服务于他人,既能带来利润,又能带来快乐。所以,只有技术,没有服务,也是生存不了的。

这就是我开篇的时候所说的,技术需要服务引领,服务需要技术驱动。

职业规划

在我们理解好了技术和服务之间的关系之后,我们在职业规划上就不难做出正确的决定。

如果你是一个热衷于技术的程序员,那么不要纠结这条路能走多远,不要顾虑要不要做管理。因为如果你立志接解决当今社会所存在的问题,你必将找到你的用武之地,看看达摩院的那些技术流们。

如果你还在忧虑,好多新东西学不完,那么请把你的关注点放在如何提高你的服务质量上,他可以更好的知道你该如何学习,学什么。

二分法(BinarySearch),是一个简单却很容易写错的算法。据说,Jon Bentley(k-d tree的发明者)做过一次调查,90%的程序员,花上几个小时,也写不出一个正确的二分算法。我写了几个,发现成败在细节上。后来总结出了一个经验,就是把重点放在二分法那个擦肩而过的美好瞬间。下面通过几个例子来体会。

找寻插入位置

1
2
3
4
5
6
7
8
9
说,你有一个整数数组,已经排序完毕,升序,并且没有重复的元素。
问,给你一个整数,求它在数组中的插入位置。

拿[1, 4, 7, 8]来说,
0 -> 0,
2 -> 1,
5 -> 2,
7 -> 2,
9 -> 4

我写了如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int searchInsert(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while(start <= end) {
int mid = start + (end - start) / 2;
if(nums[mid] == target) {
return mid;
} else if(target > nums[mid]) {
start = mid + 1;
} else {
end = mid - 1;
}
}
return start;
}
}

确保相遇

当start比end小或相等的时候,我们都继续找,而当start超过end,他们擦肩而过的时候,我们停止循环。为了避免他们停滞不前,每次我们要么增加start,要么减小end,确保他们擦肩而过。

擦肩瞬间

由于每次要么start增加,要么end减小,只有一方会发生变化,必定有一个瞬间,他们相遇落在同一个位置上。这次相遇是最令人心动的地方,也是该算法,最需要注意的地方。请问在上述代码中,从while循环出来的时候,为什么返回的是start,而非end?请认真思考一下,再看我的分析。

让我们把注意力放在他们擦肩相遇的瞬间,start和end站在了同一个位置,start看一下目标值和眼下的值,如果发现目标大,start往前一步,超越了end,新的start必定是插入的正确位置;如果发现目标小,start让end走一步,start留在了原地,正好占用当下的插入位置,因为要插的值正好比当下的小。

是不是这个瞬间很美好?

求平方根

1
2
3
求整数的平方根,结果要整数。比如:
sqrt(16) = 4,
sqrt(15) = 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int mySqrt(int x) {
if(x == 0) return 0;
if(x == 1) return 1;
int start = 1;
int end = x / 2;
while(start <= end) {
int mid = start + (end - start) / 2;
int square = mid * mid;
if(square == x) return mid;
if(square > x || (square / mid != mid) ) {
end = mid - 1;
} else {
start = mid + 1;
}
}
return end;
}
}

为什么出了while循环,我们返回的是end?擦肩而过的瞬间发生了什么?

求区间的头和尾

1
2
3
说,有一个整数数组,按升序排序完毕
求,目标值得首次和最后出现的位置,找不到返回[-1, -1]。时间复杂度必须控制在o(logn)
比如在[1, 2, 3, 3, 3, 4, 4]求3的区间,答案是[2, 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public int[] searchRange(int[] nums, int target) {
return new int[]{
searchLow(nums, target), searchHigh(nums, target)};
}

private int searchLow(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while(start <= end) {
int mid = start + (end - start) /2;
if(target <= nums[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
}
if(start > nums.length - 1) return -1;
if(nums[end + 1] == target) return end + 1; //回头看
return -1;
}

private int searchHigh(int[] nums, int target) {
int start = 0;
int end = nums.length - 1;
while(start <= end) {
int mid = start + (end - start) /2;
if(target >= nums[mid]) {
start = mid + 1;
} else {
end = mid - 1;
}
}
if(end < 0) return -1;
if(nums[start - 1] == target) return start - 1;
return -1;
}
}

你能体会到擦肩而过的美妙吗?在找区间头的过程中,end在相遇的位置上看了一下目标值和眼下值,如果发现目标值小或正好,end就会往前走一步,翻过start。最后end回头看,那么相遇时的位置,要么区间头找到了,要么目标值不存在。

总结

二分法,是有规律可寻的,首先就是要确保start和end总有一方前进,进而确保擦肩而过跳出while循环。其次就是根据需求,决定在相遇的瞬间,是start还是end动。掌握了这个规律,你定能写出来正确的二分法。

法国工程师学位的我,后台开发六年有余。如今深深爱上了数学,感慨以前学得不够。

首先,无论是哪方面的程序员,都要大量的投资算法和数据结构。因为这是你的基本技能,就像棋手需要知道每个棋子的走法一样。在算法学习的过程中,你绕不过的就是衡量算法的效率,也就不可避免的学习Big O的知识。Big O,需要你了解函数和极限的一些知识,比如说,f(n) = O(g(n))是这样定义的,当n足够大的时候,你总可以找到一个常数c,使得c * f(n) >g(n)。除了效率,还有如何证明算法的正确性。这就要求掌握一些证明方法,比如反正法,递推归纳法。

其次,如果你遇到了性能方面的问题,需要降低延迟,增加吞吐的时候,你很有可能需要去设计高性能的队列系统,这时排队论的知识就显得尤为重要了。排队论要学好,概率论是基石。队列在计算机系统中随处可见,操作系统中的进程调度问题,基于队列解藕生产者消费者的架构问题等等。

接下来如果你对离散优化问题感兴趣,如说背包问题,旅行者问题,当你需要优化一个目标函数的时候,你往往会用到梯度下降法,而理解梯度下降法,对微积分要有很好的理解。更不用说在人工智能,机器学习领域,到处都得优化目标函数,降低预测误差,想走的远,是必须要有数学基础的。

然后再说一下,线性代数,特别是在人工智能里面,很多基本的模型都是线性的,你会随处看到矩阵的用处,单纯的只知道矩阵如何相乘是远远不够的,要理解的跟深入,矩阵是一个函数,用来转换矢量,既然是函数,就会具有很多函数的特点。你应该懂得如何证明 (矩阵甲 x 矩阵乙)x 矩阵丙 = 矩阵甲 x (矩阵乙 x 矩阵丙)

最后,说点我也没有搞明白的,分布式系统中的鼻祖级论文,Lamport的时间,时钟,和事件的顺序据他所说是他从相对论中的时间和空间的关系中得到的灵感。由此可见,不仅是数学,物理也很重要。

总结一下吧,当时是学生的时候,学数学不知道怎么用,只知道考试,回想起来 ,如果应用场景更清晰的话,我会投入的更多。

数学学习资源

入春以来,鼻子痒,堵,流鼻涕,像感冒一样,其实是过敏。看了个耳鼻喉的专家,她的一番话,竟让我联想到了软件开发中的测试问题。就像Dijkstra说的,测试只能表明Bug的存在,而非它们的绝迹

过程是这样的,我做了一个抽血实验,指标说明我没有过敏。医生很耐心的跟我讲,抽血结果说你没过敏,并不代表你没过敏。因为抽血化验只针对一部分过敏原。结果是好的,只能说明你对这些物质没有过敏反应。并不能代表你对其他东西也不过敏。像什么狗毛了,猫毛了,花粉了什么的,并不在化验的范围内,所以你有可能对他们过敏。也没有必要做更多的测试,因为很可能你又测了十几种,还是没找到过敏原,也只能证明对这些测试的东西没反应,而说明不了全部。

话,绕来绕去,就是告诉我,我知道你对哪些东西不过敏,不知道你对什么过敏。这就像软件测试一样,你写了一个测试,我们只能知道,在这种情况下,软件会好用,但并不知道,在哪些情况下不好用。

我举个简单的例子,比如下面的冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BubbleSort implements Sorting {

@Override
public <T> void sort(T[] elems, Comparator<? super T> comp) {
for (int out = elems.length - 1; out > 1; out--) {
for (int in = 0; in < out; in++) {
if (lessThan(elems[in + 1], elems[in], comp)) {
swap(elems, in, in + 1);
}
}
}
}
}

针对下面的输入,都能排好

1
2
3
Arrays.asList(2, 3, 4, 8, 5, 7, 1, 6);
Arrays.asList(2, 8, 3, 4, 5, 7, 1, 6);
Arrays.asList(5, 8, 4, 3, 2, 7, 1, 6);

但有一种情况排不好,你能看出来是为什么吗?

1
Arrays.asList(2, 3, 4, 8, 5, 7, 6, 1);

医生说,没必要做所有的测试,因为成本太大,并且不一定找到好的结果。但对软件测试而言,有没有好的办法呢。我是一个热情洋溢但比较笨的工程师,所以用了一种比较笨的方法,就是随机产生很多测试,暴力解决问题,这里你可以看得到,后来才意识到,在这种情况下,大家都在用一种叫基于属性的测试方法:Property-Based Test。有没有更暴力的呢,还真有,There are Only Four Billion Floats–So Test Them All!

一个过敏测试,一个软件测试,看上去两个没有联系的东西,可认真比较后,却惊喜的找到了他们的共同点。类比之后,反而对Dijkstra的那句话有了更加深入的理解。

  1. 服务精神:超越自我
  2. 服务精神:反馈循环

上次说服务精神的时候,选择了超越自我作为切入点。这次想从反馈循环的角度来谈谈服务精神。先撂个包袱,曾经有一个同事走了,我问他为什么,他说,因为他写的代码,很快就能部署到生产环境,用在客户手里。

什么是反馈循环

反馈循环,说白了就是有去有回,有来有往。你提供了一个服务,想知道服务的质量好不好,你就会问客户,然后根据客户的反馈提升服务的质量,然后把更好的服务,再次提供给客户,往往复复,越来越好。举个例子,你下了馆子,点了个鱼香肉丝,吃完,服务员问你,好吃吗?你说肉有点少。服务员记下来了,告诉给了大厨。第二天,你来了,又点了一个鱼香肉丝,真的发现肉多了,你很高兴,从此就常来吃饭。多亏了你这样的客户,饭馆的生意越做越好。

程序员,也一样,你写了个代码,但是不知道好不好,就想问问客户体验怎么样。然而,到这一步,往往是悲剧,发现没人可问,因为没人用。这就是我今天所想说的。让我们看看反馈循环的缺失所带来的危害,还有如何避免。

有用就是有人用

我出个上联:有用就是有人用。你对个下联。请仔细思考一分钟再往下看。

我很笨,只能想到这个下联:没用就是没人用。真不是逗你玩,什么叫有用?!如果都没人用,还有什么好说的。道理很简单,可忽略了它,会有很多麻烦。不少初创公司就特爱犯这个错误。有一天,你有个好点子,激动的不行,拉上一些朋友,热火朝天的干了起来,憧憬着融资上市时的样子,干了好久,终于要问世了,然而发现,你做了一个没用的东西,对!一个没人用的东西。浪费了时间,感情,和精力。

一个没有服务精神的人,首先想到的不是他的服务对象(很可能想的是成功了怎么办呀),自然,也就不会想着去问回馈。等想起来了,发现已经走得太远了,做了一个没人用的东西。因此,要避免浪费时间,感情和经历,就要首先培养服务精神,搭建反馈循环。

搭建反馈循环

其实,这并不是一个新的话题。很多前辈在不同的领域已经做了很多研究。比如精益创业里的”构建-测试-学习“循环;测试驱动里的”红-绿-重构“循环;还有敏捷开发里的客户融入开发团队的做法;持续交付,等等。

在所有方法的背后,我所体会到的精髓就是服务精神,而服务精神的精髓就是先想到别人。

拿了工资就不算浪费生命吗?

再说最后一点吧,也是我工作六年多了,才有的感触,和服务精神有关系。很多大公司,他们的商业模式往往是经过验证的,肯定是有客户的,否则也不会活那么久,招那么多人。然而作为一个程序员,我们能碰到的往往是大山的一角。写出的代码,从你的文档编辑器到最终用户的手里,往往需要很久,短了几个月,长了可能几年,因为你的项目就是交付不了。这种情况下,我们是得不到用户反馈的。

我之前的想法是,这关我鸟事?工资照常发,代码就算没白写。可培养了服务精神之后,我改变了想法,会有点忧伤。你想想,你用了三年时间写出来的代码,没人用。对,没人用!你不觉得,你这三年可以做一些更有意义的事情吗?

如果有一天我换工作了,我想我的原因,应该和开头那同事说的一样。

  1. 服务精神:超越自我
  2. 服务精神:反馈循环

一位同事最近要离职了,去更远的地方。从他身上,我看到了很多闪光点,其中最亮的就是服务精神。餐厅里,小二接待食客,我们称之服务。作为程序员的我们,用代码给人带来便利,也是服务。

有服务精神的人,更愿意去挑战自我,超越自我。

同样是开发一个API,服务精神缺失的程序员,文档不写,测试没有,每个月能按时领工资,就心满意足了。

有一定服务精神的程序员,他会想到使用API的人。他会写个文档解释API的设计意图和使用方法。他会写一些单元测试,保证代码的正确性。达到这个阶段的程序员,已经可以算得上优秀程序员了。你可以看看周围的同事,有多少人练到了这个段位。

有强烈服务精神的人。他们会想的更远,更周到。就像你去海底捞吃火锅,在你等人的时候,人家怕你无聊,给你提供水果瓜子。这个阶段的程序员,非常注重用户的感受。他们首先要确保API可用易用性。其次,他们还会想到API的性能,让用户在这里花费的时间越少越好。最后,他们还会考虑,万一服务出现问题的时候,如何帮助用户快速的找到问题的症结。

设计一个能用的API容易,但设计一个全面周到的API,难!有服务精神的人明白,用户的赞许和微笑才是他们快乐的源泉,所以他们并不会止步于可用就好,而是追求卓越的服务质量。这种动力会引领他们不断的挑战自我,超越自我。如果你身边有这样的同事,不管是你和他一起开发API,还是使用他的API,祝贺你,你中奖了。

2018年6月更新 已离职,再见,Murex!

三年前,我谈了一次Murex面试。之后,陆陆续续一直有朋友打听Murex。这次结合我的经历和见闻,从学习氛围的角度,再谈一下Murex。

为什么谈学习氛围?除了技术团队之外,我还自愿加入了Murex Dev Branding团队。Branding团队的目的,是为了提升Murex品牌,吸引优秀的软件工程师。想要吸引优秀的程序员,必须先了解程序员们的需求。为此,我们做了大量的需求调研。结果显示,程序员们最重要的需求,是学习!

DevDailies

爱学习,但没时间?Daillies帮助你在繁忙的工作中,找到学习的时间。

我的同事Jonathan,在Devoxx讲了他的DevDaillies的经历,Les Dailies: une nouvelle façon agile d’apprendre en entreprise。大致的做法是,公司内部的一个同事,选择一个他感兴趣的话题,每天十分钟,直接在办公室里给大家传授知识。你只需要转动一下椅子,就可以享受到一个精心准备的知识点。目前,同事们分享了Dailly C++, Dailly Java, Dailly Functional Programming。

Java,C++社区

每个小组所处的环境不同,使用一种编程语言的方式就会不一样。实时金融数据小组,使用Java,注重性能的优化。服务框架小组,更看重Java的扩展性,比如依赖注入。在不同的小组,不同的程序员之间分享编程语言的不同特性,是Java,C++社区的任务。除了分享,如果你有问题,也可以像以上社区寻求帮助。

Conference JavaOne, CppConf, Devoxx

Murex的程序员,每年有机会参加各种各样的技术会议,远到三番的JavaOne,近到骑车十分钟Le Palais de Congress的Devoxx。我本人上一年九月份公费参加了JavaOne,上周刚结束了Devoxx。这些会议,可以帮助程序员在正常的工作中,抽出身来,呼吸一下新鲜空气,扩大视野。

TekTalk

不仅Murex内部之间的人,可以通过社区互相帮助,互相分享知识。我们还会时不时的邀请一些外面的热情人士给我们做演讲。上一次的精益创业的演讲就是在TekTalk的情境下进行的。下图是RedHat的朋友给我们介绍Vert.x。

Meetup

为了方便程序员们在自己的公司就能参加Meetup,Murex会时不时为Meetup提供场地。

培训

Murex目前处在转型的重要关头,如何处理技术负债,如何在老代码的基础上继续创新是一大挑战。为此Murex组织一系列的和Craftmanship相关的培训,其中最重头的就是Working Effectively with Legacy Code的作者Michael Feathers的到来。

CodingDojo

喜欢算法和数据结构吗?每周四的中午12点到14点,算法爱好者相聚CodingDojo,训练我们解决问题的能力。从LeetCode,到CodeJam,到Project Euler,作为程序员,擦亮你的枪杆子!

看这张图,拿披萨盒子当演算纸,厉害了,我的兄弟!

CodinGame/HashCode Hub

竞技编程爱好者,在Murex也能找到战友。同事Manwe在Devoxx分享了他的AI战斗秘籍,每逢CodinGame的比赛,Murex都会组织一个CodinHub。

结语

如果你也有学习的需求,却苦恼周围没有学习的氛围,那你需要怎么办?请仔细想一想。
最后,希望这篇文章能更生动地给大家展现一下Murex。

0%