最近开始看《Build Awesome Command-Line Applications in Ruby 2》这本书,看完第一章。其中提到一个Awesome的Command,应该是功能单一(single-purpose)的,但是具体什么叫功能单一呢?为了解释这个问题,书中举了两个反例,来解释什么不是功能单一。
第一个例子是一个备份数据库的脚本。其中需要备份的数据库有两个,这个脚本一次性把两个脚本都备份了,脚本大概是这么写的:
#!/usr/bin/env ruby
# File: db_backup.rb
#两个数据库的信息
databases = {
big_client: {
database: 'big_client',
username: 'big',
password: 'big',
},
small_client: {
database: 'small_client',
username: 'small',
password: 'p@ssWord!',
}
}
databases.each do |name,config|
#依次备份每个数据库
#...
end
使用的时候,只需要运行一个这个命令就好了。但是这个脚本的结构其实并不好,也并不是"功能单一",之所以这么说,是因为它一次性备份了两个数据库,而不是一个。试想一下,如果后来又有一个数据库需要备份,那么就得改这个脚本,修改上面databases
这个Hash(Map、Dictionary,whatever)。这个可不是一个好主意,如果别人给你一个备份数据库的脚本,然后你说要再备份一个数据库,结果他说你要改那个脚本,这时候你的感觉可不会太好,如果这个人离职了呢?如果他用的是一门你不熟悉的语言写的呢?如果他用的是write-only的perl写的呢?
所以对于这个脚本,更好的组织方式是,脚本本身只备份一个数据库,并且需要的数据库信息是从外面传进来的。
比如可以这样写:
#!/usr/bin/env ruby
# File: db_backup.rb
db_name = ARGV[0] #ARGV[0]就是传给这个脚本的第一个参数,以下类推
username = ARGV[1]
password = ARGV[2]
# backup database use db_name, username, password
# ...
使用的时候:
./db_backup.rb database1 user1 password1
./db_backup.rb database2 user2 password2
如果需要再备份一个数据库,只需要再加一条命令就好了:
./db_backup.rb database1 user1 password1
./db_backup.rb database2 user2 password2
./db_backup.rb database3 user3 password3
实事上,你想备份多少个数据库都可以,都不用去修改脚本的代码。
第二个例子,是一个命令行下的todo list 工具。这个工具有add task、complete task、list tasks三个功能。具体是这样设计的,每一个功能都有一个对应的脚本,即:
# 添加一个任务
./add_task.rb 'finish this post'
# 将第一项任务标记为完成
./complete_task.rb 1
# 列出所有的任务
./list_tasks.rb
这三个脚本共用一个"tasks.txt"文本文件,作为task的数据存储。这个设计也不好,有两点原因,一是这三个脚本共用一个"tasks.txt",那么他们之间必然会需要share很多的logic,试想如果后面想要给task增加一个field,比如tag,那么这三个脚本文件都得做相应的类似的修改。二是脚本太多暴露给用户,用户会疑惑,也记不清这么多脚本的名字。
那么解决办法是什么呢?那就是将这些命令组合成一个Command suite,类似于git一样,将很多同一类事情的命令归成一个命令,用“子命令”来区别不同的作用:
# 添加一个任务
todo new 'finish this post'
# 将第一项任务标记为完成
todo complete 1
# 列出所有的任务
todo list
这样只有一个todo脚本,里面很多share logic不会造成重复代码。同时只给一个顶层的命令给用户,更加干净、清爽!
我们知道在设计软件时,有一个非常重要的原则叫Single responsibility principle。我在想,这里的讲的设计脚本的单一任务的概念和标准,能不能应用到我们做App开发的时候,设计一个类或一个方法呢?应该是有一些参考价值的。比如说不要做两件同一属性的事。对于第二个例子,把多个命令合为一个Suite,看起来不算是“功能单一”,这更多是为了提高对外的易用性。“功能单一”更多是针对内部实现而言的,在内部,你可以使用好几个不同的脚本来做,同时保持每个脚本都是功能单一的,但是对外只暴露一个命令,以增加易用性。同样的,在设计一个类,一个模块时,我们也可以参考这个思路。内部可以使用多个子模块、delegate来实现不用的功能,对外则暴露一个统一的、易用的接口,这样便做到了两者兼顾。
关注公众号“小创作” 及时获取最新文章
网友评论