简单聊聊元编程

auto

什么是元编程

编程,字面意思即编写程序。

那什么是元编程呢?广义的看,我认为让任何事物按照特定的指令执行特定的动作,就可以算作编程。如果我是一个教官,组织一百号人军训,我让所有人报数,其实也算编程。不过因为人很聪明,所以我只需要一个简单的指令,就完成了这次“编程”。每一个人,我们都可以认为是一个元,Ta能够自动生成剩余的指令。元编程,其实就是一种高级抽象,让代码去写代码,让指令自动转化成代码。实际上我们绝大多数人写的代码都是高级语言,编译器或者解释器会翻译代码,生成大量的机器指令,让电脑去做正确的事。再以前端开发来说,现在炙手可热的各大框架,实际上都有元编程的思想。模版、组件化等等技术其实都是利用了这个思想。所以可以说元编程就在我们身边。


了解元编程有什么用?

简单的说就四个字——“少写代码”。

如何进行元编程?

其实我们也许在不经意间用过很多次,但是我们没有重视。我想起一本名为《写给大家看的设计书》中提到的“约书亚树”,其中有一段话是这么说的:

一旦能够说出什么东西的名字,就会很容易注意到它。你就会掌握它,拥有它,让它受你所控。

这两天我自己写了一个用来收集用户提供的配置信息的小程序,由于我们的运维工程师不懂代码,但是这个东西又需要由他来完善,于是我将那部分代码提出来,做成了一个JSON文件,当他希望这个程序收集更多用户信息时,只需要改这个文件,我的程序就会自动生成相应的交互。这里贴一点点代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Run starts the installer.
func (m *Manager) Run() {
......
data, err := ioutil.ReadFile(*optionsJSON)
if err != nil {
panic(fmt.Sprintf("can't read options.json file: %v", err))
}
var dataJSON []interface{}
err = json.Unmarshal(data, &dataJSON)
if err != nil {
panic(fmt.Sprintf("can't parse JSON file: %v", err))
}
for _, d := range dataJSON {
option, err := parseOption(d)
if err != nil {
panic(fmt.Sprintf("can't parse JSON file: %v", err))
}
if opt, ok := option.(Option); ok {
m.GetInput(opt)
} else if opt, ok := option.(Options); ok {
m.GetSelect(opt)
}
}
......
}

核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// GetSelect gets users select in given options.
func (m *Manager) GetSelect(opts Options) {
printOptionsHint(opts)
for {
fmt.Scanf("%d", &opts.Index)
if opts.Index > len(opts.Opts)+1 || opts.Index <= 0 {
fmt.Printf("invalid input(%d), retry please\n", opts.Index)
continue
}
fmt.Printf("Selected: %s\n", opts.Opts[opts.Index-1].Desc)
m.result = append(m.result, Configuration{
Desc: opts.Desc,
Key: opts.Key,
Value: opts.Opts[opts.Index-1].Value,
})
break
}
}

func printOptionsHint(opts Options) {
fmt.Printf("Please select %s:\n", opts.Desc)
for index, opt := range opts.Opts {
fmt.Printf("[%d]: %s\n", index+1, opt.Desc)
}
if opts.Index != 0 {
fmt.Printf("Default value is: %s\n", opts.Opts[opts.Index-1].Desc)
}
}

func printOptionHint(opt Option) {
fmt.Printf("Please set %s:\n", opt.Desc)
if opt.Value != "" {
fmt.Printf("Default value is: %s\n", opt.Value)
}
}

// GetInput gets users input.
func (m *Manager) GetInput(opt Option) {
printOptionHint(opt)
for {
var temp string
fmt.Scanf("%s", &temp)
if temp != "" {
opt.Value = temp
}
if opt.Tag != "" {
if err := valid.Var(opt.Value, opt.Tag); err != nil {
fmt.Printf("invalid input(%s), retry please\n", opt.Value)
opt.Value = ""
continue
}
}
fmt.Printf("Set %s:%s\n", opt.Desc, opt.Value)
m.result = append(m.result, Configuration{
Desc: opt.Desc,
Key: opt.Key,
Value: opt.Value,
})
break
}
}

部分JSON文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[
{
"Desc": "Authorization suite",
"Key": "AuthProvider",
"Index": 1,
"Opts": [
{
"Desc": "Basic (Standard Linux PAM)",
"Value": "Basic"
},
{
"Desc": "NIS (Network Information Service)",
"Value": "NIS"
},
{
"Desc": "LDAP (Lightweight Directory Access Protocol)",
"Value": "LDAP"
}
]
},
{
"Key": "InterfaceHost",
"Desc": "Host network interface name(eg: eth0)",
"Value": "",
"Tag": "required"
}
]

运行效果:
auto

这里并不是要用代码来介绍元编程,而是在这里抛砖引玉让大家了解这个概念,记住这个概念,应用到自己的代码里。

坚持原创技术分享,您的支持将鼓励我继续创作!