几个小伙伴在考虑下面这个各个语言都会遇到的问题:

问题:设计一个命令行参数解析API

一个好的命令行参数解析库一般涉及到这几个常见的方面:

1) 支持方便地生成帮助信息

2) 支持子命令,比如:git包含了push, pull, commit等多种子命令

3) 支持单字符选项、多字符选项、标志选项、参数选项等多种选项和位置参数

4) 支持选项默认值,比如:–port选项若未指定认为5037

5) 支持使用模式,比如:tar命令的-c和-x是互斥选项,属于不同的使用模式

经过一番考察,小伙伴们发现了这个几个有代表性的API设计:

1. getopt():

getopt()是libc的标准函数,很多语言中都能找到它的移植版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//C
while ((c = getopt(argc, argv, "ac:d:")) != -1) {
    int this_option_optind = optind ? optind : 1;
    switch (c) {
    case 'a':
        printf ("option a");
        aopt = 1;
        break;
    case 'c':
        printf ("option c with value '%s'", optarg);
        copt = optarg;
        break;
    case 'd':
        printf ("option d with value '%s'", optarg);
        dopt = optarg;
        break;
    case '?':
        break;
    default:
        printf ("?? getopt returned character code 0%o ??", c);
    }
}

getopt()的核心是一个类似printf的格式字符串的命令行参数描述串,如上面的”ac:d:”定义了”a”, “c”,”d”3个命令行参数,其中,a是一个标志符不需要参数,”c”和”d”需要跟参数。getopt()功能非常弱,只支持单个字符的标志选项和参数选项。如果按上面的5点来比对,基本上只能说是勉强支持第3点,其他几项只能靠程序自己来实现了,所以,想直接基于getopt()实现一个像git这样复杂的命令行参数是不可能的,只有自己来做很多的解析工作。小伙伴们看过getopt()之后一致的评价是:图样图森破。

2. Google gflags

接着,小伙伴们又发现了gflags这个Google出品C++命令行参数解析库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//C++
DEFINE_bool(memory_pool, false, "If use memory pool");
DEFINE_bool(daemon, true, "If started as daemon");
DEFINE_string(module_id, "", "Server module id");
DEFINE_int32(http_port, 80, "HTTP listen port");
DEFINE_int32(https_port, 443, "HTTPS listen port");
int main(int argc, char** argv) {
    ::google::ParseCommandLineFlags(&argc, &argv, true);
    printf("Server module id: %s", FLAGS_module_id.c_str());
    if (FLAGS_daemon) {
      printf("Run as daemon: %d", FLAGS_daemon);
    }
    if (FLAGS_memory_pool) {
      printf("Use memory pool: %d", FLAGS_daemon);
    }
    Server server;
    return 0;
}

小伙伴们看了后不由得感叹“真心好用啊”!的确,gflags简单地通过几个宏就定义了命令行选项,基本上很好的支持了上面提到的1,3,4这几项,比起getopt()来强多了。对于类似cp这样的小命令,gflags应该是够用了,但要达到git这种级别就显得有些单薄了。

3. Ruby Commander

接下来小伙伴们又发现了Ruby Commander库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Ruby
# :name is optional, otherwise uses the basename of this executable
program :name, 'Foo Bar'
program :version, '1.0.0'
program :description, 'Stupid command that prints foo or bar.'
command :bar do |c|
  c.syntax = 'foobar bar [options]'
  c.description = 'Display bar with optional prefix and suffix'
  c.option '--prefix STRING', String, 'Adds a prefix to bar'
  c.option '--suffix STRING', String, 'Adds a suffix to bar'
  c.action do |args, options|
    options.default :prefix => '(', :suffix => ')'
    say "#{options.prefix}bar#{options.suffix}"
  end
end
$ foobar bar
# => (bar)
$ foobar bar --suffix '}' --prefix '{'
# => {bar}

Commander库利用Ruby酷炫的语法定义了一种描述命令行参数的内部DSL,看起来相当高端大气上档次。除了上面的第5项之外,其他几项都有很好的支持,可以说Commander库的设计基本达到了git这种级别命令行参数解析的要求。只是,要搞懂Ruby这么炫的语法和这个库的使用方法恐怕就不如getopt()和gflags容易了。有小伙伴当场表示想要学习Ruby,但是也有小伙伴表示再看看其他库再说。

4. Lisp cmdline库

接下来,小伙伴们发现了Lisp方言Racket的cmdline库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Lisp
(parse-command-line "compile" (current-command-line-arguments)
  `((once-each
     [("-v" "--verbose")
      ,(lambda (flag) (verbose-mode #t))
      ("Compile with verbose messages")]
     [("-p" "--profile")
      ,(lambda (flag) (profiling-on #t))
      ("Compile with profiling")])
    (once-any
     [("-o" "--optimize-1")
      ,(lambda (flag) (optimize-level 1))
      ("Compile with optimization level 1")]
     [("--optimize-2")
      ,(lambda (flag) (optimize-level 2))
      (("Compile with optimization level 2,"
        "which implies all optimizations of level 1"))])
    (multi
     [("-l" "--link-flags")
      ,(lambda (flag lf) (link-flags (cons lf (link-flags))))
      ("Add a flag <lf> for the linker" "lf")]))
   (lambda (flag-accum file) file)
   '("filename"))

这是神马浮云啊?括号套括号,看起来很厉害的样子,但又不是很明白。看到这样的设计,有的小伙伴连评价都懒得评价了,但也有的小伙伴对Lisp越发崇拜,表示Lisp就是所谓的终极语言了,没有哪门语言能写出这么不明觉历的代码来!小伙伴们正准备打完收工,突然…

5. Node.js的LineParser库

发现了Node.js的LineParser库:

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
//JavaScript
var meta = {
    program : 'adb',
    name : 'Android Debug Bridge',
    version : '1.0.3',
    subcommands : [ 'connect', 'disconnect', 'install' ],
    options : {
        flags : [
            [ 'h', 'help', 'print program usage' ],
            [ 'r', 'reinstall', 'reinstall package' ],
            [ 'l', 'localhost', 'localhost' ]
        ],
        parameters : [
            [ null, 'host', 'adb server hostname or IP address', null ],
            [ 'p', 'port', 'adb server port', 5037 ]
        ]
    },
    usages : [
        [ 'connect', ['host', '[port]'], null, 'connect to adb server', adb_connect ],
        [ 'connect', [ 'l' ], null, 'connect to the local adb server', adb_connect ],
        [ 'disconnect', null, null, 'disconnect from adb server', adb_disconnect ],
        [ 'install', ['r'], ['package'], 'install package', adb_install ],
        [ null, ['h'], null, 'help', adb_help ],
    ]
};
try {
    var lineparser = require('lineparser');
    var parser = lineparser.init(meta);
    // adb_install will be invoked
    parser.parse(['install', '-r', '/pkgs/bird.apk']);
}
catch (e) {
    console.error(e);
}

天啊!?这是什么?我和小伙伴们彻底惊呆了!短短十几行代码就获得了上面5点的全面支持,重要的是小伙伴们居然一下子就看懂了,没有任何的遮遮掩掩和故弄玄虚。本来以为Ruby和Lisp很酷,小伙伴们都想马上去学Ruby和Lisp了,看到这个代码之后怎么感觉前面全是在装呢?有个小伙伴居然激动得哭着表示:我写代码多年,以为再也没有什么代码可以让我感动,没想到这段代码如此精妙,我不由得要赞叹了,实在是太漂亮了!

小伙伴们的故事讲完了,您看懂了吗?如果没有看懂的话,正题开始了:

在绝大多数语言中数据和代码可以说是泾渭分明,习惯C++、Java等主流语言的程序员很少去思考数据和代码之间的关系。与多数语言不同的是Lisp以“数据即代码,代码即数据”著称,Lisp用S表达式统一了数据和代码的形式而独树一帜。Lisp奇怪的S表达式和复杂的宏系统让许多人都感到Lisp很神秘,而多数Lisp教程要么强调函数式编程,要么鼓吹宏如何强大,反而掩盖了Lisp真正本质的东西,为此我曾写过一篇《Lisp的永恒之道》介绍Lisp思想。

设计思想和具体技术的区别在于前者往往可以在不同的环境中以不同的形式展现出来。比如,熟悉函数式编程的程序员在理解了纯函数的优点后即使是用C语言也会更倾向于写出无副作用的函数来,这就是函数式思想在命令式环境的应用。所以,理解Lisp思想一定要能在非Lisp环境应用,才算是融汇贯通。

如果真正理解了Lisp的本质,那所谓的“数据即代码,代码即数据”一点儿也不神秘,这不就是我们每天打交道的配置文件吗!?如果你还不是很理解的话,我们通过下面几个问题慢慢分析:

1) 配置的本质是什么?为什么要在程序中使用配置文件?

不知道你是否意识到了,我们每天都在使用的各种各样的配置本质上是一种元数据也是一种DSL,这和Lisp基于S表达式的“数据即代码,代码即数据”没有本质区别。在C++、Java等程序中引入配置文件的目的正是用DSL弥补通用语言表达能力和灵活性的不足。我知道不少人喜欢从计算的角度来看到程序和语言,似乎只有图灵完备的语言如C++、Java、Python等才叫程序设计语言,而类似CSS和HTML这样的东西根本不能叫做程序设计语言。其实,在我看来这种观点过于狭隘,程序的本质是语义的表达,而语义表达不一定要是计算。

2) 配置是数据还是代码?

很明显,Both!说配置是数据,因为它是声明式的描述,能方便地修改和传输;说配置是代码,因为它在表达逻辑,你的程序实际上就是配置的解释器。

3) 配置的格式是什么?

配置的格式是任意的,可以自己定义语法,只要配以相应的解释器就行。不过更简单通用的做法是基于XML、JSON、或S表达式等标准结构,在此之上进一步定义schema。甚至完全不必是文件,在我们的项目中配置经常是放到用关系数据库中的。另外,下面我们还会看到用语言的Literal数据作为配置。

4) 业务逻辑都可以放到配置中吗?

这个问题的答案显然是:Yes!我没有遇到过不可以放入配置的逻辑,只是问题在于这样做是否值得,能达到什么效果。对于需要灵活变化,重复出现,有复用价值的东西放入作为配置是明智的选择。这篇文章的主要目的就在于介绍把主要业务逻辑都放到配置中,再通过程序解释执行配置的设计方法,我称之为:元驱动编程(Meta Driven Programming)

转载于:https://www.cnblogs.com/alantu2018/p/8503445.html

数据即代码:元驱动编程相关推荐

  1. 领域驱动编程,代码怎么写?

    简介:领域驱动开发最重要的当然是正确地进行领域拆解,这个拆解工作可以在理论的指导下,结合设计者对业务的深入分析和充分理解进行.本文假定开发前已经进行了领域划分,侧重于研究编码阶段具体如何实践才能体现领 ...

  2. 从数据到代码——通过代码生成机制实现强类型编程[上篇]

    我不知道大家对CodeDOM的代码生成机制是否熟悉,但是有一点可以确定:如果你使用过Visual Studio,你就应该体验过它带给我们在编程上的便利.随便列举三种典型的代码生成的场景:在创建强类型D ...

  3. java编程数据溢出问题_Java数据溢出代码详解

    Java数据溢出代码详解 发布时间:2020-10-05 15:08:31 来源:脚本之家 阅读:103 作者:Pony小马 java是一门相对安全的语言,那么数据溢出时它是如何处理的呢? 看一段代码 ...

  4. 独家 | 10个数据科学家常犯的编程错误(附解决方案)

    作者:Norman Niemer 翻译:李润嘉 校对:李洁 本文约2000字,建议阅读10分钟. 本文为资深数据科学家常见的10个错误提供解决方案. 数据科学家是"比软件工程师更擅长统计学, ...

  5. Linux驱动编程 step-by-step (五)主要的文件操作方法实现

    主要的文件操作方法实现 文件操作函数有很多的操作接口,驱动编程需要实现这些接口,在用户编程时候系统调用时候会调用到这些操作 [cpp] view plaincopy struct file_opera ...

  6. [内核编程] 内核环境及其特殊性,驱动编程基础篇

    [内核编程] 内核环境及其特殊性,驱动编程基础篇  在学习汉江独钓一书后,打算总结一下内核编程应该注意的事项,以及有关的一些基础知识.第一次接触内核编程,还真是很生疏,很多东西不能一下马上消化.这里做 ...

  7. linux驱动read函数 copytouser,Linux驱动编程 step-by-step (五)主要的文件操作方法实现...

    主要的文件操作方法实现 文件操作函数有很多的操作接口,驱动编程需要实现这些接口,在用户编程时候系统调用时候会调用到这些操作 structfile_operations { ... loff_t (*l ...

  8. CAN 总线嵌入式驱动编程

    摘要:介绍了uclinux 操作系统的内核结构以及设备驱动编程的基本原理,并对CAN 总线的嵌入式系统进行了硬件设计及软件驱动编程,提出CAN 总线技术应用于嵌入式系统的一种方案. 1 引言     ...

  9. Windows驱动编程基础教程

    前言     本书非常适合熟悉Windows应用编程的读者转向驱动开发.所有的内容都从最基础的编程方法入手.介绍相关的内核API,然后举出示范的例子.这本书只有不到70页,是一本非常精简的小册子.所以 ...

最新文章

  1. NOI.AC NOIP模拟赛 第六场 游记
  2. matlab 中调用s函数表达式,[求助]S函数中能否调用M函数
  3. jquery animate
  4. 【转】多语言的正则表达式,我们应该掌握
  5. wamp测试mysql_Wamp 配置及测试
  6. VTK修炼之道1_初识VTK
  7. 『原创』+『参考』使用WMI在C#下获取U盘的永久不变的物理序列号
  8. 一文搞懂 CPU、GPU 和 TPU
  9. python qq邮箱发邮件_Python用QQ邮箱发邮件的实例教程
  10. android多个网络请求如何依次执行,Android 并发和串行网络请求
  11. 【PyTorch】推荐收藏!史上最全的 PyTorch trick 集锦
  12. canvas设置渐变
  13. Python星号表达式
  14. 采用Minitab进行logistic回归分析
  15. FIR 带通滤波器参数设计流程
  16. Photoshop图片变纯黑白无灰度
  17. 利用PS抠出水印字并添加到图片
  18. bootstrap table表格点击行checkbox勾选或取消勾选
  19. PLC通讯实现-C#实现欧姆龙以太网通讯FINS(二)
  20. mysql explain 的using where 到底是什么意思?

热门文章

  1. Android 弹出 Toast 时取消上一个 Toast(完美方案)增加同步
  2. Document is invalid: no grammar found. at (null:3:8)
  3. Android语言国际化values资源文件命名规则
  4. iOS App启动流程
  5. android data分区(标准)
  6. 记录一下在mac上做一个usb linux安装盘
  7. cesium obj转b3dm转换及加载
  8. 关于CS1061报错(XX不包含XXX的定义,并且找不到类型为XX的第一个参.....)的一种可能的解决的办法...
  9. CSS3幻灯片制作心得
  10. android手势识别