大家好,我是伟伟。其实最近我在想一个问题:对于程序员来说,写出有“技术含量”的代码,怎样才算是呢?为什么会想到思考这个看似严肃(做作)有力(逼迫)的问题呢?因为这是知乎上的一个问题:https://www.zhihu.com/questio...第一次看到这个问题的时候,我很快就划过去了,根本没注意这个问题。不过只是看了一会儿,脑子里偶尔会不经意地冒出这个问题。然后过了一段时间,中午查知乎又出现了这个问题。巧合的是,那天中午,我看到了这样一道面试题:第一次看到这道面试题,我想起了Dubbo服务中的一个预热功能。结合知乎的提问,我当时的感受是:Dubbo服务的预热源码在我看来是一个“技术”代码。这段函数式编码确实一点都不复杂,主要是它能体现出编码者对JVM和RPC的“内功”,能够实现由于JVM的编译特性和Dubbo作为RPC的作用framework在架构作用上,所以为了最大程度的稳定服务,可以在编码层面做一定的服务预热。但是写完相关的回答,从评论区来看,基本都是吐槽,说我举的例子与问题相反。比如我截取了点赞最高的两条评论:看完这些抱怨,我觉得这些抱怨是有道理的。我举的例子确实不好,很片面。为了更好的引出这个话题,让我在当时承载和扩展我的答案。对了,也算是回答了刚才说的那个面试题。下面这个服务预热的方法只有两行,但是这个是Dubbo服务预热功能的核心代码:framework:当我们不指定参数时,输入参数warmup和weight有默认值:即在使用默认参数的情况下,上面的方法可以简化为这样:staticintcalculateWarmupWeight(intuptime){//intww=(int)(正常运行时间/((float)10*60*1000/100));intww=(int)(正常运行时间/6000);返回ww<1?1:(Math.min(ww,100));}它的入参uptime表示服务启动时间,单位为毫秒。返回参数表示当前服务的权重。基于这个方法,我先给大家做个图。下图中,x轴是启动时间,单位是秒,y轴是对应的权重:从图中可以看出,从服务启动开始,每6秒权重就会增加1seconds到600秒,也就是10分钟后,权重变为100。比如uptime为60秒时,该方法的返回值为10。当uptime为66秒时,该方法的返回值是11。当uptime为120秒时,这个方法的返回值为20。以此类推...600秒,也就是十分钟和十多分钟后,权重为100,表示温暖-up完成了。那么这个重量有什么用呢?这必须与负载平衡相结合。Dubbo提供了以下五种负载均衡策略:RandomLoadBalance:“加权随机”策略RoundRobinLoadBalance:“加权轮询”策略LeastActiveLoadBalance:“最少活跃调用”策略ShortestResponseLoadBalance:“最短响应时间”策略ConsistentHashLoadBalance:“一致性哈希”"策略除了一致性哈希策略外,其他四种策略都必须使用权重这个参数:权重是用来决定将这个请求发送到哪个服务的关键因素。我给大家画个示意图:有A、B、C三个服务,A和B的权重都是100,服务C刚刚启动。作为刚起步的服务,不适合接收突发流量,因为服务器上运行的代码还没有完全编译,主链接上的代码可能还没有进入编译器的C2阶段。所以按理说C服务需要一个服务预热过程,也就是在刚启动的前10分钟,应该有一个逐渐接受越来越多请求的过程。比如最简单的加权随机轮询负载均衡策略,关键代码如下:org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance#doSelect看不懂没关系,我'我再给你画一张。C服务启动的第一分钟,它的权重是10:所以代码中totalWeight=210,所以下面这行代码随机生成一个210以内的数:intoffset=ThreadLocalRandom.current().nextInt(totalWeight);图中有三台服务器,因此for循环中的lenght=3。weights[]这个数组是什么?看一下代码:每个服务器的权重在每个循环中求和并放入weights[]。在上面的例子中是这样的:weights[0]=100(服务器A的权重)weights[1]=100(服务器A的权重)+100(服务器B的权重)=200weights[2]=100(服务器A的权重)+100(服务器B的权重)+10(服务器C的权重)=210当随机数偏移量在0-100之间时,服务器A处理这个请求。100-200之间,服务器B处理这个请求。200到210之间,C服务器处理这个请求:也就是说:C服务器有一定的概率被选中处理这个请求,但是概率不高。概率怎么可能高?重量很大。为什么重量这么大?启动时间越长,权重越大。比如服务启动后8分钟就变成这样了,服务器C被选中的概率就高了很多:最后10分钟后,三台服务器的权重相同,他们的流量熊几乎是一样的。C服务器承担的请求随着服务启动时间的增加而增加,直到10分钟后达到峰值,这被认为是一个预热过程。前面介绍的是一种预热方式,类似这种预热思路的可以在其他网关开源项目中找到类似的源码。但预热并不是实现这一目标的唯一方法。比如阿里基于OpenJDK搭建了一个AlibabaDragonwell,其实就是一个JDK。https://github.com/alibaba/dr...其中一个特点就是预热:除了预热点,我在知乎.java的回答中也提到了最不活跃负载均衡策略LeastActiveLoadBalance的实现:自初次投稿,没修改过几次。也可以对比一下初始版本和现在的最新版本,核心算法和核心逻辑基本没有变化:除了这个策略,其他几个策略几乎和“稳定”差不多。从评论来看,我在知乎上回答这个问题的时候,写的没有上面那么多,但是核心内容大概就是上面这些。当我回答说提到预热时,我想表达的是看似不起眼的两行代码背后有很多深层次的原因。我认为这有“技术含量”。说到负载均衡策略的实现,多年来一直没有变化。我想表达这些重要的、低级的、基本的代码。的。能写出这么稳定的代码,我觉得也是有“技术含量”的。那我带大家去评论区:几乎所有的评论都不赞成这个答案。但是我之前说过,在回答这个问题的时候,我确实觉得我的回答比较贴近主题。但看完评论后,我想明白为什么这是一个糟糕的答案。正如评论区所说:例子不够好,但是因为要解决的问题没有改变,所以解决方案比较稳定。首先,这样的代码与大多数程序员在实际工作中编写的代码相去甚远。框架的源码值得学习,但在实际开发中参考价值不大。而且评论区还提到,绝大多数程序员根本就没有机会写出这种考验“技术能力”的代码。这确实是事实。一小部分中间件的开发和大部分业务逻辑的开发是两批思维模式完全不同的程序员。然后在这个题目下看了高赞的回答:其实高赞的回答就是这样一句话:当一个优秀的程序员接到一个写《毁灭地球》的任务时,他不会简单的写一个destroyEarth()方法;相反,将编写一个destroyPlanet()方法,并将地球作为参数传入。这是一个比较接近我们实际工作的例子。这个例子,我换一个更常规的需求,比如允许你接入微信支付的需求:你可以这样定义一个类:publicclassWechatPayService{publicvoidwechatPayment(){//微信支付相关逻辑}}想用的时候,在需要用到的地方注入WechatPayService,就没什么问题了。但是随之而来的一个需求就是让你接入支付宝支付。当然,你自然而然的创建了一个类似的类:publicclassAliPayService{publicvoidaliPayment(){//支付宝支付相关逻辑}}但是你写了发现:咦,怎么回事,感觉支付宝的逻辑和微信有很多相似之处,和开发的关键步骤感觉一模一样?所以你定义一个接口,使用策略模式专门做“支付”相关的需求:开发计划,甚至当我拿到“微信支付”的需求时,我就知道我应该使用策略模式来满足这个需求,以方便以后的开发。但是,也有一个熟悉自己“知行道”的过程,刚入行工作的时候,一开始并不知道自己应该这样写。工作之后看了很多实际项目中的代码,看到项目在使用。觉得很实用,项目结构也很清晰,所以在其他类似需求中刻意模仿学习、理解、应用,打磨,慢慢融入自己的编码习惯。因为太熟悉了,渐渐觉得这是个没有技术含量的东西。直到后来,我曾经带过一个实习生去做一个项目。项目中有一个排行榜功能。排行榜需要支持各种维度。前端请求的时候会告诉我当前需要显示哪个排行榜。在需求分析、系统设计、代码实现阶段,我很自然地想到了上面提到的策略模式。后来练习的同学看到这个逻辑跟我说:这个需求的实现真好。如果让我来写,我绝对不会想到这样的落地方案。但我认为这只是一个通用的解决方案。我举这个例子表达的意思是,对于“技术含量”,每个人在每个阶段的理解是完全不同的。就我而言,站在我写这篇文章的时间点上,我认为有技术含量的代码,就是别人看到后愿意使用,模仿,并告诉后来的人:这东西真不错,你也可以用。它可以小到一个项目中只有几行代码的方法类,也可以大到一个完整的行业问题技术解决方案。除了这个例子,我想再举一个我工作不久后遇到的例子。需求也很简单,就是一个表的增删改查操作,也就是我们常吐槽的crud,没有技术含量。然而,当我看到别人提交的代码时,我惊呆了。比如一个新的操作,所有的逻辑都在一个controller里面,没有所谓的service层,dao层,一个shuttle直接把mapper注入controller,从数据校验到数据库交互都在一个里面方法。该功能是否有效?可以使用。但这段代码是不是有“技术含量”的代码?我觉得可以说是没什么技术含量。用现在的流行语,我什至认为这是一个“烂”的程序员。我想在这段代码的基础上继续开发新的功能,怎么办?没办法,原来的代码真的不想动了。我只能保证在这堆“狗屎山”上,新写的代码干净、清晰,不会继续往里面扔垃圾。然后我看了一本书,名叫《代码整洁之道》,里面有个规矩叫《童子军规矩》。兵法中有一句话是这样说的:营地比来时还干净。类比到代码其实是一件很小的事情,比如只是改了个变量名,拆分了一个有点长的函数,剔除了一点点重复的代码,清理了一个嵌套的if语句……这是是什么让项目代码最简单的事情就是随着时间的推移变得更好,持续改进是专业精神的内在组成部分。我想我已经很好地执行了这个“规则”。当我看到我没有写的东西,但是我觉得可以有更好的写法,而且改的很简单,不影响核心功能的时候,我就会主动去改。我可以保证的是,这段代码经过我之后,并没有让它变得更加混乱。把一段乱七八糟的代码拆分成清晰的,以后人家愿意按照你的结构继续写或者改进。你是说这是在写“技术”代码吗?我不这么认为。不过,我觉得这应该是追求写“有技术含量”的代码之前必须具备的能力。而且是比写“有技术含量”的代码更重要的基本能力。上面的扩展只是我个人的观点,但我还想扩展一些其他的东西。比如在写文章之前,我也在其他网站上提出过这个问题:https://segmentfault.com/q/10...每个人的看法不同,从不同的角度给出了不同的答案。这也再次印证了我之前说的一点:每个阶段每个人对“技术含量”的理解是完全不同的。把大家给我的回复贴出来,也希望能帮到你:再比如最近在知乎上看到这样一个视频:https://www.zhihu.com/zvideo/...里面的主人公黄轩说大概是这样的:这已经是程序员的另一个维度,也是回答“什么是有技术含量的代码”的另一个维度。我还远没有达到这个高度,但我喜欢这个答案:继续传承下去,成为下一代软件,或者下一代人类文明的基石。我觉得能够参与这样的事情,对我来说,可能是程序员的一种浪漫吧。那么你呢,你会如何回答这个问题?
