你好呀,我是歪歪。
我最近其实在考虑一个问题:
关于程序员来说,怎样才算是在写有“技能含量”的代码?
为什么会想起考虑这个看起来就很厉(装)害(逼)的问题呢?
因为这便是知乎上的一个问题:
www.zhihu.com/question/37…
第一次看到这个问题的时分,我很快的就划过去了,彻底就没有关注这个问题。可是便是看了那么一眼,这个问题就偶然不经意间在脑海中浮现出来。
然后隔了一段时刻,中午刷知乎的时分这个问题又冒出来了。
好巧不巧,也是那天中午,我看到了这样的一个面试题:
看到这个面试题的第一眼,我就想起了 Dubbo 服务中的一个预热功用。
在结合知乎这个问题,我其时就觉得:Dubbo 服务的预热源码在我看来便是一个“有技能含量”的代码呀。
这一块功用编码确实一点也不复杂,主要是能体现出编码的人关于 JVM 和 RPC 方面的“内功”,可以意识到,因为 JVM 的编译特色,再加上 Dubbo 在架构中充当着 RPC 框架的角色,所以为了服务最大程度上的安稳,可以在编码的层面做必定的服务预热。
可是写完相关答复之后,从谈论区来看,基本上是清一色的吐槽,说我举得这个比方和问题相悖。
比方我截取点赞最高的两个谈论:
看完这些吐槽之后,我觉得这些吐槽是有道理的,我的比方举得确实欠好,十分的片面。
为了更好的引出这个话题,我先转移并扩充一下我其时的答复吧。
趁便也算是答复一下刚刚说的那个面试题。
服务预热
下面这个办法,只要两行,可是这便是 Dubbo 服务预热功用的中心代码:
org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance#calculateWarmupWeight
看一下这个办法在框架里边调用的地方:
当咱们不指定参数的情况下,入参 warmup 和 weight 是有默认值的:
也便是在用默认参数的情况下,上面的办法可以简化为这样:
staticintcalculateWarmupWeight(intuptime){
//intww=(int)(uptime/((float)10*60*1000/100));
intww=(int)(uptime/6000);
returnww<1?1:(Math.min(ww,100));
}
它的入参 uptime 代表服务发动时刻,单位是毫秒。回来参数代表当时服务的权重。
根据这个办法,我先给你搞个图。
下面这个图,x 轴是发动时刻,单位是秒,y 轴是对应的权重:
从图上可以看出,从服务发动开始,每隔 6 秒权重就会加一,直到 600 秒,即 10 分钟之后,权重变为 100。
比方当 uptime 为 60 秒时,该办法的回来值为 10。
当 uptime 为 66 秒时,该办法的回来值为 11。
当 uptime 为 120 秒时,该办法的回来值为 20。
以此类推…
600 秒,也便是十分钟以及超越十分钟之后,权重均为 100,代表预热完成。
那么这个权重是干啥用的呢?
这个就得结合着负载均衡来说了。
Dubbo 提供了如下的五种负载均衡战略:
- Random LoadBalance :「加权随机」战略
- RoundRobin LoadBalance:「加权轮询」战略
- LeastActive LoadBalance:「最少活泼调用数」战略
- ShortestResponse LoadBalance:「最短响应时刻」战略
- ConsistentHash LoadBalance:「共同性 Hash」 战略
除了共同性哈希战略外,其他的四个战略都得用到权重这个参数:
权重,便是用来决议这次恳求发送给哪个服务的一个关键因素。
我给你画个示意图:
A、B、C 三台服务,A,B 的权重都是 100,C 服务刚刚发动。
作为一个刚刚发动的服务,是不适合承受突发流量的,以为运行在服务器上的代码还没有通过充分的编译,主链接上的代码或许还没有进入编译器的 C2 阶段。
所以按理来说 C 服务需求一个服务预热的进程,也便是刚刚发动的前 10 分钟,应该有逐渐承受越来越多的恳求这样的一个进程。
比方最简略的加权随机轮询的负载均衡战略中,关键代码是这样的:
org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance#doSelect
看不了解不要紧,我再给你画个图。
在 C 服务发动的第 1 分钟,它的权重是 10:
所以代码中的 totalWeight=210,因此下面这行代码便是随机生成 210 之内的一个数字:
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
在示意图中有三个服务器,所以 for 循环中的 lenght=3。
weights[] 这个数组是个啥玩意呢?
看一眼代码:
每次循环的时分把每个服务器的权重汇总起来,放到 weights[] 里边。
在上面的比方中也便是这样的:
- weights[0]= 100(A服务器的权重)
- weights[1]= 100(A服务器的权重)+100(B服务器的权重)=200
- weights[2]= 100(A服务器的权重)+100(B服务器的权重)+10(C服务器的权重)=210
当随机数 offset 在 0-100 之间,A 服务器处理本次恳求。在 100-200 之间 B 服务器处理本次恳求。在 200-210 之间 C 服务器处理本次恳求:
也便是说:C 服务器有必定的概率被选上,来处理这一次恳求,可是概率不大。
怎样概率才能大呢?
权重要大。
权重怎样才大呢?
发动时刻长了,权重也随之增大了。
比方服务发动 8 分钟之后,就变成这样了,C 服务器被选中的概率就大了许多:
最后到 10 分钟之后,三台服务器的权重共同,承当的流量也就几乎共同了。
C 服务器承当的恳求跟着服务发动时刻越来越多,直到 10 分钟后到达一个峰值,这就算是经历了一个预热的进程。
前面介绍的便是一个预热的手段,而相似于这样的预热思想你在其他的一些网关类的开源项目中也能找到相似的源码。
可是预热不只是有这样的一个完成办法。
比方阿里根据 OpenJDK 搞了一个 Alibaba Dragonwell,其实也便是一个 JDK。
github.com/alibaba/dra…
其中之一的特性便是预热:
除了预热这个点之外,我还在知乎的答复中提到了最少活泼数负载均衡战略的完成 LeastActiveLoadBalance.java:
从初始化提交之后,一共就没修改过几回。
你也可以比照一下,初始版别和当时最新的版别,中心算法、中心逻辑基本没有发生改动:
除了这个战略之外,其他的几个战略也是差不多相似的“安稳”。
从谈论说起
我在知乎答复这个问题的时分,没有上面这一末节写的那么多,可是中心内容大约便是上面这些。
在答复说提到预热,我是想表达看似不起眼的两行代码,背面仍是蕴含了十分多的深层次的原因,我觉得这是有“技能含量”的。
而提到负载均衡战略的完成,多年来都没有怎样改动,我是想表达这些重要的、底层的、基础的代码,写好之后,常年没动,阐明最开始写出来的代码是十分安稳的。能写出这样安稳的代码,我觉得这也是有“技能含量”的。
接着带你看看谈论区:
谈论几乎是清一色的不认可这个答复。可是我前面说了,在答复这个问题的时分,确实觉得我的答复是比较靠近主题的。
可是看了谈论之后我想了解了,为什么这是一个欠好的答案,正如谈论区说的:
比方举得不可,只不过是因为要解决的问题一向没有发生改动,所以解决方案也就相对安稳。
首先这样的代码原本就和绝大部分程序员实践作业中写的代码差距过大,框架的源码值得学习,可是在实践开发中的学习含义不大。
并且谈论区也提到了,绝大多数程序员根本就没有机会去写这样的比较考验“技能才能”的代码。
这也确实是现实,少部分中间件的开发和绝大部分业务逻辑的开发,是两个思维形式彻底不一样的程序员群体。
然后我看了一下这个话题下的高赞答复:
其实高赞答复就这一句话:
一个优秀的程序员,在接到一个要编写“消灭地球”的使命的时分,他不会简略的写一个destroyEarth()的办法;而是会写一个destroyPlanet()的办法,将earth作为一个参数传进去。
这才是比较靠近咱们实践作业的一个比方。
就着这个比方,我换个惯例一点的需求来说,比方让你接入一个微信付出的需求:
你或许会这样去界说一个类:
publicclassWechatPayService{
publicvoidwechatPayment(){
//微信付出相关逻辑
}
}
当要运用的时分,就把 WechatPayService 注入到需求运用的地方,没有任何缺点。
可是跟着而来的一个需求是让你接入付出宝付出。
你当然是自然而然的搞了一个相似的类:
publicclassAliPayService{
publicvoidaliPayment(){
//付出宝付出相关逻辑
}
}
可是你写着写着发现:诶,怎样回事,感觉付出宝的这套逻辑和微信的有许多相似之处啊,开发的关键步骤感觉都一模一样?
于是你界说了一个接口,运用战略形式来专门干“付出”相关需求:
publicinterfaceIPayService{
/**
*付出笼统接口
*/
publicvoidpay();
}
在我看来,这是一个十分惯例的开发方案,我乃至在拿到“微信付出”这个需求的时分,我就驾轻就熟的知道应该运用战略形式来做这个需求,为了便利以后的开发。
可是,我这个“驾轻就熟”也是有一个熟悉的进程的,我也不是一开始,一入行,一作业就知道应该这样去写的。
我是在作业之后,看了大量的实践项目里边的代码,看到项目在用,觉得这样很有用,项目结构也很明晰,才在其它的相似的需求中,刻意的模仿学习、了解、运用、打磨,慢慢的融入到了自己的编码习气中去,因为过分熟悉,我逐渐的以为这是没有技能含量的东西。
直到后来,有一次我带着一个实习生做一个项目,项目中有一个排行榜的功用,排行榜需求支持各个维度,前端恳求的时分会告知我当时是需求展示哪个排行榜。
在需求剖析、体系规划以及代码落地阶段我都自然而然的想到了前面说到的战略形式。
后来实习的同学看到了这一段逻辑,给我说:这个需求的完成办法真好。如果让我来写,我绝对想不出这样的落地方案。
可是我觉得这便是个惯例解决方案而已。
我举这个比方是想表达的意思便是关于“技能含量”这个东西,每个人,每个阶段的了解是天壤之别的。
与我而言,站在我现在正在写这篇文章的时刻节点上,我觉得有技能含量的代码,便是别人看到后乐意运用,乐意模仿,乐意告知后面来的人:这个东西真不错,你也可以用一用。
它可以小到一个项目里边的只要寥寥几行的办法类,也可以大到一套行业界问题的完好的技能解决方案。
除了这个比方外,我还想举我刚刚参加作业不久,遇到过的别的一个比方。
需求说来也很简略,便是针对一个表的增修改查操作,也便是咱们常常吐槽的没有技能含量的 crud。
可是,我其时看到别人提交的代码时我都震动了。
比方一个新增操作,一切的逻辑都在一个 controller 里边,没有所谓的 service 层、dao 层,一把梭直接把 mapper 注入到了 controller 里边,在一个办法里边从数据校验到数据库交互悉数包圆了。
功用能用吗?
能用。
可是这样代码是有“技能含量”的代码吗?
我觉得可以说是毫无技能含量了,用现在的流行语来说,我乃至觉得这是程序员在“摆烂”。
我要根据关于这一段代码持续开发新功用,我能做什么呢?
我无能为力,本来的代码实在不想去动。
我只能确保在这堆“屎山”上,我新写出来的代码是干净的、明晰的,不持续往里边扔废物。
后来我读了一本书,叫做《代码整洁之道》,里边有一个规则叫做“童子军军规”。
军规中有一句话是这样的:让营地比你来时更干净。
类比到代码上其实便是一件很小的工作,比方只是改好一个变量名、拆分一个有点过长的函数、消除一点点重复代码,清理一个嵌套 if 语句…
这是让项目代码跟着时刻消逝而越变越好的最简略的做法,持续改善也是专业性的内在组成部分。
我觉得我关于这一点“规则”落实的仍是挺好的,看到一些不是我写的,可是我觉得可以有更好的写法时,并且改动起来十分简略,不影响中心功用的时分,我会主动去改一下。
我能确保的是,这段代码在通过我之后,我没有让它愈加紊乱。
把一段紊乱的代码,拆分的明晰起来,再后来的人乐意按照你的结构持续往下写,或者持续改善。
你说这是在写“有技能含量”的代码吗?
我觉得不是。
可是,我觉得这应该是在追求写“有技能含量”的代码之前,必需要具有的一个才能。并且是比写出“有技能含量”的代码愈加重要的一个基础才能。
延伸
以上便是我个人的一点观点,可是我还想延伸出一些别的东西。
比方在写文章之前,我也在其他网站上提出了这个问题。
大家见仁见智,从各个视点给出了不同的答复。
这也再次印证了前面我说的观点:
关于“技能含量”这个东西,每个人,每个阶段的了解是天壤之别的。
我把大家给我的回复贴过来,希望能对你也有协助:
再比方我最近在知乎上看到了这样的一个视频:
www.zhihu.com/zvideo/1542…
里边的主人公黄玄,说了这样的一段话:
这已经是别的一个维度的程序员,关于“什么是有技能含量的代码”的别的一个维度的回答了。
我远远达不到这个高度,可是我喜欢这个答复:
不断的传承下去,成为下一代软件,或者说下一代人类文明的柱石。我觉得可以去参与这样的东西,对我来说,或许是程序员的一种浪漫。
所以你呢,关于这个问题,你会给出什么样的答案呢?