技术人具备“结构化思维”意味着什么?

技术人具备“结构化思维”意味着什么?

阿里妹导读:在日常工作中,我们时常会碰到这样的情况,有的人讲事情逻辑非常混乱,罗列了很多事项,却把握不到重点,无法把一件事情说清楚。这种思维混乱是典型的缺少结构化思维的表现。结构化思维非常重要,不仅仅体现在表达上,也体现在在我们分析问题的过程中。具备结构化思维,才能将问题分析地更全面、更深刻。

 

那么到底是什么是结构化思维呢?简单来说,结构化思维的定义就是:逻辑+套路。

表达要有逻辑

所谓逻辑是指我们的结构之间必须是有逻辑关系的。例如,你说话的时候用“第一、第二、第三”这个逻辑顺序是合理的,但是,用“第一,第二,第四”就会显得很奇怪。当然,即使你用了”一、二、三”,也不一定就意味着你的内容有逻辑关系。想让内容有逻辑关系,我们要学会四种组织思想的逻辑关系:

 

1)演绎(因果)顺序:“大前提、小前提、结论”的演绎推理方式就是演绎顺序。比如,经典三段论:所有人都要死,苏格拉底是人,苏格拉底要死。

2)时间(步骤)顺序:“第一、第二、第三”,“首先、然后、再者”等,很多的时间顺序同时也是因果顺序。

3)空间(结构)顺序:“前端、后端、数据”,“波士顿、纽约、华盛顿”,化整为零(将整体分解为部分)等都是空间顺序。

4)程度(重要性)顺序:比如“最重要、次重要、不重要”等。

实际上,所有的逻辑关系都在这四种顺序之内。只要我们的思想和表达在这四种逻辑顺序之内,就是有逻辑的,否则就会显得没有逻辑性。

做事要有套路

套路是指我们解决问题的方法论,这个也非常重要。比如,5W2H 分析法就是一个非常好的,可以帮助我们分析问题的一个”套路”。试想一下,面对任何一个问题,你都能从 Why、Who、When、Where、What、How 和 How much(如下图所示),七个方面去思考。是不是比不知道这个方法论的人,用点状的思考,5W2H 分析法就全面得多。

技术人具备“结构化思维”意味着什么?

例如,我们在对问题域进行分析和领域知识提炼的时候,就可以用上5W2H。5W2H模型给出了具有指导意义的约束,要求我们提炼的领域知识必须具备模型的六个要素。这就好比两位侃侃而谈的交谈者,因为有了确定的主题与话题边界,一场本来是漫无目的野鹤闲云似的闲聊就变成了一次深度交流的专题高端对话。


技术人具备“结构化思维”意味着什么?

 

逻辑是一种能力,而套路是方法论,是经验。逻辑是道的东西,而方法论是术的东西。二者都很重要,只有熟练的掌握二者我们才能更好的进行结构化思考。

如何进行结构化思考?

逻辑性和方法论是结构化思维的底层,那么如何进行结构化思考呢?这也是有方法论的,总的来说是有两个步骤,首先是“建立中心”,然后再进行“分解”。

建立中心

建立中心也就是要定义清楚要解决的问题,要明确目标。是我们结构的顶层节点,也是一种以终为始的思考方式。也就是说,我们首先要搞清楚 why,然后再进行 how。

★ 建立中心有两种方式:

  1. 自上而下:适用于问题比较明确的情况,我们只需要找到问题的核心要素即可,然后进行展开即可。

  2. 自下而上:对于问题不够明确的情况,我们需要对多种杂乱的内容,进行分类、剪枝、归纳汇总成一个中心。

建立中心通常不会是一次成型的,随着对问题理解的变化,对中心的抽象也会进行相应的调整。不同的抽象层次其面对的问题宽度是不一样的。具体要用哪个层次的抽象作为“中心”,要视具体情况而定。

比如面对“系统 bug 多”的问题,向上抽象是“提升代码质量”,向下抽象是“加强测试”,都可以作为中心,选择哪个为中心取决于你当前要解决的问题是什么。

 

技术人具备“结构化思维”意味着什么?

结构化分解

确定完中心之后,我们需要构建一个结构,使用结构化的思维对问题进行分解。分解的策略就是我们上文提到的四种逻辑顺序,即演绎顺序、时间顺序、空间顺序和程度顺序。

在做空间分解的时候,要注意满足 MECE(Mutually Exclusive Collectively Exhaustive,相互独立,完全穷尽)原则。

比如我们要对衣服进行分类,如果按照季节和风格进行分类,就会出现互相重叠,并且不能穷尽的情况,也就不满足 MECE。这种分类是逻辑混乱的。

 

技术人具备“结构化思维”意味着什么?

我们可以按季节分:春秋装,冬装,夏装。除了这3类之外,没有其他季节了,这个就是「不遗漏」。

 

技术人具备“结构化思维”意味着什么?

结构化思维应用

如何落地新团队?

想象这样一个场景,你刚刚入职一家新公司,或者转岗到一个新团队,作为一个技术人,你将如何落地开展你的工作呢?

这里,我们就能用上结构化思维来帮我们理清思路,从而有条不紊的开展工作了。我们要知道对一个企业来说,核心要素无外乎就是业务、技术和人,也就是说这三个要素是我们要建立的中心。基于这个中心,我们可以进行进一步拆解,形成子结构。然后对子结构再进行分析找到应对策略。这样一步步递进,我们就已经在用结构化思维解决如何落地新团队的问题了。

技术人具备“结构化思维”意味着什么?

★ 1. 熟悉业务

1)了解产品:任何一个团队都有自己要负责的产品,申请一个测试账号去用一下产品,是熟悉产品比较好的方式。

2)了解流程:任何业务都有自己的业务流程,而业务流里面最核心的是信息流。我们可以通过人员采访,了解关键节点的信息输入和信息输出;可以画一些泳道活动图,理清楚系统的主要角色,以及他们之间的交互关系。

3)客户走访:通过走访客户,我们可以更加获得业务的第一手资料,更加贴近业务和客户诉求。

★ 2. 熟悉技术

1)了解系统架构:可以让团队的技术人员介绍下他们当初系统设计和架构的思路。

2)了解领域模型:查看关键的核心表结构和系统 API,这样可以快速了解系统的领域模型。

3)了解代码结构:下载系统工程,熟悉整个工程结构和模块职责。以一个最重要的流程为入手点,阅读代码,看清楚核心的执行逻辑。做一个小需求,掌握相关的流程和权限。

★ 3. 熟悉人

1)了解组织结构:查看公司的组织树,知道公司大概是如何运作的,以及哪些是KP(Key Person,关键人)。比如,一个典型的电商公司会包括产品部、运营部、销售部、技术部、人力资源部、财务部、法务部等。

2)了解人员角色:了解公司都有哪些岗位,以及各岗位的职责范围。

3)拜山头:找到和自己工作息息相关的岗位人员,比如产品和运营。积极和他们沟通,向他们请教业务问题,多多交流。这样一方面可以建立更好的人际关系,另一方面也可以更快地熟悉业务。

打造极客文化

我最近刚刚转岗到新部门,新部门的老板抛给我一个命题:如何帮助技术团队打造极客文化?

这个问题的中心很明确,接下来,看看我是如何使用结构化思维来解这个问题的。首先我们从空间顺序进行分解,也就是打造极客文化,我们可以去做哪些事情。

 

技术人具备“结构化思维”意味着什么?
空间顺序分解

确定完要做的事情,我们还可以按照时间顺序对如何落地这些事情进行分解。

 

技术人具备“结构化思维”意味着什么?
时间顺序分解

这样把按照这两个维度进行结构化拆解的方案给到老板,老板就会很清晰地知道你的规划和落地策略了。

如何做晋升述职

作者在阿里巴巴已经做了好几年的晋升评委,发现很多同学都缺乏结构化思维,冗长的 PPT 里,却不能把价值说清楚,不能把推导过程说清楚。实际上,我们需要有一些方法论来指导我们进行关键述职。

接下来,我主要说一下述职中存在的两个典型问题:“罗列事情”和“价值的背后”

★ 1.罗列事情

对自己做过的事情进行简单罗列,也许你的确做了不少事情。但是不能体现你对问题思考的深度和做这个事情带来的价值。这样的述职很难打动评委,更结构化的表达应该是:“提出问题,定义问题,分析问题,解决问题,最后是展望未来”

这是一个经典的表述问题的结构,也是麦肯锡推荐的问题解决的框架。

 

技术人具备“结构化思维”意味着什么?

类似的框架还有 zoom in/zoom out。 我们说事情时,应该像电影镜头一样,先从远拉近,再由近拉远。zoom in 是先从宏观背景开始,首先让大家知道你的事情发生的背景,为什么这事重要?然后讲到具体细节,怎么做成的?解决了什么问题?背后的思考是什么?最后 Zoom out,再从细节调回到整体,结果是什么,带来的客户价值是什么,你对未来的思考是什么。

★ 2.价值的背后

把价值说清楚的确很重要,正所谓:路走对了,就不怕远。如果你连价值都不明确,后面做的再多也是白搭。但是,仅仅阐述价值也是不够的,会让人觉得你有邀功之嫌。

比如你说:“我主导研发的风控系统把公司的坏账率从5%降低到2%”。这样的表述是不够的,你还需要把价值背后的过程和思考说清楚。对于这个结果,评委可能会问:

  1. 之前为什么那么高?

  2. 为什么你的方法可以降低?是如何归因的?

  3. 具体解决了什么问题?

  4. 是否可以总结出一套办法,以后别人也能用这个办法解决这些问题?

如果你在评委提问之前,就能对这些问题进行深入思考和适当呈现。那么你就是既有结果又有过程了。

通过这些案例,我们可以看到具备结构化思维,可以帮助我们快速的理清处理问题的思路,提升工作效率。经常锻炼结构化思维,可以极大的提升我们职场竞争力,让工作有条不紊,事半功倍。

技术人具备“结构化思维”意味着什么?

关注「阿里技术」

把握前沿技术脉搏

终于有人把服务调用说清楚了

导读:RPC,微服务,Service Mesh这些服务之间的调用是什么原理?

作者 codedump codedump.info 博主,多年从事互联网服务器后台开发工作。可访问作者博客阅读 codedump 更多文章。

本文专注于演化过程中每一步的为什么(Why)和是什么(What)上面,尽量不在技术细节(How)上面做太多深入。

服务的三要素

一般而言,一个网络服务包括以下的三个要素:

  • 地址:调用方根据地址访问到网络接口。地址包括以下要素:IP地址、服务端口、服务协议(TCP、UDP,etc)。

  • 协议格式:协议格式指的是该协议都有哪些字段,由接口提供者与协议调用者协商之后确定下来。

  • 协议名称:或者叫协议类型,因为在同一个服务监听端口上面,可能同时提供多种接口服务于调用方,这时候需要协议类型(名称)来区分不同的网络接口。

需要说明在服务地址中:

  • IP地址提供了在互联网上找到这台机器的凭证。

  • 协议以及服务端口提供了在这台机器上找到提供服务的进程的凭证。

终于有人把服务调用说清楚了

都属于TCPIP协议栈的知识点,不在这里深入详述。

这里还需要对涉及到服务相关的一些名词做解释。

  • 服务实例:服务对应的IP地址加端口的简称。需要访问服务的时候,需要先寻址知道该服务每个运行实例的地址加端口,然后才能建立连接进行访问。

  • 服务注册:某个服务实例宣称自己提供了哪些服务,即某个IP地址+端口都提供了哪些服务接口。

  • 服务发现:调用方通过某种方式找到服务提供方,即知道服务运行的IP地址加端口。

基于IP地址的调用

最初的网络服务,通过原始的IP地址暴露给调用者。这种方式有以下的问题:

  • IP地址是难于记忆并且无意义的。

  • 另外,从上面的服务三要素可以看到,IP地址其实是一个很底层的概念,直接对应了一台机器上的一个网络接口,如果直接使用IP地址进行寻址,更换机器就变的很麻烦。

“尽量不使用过于底层的概念来提供服务”,是这个演化流程中的重要原则,好比在今天已经很少能够看到直接用汇编语言编写代码的场景了,取而代之的,就是越来越多的抽象,本文中就展现了服务调用这一领域在这个过程中的演进流程。

在现在除非是测试阶段,否则已经不能直接以IP地址的形式将服务提供出去了。

域名系统

前面的IP地址是给主机做为路由器寻址的数字型标识,并不好记忆。此时产生了域名系统,与单纯提供IP地址相比,域名系统由于使用有意义的域名来标识服务,所以更容易记忆。另外,还可以更改域名所对应的IP地址,这为变换机器提供了便利。有了域名之后,调用方需要访问某个网络服务时,首先到域名地址服务中,根据DNS协议将域名解析为相应的IP地址,再根据返回的IP地址来访问服务。

从这里可以看到,由于多了一步到域名地址服务查询映射IP地址的流程,所以多了一步解析,为了减少这一步带来的影响,调用方会缓存解析之后的结果,在一段时间内不过期,这样就省去了这一步查询的代价。

协议的接收与解析

以上通过域名系统,已经解决了服务IP地址难以记忆的问题,下面来看协议格式解析方面的演进。

一般而言,一个网络协议包括两部分:

  • 协议包头:这里存储协议的元信息(meta infomation),其中可能会包括协议类型、报体长度、协议格式等。需要说明的是,包头一般为固定大小,或者有明确的边界(如HTTP协议中的rn结束符),否则无法知道包头何时结束。

  • 协议包体:具体的协议内容。

无论是HTTP协议,又或者是自定义的二进制网络协议,大体都由这两部分组成。

终于有人把服务调用说清楚了

由于很多时候不能一口气接收完毕客户端的协议数据,因此在接收协议数据时,一般采用状态机来做协议数据的接收:

终于有人把服务调用说清楚了

接收完毕了网络数据,在协议解析方面却长期停滞不前。一个协议,有多个字段(field),而这些不同的字段有不同的类型,简单的raw类型(如整型、字符串)还好说,但是遇到复杂的类型如字典、数组等就比较麻烦。

当时常见的手段有以下几种:

  • 使用json或者xml这样的数据格式。好处是可视性强,表达起上面的复杂类型也方便,缺陷是容易被破解,传输过去的数据较大。

  • 自定义二进制协议。每个公司做大了,在这一块难免有几个类似的轮子。笔者见过比较典型的是所谓的TLV格式(Type-Length-Value),自定义二进制格式最大的问题出现在协议联调与协商的时候,由于可视性比较弱,有可能这边少了一个字段那边多了一个字段,给联调流程带来麻烦。

上面的问题一直到Google的Protocol Buffer(以下简称PB)出现之后才得到很大的改善。PB出现之后,也有很多类似的技术出现,如Thrift、MsgPack等,不在这里阐述,将这一类技术都以PB来描述。

与前面的两种手段相比,PB具有以下的优点:

  • 使用proto格式文件来定义协议格式,proto文件是一个典型的DSL(domain-specific language)文件,文件中描述了协议的具体格式,每个字段都是什么类型,哪些是可选字段哪些是必选字段。有了proto文件之后,CS两端是通过这个文件来进行协议的沟通交流的,而不是具体的技术细节。

  • PB能通过proto文件生成各种语言对应的序列化反序列化代码,给跨语言调用提供了方便。

  • PB自己能够对特定类型进行数据压缩,减少数据大小。

终于有人把服务调用说清楚了

服务网关

有了前面的演化之后,写一个简单的单机服务器已经不难。然而,当随着访问量的增大,一台机器已经不足以支撑所有的请求,此时就需要横向扩展多加一些业务服务器。

而前面通过域名访问服务的架构就遇到了问题:如果有多个服务实例可以提供相同的服务,那么势必需要在DNS的域名解析中将域名与多个地址进行绑定。这样的方案就有如下的问题:

  • 如何检查这些实例的健康情况,同时在发现出现问题的时候增删服务实例地址?即所谓的服务高可用问题。

  • 把这些服务实例地址都暴露到外网,会不会涉及到安全问题?即使可以解决安全问题,那么也需要每台机器都做安全策略。

  • 由于DNS协议的特点,增删服务实例并不是实时的,有时候会影响到业务。

为了解决这些问题,就引入了反向代理网关这一组件。它提供如下的功能:

  • 负载均衡功能:根据某些算法将请求分派到服务实例上。

  • 提供管理功能,可以给运维管理员增减服务实例。

  • 由于它决定了服务请求流量的走向,因此还可以做更多的其他功能:灰度引流、安全防攻击(如访问黑白名单、卸载SSL证书)等。

终于有人把服务调用说清楚了

 

有四层和七层负载均衡软件,其中四层负载均衡这里介绍LVS,七层负载均衡介绍Nginx。

终于有人把服务调用说清楚了

上图是简易的TCPIP协议栈层次图,其中LVS工作在四层,即请求来到LVS这里时是根据四层协议来决定请求最终走到哪个服务实例;而Nginx工作在七层,主要用于HTTP协议,即根据HTTP协议本身来决定请求的走向。需要说明的是,Nginx也可以工作在四层,但是这么用的地方不是很多,可以参考nginx的stream模块。

做为四层负载均衡的LVS

(由于LVS有好几种工作模式,并不是每一种我都很清楚,以下表述仅针对Full NAT模式,下面的表述或者有误)

LVS有如下的组成部分:

  • Direct Server(以下简称DS):前端暴露给客户端进行负载均衡的服务器。

  • Virtual Ip地址(以下简称VIP):DS暴露出去的IP地址,做为客户端请求的地址。

  • Direct Ip地址(以下简称DIP):DS用于与Real Server交互的IP地址。

  • Real Server(以下简称RS):后端真正进行工作的服务器,可以横向扩展。

  • Real IP地址(以下简称RIP):RS的地址。

  • Client IP地址(以下简称CIP):Client的地址。

终于有人把服务调用说清楚了

 

客户端进行请求时,流程如下:

  1. 使用VIP地址访问DS,此时的地址二元组为<src:CIP,dst:VIP>。

  2. DS根据自己的负载均衡算法,选择一个RS将请求转发过去,在转发过去的时候,修改请求的源IP地址为DIP地址,让RS看上去认为是DS在访问它,此时的地址二元组为<src:DIP,dst:RIP A>。

  3. RS处理并且应答该请求,这个回报的源地址为RS的RIP地址,目的地址为DIP地址,此时的地址二元组为<src:RIP A,dst:DIP>。

  4. DS在收到该应答包之后,将报文应答客户端,此时修改应答报文的源地址为VIP地址,目的地址为CIP地址,此时的地址二元组为<src:VIP,dst:CIP>。

做为七层负载均衡的Nginx

在开始展开讨论之前,需要简单说一下正向代理和反向代理。

所谓的正向代理(proxy),我的理解就是在客户端处的代理。如浏览器中的可以配置的访问某些网站的代理,就属于正向代理,但是一般而言不会说正向代理而是代理,即默认代理都是正向的。

而反向代理(reverse proxy)就是挡在服务器端前面的代理,比如前面LVS中的DS服务器就属于一种反向代理。为什么需要反向代理,大体的原因有以下的考量:

  • 负载均衡:希望在这个反向代理的服务器中,将请求均衡的分发到后面的服务器中。

  • 安全:不想向客户端暴露太多的服务器地址,统一接入到这个反向代理服务器中,在这里做限流、安全控制等。

  • 由于统一接入了客户端的请求,所以在反向代理的接入层可以做更多的控制策略,比如灰度流量发布、权重控制等等。

反向代理与所谓的gateway、网关等,我认为没有太多的差异,只是叫法不同而已,做的事情都是类似的。

Nginx应该是现在用的最多的HTTP 七层负载均衡软件,在Nginx中,可以通过在配置的server块中定义一个域名,然后将该域名的请求绑定到对应的Upstream中,而实现转发请求到这些Upstream的效果。

 

如:

upstream hello 
{       server A:11001; 
         server B:11001;
}

location / 
{       root   html;
         index  index.html index.htm; 
         proxy_pass http://hello;
}

这是最简单的Nginx反向代理配置,实际线上一个接入层背后可能有多个域名,如果配置变动的很大,每次域名以及对应的Upstream的配置修改都需要人工干预,效率会很慢。这时候就要提到一个叫DevOps的名词了,我的理解就是开发各种便于自动化运维工具的工程师。

有了上面的分析,此时一个提供七层HTTP访问接口的服务架构大体是这样的:

终于有人把服务调用说清楚了

服务发现与RPC

前面已经解决单机服务器对外提供服务的大部分问题,来简单回顾:

  • 域名系统解决了需要记住复杂的数字IP地址的问题。

  • PB类软件库的出现解决协议定义解析的痛点。

  • 网关类组件解决客户端接入以及服务器横向扩展等一系列问题。

然而一个服务,通常并不见得只由本身提供服务就可以,服务过程中可能还涉及到查询其他服务的流程,常见的如数据类服务如Mysql、Redis等,这一类供服务内调用查询的服务被成为内部的服务,通常并不直接暴露到外网去。

面向公网的服务,一般都是以域名的形式提供给外部调用者,然而对于服务内部之间的互相调用,域名形式还不够,其原因在于:

  • DNS服务发现的粒度太粗,只能到IP地址级别,而服务的端口还需要用户自己维护。

  • 对于服务的健康状况的检查,DNS的检查还不够,需要运维的参与。

  • DNS对于服务状态的收集很欠缺,而服务状态最终应该是反过来影响服务被调用情况的。

  • DNS的变更需要人工的参与,不够智能以及自动化。

 

综上,内网间的服务调用,通常而言会自己实现一套“服务发现”类的系统,其包括以下几个组件:

  • 服务发现系统:用于提供服务的寻址、注册能力,以及对服务状态进行统计汇总,根据服务情况更改服务的调用情况。比如,某个服务实例的响应慢了,此时分配给该实例的流量响应的就会少一些。而由于这个系统能提供服务的寻址能力,所以一些寻址策略就可以在这里做,比如灰度某些特定的流量只能到某些特定的实例上,比如可以配置每个实例的流量权重等。

  • 一套与该服务系统搭配使用的RPC库,其提供以下功能:

    • 服务提供方:使用RPC库注册自己的服务到服务发现系统,另外上报自己的服务情况。

    • 服务调用方:使用RPC库进行服务寻址,实时从服务发现系统那边获取最新的服务调度策略。

    • 提供协议的序列化、反序列化功能,负载均衡的调用策略、熔断限流等安全访问策略,这部分对于服务的提供方以及调用方都适用。

终于有人把服务调用说清楚了

 

有了这套服务发现系统以及搭配使用的RPC库之后,来看看现在的服务调用是什么样的。

  • 写业务逻辑的,再也不用关注服务地址、协议解析、服务调度、自身服务情况上报等等与业务逻辑本身并没有太多关系的工作,专注于业务逻辑即可。

  • 服务发现系统一般还有与之搭配的管理后台界面,可以通过这里对服务的策略进行修改查看等操作。

  • 对应的还会有服务监控系统,对应的这是一台实时采集服务数据进行计算的系统,有了这套系统服务质量如何一目了然。

  • 服务健康状态的检查完全自动化,在状况不好的时候对服务进行降级处理,人工干预变少,更加智能以及自动化。

现在服务的架构又演进成了这样:

终于有人把服务调用说清楚了

ServiceMesh

架构发展到上面的程度,实际上已经能够解决大部分的问题了。这两年又出现了一个很火的概念:ServiceMesh,中文翻译为“服务网格”,来看看它又能解决什么问题。

前面的服务发现系统中,需要一个与之配套的RPC库,然而这又会有如下的问题:

  • 如果需要支持多语言,该怎么做?每个语言实现一个对应的RPC库吗?

  • 库的升级很麻烦,比如RPC库本身出了安全漏洞,比如需要升级版本,一般推动业务方去做这个升级是很难的,尤其是系统做大了之后。

可以看到,由于RPC库是嵌入到进程之中的组件,所以以上问题很麻烦,于是就想出了一个办法:将原先的一个进程拆分成两个进程,如下图所示。

终于有人把服务调用说清楚了

在服务mesh化之前,服务调用方实例通过自己内部的RPC库来与服务提供方实例进行通信。

在服务mesh化之后,会与服务调用方同机部署一个local Proxy也就是ServiceMesh的proxy,此时服务调用的流量会先走到这个proxy,再由它完成原先RPC库响应的工作。至于如何实现这个流量的劫持,答案是采用iptables,将特定端口的流量转发到proxy上面即可。

有了这一层的分拆,将业务服务与负责RPC库作用的Proxy分开来,上面的两个痛点问题就变成了对每台物理机上面的mesh proxy的升级维护问题,多语言也不是问题了,因为都是通过网络调用完成的RPC通信,而不是进程内使用RPC库。

然而这个方案并不是什么问题都没有的,最大的问题在于,多了这一层的调用之后,势必有影响原来的响应时间。

截止目前(2019.6月),ServiceMesh仍然还是一个概念大于实际的产品。

从上面的演进历史可以看到,所谓的“中间层理论”,即“Any problem in computer science can be solved by another layer of indirection(计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决)”在这个过程中被广泛使用,比如为了解决IP地址难于记忆的问题,引入了域名系统,比如为了解决负载均衡问题引入了网关,等等。然而每引入一个中间层,势必带来另外的影响,比如ServiceMesh多一次到Proxy的调用,如何权衡又是另外的问题了。

另外,回到最开始的服务三要素中,可以看到整个演化的历史也是逐渐屏蔽了下层组件的流程,比如:

  • 域名的出现屏蔽了IP地址。

  • 服务发现系统屏蔽协议及端口号。

  • PB类序列化库屏蔽了使用者自己对协议的解析。

可以看到,演进流程让业务开发者更加专注在业务逻辑上,这类的演进流程不只发生在今天,也不会仅仅发生在今天,未来类似的演进也将再次发生。

技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。转载请注明来自高可用架构「ArchNotes」微信公众号及包含以下二维码。

高可用架构

改变互联网的构建方式

终于有人把服务调用说清楚了

长按二维码 关注「高可用架构」公众号

 

什么才是软件开发中最佳实践呢?

“描述一个事物,唯有一个名词定义它的概念,唯有一个动词揭露它的行为,唯有一个形容词表现它的特征。要做的,就是用心去寻找那个名词、那个动词、那个形容词……”

—— 福楼拜 (Gustave Flaubert)

  我想讲个故事。

  很久很久以前(一般讲故事都是这样开头吧), 两个老工程师在一起聊天,谈各自生涯中最自豪的工程。其中一个先讲述了他的杰作:

  “ 我们建造的桥,横跨一个峡谷,峡谷很宽很深。我们花了两年时间研究地质,选择材料。聘请了最好的工程师团队来设计方案,而这又花了五年时间。 我们签下了最大的工程队,委托他们建造基础结构、塔墩、收费亭,以及用于连接桥梁和高速公路的道路。桥面下层是铁路,我们甚至还修了自行车道。 那座桥花费了我数年的心血。”

 

  另外一个听完之后,陷入了沉思,过了一会儿,说到:

  “ 有一天晚上,我和一个朋友喝了点伏特加,然后我俩扔了一根绳子,越过一个河谷。呃…… 就是一根绳子,两头系在两颗树上。 河谷两边各有一个村庄,起初,有人加了个滑轮,用来传递包裹。然后,有人拉起了第二根绳子,勉强可以走走,虽然很危险,但小伙子们很喜欢。 后来,一群人重新修建了一下,使得更牢固。于是,女人们也开始从上面走,每天带着她们的农产品过桥。 就这样,在桥的另一边形成了一个市场。因为地方开阔,造了很多房子,慢慢地发展成了一个镇子。 绳索桥被木桥替代,这样就可以走马车了。 后来,镇上的人们修了一座真正的石桥。再然后,人们又把石料改成了钢材。 如今,那座钢构悬索桥依然伫立在那里。”

 

  前一个工程师沉默良久,说到:“ 有意思。我那座桥建成大约十年后,被拆除了。事实证明我们选错了地点,建好的桥没人用。据说有几个野路子的家伙,在下游几英里处,拉了一根绳子,所有人都从那走。”

 

什么才是软件开发中最佳实践呢?

金门大桥(旧金山)

 

  我很喜欢这个故事。故事的出处,在一款消息队列产品—— ZeroMQ 的官方指南第6章里。

  

  说完故事,我想聊聊软件开发中,常常可以听到的一个概念 —— Best Practice :最佳实践。Wikipedia 上对其解释为:

A best practice is a method or technique that has been generally accepted as superior to any alternatives because it produces results that are superior to those achieved by other means or because it has become a standard way of doing things. 

  (最佳实践是一种:因其产生的结果优于其它选择下的结果,或其已经成为一种做事的标准,从而被普遍认可优于任何替代方案的方法或技术。)

 

  这个概念源于管理学,然后在 IT 界泛滥。简而言之,就是所谓“正确的做法”。

  最佳实践本身是美好的存在,犹如夜空中的一轮明月,照亮黑暗中的方向,指引着摸索前行的凡人。

  但凡事有度,子曰:“过犹不及。”

  

  我今天想说的,就是这月亮的背面。(传说中,月球背面隐藏着…… 嘘~)

什么才是软件开发中最佳实践呢?

 潮汐锁定导致月球永远以同一面朝向地球

 

  首先,最佳实践容易带来思想包袱,让人无法专注于解决问题本身。

  总是希望采用最好的技术方法,不愿意在不正确的做法上浪费时间,导致瞻前顾后,甚至裹足不前。此时的最佳实践,已然成为了一种毒药,一旦偏离了问题本身这个出发点,就会不知不觉走进“宏大构想”的思维陷阱。把简单的问题复杂化,阻碍了迈出第一步,直到能规划出“包罗万象”的解决方案后才肯动手,拖延症就这样来了,时间却走了。

  你想好了未来每一天怎么过吗…… 没想好? 那……不活了?

 

  其次,对最佳实践的执念容易让人钻牛角尖,将目标的重心带偏。

  过度关注实施过程是否符合标准化,忽视了项目中其它重要的东西,比如用户体验,比如实际需求。就像故事里讲的那样:第一座大桥,几乎是教科书般的标准化路数,可产品落地后和客户需求却差了好几英里;第二个看上去很野路子,但精准地解决了痛点,从始自终都是紧紧围绕实际需求迭代,每一次的进步都可以产生效用,这才叫杀手级应用。

  这让我想起了 Plan-9 的传说。

  你听说过 Plan-9 OS 吗? 一款由贝尔实验室的极客们打造的用于完善 UNIX 不足的操作系统。什么不足?在 UNIX 的哲学中,有一条叫做 “一切皆文件” ,但实际上UNIX本身并没有严格遵从这一条。于是,Plan-9 OS 完美实现了这一点。然后呢……? 没有然后了。它从没进过市场,所以如果你没听说过它,一点也不奇怪。Plan-9 OS 没有解决任何现实问题,没人在乎 “一切皆不皆文件”。

 

  这种执念的另一种表现就是工程师思维,沉迷于奇技淫巧中无法自拔,程序员尤其容易中招。

  比如性能优化。“优秀的程序员应该榨干每一字节内存”,听起来很熟悉,不是吗?但经济学上来讲,边际效应决定了一次项目中,越优化性价比越低。有一个很容易被忽略的事实:硬件其实比程序员要便宜

  再比如对设计模式的崇拜。设计模式当然是好东西,但如果像强迫症一样使用它们,坚持用上它们才是正确的编程,就会导致按图索骥,强行让问题去适应设计模式,而不是让解决方案针对问题,这就本末倒置了。

 

  我有个基友,C++ 极客。毕业后入了腾讯,积累了巨额财富后,自己创业了。当然,当老板可比写 C++ 难多了,于是现在又去积累巨额财富了。想当年和那厮聊天,言必出设计模式,没事侃正则,再没事就研究 GC 策略 (好像玩 C++ 的普遍这德性) 。前不久看他代码,差点没认出来,这家伙画风一转,现在连接口都懒得多用(估计看到这,某些狂热分子肯定在破口大骂:你什么意思,你说你没用面向接口编程?)那位兄台甚至都懒得多聊,轻描淡写来一句,“没心思,以后有需要再加。” 

  顺便扯一句,那哥们最近负责开发一款手游,他跟老板汇报的时候,预估的研发周期要12个月,然后老板跟他说:“好,12月出公测。” (哈~ 估计他肯定舌头打结把“12个月”说成了“12月”)。看到这的你,是否回忆起了你的老板?

 

  这也是我接下来想说的关于最佳实践的另一个问题:项目实施。

  工作数年,大小项目经历若干,慢慢体会到,一个项目的开发顺利与否,并不在于技术选型是否为最佳实践,更多的时候,取决于开发方案和技术储备之间的平衡。做项目毕竟是要讲方案落地的,如果最佳实践中的技术成本,超出了开发者的落实能力,那就是坑,这时盲从最佳实践无异于挖坟。如果是一个人的项目,抽时间恶补一通,兴许能填填坑,这取决于IQ。但要是一个团队,那就不是什么 IQ,EQ,QQ 的问题了,这中间产生的学习成本,集体培训成本,反复沟通成本,大量的初级错误,千奇百怪的代码,互相冲突引发的焦躁情绪,等等。这些负面的东西如果不能妥善的处理,足以抵消掉最佳实践带来的好处。别忘了,deadline 正在迫近。

  我自己曾经在一个项目组里,强行推行 Git 做源代码管理,当时组里共9人,有7人只会 SVN,但我坚持 Git 是 “最佳实践”。要不说年少无知少不更事呢,罢了,后来的事情我不想回忆了…… 那次项目之后,我再也不在一群只会 SVN 的队伍里提 Git 了。

  

  一个人做软件已经很难,比这更难的,是一群人做软件。

 

  当尘埃落定,蓦然回首,最佳实践很可能没你想象中那么重要。它更多的是一种精神层面的求道,并非物质世界的必要。

  扎克伯格 ( Mark Zuckerberg ) 于2004年在哈佛柯克兰公寓 ( Kirkland House ) 里写出 TheFacebook 的时候 ( 次年更名为Facebook ) ,用的是 “世界上最好的编程语言” PHP。这门可能是业界被吐槽次数最多的语言一直支撑着FB帝国的诞生,直到席卷全球。Stack Overflow 的联合创始人 Jeff Atwood 曾公开揶揄 Facebook 是一家 “召集全球顶级程序员在 Windows XP 上写 PHP ” 的公司。但这无所谓,十四年前的马克也不纠结。一直等到需要的时候 (2010年),Facebook自己动手研发了一个编程语言 —— Hack,来解决 PHP 带来的危机。

什么才是软件开发中最佳实践呢?

《社交网络》

  最佳实践,关键在时机(Timing)。

  如果说用 Facebook 这个 “根本不存在” 的网站来举例,纯属虚构的话,那我们来说点真实的例子,Web 技术的基石——HTML。由20世纪最重要的100人之一的 Tim Berners-Lee 创造的 HTML,其发明之伟大,足以单独开篇博文来赞美了,这里就不赘述了。

  这样一个造福全人类的神作,本身的设计结构绝非完美,甚至可以用混乱不堪来形容。没有严格统一的约束,形同虚设的规范,标准化进程的难产。以至于在很长一段时间内,连自身元素的定义,都可以向浏览器厂商妥协。但是,种种被人诟病的存在,丝毫不影响 HTML 改变世界的脚步。你我今天能相会于园,皆仰赖它的诞生。

  同样的例子还发生在 Web 世界另一个巨擎上——JavaScript。当今世界,Web 前端技术已经水银泻地般肆虐整个开发界,前端框架百花齐放、JS 衍生品鳞次栉比。所有这一切的背后,全都源于上世纪90年代横空出世的 JavaScript。

  那么,JavaScript是最佳实践吗?

  别逗了,如果有什么语言可以和刚才说到的 PHP 竞争一下谁被骂的次数更多,那非 JavaScript 莫属。这个仅花了十天设计出来的语言,打一出身就被贴上了怪胎的标签。混乱的标准,多样的实现,安全漏洞,语法随意,反人类…… 总之,JavaScript 和最佳实践半毛钱关系都扯不上,但它却是撑起当今互联网半壁江山的擎天柱。

  所以,用最接地气地话来说,不管黑猫白猫,逮着耗子就是最佳实践猫。

 

  汝之蜜糖,吾之砒霜。所谓最佳实践,其定义本身往往也是分歧的源头。什么是最佳?这个最佳是独一无二的吗?世界上有很多很多现实问题,可能根本就没有所谓的最佳实践。

  请听题,世界上最好的编程语言是哪个?

  第二题,世界上最好的文本编辑器是哪个?

  朋友,这天还聊得下去吗……

  

  最后,说一个我自己的故事。

  很久很久以前,为了找一款满意的文本编辑器,我干了一件可能是前无古人,后不知道有没有来者的蠢事 —— 我打开 Wikipedia,搜索 “ text editor ” ,然后转到一个叫做 “ List of text editors ” 的页面,接下来的一个月,我几乎把当时那个页面上,所有我能下载安装的文本编辑器,全部试用了一遍……

  嗯?你问我为什么这么做?呵呵,不把全世界的文本编辑器遍历一遍,我怎么知道哪个是最好的?

  这事细节我不想再提了,我也不想回忆了。要不说年少无知少不更事呢,时至今日,我想不出比这更愚蠢的事了。WTF~~

什么才是软件开发中最佳实践呢?

这个页面上的表格行数逐年增多

 

  如今,再有人问我最好的编程语言或者最好的文本编辑器的问题的话,我会说: 

 

  “朋友,要打架吗?”  

 

  这两个问题的最佳实践,唯有暴力