34 你的代码是怎么变混乱的?

前面几讲,我给你讲了开发过程的各种自动化,从构建、验证到上线部署,这些内容都是站在软件外部看的。

从这一讲开始,我准备带领大家进入到软件内部。

今天的话题就从写代码开始说起。

逐步腐化的代码

代码是程序员改造世界最直接的武器,却也是程序员抱怨最多的东西。为什么程序员会对代码如此不满呢?

你会抱怨写一段代码吗?你肯定不会,毕竟这是你养家糊口的本领,最基本的职业素养我们还是有的。

那抱怨的是什么呢?是维护一段代码。

为什么维护代码那么难?因为通常来说,你维护的这段代码是有一定年龄的,所以,你总会抱怨前人没有好好写这段代码。

好,现在你拿到了一个新的需求,要在这段代码上添加一个新功能,你会怎么做呢?

很多人的做法是,在原有的代码上添加一段新的逻辑,然后提交完工。

发现问题了吗?你只是低着头完成了一项任务,而代码却变得更糟糕了。如果我问你,你为什么这么做?

你的答案可能是:“这段代码都这样了,我不敢乱改。”或者是:“之前就是这么写的,我只是遵循别人的风格在写。”

行业里有一个段子,对程序员最好的惩罚是让他维护自己三个月前写的代码。你一不小心就成了自己最讨厌的人。

从前,我也认为很多程序员是不负责任,一开始就没有把代码写好,后来,我才知道很多代码其实只是每次加一点。你要知道,一个产品一旦有了生命力,它就会长期存在下去,代码也就随着时间逐渐腐烂了。

而几乎每个程序员的理由都是一样的,他们也很委屈,因为他们只改了一点点。

这样的问题有解吗?一个解决方案自然就是我们前面说过的重构,但重构的前提是,你得知道代码驶向何方。

对于这个问题,更好的答案是,你需要了解一些软件设计的知识。

SOLID 原则

提到软件设计,大部分程序员都知道一个说法“高内聚、低耦合”,但这个说法如同“期待世界和平”一样,虽然没错,但并不能很好地指导我们的具体工作。

人们尝试着用各种方法拆解这个高远的目标,而比较能落地的一种做法就是 Robert Martin 提出的面向对象设计原则:SOLID,这其实是五个设计原则的缩写,分别是

  • 单一职责原则(Single responsibility principle,SRP)

  • 开放封闭原则(Open–closed principle,OCP)

  • Liskov 替换原则(Liskov substitution principle,LSP)

  • 接口隔离原则(Interface segregation principle,ISP)

  • 依赖倒置原则(Dependency inversion principle,DIP)

早在1995年,Robert Martin 就提出了这些设计原则的雏形,然后在他的《敏捷软件开发:原则、实践与模式》这本书中,比较完整地阐述了这五个原则。

后来,他有把这些原则进一步整理,成了今天的 “SOLID”。

学习这些设计原则有什么用呢?

今天的程序员学习软件设计多半是从设计模式入门的,但不知道你是否有这样的感觉,在学习设计模式的时候,有几个设计模式看上去如此相像,如果不是精心比较,你很难记得住它们之间的细微差别。

而且,真正到了工作中,你还能想得起来的可能就剩下几个最简单的模式了,比如工厂方法、观察者等等。

另外,有人常常“为赋新词强说愁”,硬去使用设计模式,反而会让代码变得更加复杂了。

你会有一种错觉,我是不是学了一个假的设计模式,人人都说好的东西,我怎么就感受不到呢?

初学设计模式时,我真的就被这个问题困扰了好久。直到我看到了 Robert Martin 的《敏捷软件开发:原则、实践与模式》。这是一本被名字糟蹋了的好书。

这本书出版之际,敏捷软件开发运动正风起云涌,Robert Martin 也不能免俗地蹭了热点,将“敏捷”挂到了书名里。其实,这是一本讲软件设计的书。

当我看到了 SOLID 的五个原则之后,我终于想明白了,原来我追求的方向错了。如果说设计模式是“术”,设计原则才是“道”。设计模式并不能帮你建立起知识体系,而设计原则可以。

当我不能理解“道”的时候,“术”只能死记硬背,效果必然是不佳的。想通这些之后,我大大方方地放弃了对于设计模式的追求,只是按照设计原则来写代码,结果是,我反而是时常能重构出符合某个设计模式的代码。至于具体模式的名字,如果不是有意识地去找,我已经记不住了。

当然,我并不是说设计模式不重要,之所以我能够用设计原则来写代码,前提条件是,我曾经在设计模式上下过很多功夫。

道和术,是每个程序员都要有的功夫,在“术”上下过功夫,才会知道“道”的价值,“道”可以帮你建立更完整的知识体系,不必在“术”的低层次上不断徘徊。

单一职责原则

好,下面我就单拿 SOLID 中单一职责原则稍微展开讲一下,虽然这个原则听上去是最简单的,但也有很多误解存在。

首先,什么是单一职责原则呢?

如果读过《敏捷软件开发:原则、实践与模式》,你对单一职责的理解应该是,一个模块应该仅有一个修改的原因。

2017年,Robert Martin 出版了《架构整洁之道》(Clean Architecture),他把单一职责原则的定义修改成“一个模块应该仅对一类 actor 负责”,这里的 actor 可以理解为对系统有共同需求的人。

不管是哪个定义,初读起来,都不是那么好理解。

我举个例子,你就知道了。

我这里就用 Robert Martin 自己给出的例子:在一个工资管理系统中,有个 Employee 类,它里面有三个方法:

  • calculatePay(),计算工资,这是财务部门关心的。

  • reportHours(),统计工作时长,这是人力部门关心的。

  • save(),保存数据,这是技术部门关心的。

之所以三个方法在一个类里面,因为它们的某些行为是类似的,比如计算工资和统计工作时长都需要计算正常工作时间,为了避免重复,团队引入了新的方法:regularHours()。

接下来,财务部门要修改正常工作时间的统计方法,但人力部门不需要修改。负责修改的程序员只看到了 calculatePay() 调用了 regularHours(),他完成了他的工作,财务部门验收通过。

但上线运行之后,人力部门产生了错误的报表。

这是一个真实的案例,最终因为这个错误,给公司造成了数百万的损失。

如果你问程序员,为什么要把 calculatePay() 和 reportHours()放在一个类里,程序员会告诉你,因为它们都用到了 Employee 这个类的数据。

但是,它们是在为不同的 actor 服务,所以,任何一个 actor 有了新的需求,这个类都需要改,它也就很容易就成为修改的重灾区。

更关键的是,很快它就会复杂到没人知道一共有哪些模块与它相关,改起来会影响到谁,程序员也就越发不愿意维护这段代码了。

我在专栏“开篇词”里提到过,人的大脑容量有限,太复杂的东西理解不了。所以,我们唯一能做的就是把复杂的事情变简单。

我在“任务分解”模块中不断强调把事情拆小,同样的道理在写代码中也适用。单一职责原则就是给了你一个指导原则,可以按照不同的 actor 分解代码。

上面这个问题,Robert Martin 给了一个解决方案,就是按照不同的 actor 将类分解,我把分解的结果的类图附在了下面:

calc

编写短函数

好,你已经初步了解了单一职责原则,但还有一点值得注意。

我先来问个问题,你觉得一个函数多长是合适的?

曾经有人自豪地向我炫耀,他对代码要求很高,超过50行的函数绝对要处理掉。

我在专栏中一直强调“小”的价值,能看到多小,就可以在多细的粒度上工作。

单一职责这件事举个例子很容易,但在真实的工作场景中,你能看到一个模块在为多少 actor 服务,就完全取决于你的分解能力了。

回到前面的问题上,就我自己的习惯而言,通常的函数都在十行以内,如果是表达能力很强的语言,比如 Ruby,函数会更短。

所以,你可想而知我听到“把50行代码归为小函数”时的心情。

我知道,“函数长短”又是一个非常容易引起争论的话题,不同的人对于这个问题的答案,取决于他看问题的粒度。

所以,不讨论前提条件,只谈论函数的长短,其实是没有意义的。

单一职责原则可以用在不同的层面,写一个类,你可以问问这些方法是不是为一类 actor 服务;写方法时,你可以问问这些代码是不是在一个层面上;甚至一个服务,也需要从业务上考虑一下,它在提供是否一类的服务。

总之,你看到的粒度越细,也就越能发现问题。

总结时刻

今天,我讲的内容是软件设计,很多代码的问题就是因为对设计思考得不足导致的。

许多程序员学习设计是从设计模式起步的,但这种学法往往会因为缺乏结构,很难有效掌握。

设计原则,是一个更好的体系,掌握设计原则之后,才能更好地理解设计模式这些招式。

Robert Martin 总结出的“SOLID”是一套相对完整易学的设计原则。

我以“SOLID” 中的单一职责原则为例,给你稍做展开,更多的内容可以去看 Robert Martin 的书。

不过,我也给你补充了一些维度,尤其是从“小”的角度告诉你,你能看到多小,就能发现代码里多少的问题。

如果今天的内容你只能记住一件事,那请记住:把函数写短。

最后我想请你思考一下,你是怎么理解软件设计的呢?欢迎在留言区写下你的想法。

35 总是在说MVC分层架构,但你真的理解分层吗?

作为程序员,你一定听说过分层,比如,最常见的 Java 服务端应用的三层结构,在《15 一起练习:手把手带你分解任务》中,我曾提到过:
  • 数据访问层,按照传统的说法,叫 DAO(Data Access Object,数据访问对象),按照领域驱动开发的术语,称之为 Repository;

  • 服务层,提供应用服务;

  • 资源层,提供对外访问的资源,采用传统做法就是 Controller。

这几乎成为了写 Java 服务的标准模式。

但不知道你有没有想过,为什么要分层呢?

设计上的分解

其实,分层并不是一个特别符合直觉的做法,符合直觉的做法应该是直接写在一起。

在编程框架还不是特别流行的时候,人们就是直接把页面和逻辑混在一起写的。

如果你有机会看看写得不算理想的 PHP 程序,这种现象还是大概率会出现的。

即便像 Java 这个如此重视架构的社区,分层也是很久之后才出现的,早期的 JSP 和 PHP 并没有什么本质区别。

那为什么要分层呢?

原因很简单,当代码复杂到一定程度,人们维护代码的难度就急剧上升。一旦出现任何问题,在所有一切都混在一起的代码中定位问题,本质上就是一个“大海捞针”的活

前面讲任务分解的时候,我不断在强调的观点就是,人们擅长解决的是小问题,大问题怎么办?拆小了就好。

分层架构,实际上,就是一种在设计上的分解。

回到前面所说的三层架构,这是行业中最早普及的一种架构模式,最开始是 MVC,也就是 Model、View 和 Controller。

MVC 的概念起源于 GUI (Graphical User Interface,图形用户界面)编程,人们希望将图形界面上展示的部分(View)与 UI 的数据模型(Model)分开,它们之间的联动由 Controller 负责。

这个概念在 GUI 编程中是没有问题的,但也仅限于在与 UI 有交互的部分。

很多人误以为这也适合服务端程序,他们就把模型部分误解成了数据库里的模型,甚至把它理解成数据库访问。

于是,你会看到有人在 Controller 里访问数据库。

不知道你是不是了解 Ruby on Rails,这是当年改变了行业认知的一个 Web 开发框架,带来很多颠覆性的做法。

它采用的就是这样一种编程模型。当年写 Rails 程序的时候我发现,当业务复杂到了一定规模,代码就开始难以维护了。

我想了好久,终于发现,在 Rails 的常规做法中少了服务层(Service)的设计。

这个问题在 Java 领域,爆发得要比 Rails 里早,因为 Ruby 语言的优越性,Rails 实现的数据访问非常优雅。

正是因为 Rails 的数据访问实在太容易了,很多服务实际上写到 Model 层里。在代码规模不大时,代码看上去是不复杂的,甚至还有些优雅。

而那时的 Java 可是要一行一行地写数据访问,所以,代码不太可能放在 Model 层,而放在Controller 里也会让代码变复杂,于是,为业务逻辑而生的 Service 层就呼之欲出了。

至此,常见的 Java 服务端开发的基础就全部成型了,只不过,由于后来 REST 服务的兴起,资源层替代了 Controller 层。

到这里,我给你讲了常见的 Java 服务三层架构的来龙去脉。

但实际上,在软件开发中,分层几乎是无处不在的,因为好的分层往往需要有好的抽象。

无处不在的分层

作为程序员,我们几乎每天都在与分层打交道。

比如说,程序员都对网络编程模型很熟悉,无论是 ISO 的七层还是 TCP/IP 的五层。

但不知道你有没有发现,虽然学习的时候,你要学习网络有那么多层,但在使用的时候,大多数情况下,你只要了解最上面的那层,比如,HTTP。

很多人对底层的协议的理解几乎就停留在“学过”的水平上,因为在大多数情况下,除非你要写协议栈,不然你很难用得到。

即便偶尔用到,90%的问题靠搜索引擎就解决了,你也很少有动力去系统学习。

之所以你可以这么放心大胆地“忽略”底层协议,一个关键点就在于,网络模型的分层架构实现得太好了,好到你作为上层的使用者几乎可以忽略底层。

而这正是分层真正的价值:构建一个良好的抽象。

这种构建良好的抽象在软件开发中随处可见,比如,你作为一个程序员,每天写着在 CPU 上运行的代码,但你读过指令集吗?

你之所以可以不去了解,是因为已经有编译器做好了分层,让你可以只用它们构建出的“抽象”——编程语言去思考问题。

比如,每天写着 Java 程序的程序员,你知道 Java 程序是如何管理内存的吗?

这可是令很多 C/C++程序员寝食难安的问题,而你之所以不用关心这些,正是托了 Java 这种“抽象”的福。

对了,你甚至可能没有注意到编程语言也是一种抽象。

有抽象有发展

只有构建起抽象,人们才能在此基础上做出更复杂的东西。

如果今天的游戏依然是面向显示屏的像素编程,那么,精彩的游戏视觉效果就只能由极少数真正的高手来开发。

我们今天的大部分游戏应该依然停留在《超级玛丽》的水准。

同样,近些年前端领域风起云涌,但你是否想过,为什么 Web 的概念早就出现了,但前端作为一个专门的职位,真正的蓬勃发展却是最近十年的事?

2009年,Ryan Dahl 发布了Node.js,人们才真正认识到,原来 JavaScript 不仅仅可以用于浏览器,还能做服务器开发。

于是,JavaScript 社区大发展,各种在其他社区已经很流行的工具终于在 JavaScript 世界中发展了起来。

正是有了这些工具的支持,人们才能用 JavaScript 构建更复杂的工程,前端领域才能得到了极大的发展。

如今,JavaScript 已经发展成唯一一门全平台语言,当然,发展最好的依然是在它的大本营:前端领域。

前端程序员才有了今天幸福的烦恼:各种前端框架层出不穷。

在这里,Node.js 的出现让 JavaScript 成为了一个更好的抽象。

构建你的抽象

理解了分层实际上是在构建抽象,你或许会关心,我该怎么把它运用在自己的工作中。

构建抽象,最核心的一步是构建出你的核心模型。

什么是核心模型呢?就是表达你业务的那部分代码,换句话说,别的东西都可以变,但这部分不能变

这么说可能还是有点抽象,我们回到前面的三层架构。

在前面介绍三层架构的演变时,提到了一个变迁:REST服务的兴起,让 Controller 逐渐退出了历史舞台,资源层取而代之。

换句话说,访问服务的方式可能会变。放到计算机编程的发展中,这种趋势就更明显了,从命令行到网络,从 CS(Client-Server) 到 BS(Browser-Server),从浏览器到移动端。所以,怎么访问不应该是你关注的核心。

同样, 关系型数据库也不是你关注的核心,它只是今天的主流而已。从前用文件,今天还有各种 NoSQL。

如此说来,三层架构中的两层重要性都不是那么高,那重要的是什么?答案便呼之欲出了,没错,就是剩下的部分,我们习惯上称之为服务层,但这个名字其实不能很好地反映它的作用,更恰当的说法应该可以叫领域模型(Domain Model)。

它便是我们的核心模型,也是我们在做软件设计时,真正应该着力的地方。

为什么叫“服务层”不是一个好的说法呢?

这里会遗漏领域模型中一个重要的组成部分:领域对象。

很多人理解领域对象有一个严重的误区,认为领域对象属于数据层。数据存储只是领域对象的一种用途,它更重要的用途还是用在各种领域服务中。

由此还能引出另一个常见的设计错误,领域对象中只包含数据访问,也就是常说的 getter 和 setter,而没有任何逻辑。

如果只用于数据存储,只有数据访问就够了,但如果是领域对象,就应该有业务逻辑。

比如,给一个用户修改密码,用户这个对象上应该有一个 changePassword 方法,而不是每次去 setPassword。

严格地说,领域对象和存储对象应该是两个类,只不过它俩实在太像了,很多人经常使用一个类,这还是个小问题。

但很多人却把这种内部方案用到了外部,比如,第三方集成。

为数不少的团队都在自己的业务代码中直接使用了第三方代码中的对象,第三方的任何修改都会让你的代码跟着改,你的团队就只能疲于奔命。

解决这个问题最好的办法就是把它们分开,你的领域层只依赖于你的领域对象,第三方发过来的内容先做一次转换,转换成你的领域对象。这种做法称为防腐层

当我们把领域模型看成了整个设计的核心,看待其他层的视角也会随之转变,它们只不过是适配到不同地方的一种方式而已,而这种理念的推广,就是一些人在说的六边形架构。

six

怎么设计好领域模型是一个庞大的主题,推荐你去了解一下领域驱动设计(Domain Driven Design,DDD),这个话题我们后面还会再次提到。

讨论其实还可以继续延伸下去,已经构建好的领域模型怎么更好地提供给其他部分使用呢?

一个好的做法是封装成领域特定语言(Domain Specific Language,DSL)。

当然,这也是一个庞大的话题,就不继续展开了。

总结时刻

我从最常见的服务端三层架构入手,给你讲了它们的来龙去脉。分层架构实际是一种设计上的分解,将不同的内容放在不同的地方,降低软件开发和维护的成本。

分层,更关键的是,提供抽象。这种分层抽象在计算机领域无处不在,无论是编程语言,还是网络协议,都体现着分层抽象的价值。

有了分层抽象,人们才能更好地在抽象的基础上构建更复杂的东西。

在日常工作中,我们应该把精力重点放在构建自己的领域模型上,因为它才是工作最核心、不易变的东西。

如果今天的内容你只能记住一件事,那请记住:构建好你的领域模型。

最后我想请你思考一下,你还知道哪些技术是体现分层抽象的思想吗?

欢迎在留言区写下你的想法。

36 为什么总有人觉得5万块钱可以做一个淘宝?

今天,我们从软件行业的一个段子说起。

甲方想要做个电商网站,作为乙方的程序员问:“你要做个什么样的呢?”甲方说:“像淘宝那样就好。”程序员问:“那你打算出多少钱?”甲方想了想,“5万块钱差不多了吧!”

这当然是个调侃客户不懂需求的段子,但你有没有想过,为什么在甲方看来并不复杂的系统,你却觉得困难重重呢?

因为你们想的根本不是一个东西。

在客户看来,我要的不就是一个能买东西的网站吗?只要能上线商品,用户能看到能购买不就好了,5万块钱差不多了。

而你脑中想的却是,“淘宝啊,那得是多大的技术挑战啊,每年一到‘双11’,那就得考虑各种并发抢购。淘宝得有多少程序员,5万块你就想做一个,门都没有。”

如果放在前面“沟通反馈”的模块,我可能会讲双方要怎么协调,把想法统一了。但到了“自动化”的模块,我想换个角度讨论这个问题:系统是怎么变复杂的。

淘宝的发展历程

既然说到了淘宝,我们就以一些公开资料来看看淘宝的技术变迁过程。

2013年,子柳出版了一本《淘宝技术这十年》,这本书里讲述了淘宝是怎么一步步变化的。

按照书中的说法,第一个淘宝是“买来的”,买的是一个叫做 PHPAuction 的系统,即便选择了最高配,也才花了2000美元左右。

这是一个采用 LAMP 架构的系统,也就是 Linux + Apache + MySQL + PHP,这在当年可是典型的开源架构。

团队所做的主要就是一些订制化工作,最大的调整就是将单一数据库的读写进行了拆分,变成了一个主库和两个从库。

这种结构在今天来看,依然是很多团队做调整的首选。

淘宝的发展历程

当访问量和数据量不断提升,MySQL 数据库率先扛不住了。

当年的 MySQL 默认采用的是 MyISAM 引擎,写数据的时候会锁住表,读也会被卡住,当然,这只是诸多问题中的一个。

2003年底,团队将 MySQL 换成了 Oracle。

由于 Oracle 的性能要好上许多,主从的数据库架构又改回了单一数据库。

但由于 PHP 访问数据库的缺省方案没有连接池,只好找了开源的 SQL Relay,这也为后续的改进埋下了伏笔。

oracle

当数据量继续加大,本地存储就已经无法满足了,只能通过引入网络存储解决问题。

数据量进一步增大之后,存储节点一拆再拆,依然不能解决问题,淘宝就踏上了购买小型机的道路。

IBM 的小型机、Oracle 的数据库和 EMC 的存储,这个阶段就踏上了 IOE 之路。

2004年初,SQL Relay 已经成了一个挥之不去的痛点,于是,只能从更根本的方案上动脑筋:更换程序设计语言。

作为当时的主流,Java 成了不二之选。

替换的方案就是给业务分模块,一块一块地替换。

老模块只维护,不增加新功能,新功能只在新模块开发,新老模块共用数据库。新功能上线,则关闭老模块对应功能,所有功能替换完毕,则老模块下线。

淘宝的数据量继续增长,单台 Oracle 很快到了上限,团队采用了今天常见的“分库分表”模式,但“分库分表”就会带来新的问题,跨数据库的数据怎么整合?

于是,打造出了一个 DBRoute,用以处理分库的数据。

但是,这种做法也带来了一个新的问题,同时连接多个数据库,任何一个数据库出了问题,都会导致整个网站的故障。

当淘宝的数据量再次增长,每次访问都到了数据库,数据库很难承受。

一个解决方案就是引入缓存和 CDN(Content Delivery Network,内容分发网络),这样,只读数据的压力就从数据库解放了出来。

当时的缓存系统还不像今天这么成熟,于是,团队基于一个开源项目改出了一个。他们用的 CDN 最开始是一个商用系统,但流量的增加导致这个系统也支撑不住了,只好开始搭建自己的 CDN。

后来,因为 CDN 要消耗大量的服务器资源,为了降低成本,淘宝又开始研发自己的低功耗服务器。

随着业务的不断发展,开发人员越来越多,系统就越来越臃肿,耦合度也逐渐提升,出错的概率也逐渐上升。这时,不得不对系统进行分解,将复用性高的模块拆分出来,比如,用户信息。

业务继续发展,拆分就从局部开始向更大规模发展,底层业务和上层流程逐渐剥离,并逐渐将所有业务都模块化。

有了一个相对清晰地业务划分之后,更多的底层业务就可以应用于不同的场景,一个基础设施就此成型,新的业务就可以使用基础设施进行构建,上层业务便如雨后春笋一般蓬勃发展起来。

在这个过程中,有很多技术问题在当时还没有好的解决方案,或者是不适合于它们所在的场景。

所以,淘宝的工程师就不得不打造自己的解决方案,比如:分布式文件系统(TFS)、缓存系统(Tair)、分布式服务框架(HSF)等等。还有一些技术探索则是为了节省成本,比如,去 IOE 和研发低功耗服务器等等。

我这里以淘宝网站的发展为例,做了一个快速的梳理,只是为了让你了解一个系统的发展,如果你有兴趣了解更多细节,不妨自己找出这本书读读。

当然,现在的淘宝肯定比这更加完整复杂。

同样的业务,不同的系统

为什么我们要了解一个系统的演化过程呢?因为作为程序员,我们需要知道自己面对的到底是一个什么样的系统。

回到我们今天的主题上,5万块钱可以不可以做一个淘宝?

答案是,取决于你要的是一个什么样的系统。最开始买来的“淘宝”甚至连5万块钱都不用,而今天的淘宝和那时的淘宝显然不是一个系统。

从业务上说,今天的淘宝固然已经很丰富了,但最核心的业务相差并不大,无非是卖家提供商品,买家买商品。

那它们的本质差别在哪呢?

回顾上面的过程,你就可以看到,每次随着业务量的增长,原有技术无法满足需要,于是,就需要用新的技术去解决这个问题。

这里的关键点在于:不同的业务量。

一个只服务于几个人的系统,单机就够了,一个刚刚入行的程序员也能很好地实现这个系统。而当业务量到达一台机器抗不住的时候,就需要用多台机器去处理,这个时候就必须考虑分布式系统的问题,可能就要适当地引入中间件。

而当系统变成为海量业务提供服务,就没有哪个已经打造好的中间件可以提供帮助了,需要自己从更底层解决问题。

虽然在业务上看来,这些系统是一样的,但在技术上看来,在不同的阶段,一个系统面对的问题是不同的,因为它面对业务的量级是不同的。

更准确地说,不同量级的系统根本就不是一个系统

只要业务在不断地发展,问题就会不断出现,系统就需要不断地翻新。我曾听到一个很形象的比喻:把奥拓开成奥迪。

你用对技术了吗?

作为一个程序员,我们都知道技术的重要性,所以,我们都会努力地去学习各种各样的新技术。

尤其是当一个技术带有大厂光环的时候,很多人都会迫不及待地去学习。

我参加过很多次技术大会,当大厂有人分享的时候,通常都是人山人海,大家都想学习大厂有什么“先进”技术。

知道了,然后呢?

很多人就想迫不及待地想把这些技术应用在自己的项目中。我曾经面试过很多程序员,给我讲起技术来滔滔不绝,说什么自己在设计时考虑各种分布式的场景,如果系统的压力上来时,他会如何处理。

我就好奇地问了一个问题,“你这个系统有多少人用?”结果,他做的只是一个内部系统,使用频率也不高。

为了技术而技术的程序员不在少数,过度使用技术造成的结果就是引入不必要的复杂度。即便用了牛刀杀鸡,因为缺乏真实场景检验,也不可能得到真实反馈,对技术理解的深度也只能停留在很表面的程度上。

在前面的例子中,淘宝的工程师之所以要改进系统,真实的驱动力不是技术,而是不断攀升的业务量带来的问题复杂度。

所以,评估系统当前所处的阶段,采用恰当的技术解决,是我们最应该考虑的问题。

也许你会说,我做的系统没有那么大的业务量,我还想提高技术怎么办?

答案是到有好问题的地方去

现在的 IT 行业提供给程序员的机会很多,找到一个有好问题的地方并不是一件困难的事,当然,前提条件是,你自己得有解决问题的基础能力。

总结时刻

今天,我以淘宝的系统为例,给你介绍了一个系统逐渐由简单变复杂的发展历程,希望你能认清不同业务量级的系统本质上就不是一个系统。

一方面,有人会因为对业务量级理解不足,盲目低估其他人系统的复杂度;另一方面,也有人会盲目应用技术,给系统引入不必要的复杂度,让自己陷入泥潭。

作为拥有技术能力的程序员,我们都非常在意个人技术能力的提升,但却对在什么样情形下,什么样的技术更加适用考虑得不够。采用恰当的技术,解决当前的问题,是每个程序员都应该仔细考虑的问题。

如果今天的内容你只能记住一件事,那请记住:用简单技术解决问题,直到问题变复杂

最后,我想请你回想一下,你身边有把技术做复杂而引起的问题吗?欢迎在留言区写下你的想法。

参考资料

http://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/10x%e7%a8%8b%e5%ba%8f%e5%91%98%e5%b7%a5%e4%bd%9c%e6%b3%95/34%20%e4%bd%a0%e7%9a%84%e4%bb%a3%e7%a0%81%e6%98%af%e6%80%8e%e4%b9%88%e5%8f%98%e6%b7%b7%e4%b9%b1%e7%9a%84%ef%bc%9f.md