Post

配置驱动(Configure Driven)的应用程序

理解配置

今天我们来学习软件开发中的一个方法,这个方法用好了可以让程序更加灵活,并帮助我们避免后期大量的代码更改。在说这个方法之前,我们需要理解配置参数(Configuration)这个东西,我相信做过开发的人都应该清楚它是什么,这里举个简单的例子,比如下面这个命令

1
>$ git diff src/

我们可以把上面这个命令分成两部分—— git diffsrc/,前面的 git diff 表示的是具体的指令,而后面的 src/ 就是传入的参数,指令是具体且固定的,而参数是可以变化的。参数可以让一个指令灵活地应用在不同的场景,并作用于不同的案例,我们在上面的指令的基础之上再传入一个参数,整个指令的结果又会不一样:

1
>$ git diff --cached src/

上面这个只是一个简单的例子,很多工具类的程序(指令)都是将配置参数发挥到了极致,比如 Linux 中广为人知的命令行工具 sedawk,如果我问你这两个指令是做什么的,你可能也只会简单地告诉我它们是处理文本文件的工具,最多附加一句,它们可以用在许多场景下。如果深入下去,你会发现这两个工具设计的极为精妙,用户可以通过附加各种配置参数来让它们做完全不一样的事情,小到简单的文件内的搜索,大到数据分析和统计,这里面起关键性作用的就是配置,或者说是配置参数。

当然配置参数并不局限于命令行,更不局限于 Linux 系统,只要是允许变化的,都可以称之为配置。区别于指令和命令,配置有点类似声明式的语言,比如下面这个 SQL 语句:

1
2
3
4
5
6
7
8
9
10
11
SELECT
    t1.field1, t1.field2
FROM
    table1 t1
LEFT JOIN table2 t2
ON (table1.id=table2.id)
WHERE
    t1.filter = true
    and t2.filter = false
GROUP BY
    t1.field1, t1.field2

这里面小写部分的内容都是用户自己定义的,和配置参数一样,它们都是允许变化的。用户通过这些输入的东西,告诉应用程序他们想要什么,至于说如何得做的,怎样做的,用户并不关心。类似的声明式的语言还有 HTML、XML、JSON、CSS 等等。想想看,相比于传统的语言,这些声明式的语言是不是简单且易用,而且应用场景也非常的广?通过设计合理的配置参数,我们也可以让我们的应用程序做到这一点。

配置驱动

与配置参数对应的就是控制单元,比如编程语言中的条件语句 if-else,或是循环语句 for。控制单元来控制程序的执行,说大白话就是回答 “怎么做,如何做” 的问题。一个应用程序中,肯定是既包括控制单元,也包括配置参数。还是举个例子,比如说一个应用程序调用一个 Restful API 从服务器获取业务数据,然后直接返回给客户端,别看这功能这么简单,这里面包含着各种各样的控制单元,比如如何打日志?在哪打日志?怎么做数据验证?怎么应对请求超时?怎么应对程序异常等等?以此同时,它也包含着许多的参数配置,比如服务器地址和端口号、Restful API 的具体路径以及对应的方法(比如 GETPOST)、服务器可能的返回码、应用程序运行的环境(比如 devprod)、日志中的标签(用于区分不同的日志)以及该应用程序在不同场景下的返回码和返回信息等等。

加上各种各样的细节,这还是一个简单的应用程序吗?想想看,这还只是实现了一个基本的 API 调用的功能,如果说是一个常见的应用,肯定还包括许许多多的其他功能和业务逻辑,比如对数据库进行 CRUD、对获取到的数据进行整合计算、在不同场景下调用不同的 API、服务器备份等等。那毫无疑问,在这么多的功能和业务逻辑的加持下,就会有更多的控制单元,也会有更多的配置参数。这些控制单元和配置参数搅合在一起,让整个程序变得晦涩难懂不说,还非常容易出问题,比如 API 地址变更了,代码需要更改并重新部署,数据库的字段发生了变化了,代码需要更改并重新部署,另外,这里面各种各样的依赖混杂在一起,一不留神没注意,改错了,或者有些情况没考虑到,代码部署到线上出了问题还需要回滚,这一来二去,看似做了很多事情,但其实都是无用功。

那怎么办呢?有没有更好的方式呢?这就需要捋清控制单元和配置参数的性质了。配置参数是什么?我们前面举了各种各样的例子,从中你也可以体会得到,配置参数是细节,是具体的依赖,而控制单元是一个框架,是抽象,或者这么说,你如果只想要了解这个程序大致是干什么的,那么你根本不需要看配置参数,你只需要看控制逻辑就好了。但是问题是,配置参数和控制单元被放在了一起,框架被细节填充的满满的,我们很难看清整个程序的框架和脉络。这问题也好解决,就是把细节和抽象拆开来就行了。

我们把配置参数统一放到某个配置文件中,应用程序中只留控制单元,配置会在程序运行时加载进程序中。这么做有如下这些好处:

  • 程序中只留下控制单元,这样代码的可读性变高,代码变得更容易理解
  • 当业务发生变化,需要更改细节,我们只需要修改配置文件,不需要更改代码,更不需要部署,一切都发生在运行时,这样程序不容易出问题,更容易维护
  • 程序更具灵活性,我们只需要修改配置文件,同一个程序就可以帮我们做不同的事情,比如前面那个 “调用一个 Restful API 从服务器获取业务数据” 的例子,我们把服务器地址、API 路径、日志标签以及其他的配置更换后,这个程序就会从不同的服务器获取不同的数据,一个应用程序好像变成了多个程序,代码的利用率提升
  • 这也可以让我们在做软件设计的时候暂时忽略细节,将目光聚焦在更加高层、更加抽象的东西上,我们更容易写出通用且兼容的程序
  • 这在某种程度上实现了解耦,程序会更加地 正交,这样的程序更利于扩展,除此之外,所有的细节都放在同一个地方,也有效地避免了重复的代码

说了这么多好处,那么具体的做法呢,有什么需要注意的地方吗?首先,在程序最初的设计和思考的时候,就要像设计声明式语言那样去思考,尝试着去思考如何让控制单元变得简洁的同时也能满足各种业务需求。另外,我们还要学会区分控制单元和配置参数,一般来说可以从下面几个方面去思考:

  • 程序中可能会发生变化的东西一般都可以归为配置参数,比如数据库的 schema、服务器的请求地址、环境变量等等
  • 思考哪些东西不是由应用程序决定的,这些东西都是配置参数,类似数据库的字段名、一些阈值还有在其他服务器定义的内容等等,这些东西都是外部依赖,应用程序没法控制它们的变化,因此最好提取出来,否则就会受它们的控制
  • 具体的数据或者类型相关的内容都可归为配置参数,比如一些区分业务的常量、white list、black list、标签等等,这些东西被删改的几率很大

剩下的建议就是多实践,多尝试。上面说的这些东西可以为你写好程序提供一个方向,但要清楚的是,好程序不是学出来的,更不是想出来的,而是一点一点改出来的,每当我们对程序中的某些地方不太满意,就想方设法地应用我们学过的理论或原则对其重构,对其优化,久而久之,你就会对这些方法了然于心,应用自如。

This post is licensed under CC BY 4.0 by the author.