复杂性应对之道

抽象

写本文的原因是,抽象是软件设计中最重要的概念。但抽象这个概念本身又很抽象,我们有必要花一些时间深入理解抽象、抽象的层次性,以及不遗余力的不断提升我们抽象能力。

抽象的力量

没有抽象思维,就没有人类光辉灿烂的现代文明。

原始人看到一片树林,不会给它们一个名字,比如叫“松树”。他们会给每一棵树取一个独特的名字,可能叫“silisiba”。原始人只知道某棵具体的树。

随着意识水平的发展,人类开始有意识地将具有相同特征的事物归并到一起,从“silisiba”到“松树”——到“树木”——到“植物”——到“物质”,从具象思维到抽象思维,我们人类花了几万年漫长的时间。

赫拉利在《人类简史》里说,人类之所以成为人类,是因为人类能够想象。这里的想象,作者认为很大程度上也是指抽象能力。正是这样的抽象思维帮助人类能够从具体事物中,抽象出各种概念,然后再用这些概念去构筑种种虚构故事。这些概念,包括政治(例如民族、国家)、经济(例如货币、证券)、文学、艺术和科学等,都是建立在抽象的基础之上。

什么是抽象

那到底什么是抽象呢?“抽”就是抽离,“象”就是具象,字面上理解抽象,就是从具体中抽离出来。英文的抽象——abstract来自拉丁文abstractio,它的原意是排除、抽出。

按照Wikipedia上的解释,抽象是指为了某种目的,对一个概念或一种现象包含的信息进行过滤,移除不相关的信息,只保留与某种最终目的相关的信息。例如,一个 皮质的足球 ,我们可以过滤它的质料等信息,得到更一般性的概念,也就是 球 。从另外一个角度看,抽象就是简化事物,抓住事物本质的过程。

用语言来说明抽象还是太抽象:),我们都知道在绘画里面有一个流派叫抽象主义,最著名的抽象派大师就是毕加索了,下面我们通过他的一幅画,直观的感受一下什么是抽象。 怎么样? 是不是够抽象,只是几根线条,不过这几根线条是做了高度抽象之后的线条,过滤了绝大部分水牛的细节,只保留了牛的最主要的一些特征,正因为其抽象层次更高,因此,其泛化能力更强,不仅可以表示水牛,什么黄牛、奶牛、野牦牛只要是牛都逃不过这几根线。

抽象是OO的基础

OO(Object Oriented,面向对象)的思考方式,就是万物皆对象,抽象帮助我们将现实世界的对象抽象成类。帮助我们完成从现实世界的概念到计算机世界的模型的映射。比如,这里有一堆苹果,对其进行抽象的话,我们可以得到Apple这个类,通过这个类,我们可以实例化一个红色的苹果: newApple("red")。此时,如果我们需要把香蕉,橘子等也纳入到考虑范围,那么Apple的抽象层次就不够了,我们需要Fruit这个更高层次的抽象来表达“水果”这个概念。

面向对象的思想主要包括三个方面:OOA(面向对象的分析:Object Oriented Analysis)、OOD(面向对象的设计:Object Oriented Design)、以及我们经常说的OOP(面向对象的编程:Object Oriented Programming)

OOA是根据抽象关键问题域来分解系统。OOD是一种提供符号设计系统的面向对象的实现过程,它用非常接近实际领域术语的方法把系统构造成“现实世界”的抽象。OOP可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。

可以看到抽象贯穿于OO的始终,是OO的前提和底层能力,抽象能力差的人是很难做好OO的。

抽象的层次性

对同一个对象的抽象是有不同层次的。层次越往上,抽象程度越高,它所包含的东西就越多,其含义越宽泛,忽略的细节也越多;层次越往下,抽象程度越低,它说包含的东西越少,越细节。也就是我们常说的是,内涵越小外延越大,内涵越大外延越小。不同层次的抽象有不同的用途。

分层抽象,在软件的世界里随处可见,是软件架构的核心,也是我们构建庞大复杂的信息科技的基石。

例如,我们的系统就是分层的。最早的程序是直接运行在硬件上的,开发成本非常高。然后慢慢开始有了操作系统,操作系统提供了资源管理、进程调度、输入输出等所有程序都需要的基础功能,开发程序时调用操作系统的接口就可以了。再后来发现操作系统也不够,于是又有了各种运行环境(如 JVM)。

编程语言也是一种分层的抽象。机器理解的其实是机器语言,即各种二进制的指令。但是使用二进制编程效率极低,所以我们用汇编语言抽象了二进制指令,用C语言抽象了汇编语言,而高级语言Java抽象了低级语言。

在软件开发中,我们应该也都听说过各种分层模型。例如经典的三层模型(展现层、业务逻辑层、数据层),还有 MVC 模型等。有一句名言:“软件领域的任何问题,都可以通过增加一个间接的中间层来解决”。分层架构的核心其实就是抽象的分层,每一层的抽象只需要而且只能关注本层相关的信息,从而简化整个系统的设计。

很难想象,如果没有抽象分层,会有人可以应对软件世界这么大的复杂性。譬如阿里的软件系统其复杂程度绝对不亚于一个城市,若是没有抽象分层,就像要在一张画布上画一整个城市的细节,从摩天大厦到门把手,从公园到街道上的猫,难以想象。

如何进行抽象

归类分组

简单来说,抽象的过程就是归类分组,总结归纳的过程。归类分组就是将有内在逻辑关系,或者叫有共性的事物放在一起。总结归纳就是给这一个分组或者叫分类进行命名,这个名字就代表了这组分类的抽象。

在我们的生活中,无时无刻不在进行这样的抽象,我们的语言作为符号系统,本身就是对现实世界的抽象表达。比如当你说苹果的时候,你就是在运用抽象的概念,用来指称所有的红红的,有水分的,吃起来酸甜的水果。同样花也是一个抽象概念,面对千变万化、多种多样的花,它概括了所有花的本性。

实际上这种归类分组的能力是我们的天性,人类很早以前就认识到,大脑会自动将发现的所有事物以某种秩序组织起来。基本上,大脑会认为同时发生的任何事物之间都存在某种关联,并且会将这些事物按照某种逻辑模式组织起来。

大脑会将其认为具有“共性”的任何事物组织在一起,“共性”指的是具有某种相似的共同点或所处的位置相近。看一下下面这个图

无论是谁,看到上面的6个黑点,都会认为共有两组黑点,每组3个。这种分类思维是我们与生俱来的天性。

然而,实际工作中的抽象也不会都像上图这么的简单直观。

首先,很多的共性并不是那么容易就能被发现,即使被发现了也不是那么容易就能被归类。

其次,给新抽象命名也不是一件容易的事,这是为什么命名被评为最让程序员头疼的事之首了。stackoverflow的创始人Jeff Atwood也说过:“Creating good names is hard, but it should be hard, because a great name captures essential meaning in just one or two words”。

反过来看,如果你不能很好的给一个类,一个方法,一个变量命名的话,往往意味着你的抽象不够好。这种现象在我的实际工作中屡有发生,每当大家对一个命名纠结有争议时,都会发现概念的混淆,或者抽象的缺失。

提升抽象层次

当发现有些东西就是不能归到一个类别中是,我们应该怎么办呢?此时,我们可以通过拔高一个抽象层次的方式,让它们在更高抽象层次上产生逻辑关系。

比如,你可以合乎逻辑地将苹果和梨归类概括为水果,也可以将桌子和椅子归类概括为家具。但是怎样才能将苹果和椅子放在同一组中呢?仅仅提高一个抽象层次是不够的,因为上一个抽象层次是水果和家具的范畴。因此,你必须提高到更高的抽象层次,比如将其概括为“商品”,如果要把大肠杆菌也纳入其中呢?那么“商品”这个抽象=也不够了,需要再提高一个抽象层次,比如叫“物质”,但是这样的抽象太过于宽泛,难以说明思想之间的逻辑关系。类似于我们在Java里面的顶层父类对象(Object),万物皆对象。

在我们的开发工作中,很多时候就需要通过抽象层次的提升,来提高代码的可读性和通用性。

金字塔结构

《金字塔原理》是一本教人如何进行结构化思考和表达的书,核心思想是通过归类分组搭建金字塔结构,让我们可以更加全面的思考,表达观点时更加清晰。是一种非常有用的思维框架,我们平时说这个人逻辑很清晰,正是因为他具备结构化思维,可以把一件事情说的很有条理,很清晰。

书里说我们要自下而上的思考,总结概括;自上而下的表达,结论先行。其中自下而上的总结概括的过程就是抽象的过程,构建金字塔的过程就是寻找逻辑关系,抽象概括的过程。经常锻炼用结构化的方式去处理问题,搭建自己的金字塔,可以帮助我们理清问题的脉络,提升我们的抽象能力。

金字塔结构让我们把混乱无序的信息,通过抽象概括形成不同的抽象层次,从而方便我们理解和记忆,这个方法论值得我们每个人好好掌握。

举个例子,你出门买报纸,你老婆让你带点东西,你老婆给你列了一个清单,里面有葡萄,橘子,咸鸭蛋,土豆,鸡蛋、牛奶和胡萝卜,当你准备出门的时候,你妻子说,顺便还带点苹果和酸奶吧。

如果不用纸记下来,你还能记住妻子让你买的9样东西吗?大部分人应该都不可以,因为我们的大脑短期记忆无法一次容纳7个以上的记忆项目,超过5个时,我们就会开始讲起归类到不同的逻辑范畴,以便于记忆。

对于葡萄,橘子,牛奶,咸鸭蛋,土豆,鸡蛋、胡萝、苹果,酸奶。我们可以按照逻辑关系进行分类,然后搭建一个如下的金字塔结构:

分类的作用不只是将一组9个概念,分成每组各有4个、3个和2个概念的3组概念,因为这样还是9个概念,你所要做的是提高一个抽象层次,将大脑需要处理的9个项目变成3个项目。

这样做以后,你无须再记忆9个概念中的每一个概念,仅需记忆9个概念所属的3个组。这样,你的思维的抽象程度就提高了一层。由于处于较高层次的思想总是能够提示其下面一个层次的思想,因而更容易理解和记忆。

在程序设计中,也是一样,如果在一个类或者一个函数中涉及过多的内容和概念,我们大脑也会显得不知所措,会觉得很复杂,不能理解。其实有时候,只要按照上面给日用品分类的方法,将一个大方法,按照逻辑关系,整理成一组更高层次的小而内聚的子程序的集合,那么整个代码逻辑就会呈现出完全不一样的风貌,显得干净、容易理解的多。

在这方面,Spring里面核心类 AbstractApplicationContext做上下文初始化的refresh方法,给我们做了一个很好的示范:

试想下,上面的逻辑,如果是平铺在refresh方法中的,其结果会怎样。

如何提升抽象思维

说了这么多关于抽象的概念,抽象思维又是如此重要。那我们有没有办法来锻炼和提升我们的抽象思维呢?

是的,抽象思维也是可以习得的。婴幼儿是没有抽象思维的,你和他玩躲猫猫游戏,把他眼睛蒙上,他就以为你消失了,再放开,他看到你就会很高兴,咯咯笑。因为他只能意识到你这个具象的人,他的意识还到不了抽象的程度。理解具象的内容要比抽象更加简单容易,而对抽象内容的理解则要困难和复杂很多。

多阅读

为什么阅读书籍比看电视更好呢?因为图像比文字更加具象,阅读的过程可以锻炼我们的抽象能力、想象能力,而看画面的时候会将你的大脑铺满,较少需要抽象和想象。

这也是为什么我们不提倡让小孩子过多的暴露在电视或手机屏幕前的原因,因为这样不利于他抽象思维的锻炼。

抽象思维的差别让孩子们的学习成绩从初中开始分化,许多不能适应这种抽象层面训练的,就去读技校,这里比大学更加具象:车铣刨磨、零件部件都能看到能摸到。

多总结

小时候不理解,语文老师为什么总是要求我们总结段落大意、中心思想什么的。现在回想起来,这种思维训练在基础教育中是非常必要的,其实质就是帮助学生提升抽象思维能力。

记录也是很好的总结习惯。就拿读书笔记来说,最好不要原文摘录书中的内容,而是要用自己的话总结归纳书中的内容,这样不仅可以加深理解,而且还可以提升自己的抽象思维能力。

我们的现象世界繁杂复杂,只有具备较强的抽象思维能力才能够具备抓住事物本质的能力。

多做领域建模

对于技术同学,我们还有一个非常好的提升抽象能力的手段——领域建模。当我们对问题域进行分析、整理和抽象的时候,当我们对领域进行划分和建模的时候,实际上也是在锻炼我们的抽象能力。

关于这一点,作者深有感触,当我一开始使用上一章介绍的建模方法论进行建模的时候,会觉得无从下手,建出来的模型也感觉很别扭。然而,经过几次锻炼之后,很明显可以感觉到我的建模能力和抽象能力都有很大的提升。不但分析问题的速度更快了,而且建出来的模型也更加优雅了。

解决问题黄金三步:定义问题—分解问题—归类分组

我们经常说软件开发中,没有银弹。的确,单看软件领域,很少有什么方法论是普适的。然而再拔高一些,跳出软件的范畴,是有一些东西是普世的,比如老子说的“道”。

好吧,“道”说的有点大。不过,最近我的确有一个新发现——我发现“归类分组”在我们解决问题中起着巨大的作用。而这里的“问题”不仅仅限于软件设计,从日常生活、产品设计到公司战略、生物分类。都有它的身影。

这是为什么呢?究其原因可能有两个:

一、归类分组是抽象的重要方法,解决复杂问题我们离不开抽象。

二、归类分组是结构化的重要步骤,结构化的表达离不开归类分组。

当然,归类分组要先有素材可以“归类”才行。因此,完整的方法论是这样的,我给他起了一个好听的名字——解决问题黄金三步:定义问题——分解问题——归类分组。

这三步的详细操作如下:

  1. 第一步,定义问题:也就是要清楚我们要解决的问题是什么?
  2. 第二步,分解问题:对问题进行分析拆解,形成平铺的多个子问题,此步可以尽量发散。
  3. 第三步,归类分组:对子问题进行归纳、剪枝,将趋同的子问题,合并成一类问题。

image.png

如上图所示,通过黄金三步以后,我们就可以得到一个形同“金字塔”的结构,也就是我们经常说的金字塔结构。根据问题的复杂程度,这个金字塔结构可能是三层,也可能是二层和多层。

看似简单的三步操作会有那么大的作用吗?废话不多说,直接上案例。

在日常生活中的运用

你出门买报纸,你老婆说家里冰箱空了,顺便带点东西回来吧。她给你列了一个清单,里面有葡萄,橘子,咸鸭蛋,土豆,鸡蛋。你说就这么多了吗?她说苹果和胡萝卜也可以买一点。当你准备出门的时候,她说家里的牛奶和酸奶也没有了,最好也买一点回来。

你觉得你能把老婆交代的东西都买齐吗?我看很难,因为我们的大脑短期记忆无法一次容纳7个以上的记忆项目,超过5个时,我们就会开始将不同的项目归类到不同的逻辑范畴,以便于记忆。

如果我们将葡萄,橘子,牛奶,咸鸭蛋,土豆,鸡蛋、胡萝、苹果,酸奶。按照逻辑关系,进行下归类分组,比如把葡萄、橘子、苹果归为水果类,把土豆、胡萝卜归为蔬菜类,便可以大大帮助我们提高记忆效率。

注意,这里分类的作用不只是将一组9个概念,分成每组各有4个、3个和2个概念的3组概念,因为这样还是9个概念,你所要做的是提高一个抽象层次,将大脑需要处理的9个项目变成3个项目。

如果你已经这么做了,恭喜你,你已经在实践黄金三步了:

  1. 定义问题:冰箱空了,需要购买补给。
  2. 分解问题:你要分析购买哪些东西呢?
  3. 归类分组:为了方便记忆,你将要买的9个东西按性质分成了三组,形成如下的金字塔结构。

image.png

这就是结构化思维,下次你再接到老婆这样的“需求”,记得把这个方法论用上,她一定会对你刮目相看。

在工作汇报中的运用

定义问题

“小张,客户对销售报告和库存报告不满意,你去看一下什么原因。” 面对这种典型的工作问题,你打算怎么处理呢?

分解问题

首先,你肯定要去调研客户不满意的原因,经过调查你发现,客户不满意主要有以下原因造成的:

  1. 提交报告的周期不恰当;
  2. 库存数据不可靠;
  3. 获得库存数据的时间太迟;
  4. 库存数据与销售数据不吻合;
  5. 客户希望能改进报告的格式;
  6. 客户希望除去无意义的数据;
  7. 客户希望突出说明特殊情况;
  8. 客户希望减少手工计算。

虽然你做了大量的工作,调查也很充分。但是,如果你要是把这8个原因直接给老板汇报,估计效果不会太好。 你可以自己做个试验,仔细阅读上面的列表3分钟,你能从中获得什么?同样的,你老板看到这样的罗列也会是一脸懵逼,不知道重点在哪。

归类分组

所以我们有必要加上第三步,对问题进行进一步的归类分组,我们可以将8个问题概括为3组:

  1. 报告中含有不可靠的数据;
  2. 报告的格式混乱;
  3. 产生报告的时间太晚,无法采取有效措施

进行分组之后,我们就可以得到如下的金字塔结构:
image.png

这种结构化的表达,很明显让问题的表述更加清晰。领导也可以很快抓住问题的要点,并作出相应的决策。当然,这里是为了着重介绍归类分组,真正的汇报你还要提供对应的解决方案,然后让老板做“选择题”。

在写代码时的运用

你遇到一个相当复杂的业务场景,在这个业务操作中,涉及到大量的校验和执行操作。这种代码,如果没有一定的策略,很容易写成大泥球。

定义问题

例如,在我们的业务中有一个商品上架的操作,是一个非常复杂的业务操作。

分解问题

对这种业务问题的分解,通常是产品经理的职责,但是作为工程师也不能完全依赖PRD。因为产品视角和工程视角还是有差别的。

针对“商品上架”,我们做了如下的功能分解:
image.png

归类分组

同样,在分解之后,我们需要有一个归类分组的过程。否则,这些步骤的平铺会让代码显得凌乱,不方便记忆和维护。通过分析,我们可以把分解后的步骤分成三个阶段:

  1. 初始化阶段
  2. 校验阶段
  3. 执行阶段

通过黄金三步,我们可以得到如下的金字塔结构:
image.png

最后,我们按照这个结构去组织我们的代码,整个代码结构会更加清晰,代码的可维护性也会好很多。
image.png

在应用架构中的运用

应用架构主要解决的是模块、组件定义和模块、组件关系的问题。
image.png

从宏观层面来说,架构设计也是遵循这三个步骤的。比如,我们的架构要如何分层,分模块、分组件就是在做问题分解。然后,模块和组件要归属在哪个层次,要如何命名,就是在做抽象,在做分类归组。

在综合考虑功能属性+质量属性,然后通过黄金三步,就能得到我们想要的架构设计。例如,我们的COLA 2.0架构也是在这个方法论的指导下完成的。

image.png

在产品架构中的运用

定义问题

“小张,为了做新零售,我们打算做一款智能互联网POS机,你先做一下产品设计。”

分解问题

通过调研你发现,作为POS机,其核心功能是收银和经营管理。所以至少需要包含收银的功能、服务核销的功能、商品管理的功能、库存管理的功能等等。

但如果仅仅是满足这些功能,和传统的POS并没有多大的区别,为了满足“智能”和“互联网”的要求,你去深入百度了一下智能POS应该具备的功能,大致包含以下功能:

  1. 刷脸支付;
  2. 支持品牌商营销;
  3. 支持自主营销;
  4. 智能定价;
  5. 外卖对接;
  6. 彩票对接;
  7. 虚拟充值等等。

在问题分解阶段,我们应该尽量多的收集信息,多发散,多头脑风暴。

归类分组

发散完,我们还是要收回来。在收敛之前,我们先看一下产品框架应该包含哪些东西,通常,一个产品架构至少要包含三个层次:

  1. 用户感知层(在何种场景下通过何种方式触达用户);
  2. 功能模块层(通过哪些功能模块实现产品的核心功能,和哪些外部平台功能有信息交互);
  3. 数据层(产品的数据从哪里来、产品的数据沉淀到何处去)。

在这三个层次的基础上,我们再对每个层次内的模块进行分组。例如在功能模块层,我们要对功能进行分类,让分散的功能点内聚成更大的产品模块(体现在用户界面上,往往是一级菜单和子菜单的关系)。

比如对于POS的收银产品模块,我们可以提供以下的产品功能:

  • 支付宝收银
  • 现金收银
  • 微信收银
  • 刷脸支付
  • 记账等

通过层次划分,模块划分我们就可以得到一个相对清晰的产品架构,以智能POS为例,我们可以画出如下的产品架构:
image.png

分类是科学也是艺术

通过上面的案例,我想你已经领会到黄金三步:定义问题——分解问题——归类分组的要义了。其中前两步相对比较直观,而第三步往往是不容易做好,也容易被忽略的关键步骤。

实际上,对事物的归类分组是我们人类的天性。人类大脑会自动将发现的所有事物以某种持续组织起来。基本上,大脑会认为同时发生的任何事物之间都存在某种关联,并且会将这些事物按某种逻辑模式组织起来。

比如,下面这张图片:
image.png

无论是谁,乍一看到上面的六个黑点,都会认为共有两组墨点,每组三个。造成这种印象的原因主要是有些黑点之间的距离比另一些黑点之间的距离大。

空间是一个相对比较直观的逻辑关系,然而,并不是所有的逻辑关系都是如此的显性化。实际上,很多的概念会在多个维度进行交叉耦合,这就给我们的归类分组带来了很大的挑战。

生物分类学

生物分类学通常直接称分类学(Taxonomy),是一门研究生物类群间的异同以及异同程度,阐明生物间的亲缘关系、基因遗传、物种进化过程和发展规律的基础科学。

最流行的分类是五界系统。通常包括七个主要级别:界(Kingdom)、门(Phylum)、纲(Class)、目(Order)、科(Family)、属(Genus)、种(Species)。种(物种)是基本单元,近缘的种归合为属,近缘的属归合为科,科隶于目,目隶于纲,纲隶于门,门隶于界。

不过分类学到不是一门很严谨的“科学”。就像比尔.布莱森在《万物简史》里说的:

分类学有时候被描述成一门科学,有时候被描述成一种艺术,但实际上那是一个战场。即使到了今天,那个体系比许多人认为的还要混乱。以描述生物基本结构的门的划分为例。许多生物学家坚持认为总数30个门,但有的认为20来个门,而爱德华在《生命的多样性》一书里提出的数字高达令人吃惊的89门。

由此可见,分类并不像我们想的那么简单。我们观察事物的视角不同,对问题的认知程度不同,得出来的分类很可能也完全不同。

特别是当概念之间有交叉情况,分类就会变得更加棘手。比如,在你的笔记本中,有“读书笔记”和“哲学笔记”两个平级的分类,此时你阅读了一本哲学书籍,那么你会把这本书的读书笔记放在哪个分类里呢?

分类的原则

分类的基本原则是MECE法则。透过结构看世界,说的就是MECE法则。

MECE法则即mutually exclusive collectively exhaustive的缩写,是麦肯锡咨询顾问芭芭拉·明托在《金字塔原理》中提出的一个思考工具,意思是“相互独立,完全穷尽”,也常被称为“不重叠,不遗漏”。

image.png

MECE原则的思想精髓,就是全维度的去分析一件事情,不要有遗漏和重复的部分。

我们可以借助已有的结构化思维模型(分类的框架)来分析问题,确保每一层要素之间“不重复、不遗漏”。

分类的思维模型

实际上,在上文中我们已经提到了一些分类的思维模型。比如,在应用架构中,我们通常有展现层、控制层、应用层、领域层和基础实施层;在产品架构中,有用户感知层、功能模块层、数据层。这些框架可以有效的指导我们在各自领域中开展工作。

类似于这样的分类思考模型还有很多,比如:
1、制定市场营销策略的“4P”模型,即产品策略(Product Strategy)、价格策略(Price Strategy)、渠道策略(Place Strategy)、促销策略(Promotion Strategy)。

2、分析问题的“5W2H"模型,即Why、What、Who、When、Where、How和How much。

3、思考组织战略的“7S”模型,即经营策略(Srategy)、组织结构(Structure)、运营系统(System)、经营风格(Style)、职员(Staff)、组织技能(Skill)和共享价值观(Shared value)。

4、分析竞争力的SWOT模型,SWOT分析代表分析企业优势(Strengths)、劣势(Weakness)、机会(Opportunity)和威胁(Threats)。

5、制定目标的SMART模型,即制定目标要满足确定性(Specific) 、可度量性(Measurable)、可实现性(Attainable)、相关性(Relevant)和时效性(Time-based)。

这些思维模型都是宝贵的经验总结,相当于已经帮我们做好了第三步“归类分组”的工作,我们只需要按照模型制定的框架往里面填充要素即可。

因此擅用模型,活用框架。可以极大的提升我们解决问题的效率,同时帮助我们做更加全面的、更加结构化的思考。做了“无遗漏,不重复”。

关于面向接口编程,你真的弄懂了吗?

什么是接口(What)

百度说:接口泛指实体把自己提供给外界的一种抽象化物,用以由内部操作分离出外部沟通方法,使其能被内部修改而不影响外界其他实体与其交互的方式。

牛津字典说:Interface: A point where two systems, subjects, organizations, etc. meet and interact.

维基百科说:In computing, an interface is a shared boundary across which two or more separate components of a computer system exchange information.

Frank说:上面说的都对,但是关于接口我还有更多想说的。

接口有什么好处(Why)

在我看来,接口在软件设计中主要有两大好处:

1. 制定标准

标准规范的制定离不开接口,制定标准的目的就是为了让定义和实现分离,而接口作为完全的抽象,是标准制定的不二之选。

这个世界的运转离不开分工协作,而分工协作的前提就是标准化。试想一下,你家的电脑能允许你把显卡从NVIDIA换成七彩虹;你家的灯泡坏了,你可以随便找一个超市买一个新的就可以换上;你把数据从Oracle换成了MySQL,但是你基于JDBC写的代码都不用动。等等这些事情的背后都是因为接口,以及基于接口定制的标准化在起作用。

在Java的世界里,有一个很NB的社区叫JCP( Java Community Process),就是专门通过JSR(Java Specification Request)来制定标准的。正是有了JSR-315(Java Servlet),我们服务端的代码才能在Tomcat和Jetty之间自由切换。
image.png

最后,我想用一句话来总结一下标准的重要性,那就是:“一流的企业做标准,二流的企业做品牌,三流的企业做产品。

2. 提供抽象

除了标准之外,接口还有一个特征就是抽象。正是这样的抽象,得以让接口的调用者和实现者可以完全的解耦。

解耦的好处是调用者不需要依赖具体的实现,这样也就不用关心实现的细节。这样,不管是实现细节的改动,还是替换新的实现,对于调用者来说都是透明的。

这种扩展性和灵活性,是软件设计中,最美妙的设计艺术之一。一旦你品尝过这种“依赖接口”的设计来带的美好,就不大会再愿意回到“依赖实现”的简单粗暴。平时我们说的“面向接口编程原则”和“依赖倒置原则”说的都是这种设计。

另外,一旦你融会贯通的掌握了这个强大的技巧——面向抽象、面向接口,你会发现,虽然面向实现和面向接口在代码层面的差异不大,但是其背后所隐含的设计思想和设计理念的差异,不亚于我篮球水平和詹姆斯篮球水平之间的差异!

作为一名资深职场老兵,我墙裂建议各位在做系统设计、模块设计、甚至对象设计的时候。要多考虑考虑更高层次的抽象——也就是接口,而不是一上来就陷入到实现的细节中去。要清楚的意识到接口设计是我们系统设计中的主要工作内容。而这种可以跳出细节内容,站在更高抽象层次上,来看整个系统的模块设计、模块划分、模块交互的人,正是我们软件设计队伍中,非常稀缺的人才。有时候,我们也管这些人叫架构师。

什么时候要用接口(When)

有扩展性需求的时候

可扩展设计,主要是利用了面向对象的多态特性,所以这里的接口是一个广义的概念,如果用编程语言的术语来说,它既可以是Interface,也可能是Abstract Class。

这种扩展性的诉求在软件工作中可以说无处不在,小到一个工具类。例如,我现在系统中需要一个开关的功能,开关的配置目前是用数据库做配置的,但是后续可能会迁移到Diamond配置中心,或者SwitchCenter上去。

简单做法就是,我直接用数据库的配置去实现开关功能,如下图所示:
image.png

但是这样做的问题很明显,当需要切换新的配置实现的话,就不得不扒开原来的应用代码做修改了。更恰当的做法应该是提供一个Switch的接口,让不同的实现去实现这个接口,从而在切换配置实现的时候,应用代码不再需要更改了。
image.png

如果说,上面的重构只是使用策略模式对代码进行了局部优化,做了当然更好,不做的话,影响也还好,可以将就着过。

那么接下来我要给大家介绍的场景,就不仅仅是“要不要”的问题,而是“不得不”的问题了。

例如,老板给你布置了一个任务,实现一个类似于eclipse可以可插拔(Pluggable)的产品,此时,使用接口就不仅仅是一个选择问题了,而是你不得不使用的架构方法了。因为,可插拔的本质就是,你制定一个标准接口(API),然后有不同的实现者去做插件的实现,最后再由PluginManager把这个插件机制串起来而已。

下图是我当时给ICBU设计的一个企业协同云的Pluggable架构,其本质上,也就是基于接口的一种标准和扩展的设计。
image.png

需要解耦的时候

上面介绍的关于Switch的例子,从表面上来看,是扩展性的诉求。但不可扩展的本质原因正是因为耦合性。当我们通过Switch Interface来解开耦合之后,扩展性的诉求也就迎刃而解了。

发现这种耦合性,对系统的可维护性至关重要。有一些耦合比较明显(比如Switch的例子)。但更多的耦合是隐式的,并没有那么明显,而且在很长一段时间,它也不是什么问题,但是,一旦它变成一个问题,将是一个非常头痛的问题。

一个真实的典型案例,就是java的logger,早些年,大家使用commons-logging、log4j并没有什么问题。然而,此处一个隐患正在生长——那就是对logger实现的强耦合。

当logback出来之后,事情开始变得复杂,当我们想替换一个新的logger vendor的时候,为了尽量减少代码改动,不得不上各种Bridge(桥接),到最后日志代码变成了谁也看不懂的代码迷宫。下图就是我费了九头二虎之力,才梳理清楚的一个老业务系统的日志框架依赖情况。
image.png

试想一下,假如一开始我们就能遇见到这种紧耦合带来的问题。在应用和日志框架之间加入一层抽象解耦。后续的那么多桥接,那么多的向后兼容都是可以省掉的麻烦。而我们所要做的事情,实际上也很简单——就是加一个接口做解耦而已(如下图所示):
image.png

要给外界提供API的时候

上文已经介绍过JCP和JSR了,大家有空可以去阅读一些JSR的文档。不管是做的比较成功的JSR-221(JDBC规范)、JSR-315(Servlet规范),还是比较失败的JSR-94(规则引擎规范)等等。其本质上都是在定义标准、和制定API。其规范的内容都是抽象的,其对外发布的形式都是接口,它不提供实现,最多会指导实现。

还有就是我们通常使用的各种开放平台的SDK,或者分布式服务中RPC的二方库,其包含的主要成分也是接口,其实现不在本地,而是在远程服务提供方。

类似于这种API的情况,都是在倒逼开发者要把接口想清楚。我想,这也算微服务架构一个漂亮的“副作用”吧。当原来单体应用里的各种耦合的业务模块,一旦被服务化之后,就自然而然的变成“面向接口”的了。

通过依赖倒置来实现面向接口(How)

关于依赖倒置,我以前写过不少文章,来阐述它的重要性。实际上,我上面给出的关于扩展需求的Switch案例,关于解耦的logger案例。其背后用来解决问题的方法论都是依赖倒置。

image.png
如上图所示,依赖倒置原则主要规定了两件事情:

  1. 高层模块不应该依赖底层模块,两者都应该依赖抽象(如上面的图2所示)
  2. 抽象不应该依赖细节,细节应该依赖抽象。

我们回头看一下,不管是Switch的设计,还是抽象Logger的设计,是不是都在遵循上面的两条定义内容呢。

实际上,DIP(依赖倒置原则)不光在对象设计,模块设计的时候有用。在架构设计的时候也非常有用,比如,我在做COLA 1.0的时候,和大多数应用架构分层设计一样,默许了Domain层可以依赖Infrastructure层。
image.png

这种看起来“无伤大雅”的设计,实际上还是存在不小的隐患,也违背了我当初想把业务复杂度和技术复杂度分开的初心,当业务变得更加复杂的时候,这种“偷懒”行为很可能会导致Domain层堕落成大泥球(Big mud ball)。因此,在COLA 2.0的时候,我决定用DIP来反转Domain层和Infrastructure层的关系,最终形成如下的结构:
image.png

这样做的好处是Domain层会变得更加纯粹,其好处体现在以下三点:

1、解耦: Domain层完全摆脱了对技术细节(以及技术细节带来的复杂度)的依赖,只需要安心处理业务逻辑就好了。

2、并行开发: 只要在Domain和Infrastructure约定好接口,可以有两个同学并行编写Domain和Infrastructure的代码。

3、可测试性: 没有任何依赖的Domain里面都是POJO的类,单元测试将会变得非常方便,也非常适合TDD的开发。

什么时候不需要接口

"劲酒虽好,可不要贪杯哦!"

和许多其它软件原则一样,面向接口很好,但也不应该是不分背景、不分场合胡乱使用的杀手锏和尚方宝剑。因为过多的使用接口,过多的引入间接层也会带来一些不必要的复杂度。

比如,我就看过有些应用的内部模块设计的过于“灵活”,给什么DAO、Convertor都加上一层Interface,但实际情况是,应用中对DAO、Convertor的实现进行替换的可能性极低。类似于这样的,装模作样,装腔作势的Interface就属于可有可无的鸡骨头(比鸡肋还低一个档次)。

就像《Effective Java》的作者Joshua Bloch所说:

“同大多数学科一样,学习编程的艺术首先要学会基本的规则,然后才能知道什么时候可以打破这些规则。”

结构化思维,让你的工作有条不紊

在日常工作中,我们时常会碰到这样的情况,有的人讲一件事情的时候逻辑非常混乱,前后没有逻辑性关联,不能把一件事情说清楚。这种思维混乱是缺少结构化思维的典型表现。实际上,我们不仅在表达上要结构化,在分析问题的时候更要有结构化思维,这样才能分析的更全面、更深刻。

什么是结构化思维

那么到底是什么是结构化思维呢?我给结构化思维的定义就是逻辑+套路。

表达要有逻辑

所谓逻辑是指我们的结构之间必须是有逻辑关系的。例如,你说话的时候用“第一,第二,第三”这个逻辑顺序是合理的,而如果是用“第一,第二,第四”就会显得很奇怪。实际上,组织思想的逻辑只有四种:
1)演绎顺序:“大前提、小前提、结论”的演绎推理方式就是演绎顺序的。比如,经典三段论,”所有人都要死,苏格拉底是人,苏格拉底要死“。

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

3)空间(结构)顺序:“前端,后端,数据”,“波士顿,纽约,华盛顿。 ”,化整为零(将整体分解为部分)等都是空间顺序。 在做空间分解的时候,要注意满足MECE(Mutually Exclusive Collectively Exhaustive)原则。

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

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

做事要有套路

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

逻辑是一种能力,而套路是方法论、是经验。逻辑是道的东西,而方法论是术的东西。二者都很重要,只有熟练的掌握二者我们才能很好的结构化思维。接下来,我们通过两个案例来介绍一下结构化思维是实际工作场景中的应用。

结构化思维案例

如何落地新团队

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

这里,我们就能用上结构化思维来帮我们理清思路,从而有条不紊的开展工作了。我们要知道对一个企业来说,核心要素无外乎就是业务、技术和人。我们所要做的就是如何去熟悉业务,熟悉技术,熟悉人,然而每一块又可以进行进一步的结构化拆解,如下图所示。
undefined

1. 熟悉业务

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

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

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

2. 熟悉技术

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

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

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

3. 熟悉人

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

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

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

如何做晋升述职

作者在阿里巴巴已经做了好几年的晋升评委,发现很多同学都缺乏结构化思维,冗长的PPT里,却不能很好的把一件事情说清楚。实际上在做工作汇报或者述职的时候,是很容易结构化的。

最清晰、最实用的结构化表达是:提出问题,定义问题,分析问题,解决问题,最后展望未来。 如果按照这个逻辑顺序去阐述一件事情,会比不知道这个“套路”写出来的东西,要好的多。这也是麦肯锡常用的解决问题的框架。

我们很多同学忽视了“提出问题”和“定义问题”的重要性,上来就开始将解决方案,就开始进入细节。殊不知“提出问题”和“定义问题”是比“解决问题”更重要的能力,如果你连问题是什么,以及为什么要解决这个问题都没有搞清楚,后面的努力又有什么意义呢。正所谓:路走对了,就不怕远。首先我们必须要保证我们是在“做正确的事”,然后才是“正确的做事”。

因此,回答前面两个问题非常重要。不仅是在晋升答辩的时候,在我们日常工作中也是如此,我们要善于发现有价值的问题,然后将问题定义清楚、理解透彻,然后再着手解决。就像爱因斯坦说的:

“If I had an hour to solve a problem I’d spend 55 minutes thinking about the problem and 5 minutes thinking about solutions.”

image.png

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

可以看到,具备这些结构化的方法论,可以帮助我们做到事半功倍,经常锻炼结构化思维,可以极大的提升我们职场竞争力。

from:

https://blog.csdn.net/significantfrank/article/details/101095292

https://blog.csdn.net/significantfrank/article/details/100526155

https://blog.csdn.net/significantfrank/article/details/102877680

https://blog.csdn.net/significantfrank/article/details/91973015

 

posted @ 2020-08-14 17:55  CharyGao  阅读(557)  评论(0编辑  收藏  举报