前言
(本文假设您对Unix Pipe和Unix/Linux的基本用法有所了解)
“Make each program do one thing well”,这是Unix的哲学中最出名的一条。这句话的作者是Doug Mcllroy,他同时也是Unix管道的发明者。
上面这句话的字面意思是,Unix建议每一个程序都只做一件事,并专注于把这一件事做好。隐含的意思是:把一个个只能做好一件事的程序进行组合,就能非常优雅地完成一些复杂的工作。
在Unix中, 管道就是用来进行做这件事的。
管道模式
多个命令组合成了一个管道,管道中**每一个命令的输出就是下一个命令的输入。**让我们从一段非常简单的Linux命令开始:
ps -ef | grep aaa | grep -v bbb | wc -l | sort -n -k 2 -r > sorted.log
这里面用到了四个非常基础的系统自带命令: ps
, grep
, wc
, sort
。 有 Linux/Unix基础的人都知道这四个命令分别是用来做什么的。
ps: 看进程信息
grep: 匹配字符串
wc: word count
sort: 排序
而如果你不知道这个命令做什么,又可以通过 man
来查看其用法。
在Linux下, 如果你想对内容进行排序,首先想到的一定是sort
命令。哪怕你想要的搜索方式、条件不知道用哪个命令,比如“按第二列字段进行数字序的倒序排序”,但你知道,sort
命令大概率是可以通过某些参数的搭配来实现想要的排序效果。这时,你输入了 man sort
, 并查到了正确的参数: sort -n -k 2 -r
。
同样的,如果你想查看当前进程的信息,想到的一定是ps
命令,并知道一定可以通过参数实现按不同条件进行查看,或实在不行,也能通过与grep
awk
等命令的搭配来实现。
这便是“Make each program do one thing well”。每一个程序都很小巧,但都有一个清晰的、聚焦的功能。而且都非常高质量地实现这个功能。
这套机制对我们实现高质量的软件也有很多启示。因此,我更希望把它认为是一种软件的设计模式,并称之为**“管道模式”**。
启示
一个好的软件架构设计往往是高内聚+低耦合的,但这还不够,从上面的管道模式中我又补充了两条:清晰的输入和输出、通用的接口。
高内聚:每个程序都聚焦于某一个特定功能,不越界。即“单一职责”。这也降低了对这些命令的学习成本。比如当需要排序时,自然而然想到sort
命令
低耦合:各程序之间没有任何依赖关系,每个都是独立的程序,都能独立运行。
清晰的输入输出:每一个软件的输入和输出的格式都是清晰明确可预知的。比如tar
命令的输出就是一个tar包。一个常见的反例是某些函数在正常时返回值是一个json, 异常时却返回一个字符串类型的错误信息。
通用的接口:还是以上面的管道为例。无论每个命令进行了什么处理,它的输出一定都是纯文本,而这就是最通用的“接口”。假如grep
现在的输出变成了json {"code":1, "result:"xxx"}
,原来的输出结果变成了json中的"result"字段,每次从grep中读取数据就需要专门为此解一次json, 就麻烦很多了; 再进一步,假如grep
输出的结果是这个命令的作者自创的一种加密格式...
实用
接下来,我们要考虑如何把启示用到实践中。实践方法有很多,我先抛砖引玉,举一个可行的方案:
首先,在软件设计之初,梳理其业务和技术上的功能点。
然后,将程序分为接口层 - 业务层 - 基础层
接口层对外提供高质量且清晰的接口,对内操作业务层。
业务层将业务功能抽象为一个个独立的子模块,各模块之间相互独立。
业务层在实现功能时,可能会用到技术层提供的功能。
最终,业务层通过一个主逻辑模块将各种子模块提供的功能进行组合。
看到这里有些人可能会说,这怎么有点像六边形架构? 可能这也是一种殊途同归吧。
下篇中我将进一步探讨管道模式在软件上的实践。