惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Forbes - Security
Forbes - Security
L
Lohrmann on Cybersecurity
Simon Willison's Weblog
Simon Willison's Weblog
P
Proofpoint News Feed
P
Privacy International News Feed
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
S
Securelist
P
Proofpoint News Feed
Recent Announcements
Recent Announcements
GbyAI
GbyAI
B
Blog RSS Feed
A
About on SuperTechFans
C
CXSECURITY Database RSS Feed - CXSecurity.com
Y
Y Combinator Blog
Microsoft Azure Blog
Microsoft Azure Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Cyberwarzone
Cyberwarzone
I
Intezer
T
Tor Project blog
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
Recorded Future
Recorded Future
aimingoo的专栏
aimingoo的专栏
Cisco Talos Blog
Cisco Talos Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
W
WeLiveSecurity
D
DataBreaches.Net
U
Unit 42
Project Zero
Project Zero
Martin Fowler
Martin Fowler
V
V2EX
The Last Watchdog
The Last Watchdog
Security Archives - TechRepublic
Security Archives - TechRepublic
C
Cisco Blogs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V2EX - 技术
V2EX - 技术
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Threat Research - Cisco Blogs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tenable Blog
F
Full Disclosure
T
The Exploit Database - CXSecurity.com
H
Heimdal Security Blog
Latest news
Latest news
Webroot Blog
Webroot Blog

酷 壳 – CoolShell

感染新冠的经历 | 酷 壳 - CoolShell 从一次经历谈 TIME_WAIT 的那些事 | 酷 壳 - CoolShell 网络数字身份认证术 | 酷 壳 - CoolShell 我做系统架构的一些原则 | 酷 壳 - CoolShell 源代码特洛伊木马攻击 | 酷 壳 - CoolShell Go编程模式 : 泛型编程 | 酷 壳 - CoolShell 如何做一个有质量的技术分享 | 酷 壳 - CoolShell Go 编程模式:k8s Visitor 模式 | 酷 壳 - CoolShell Go编程模式:Pipeline | 酷 壳 - CoolShell Go编程模式:委托和反转控制 | 酷 壳 - CoolShell Go 编程模式:Go Generation | 酷 壳 - CoolShell Go编程模式:Map-Reduce | 酷 壳 - CoolShell Go 编程模式:错误处理 | 酷 壳 - CoolShell Go编程模式:切片,接口,时间和性能 | 酷 壳 - CoolShell 百度为什么掉队了 | 酷 壳 - CoolShell 程序员如何把控自己的职业 | 酷 壳 - CoolShell 计时攻击 Timing Attacks | 酷 壳 - CoolShell Rust语言的编程范式 | 酷 壳 - CoolShell HTTP的前世今生 | 酷 壳 - CoolShell 记一次Kubernetes/Docker网络排障 | 酷 壳 - CoolShell 可视化编程 | 酷 壳 - CoolShell 程序的本质复杂性和元语言抽象 | 酷 壳 - CoolShell 伙伴分配器的一个极简实现 | 酷 壳 - CoolShell C++11的Lambda使用一例:华容道求解 | 酷 壳 - CoolShell C++面试中string类的一种正确写法 | 酷 壳 - CoolShell C++模板”>>”编译问题与词法消歧设计 | 酷 壳 - CoolShell 数据的游戏:冰与火 | 酷 壳 - CoolShell 7个示例科普CPU Cache | 酷 壳 - CoolShell 加班与效率 | 酷 壳 - CoolShell 类型的本质和函数式实现 | 酷 壳 - CoolShell C语言全局变量那些事儿 | 酷 壳 - CoolShell 二叉树迭代器算法 | 酷 壳 - CoolShell Alan Cox:大教堂、市集与市议会 | 酷 壳 - CoolShell IoC/DIP其实是一种管理思想 | 酷 壳 - CoolShell 无锁HashMap的原理与实现 | 酷 壳 - CoolShell 浏览器的渲染原理简介 | 酷 壳 - CoolShell 疫苗:Java HashMap的死循环 | 酷 壳 - CoolShell Unix考古记:一个“遗失”的shell | 酷 壳 - CoolShell PFIF网上寻人协议 | 酷 壳 - CoolShell 实例分析Java Class的文件结构 | 酷 壳 - CoolShell 并发框架Disruptor译文 | 酷 壳 - CoolShell AWK 简明教程 | 酷 壳 - CoolShell Linus:利用二级指针删除单向链表 | 酷 壳 - CoolShell 从面向对象的设计模式看软件设计 | 酷 壳 - CoolShell 应该知道的Linux技巧 | 酷 壳 - CoolShell Web工程师的工具箱 | 酷 壳 - CoolShell 程序员疫苗:代码注入 | 酷 壳 - CoolShell 如何测试洗牌程序 | 酷 壳 - CoolShell TF-IDF模型的概率解释 | 酷 壳 - CoolShell xkcd 神图“Click and Drag” | 酷 壳 - CoolShell Bret Victor – Learnable Programming | 酷 壳 - CoolShell C/C++语言中闭包的探究及比较 | 酷 壳 - CoolShell 对九个超级程序员的采访 | 酷 壳 - CoolShell “单元测试要做多细?” | 酷 壳 - CoolShell 一次Ajax查错的经历 | 酷 壳 - CoolShell 为什么我反对纯算法面试题 | 酷 壳 - CoolShell GCC 用 C++ 来编译 | 酷 壳 - CoolShell K Nearest Neighbor 算法 | 酷 壳 - CoolShell 对技术的态度 | 酷 壳 - CoolShell InfoQ的ArchSummit大会对我的采访 | 酷 壳 - CoolShell 各式各样的验证码 | 酷 壳 - CoolShell 代码执行的效率 | 酷 壳 - CoolShell 28个Unix/Linux的命令行神器 | 酷 壳 - CoolShell 少即是极多 | 酷 壳 - CoolShell 关于闰秒 | 酷 壳 - CoolShell K-Means 算法 | 酷 壳 - CoolShell 持续部署,并不简单! | 酷 壳 - CoolShell Git显示漂亮日志的小技巧 | 酷 壳 - CoolShell 性能调优攻略 | 酷 壳 - CoolShell 抄袭,腾讯 和 产品 | 酷 壳 - CoolShell Lisp的永恒之道 | 酷 壳 - CoolShell Javascript 中的 var | 酷 壳 - CoolShell Huffman 编码压缩算法 | 酷 壳 - CoolShell 扎克伯格的一封信:关于Facebook IPO | 酷 壳 - CoolShell 用Unix的设计思想来应对多变的需求 | 酷 壳 - CoolShell 做个环保主义的程序员 | 酷 壳 - CoolShell 游戏:VIM大冒险 | 酷 壳 - CoolShell 我们需要专职的QA吗? | 酷 壳 - CoolShell 谈谈数据安全和云存储 | 酷 壳 - CoolShell 需求变化与IoC | 酷 壳 - CoolShell 再谈javascript面向对象编程 | 酷 壳 - CoolShell 千万别惹程序员 | 酷 壳 - CoolShell 由一个问题到 Resin ClassLoader 的学习 | 酷 壳 - CoolShell 腾讯,竞争力 和 用户体验 | 酷 壳 - CoolShell SteveY对Amazon和Google平台的吐槽 | 酷 壳 - CoolShell 千万不要把 bool 设计成函数参数 | 酷 壳 - CoolShell 重构代码的7个阶段 | 酷 壳 - CoolShell 10个必需的iOS开发工具和资源 | 酷 壳 - CoolShell 你确信你了解时间吗? | 酷 壳 - CoolShell 为什么Scrum不行? | 酷 壳 - CoolShell 一个空格引发的惨剧 | 酷 壳 - CoolShell 一些软件设计的原则 | 酷 壳 - CoolShell 再谈“我是怎么招聘程序员的”(上) | 酷 壳 - CoolShell 再谈“我是怎么招聘程序员的”(下) | 酷 壳 - CoolShell 超强的验证码 | 酷 壳 - CoolShell 我是怎么招聘程序员的 | 酷 壳 - CoolShell 到处都是Unix的胎记 | 酷 壳 - CoolShell 谁写了Linux | 酷 壳 - CoolShell 在线代码编译服务Codepad.org | 酷 壳 - CoolShell 简单实用的Code Review工具 | 酷 壳 - CoolShell
数据即代码:元驱动编程 | 酷 壳 - CoolShell
Todd · 2013-08-09 · via 酷 壳 – CoolShell

(感谢 @文艺复兴记(todd) 投递此文)

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

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

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

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

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

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

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

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

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

1. getopt():

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

//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++命令行参数解析库。

//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库:

//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库

//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库:

[javascript]
//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);
}
[/javascript]

天啊!?这是什么?我和小伙伴们彻底惊呆了!短短十几行代码就获得了上面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)

Loading...