如何学习软件设计与架构|全栈式软件设计与架构图

257 阅读19分钟

本主题摘自 Solid Book - The Software Architecture & Design Handbook w/ TypeScript + Node.js。如果你喜欢这个帖子,请查看

由读者翻译成。日语 (Japan語)

你有没有想过,世界上一些最熟练的开发人员花了多少时间来学习如何在Uber、YouTube、Facebook或Github等公司内构建系统?

对我来说,考虑到Facebook曾经是某人电脑上的一个空文本文件,而现在它已经成为这个巨大的公司,几乎涉足所有领域,并对全球超过15.9亿人产生了个人影响,这对我来说是很疯狂的。

作为一个初级的、自学成才的开发者,甚至是中级的开发者,要想继续成长,真正学会如何设计干净可扩展的系统,这个路线图似乎有点令人生畏。

对于我们中的很多人来说,我们的项目在一两次迭代后就会死亡,因为代码变成了无法维护的混乱。

那么,为了学习如何改进我们的设计,我们该哪里开始呢?

事实是。

软件设计和架构是一个巨大的话题

了解如何。

  • 架构一个系统以满足其用户的需求
  • 编写易于修改的代码
  • 编写易于维护的代码
  • 编写易于测试的代码

...是非常困难的。需要学习的广度实在是太大了。

而且,即使你知道如何写代码使事情至少运作一次,更大的挑战是要弄清楚如何写代码,使其容易改变,以跟上当前的要求

但还是那句话,该从哪里开始呢?


任何时候,当我面对一个复杂的问题时,我都会回到第一原则上来。

第一原则

第一原则是分解问题的最有效方法。

它的工作原理是将一个问题一直分解到我们无法再分解的原子层面,然后从我们绝对确定的部分重建一个解决方案。

因此,让我们把它应用于软件,首先说明目标。

软件的主要目标是什么?

软件的目标是不断地 生产出满足用户需求的东西,同时尽量减少为此付出的努力。

我为提出最好的定义斗争了很久,我准备和你争论一下为什么我认为这个定义是准确的。

不为用户需求服务的软件,根本就不是好软件。

由于我们用户的需求经常变化,因此必须确保软件的设计是为了能够被改变

如果软件不能被改变(很容易),那它就是坏软件,因为它使我们不能满足用户的当前需求。


我们已经确定了设计的重要性,学习如何制作设计良好的软件也很重要,但这可能是一条漫长的道路。

在这篇文章中,我想向你介绍我认为是软件设计和架构的具体支柱。

堆栈

在我给你看地图之前,让我给你看一下堆栈

OSI模型类似,每一层都是建立在前一层的基础之上的。

软件设计和架构栈显示了软件设计的所有层次,从最高级的概念到最低级的细节。

在堆栈中,我包括了该层中一些最重要的概念的例子,但不是全部(因为有太多的概念)。

现在,看一下地图。虽然我认为堆栈很好,可以看到更大的画面,但地图更详细一点,因此,我认为它更有用。

地图

为了避免耗费我的带宽,我降低了现场显示的地图的质量。如果你想得到高质量的png,你可以在我的GitHub上找到。

下面是软件设计和架构的地图。

第一阶段:干净的代码

创建持久的软件的第一步是弄清楚如何编写干净的代码

如果你问任何人他们认为什么是干净的代码,你可能每次都会得到一个不同的答案。很多时候,你会听到干净的代码是容易理解和改变的代码。在低层次上,这表现在一些设计上的选择,比如。

  • 保持一致
  • 倾向于使用有意义的变量、方法和类名,而不是写注释
  • 确保代码的缩进和间距正确
  • 确保所有的测试都能运行
  • 编写没有副作用的纯函数
  • 不传递空值

这些看起来都是小事,但把它想成是一个Jenga游戏。为了使我们的项目结构长期保持稳定,像缩进、小类和方法以及有意义的名字这样的事情,从长远来看是很有好处的。

如果你问我,清洁代码的这个方面就是要有良好的编码习惯并遵守它们。

我相信这只是写干净代码一个方面。

我对干净代码的明确解释包括:。

  • 🧠 你的开发者心态(同理心、工匠精神、成长心态、设计思维)。
  • ⚙️你的编码习惯(命名、重构、测试等)。
  • 🤹🏼你的技能和知识(模式、原则以及如何避免代码臭味和反模式)

如果你想写出干净的代码,进入正确的心态是非常重要的。一个要求是,你应该足够关心和了解你正在编写代码的业务。如果我们不关心这个领域,不足以了解它,那么我们怎么能确保我们用好的名字来表示领域的概念?我们怎么能确定我们已经准确地捕获了功能需求?

如果我们不关心我们所写的代码,那么我们就更不可能去执行基本的编码规范,进行有意义的讨论,并要求对我们的解决方案进行反馈。

我们经常认为写代码只是为了满足最终用户的需求,但我们忘记了我们为其他的人写代码:我们自己,我们的队友,以及项目未来的维护者。对设计的原则以及人类心理学如何决定什么是好的坏的设计有所了解,将有助于我们写出更好的代码。

因此,从本质上讲,描述你这一步旅程的最好的词是什么?同理心

一旦我们掌握了这一点,就可以通过提高你对基本的软件开发模式和原则的认识,学习其中的技巧,并随着时间的推移继续改进它们。

学习资源

  • 清洁代码》,作者:罗伯特-C-马丁
  • 重构》,马丁-福勒著(第二版)。
  • 实用主义程序员》,安迪-亨特和戴夫-托马斯著
  • 日常事物的设计》,作者:Don Norman

学习如何编写干净代码的最好资源是鲍勃叔叔的书《干净代码》。

第二阶段:编程范式

现在,我们正在编写易于维护的可读代码,如果能真正理解3种主要的编程范式以及它们影响我们编写代码的方式,那将是一个好主意。

在鲍勃叔叔的书"干净的架构"中,他提请大家注意这样一个事实。

  • 面向对象编程是最适合定义我们如何用多态性和插件跨越架构边界的工具
  • 功能性编程是我们用来将数据推到我们的应用程序边界的工具
  • 而结构化编程是我们用来编写算法的工具。

这意味着有效的软件在不同的时间使用混合的三种编程范式。

虽然你可以采取严格的函数式或严格的面向对象的方法来编写代码,但了解每一种方法的优势将提高你设计的质量。

如果你拥有的只是一把锤子,那么一切都像是钉子。

学习资源

  • 清洁的架构》,作者:Robert C. Martin
  • 领域建模功能化》,Scott Wlaschin著
  • 编程语言的概念》,Robert W. Sebesta(第十版)。

第三阶段:面向对象的编程

了解每一种范式的工作方式以及它们如何敦促你在其中构建代码是很重要的,但在架构方面,面向对象编程是明确的工具

面向对象的编程不仅使我们能够创建一个插件架构,并在我们的项目中建立灵活性;面向对象的编程带有4个原则(封装、继承、多义性和抽象),帮助我们创建丰富的领域模型

大多数学习面向对象编程的开发者从未接触到这一部分:学习如何创建问题域的软件实现,并将其定位在分层网络应用的中心。

在这种情况下,功能编程似乎是达到所有目的的手段,但我建议熟悉模型驱动设计和领域驱动设计,以了解对象建模者如何在零依赖的领域模型中封装整个业务的大局。

为什么这是一个巨大的交易?

这很重要,因为如果你能创建一个业务的心理模型,你就能创建该业务的软件实现。

学习资源

  • 对象设计风格指南》,作者:Matthias Noback
  • 清洁架构》,作者:Robert C. Martin
  • 领域驱动设计》,作者:Eric Evans

第四阶段:设计原则

在这一点上,你会明白面向对象的编程对于封装丰富的领域模型和解决第三类 "硬软件问题"--复杂领域--非常有用。

但OOP会带来一些设计上的挑战。

什么时候我应该使用组合?

什么时候应该使用继承?

什么时候应该使用抽象类?

设计原则实际上是已经确立并经过战斗检验的面向对象的最佳实践,你可以把它当作护栏。

你应该熟悉的常见设计原则的一些例子是。

不过,请确保得出你自己的结论。不要只是跟随别人说你应该做什么。要确保它对你有意义。

学习资源

  • Head First Design Patterns,由不同的作者撰写
  • GoF设计模式,由不同的作者编写

第五阶段。设计模式

几乎所有的软件问题都已经被归类并解决了。我们称这些模式为:设计模式,实际上。

设计模式有3类:创造型结构型行为型。

创造性的

创造性模式是控制对象如何被创建的模式。

创造性模式的例子包括。

  • 单子模式,用于确保一个类只存在一个实例。
  • 抽象工厂模式,用于创建几个系列的类的实例。
  • 原型(Prototype)模式,用于从现有实例中克隆出一个实例。

结构性

结构模式是简化我们如何定义组件之间关系的模式。

结构设计模式的例子包括。

  • 适配器模式,用于创建一个接口,使那些通常不能一起工作的类能够一起工作。
  • 桥接模式,用于将一个实际上应该是一个或多个的类分割成一组属于一个层次结构的类,使实现可以独立开发。
  • 装饰者(Decorator)模式,用于动态地给对象添加责任。

行为性的

行为模式是促进对象之间优雅交流的常见模式。

行为模式的例子有

  • 模板模式,用于将算法的具体步骤推迟到子类。
  • 调解器模式,用于定义类之间允许的确切的通信渠道。
  • 观察者模式,用于使类能够订阅感兴趣的东西,并在发生变化时得到通知。

设计模式的批评

设计模式是伟大的,但有时它们会给我们的设计带来额外的复杂性。重要的是要记住YAGNI,并试图使我们的设计尽可能的简单。只有在你真的确定你需要它们的时候才使用设计模式。你会知道你什么时候会。


如果我们知道这些模式中的每一个是什么,什么时候使用它们,什么时候甚至不需要使用它们,我们就可以很好地开始理解如何架构更大的系统。

这背后的原因是,架构模式只是将设计模式的规模放大到高层,而设计模式是低层的实现(更接近于类和函数)。

学习资源

  • Head First Design Patterns, by various authors

第六阶段。架构原则

现在,我们的思维已经超越了类的层面,进入了更高的层次。

我们现在明白,我们在高层和低层的组件之间组织和建立关系的决定,将对我们项目的可维护性、灵活性和可测试性产生重大影响。

学习指导原则,帮助你建立你的代码库所需要的灵活性,以便能够对新的功能和需求做出反应,并尽可能地减少工作量。

以下是我建议一开始就学习的内容。

  • 组件设计原则。稳定的抽象原则稳定的依赖原则和非循环依赖原则,以了解如何组织组件、它们的依赖关系、何时将它们结合起来,以及意外地创造依赖循环和依赖不稳定组件的影响。
  • 政策与细节,了解如何将你的应用程序的规则与实施细节分开。
  • 边界,以及如何确定你的应用程序的功能属于哪个子域

鲍勃叔叔发现并最初记录了许多这些原则,所以学习这些的最好资源还是《清洁架构》。

学习资源

  • 清洁架构》,作者:Robert C. Martin

第七阶段。建筑风格

架构是关于那些重要的东西。

它是关于确定一个系统需要什么才能成功,然后通过选择最适合要求的架构来增加成功的机会。

例如,一个有很多业务逻辑复杂性的系统会从使用分层架构来封装这种复杂性中受益。

像Uber这样的系统需要能够同时处理大量的实时事件并更新司机的位置,所以发布-订阅式架构可能是最有效的。

我在这里重复一下,因为需要注意的是,3类架构风格与3类设计模式类似,因为架构风格是高层的设计模式

结构

具有不同层次的组件和广泛功能的项目将从采用结构化架构中受益或受到影响。

这里有几个例子。

  • 基于组件的架构强调系统内各个组件之间的关注点分离。想一想谷歌。考虑一下他们在企业内有多少应用程序(Google Docs、Google Drive、Google Maps等)。对于具有大量功能的平台,基于组件的架构将关注点划分为松散耦合的独立组件。这是一种横向的分离。
  • 单片机是指应用程序被组合成一个单一的平台或程序,整体部署。注意:如果你适当地分离你的应用程序,但又把它作为一个整体来部署,你可以有一个基于组件和单片的架构
  • 分层架构通过将软件切割成基础设施层、应用层和领域层,垂直地分离关注点。

Clean Architecture

一个通过使用分层架构垂直切割应用程序的关注点的例子。阅读这里,了解更多关于如何做到这一点的信息。

消息传递

根据你的项目,消息传递可能是系统成功的一个非常重要的组成部分。对于这样的项目,基于消息的架构建立在功能性编程原则和行为设计模式(如观察者模式)之上。

下面是基于消息的架构风格的几个例子。

  • 事件驱动架构将所有对状态的重大改变视为事件。例如,在黑胶交易应用程序中,当双方同意交易时,报价的状态可能从 "待定 "变为 "接受"。
  • 发布-订阅架构建立在观察者设计模式之上,使其成为系统本身、终端用户/客户以及其他系统和组件之间的主要通信方法。

分布式

分布式架构只是意味着系统的各个组件是单独部署的,并通过网络协议进行通信。分布式系统对于扩展吞吐量、扩展团队以及将(潜在的昂贵任务或)责任委托给其他组件来说是非常有效的。

分布式架构风格的几个例子是。

  • 客户机-服务器架构。最常见的架构之一,我们把要做的工作分给客户(展示)和服务器(业务逻辑)。
  • 点对点架构将应用层的任务分配给具有同等权限的参与者,形成一个点对点网络。

学习资源

  • 清洁架构》,罗伯特-C-马丁著
  • 软件架构师手册》,作者:Joseph Ingeno

第八阶段。架构模式

架构模式在战术上更详细地解释了如何实际实现这些架构风格中的一种。

这里有几个架构模式的例子和它们所继承的风格。

  • 领域驱动设计是一种针对真正复杂问题领域的软件开发方法。为了使DDD获得最大的成功,我们需要实现一个分层的架构,以便将领域模型的关注点与使应用程序实际运行的基础设施细节分开,如数据库、网络服务器、缓存等。
  • 模型-视图-控制器可能是开发基于用户界面的应用程序的最著名的架构模式。它的工作原理是将应用程序分为三个部分:模型、视图和控制器。当你刚开始的时候,MVC是非常有用的,它可以帮助你向其他架构借力,但是当我们意识到MVC对于有大量业务逻辑的问题来说是不够的时候,就会有一个问题。
  • 事件源是一种功能性方法,我们只存储事务,而不存储状态。如果我们需要状态,我们可以从一开始就应用所有的事务。

学习资源

  • 领域驱动设计,作者:Eric Evans
  • 实现领域驱动设计》,作者:Vaughn Vernon

第9阶段。企业模式

你选择的任何架构模式都会引入一些构造和技术术语,让你熟悉并决定它是否值得使用。

举个我们很多人都知道的例子,在MVC中,视图持有所有的表现层代码,控制器是将来自视图的命令和查询翻译成请求,由模型处理并由控制器返回。

在模型(M)中,我们在哪里处理这些事情?

  • 验证逻辑
  • 不变规则
  • 领域事件
  • 用例
  • 复杂的查询
  • 和业务逻辑

如果我们简单地使用一个ORM(对象关系映射器),如Sequelize或TypeORM作为模型,所有这些重要的东西都会被留在解释它应该去哪里,它发现自己在(应该是一个丰富的)模型控制器之间的一些未指定的层。

mvc-2

摘自solidbook.io中的 "3.1-超薄(无逻辑)模型"。

如果说在我超越MVC的旅程中,到目前为止我学到了什么,那就是一切都有一个构造

对于MVC未能解决的每一件事,特别是领域驱动设计中,存在着几个企业模式来解决它们。比如说。

  • **实体**描述有身份的模型。
  • **值对象**是没有身份的模型,可以用来封装验证逻辑。
  • **领域事件**是标志着一些相关业务事件发生的事件,并可以从其他组件中订阅。

根据你所选择的架构风格,会有大量的其他企业模式供你学习,以实现该模式的最大潜力。

学习资源

这些只是一些不同的学习资源,主要集中在领域驱动设计和Enteprise应用架构。但这是值得学习的地方,也是你可以最深入学习的地方,因为它建立在我们迄今为止所学的一切之上。

  • 企业应用架构模式》,作者:Martin Fowler
  • 企业集成模式》,作者:Gregor Hohpe
  • 领域驱动设计》,Eric Evans著
  • 实施领域驱动设计》,作者:Vaughn Vernon

资源和结论

我们在这个博客上谈了很多关于领域驱动设计的内容,但在我们深入研究用TypeScript构建丰富的领域模型之前,有很多读者会从了解这些内容中受益(比如分层架构、OOP、模型驱动设计、设计原则和模式)。

如果你正在寻找一个一站式的资源,我刚刚预先推出了solidbook.io--《软件设计与架构手册》。我向读者传授我认为他们在这个地图的每个阶段真正需要知道的东西,以便产生像我们在这篇文章中讨论的好软件。这本书目前正在销售,直到它完全完成,但我也很乐意推荐其他一些我个人在学习软件设计和架构时使用的优秀资源。

参考文献