软件开发:组织大规模逻辑的技艺

发布时间 2023-12-02 10:52:47作者: 琴水玉

技艺永恒,生命短暂,机会易逝,实验莫测,抉择艰难。

程序员是吃“逻辑”这碗饭的。那么,怎么才能安心地吃这碗饭呢?你需要掌握组织大规模逻辑的技艺。

要掌握这门技艺,需要有一些思想方法论来指导。本文谈谈,软件业界是如何应对组织大规模逻辑的。

思想

结构化抽象

软件,本质上是一种可动态而弹性变化的逻辑装置。结构化,即是将逻辑进行抽象、提炼、分离、聚合,构建成更加缜密、动态、弹性的结构流。

结构化抽象,是软件设计与开发的第一性原理。软件开发与设计上的任何问题,如果找不到现成的方法,就可以回归到结构化思想与方法论上,通过调整结构来解决问题。所谓“术不成,则返道溯源”。

从单核到多核,是结构化调整;从同步到异步,是结构化调整;至于各种架构模式和设计模式,更是结构模式的固化。可见,结构化思想与方法,是从事软件设计与开发的第一大法。

详阅:“软件设计的第一性原理:结构化抽象”。

关注点分离

要编写大规模逻辑还保持整洁的秩序,需要做好关注点分离。

我深认为,软件中蕴藏着不计其数的大大小小的关注点。软件开发和设计的本质,就是将关注点分离、组织、连接。 能够将不同的关注点分离开,再合理有序地组织起来,呈现在代码里,就离写出清晰代码不远了。

关注点,可以分为技术关注点和业务关注点。通俗地理解,一个“关注点”,可视之为一个“事实”;但关注点的含义,比事实要广泛得多。

技术关注点,侧重于解决某一类技术问题。比如线程池、连接池、事务、幂等、切面、异步、MVC等。 “代码抽象与分层” 列举了代码里很多细小的抽象和关注点。

业务关注点,侧重于描述某个业务点。比如订单已发货、订单全额退款、获取订单已退金额,等都是业务关注点。业务关注点可大可小,小至一个业务常量,大至一个下单的基本流程。

掌握关注点分离的思想和技能,就打好了组织大规模逻辑的基础。

语义为导向

清晰性至为重要。

很多程序员喜欢用一些技巧和 tricky 的方式来实现,比如用行为代表状态,用“隔离”的行为来表示“已隔离”的状态搜索,因为隔离成功之后就是已隔离状态。这是因为行为与状态当前只存在一一映射,故可行。

对此,我不敢苟同。我认为,在满足其它要素的情况下,语义的清晰性是最高等级的。代码写出来,最好一眼就明白是什么意思,不需要拐弯抹角的推理。行为就是行为,状态就是状态。两者不能等同。你可以实现一个行为状态映射图,让行为和状态在其中正确转化,而在其它地方,就不需要费脑筋了。否则,今天写几个映射,明天又添几个状态,这些映射关系散布在代码里,以后很容易出BUG,后面维护的人也很头疼,每个人都得学习一遍这种映射关系。

集中管理与一致性

最后一个重要思想是集中与一致性。所有紧密相关的逻辑,最好放在一起,一致处理。比如“共同复用和共同闭包原则”。

对于上面那种情况,如果真要那么做,也最好把所有的映射关系都放到一个类里管理,不要随手散布在代码里(比如直接写 if-else-set)。

避免重复,也是保持一致性的方法。之所以要避免重复,是因为一些相同的逻辑如果散布在代码里,很可能改了这一处漏了那一处,惩罚就是 BUG 的产生,需要花费不必要的时间和精力去修复。

方法

领悟四大核心思想之后,我们要开始构建一系列方法,这些方法可以指导我们有序地组织逻辑。

分解-组合

“分解-组合”是第一万能之法。任何事情,如果你感觉有点吃力,就把它分解成小的事情,直到你的能力能够处理的范围。

分解之后,就要思考如何把分解后求得的子结果组合起来。这一步也很关键。任何事情,总可以分解成吃、住、行、说、写五件事的组合。

分得好,合得也好,那么“分解-组合”就是一种异常强大的方法。

归并排序,就是典型的“分解-组合”范例;Map-Reduce,也是将大数据集先分解成多个子数据集调度在不同节点上处理,再进行合并结果集处理。

封装

一旦当你完成某个工作成果后,将它封装起来,以备后用。

库、框架、工具、中间件等,都可以封装起来。

封装是快速累积和构建的良好法宝。

模块化

要构建更大的逻辑,模块化必不可少。

所谓模块化,就是把一些紧密相关的逻辑放在一起,使之共存共生。当需要修改某个功能时,尽量只修改这一小部分,影响局部化。

如果不做好模块化,逻辑就会散布在整个应用里,光是把所有相关的地方定位好,就得花费大量时间,还可能遗漏。

此外,软件的很多功能是联动的。如果不做好模块化,软件就会像跷跷板,这里压下去了,那里又翘起来了。BUG 永远都改不完。

解耦

解耦是针对模块化而言的。做好模块化之后,就要做好解耦。

解耦解决的是,如何把关联又相互独立的逻辑放置有序,使之独立变化,互不干扰。比如一个下单之后,要支付、要扣减库存、要分发优惠券等。如果全部写在一起,支付出了问题影响扣库存,扣库存出了问题影响发优惠券等,缠在一起,难以理清。

解耦是保证现有系统持续扩展的重要方法。微服务本质上就是一种解耦之法。

详阅: “软件工程中的耦合与解耦方式

设计

有了思想和方法之后,我们需要学习一些常用设计模式。所谓设计模式,实际上就是结构模式的固化。使用这些结构模式(套路),就能解决很多问题了,根本无需“重新发明轮子”。

设计原则

设计原则是进行软件设计的基本原则。遵循这些原则,通常可以设计出可扩展的软件。

详阅: “【转载】一些软件设计原则”。

编程思想

编程思想是设计原则的细化,是实际编程的有力武器。

详阅“计算机编程领域的三十六种基本思想概览”。

架构模式

架构模式关乎应用的整体结构,确定应用的数据流结构和控制流结构。选择合适的架构模式,就是给代码初步定了个合适的“模块化结构”。

详阅:“软件设计要素初探:架构模式”。

设计模式

设计模式是关于实际问题的解决方案,也是关于职责与交互如何变化扩展的技艺。详阅:“软件设计要素初探:基础设计模式概览”,“理解设计模式之“道” ”。

构建块

掌握思想、方法和设计之后,就需要脚踏实地开始干活了。

首先,要确定一些基本构建块,通过这些基本构建块,来构建程序的雏形。

数据结构与算法

数据结构与算法是软件的基本构建块。几乎所有强大、实用而高效的功能都离不开数据结构与算法。这部分的重要性实在不言而喻。

看不到花,不能混说花不存在。因为花始终散发着清香。你看不到,不代表别人看不到。

详阅:“【整理】盘点程序员必知必会的常见数据结构和算法

流程模式

流程模式,是组织代码流程的常用模式。掌握流程模式,可以自如地编写更复杂的流程。

详阅:““驯服”业务流程:盘点业务开发中的常见流程模式

结构模式

如前述,结构化抽象是软件开发与设计的第一大法。掌握常用的结构模式,应对软件开发各种需求时才能有章可循,不慌不忙。

详阅:“软件的结构模式及结构的扩展

技术手段

即使是逻辑,对于逻辑也是有特定要求的。比如转账,这个账号钱出去了,那个账号钱就应当以相同数额进来。操作完成之后,两个账号的总金额应当保持不变。这就是一个一致性约束。一个操作应当在用户可接受的时间范围内完成,这就是一个性能约束。

技术手段是解决实际问题、满足特定约束的具体方法。掌握这些技术手段,才能应对实际工作中的诸多挑战,满足千变万化的约束要求。

索引、事务、幂等、异步、分区、限流、熔断等,都是技术手段。

详阅:“互联网应用服务端的常用技术思想与机制纲要

中间件

中间件是实际可用的成品构件。熟悉这些构件的用法,才能构建复杂的现代互联网应用。

  • 数据存储(MySQL, Mongo, HBase, ES etc.)
  • 消息(Kafka、RabbitMQ etc.)
  • 缓存(Redis、Memcached etc.)
  • 服务发现(Etcd, Eureka etc.)
  • 配置(Appllo, Vault etc.)
  • 容器(Tomat, Jetty, Undertow etc.)
  • 调度系统(K8S etc.)

框架与库

应用框架为应用创建了一个应用模板(基本固定好的代码组织结构),应用只需要根据这个模板填写实际业务逻辑即可。Spring 和 MyBatis 是 Java 程序员最为熟知的两个框架。

库是程序员手头常备的工具箱,可以提供很多实用的功能。比如集合、日志、时间日期处理、编码、字符串、对象池、并发库、本地缓存、JSON解析与转换、HTTP调用、网络通信等。

编程语言与代码组织

语言影响思维。编程语言是程序员最直接的武器,用于表达各种逻辑。编程与写作类似,也是写下一连串符号;但编程与写作又不一样。写作的内容非常广泛,兼具理性与感性,而编程的主要内容是表达计算与存储,理性色彩极浓。

编程语言通常提供了代码组织的基本方法,影响着代码的组织。比如 Java 的 Package 就提供了包的概念。在包里的类可以访问彼此的公开方法和 package 方法,而包外面的类只能访问包里面的 public 方法。编程语言提供的代码组织主要在于访问控制上。

小结

看上去,这世界已经为你提供了这么多有用的东西,只待你信心满满地去构建应用。那么难点在哪里呢?

当逻辑铺天盖地、排山倒海地席卷过来的时候,人脑没法驾驭这么多的逻辑。想一想,光是一个库,就有几千行代码,光是一个中间件,就有几万行代码。怎么看得过来呢?因此,即使你掌握了这么多,还是对大规模逻辑的组织心有余而力不足。

本文只是揭示了这些大规模逻辑是如何组织起来的,但是如何去驾驭它,依然是难题。