大师兄

开篇词 | 软件设计,应对需求规模的“算法”

你好,我是郑晔!

作为一个能把基本功能实现出来的程序员,偶尔仰望天空时,你是否这样问过自己,“我写过的代码还有没有更好的写法呢?如果有一天,我能够把它重写一遍,我该怎么做呢?”

这就是我当年问过自己的问题,因为我对自己的代码不满意:

  • 我厌倦了,把各种代码堆砌在一起,然后,在出现Bug时,犹如“大家来找茬”一样在其中定位问题;
  • 我厌倦了,仅仅为了一个小需求,要在无数的地方小心翼翼地做着各种微调,还被产品经理嫌弃改得慢;
  • 我厌倦了,自己辛辛苦苦写好的代码,被别人在其他地方不经意地修改,给弄崩溃了;
  • ……

我四处寻找答案,直到后来,我找到了一个东西,叫做“软件设计”。在如饥似渴地学习了软件设计之后,我对做软件这件事有了全新的认识:

  • 我知道了,写软件不仅要追求如何实现功能,还要考虑未来的维护和扩展;
  • 我知道了,代码不应该毫无目的地堆在那里,而是要考虑如何组织更为恰当;
  • 我知道了,原来后期遇到很多问题,只是因为前期缺乏设计而留下了隐患;
  • ……

如果你也曾有同样的迷茫,想破茧成蝶,欢迎你与我一起探索“软件设计”!

关注长期变化的软件设计

设计是为了让软件在长期更容易适应变化。

Design is there to enable you to keep changing the software easily in the long term.

—— Kent Beck

软件设计,是一门关注长期变化的学问。它并不是程序员的入门第一课,因为初窥编程门径的程序员首先追求的是把一个功能实现出来,他无法看到一个软件长期的变化。

或许未曾学过软件设计,但这并不妨碍你凭借一股蛮力把软件写出来。但如果你在做的是一个有生命力的软件,长期意味着什么呢?意味着会有源源不断的需求扑面而来。面对一拨又一拨的需求,你该如何应对呢?

你可以想象一下,小米加步枪的解决方案,在敌人不多的时候,你还可以应付得游刃有余,但当敌人已经漫山遍野时,你需要的是大规模杀伤性武器。而软件设计就是这个大规模杀伤性武器。

用程序员们更熟悉的排序算法为例,快速排序的平均复杂度是O(nlogn),而插入排序是O(n^2)。所以,一般我们说快速排序比插入排序有优势。但是,这种优势只有在一定规模下才能体现出来。当数据规模很小的时候,二者差别并不明显,更有甚者,插入排序在某些情况下表现得会更好。但当数据规模很大时,快速排序的优势就非常明显了。对比两个算法的优劣,关键在于数据规模。

所以你会发现,算法和软件设计其实是一样的,二者对抗的都是规模问题,只不过,算法对抗的是数据的规模,而软件设计对抗的是需求的规模

你现在应该理解了,为什么软件设计是一门关注长期的学问了,因为只有长期的积累,需求才会累积,规模问题才会凸显出来。软件设计,实际上就是应对需求的“算法”

如何学习软件设计?

软件设计的相关知识有很多,你可能听说过一些,比如,设计模式、领域驱动设计等等。但是,分别学习这些知识时,总有一些令人困惑的地方。比方说,学了那么多设计模式,你能用上的却没有几个;领域驱动设计中的概念那么多,你都不知道该从哪学起。

你的困惑我也有过,我花了很长时间才知道,我们困惑的,并不是这些知识本身,而是在于缺乏一个整体结构将它们贯穿起来。比如,多态,这个词在你看来只是一个普通的语法规则,但只有你懂得,它是把变的部分和不变的部分隔离开来,你才能理解开放封闭、接口隔离和依赖倒置这些设计原则的价值所在,理解了这些原则,才能知道某些设计模式只是这些原则在具体场景下的应用。

也就是说,软件设计学习的难度,不在于一招一式,而在于融会贯通

所以,在这个课程中,我会尝试将软件设计的相关知识贯通起来,帮你建立起对软件设计的整体认知。

那具体要从哪里入手呢?其实对于“软件设计”,我们可以将其划分为两个维度:“了解现有软件的设计”和“自己设计一个软件”。

了解现有软件的设计,是能够在这个软件上继续添砖加瓦的前提。事实上,无论是初入职场,还是加入一个新公司,在工作初期,我们能做的往往也只是添砖加瓦,这时候如果能快速了解现有软件的设计,就可以尽快投入工作中去。此外,当你想从一个开源项目上汲取养分时,了解其背后的设计,也是一种不可或缺的能力。

大多数人在理解一个软件时,总会出现一个问题,就是眉毛胡子一把抓,直奔代码细节而去。这样不仅增加了我们的时间成本,还会迷失在细节之中,只见树木不见森林。所以在这个课程中,我会教你一个快速了解现有软件设计的方法,那就是抓住这个软件最核心的三个部分:模型、接口和实现

同时我会以一些开源项目为案例,教你如何用这个方法去解读它们的设计,比如:

  • 我们怎样理解Spring DI容器模型,它的模型怎样有效解决了其面对的问题;
  • 如何理解Ruby on Rails的接口,我们可以从其接口设计中借鉴哪些内容;
  • Kafka的实现有哪些独特之处,实现的诸多细节中,我们应该关注哪些内容。

通过对这些案例的解读,你会切实地感受到融会贯通的好处,真正做到快速了解一个软件的设计。

慢慢地,当你在业务和技能上有了一定的积累,你将有机会做属于自己的设计。你负责的工作内容也将会从一个小功能到一个完整的小业务,从到一个模块到一个系统。随着你的能力不断提升,你负责的内容会逐渐增多,复杂度逐步升级,对你设计能力的要求也会随之攀升。

这时,你就需要掌握一些软件设计的基础知识。我会把软件设计中最重要的部分交付给你,包括:

  • 程序设计语言;
  • 编程范式;
  • 设计原则;
  • 设计模式;
  • 设计方法。

程序设计语言,是软件设计落地的基础。任何设计都依赖程序设计语言来实现。但任何语言也都有自己的局限,我将带领你横跨语言学语言,让你不再局限于某一种语言,而是择其善者而从之,更好地落地你的设计。

编程范式,是代码编写的风格,决定着你在设计时可以用到哪些元素:是模块、是对象,还是函数。在不同层次的设计中,选择不同的编程范式已经成为今天开发的主流。在这个主题下,我选择了几个最主流的编程范式,包括结构化编程、面向对象和函数式编程,帮你建立起软件设计的根基。

设计原则,是你在进入到具体设计的层面时,可以用来评判自己工作结果的一个衡量标准。我会给你介绍面向对象的主流设计原则:SOLID原则。一来面向对象是当今的主流开发方式,二来SOLID原则也是比较成体系的设计原则,它本身也在不断发展。

设计模式,是设计原则在具体场景下的应用。不过,这个话题展开之后,内容会非常多,而且有很多书和课程都讲到了,所以,我并不准备把它当作重点。但我会和你分享一些学习设计模式的心得,帮助你将设计模式的相关知识贯穿起来。

当你手里有了诸多工具之后,接下来就需要用这些工具去做自己的设计了。这就轮到设计方法登场了。

我会用领域驱动设计(也就是 DDD,Domain Driven Design)进行讲解,这是目前最为完整、有效的应对复杂业务场景的设计方法,包括了从如何识别概念到如何建立模型。在这个课程中,我准备将DDD的基础知识贯穿起来,做一个结构性的介绍。你会发现,有了前面知识的铺垫,DDD理解起来一点都不困难。

有了基础知识,在课程最后,我们还会在巩固篇中操练一下,将学到的软件知识应用起来。在这个模块中,我会结合自己的开源项目Moco,来讲讲如何设计一个程序库;还会借着一个数据采集的项目,谈谈如何构建起一个可扩展的模型。另外,因为大多数人在实际工作中面对的都是一个既有的项目,所以,我还会讲讲,如何对既有项目做设计上的改进。

写在最后

最后,再自我介绍一下。我叫郑晔,一个从业近二十年的程序员,《10x程序员工作法》专栏作者。很高兴又回到极客时间,和你分享我对软件设计的理解。

如果说《10x 程序员工作法》这门课是在告诉你要做正确的事,做有价值的需求,别把时间浪费在不该做的事情上,那《软件设计之美》这门课就是告诉你如何把事做对,如何建立有效的模型,划清模块之间的边界,所以,二者可谓一脉相承。

不想当将军的士兵不是一个好士兵,不想做设计的程序员不是一个好程序员。写程序的人谁不想操刀一个大型的系统,但不懂软件设计的人能摆弄的代码规模是有限的,而这也限定了一个人的成长高度。

学习软件设计,是让你的把控能力从一段代码扩展到一个模块,再扩展到一个子系统,再扩展到一个大系统的必备手艺,是程序员从“家常菜厨师”到“大厨”的进阶之路。

不过,你也不必把软件设计想象得过于高大上,很多设计理念既可以用来设计一个系统,也可以运用于日常开发之中,它就在你的身边。今天多学习一点设计,明天就能多发现一个问题。

如果你曾与我一样,走入过软件开发的迷途;如果你希望自己的软件开发能力再上层楼;或者你只是对软件设计充满好奇,那么,欢迎加入我的课程。也欢迎你把自己的现状和预期写在留言区,当课程结束时,让我们共同见证你的成长!

你准备好了吗?让我们正式开启软件设计之旅,一起领略软件设计的美妙!