[design decision]让工具依赖于naming convention是个拙劣的做法

先大概描述一下环境:

这是一个C++的build system, 基于premake扩展而来,可以管理各个package[1]/library之间的dependency关系,每个package都会随binary发布一个lua文件用来描述其导出的library和依赖的package,比如:

LIBRARIES = {}
DEPENDENCIES = {}

从而把整个dependency closure给串起来。

现在的需求要用户可以在这个lua文件中定义自己的函数,让用户在定义他们的project的时候调用,目的是为consumer在使用library时提供更精确的控制。

比如在boost的use.lua中定义:

function use_boost_thread()
    links('boost_1_46_1_thread')
    -- do anything special...
end

并且foo依赖于boost:

project('foo')
    kind('SharedLib')
  use_boost_thread()

 

应该说这是一个很灵活的设计, 给了库的发布者与使用者更大的控制力,但premake该如何来实现呢?

用户定义在他们的use.lua的文件中的函数,并不是直接可用的 - 因为在premake运行过程中有很多use.lua文件会被load,而他们需要被load到一个独立的table中(通过setfenv)以避免LIBRARIES/DEPENDENCIES的名字冲突,所以我们需要把这些被load到独立table中的函数引入到全局表中[2],比如:

_G['functionname'] = ns.use_boost_thread()

问题是,用户可以在他们的use.lua中定义很多函数,你需要知道哪个应该被导入到全局表中 - 用户必须通过某种方法来告诉我,什么方法? 一个方法是通过naming convention,比如凡是要导出的函数都用use_开头,然后只要把所有use_开头的函数导入即可,但这种方法的问题的不灵活,不健壮,而且至显的有点愚蠢:

  • 如果我要到处某个函数,他就必须以这种丑陋的use_开头
  • 如果某个内部函数无意中起了个use_开头的名字,就被不幸的导出了
  • 修改函数是否导出,竟然需要修改函数名

那么有更好的方法吗? 有,通过某个数据结构明确指定,比如:

EXPORTS = {'use_boost_thread'}

function use_boost_thread()
    links('boost_1_46_1_thread')
    -- do anything special...
end

然后,你可以任意命名你的函数,然后把需要导出的放到EXPORTS表中。

处理的时候,先读入EXPORTS,找到导出的函数名,并把他们导出。

这相当于加了了间接层,这不由得让我想起了一句话:All problems in computer science can be solved by another level of indirection[3]。

当然,有另外一个更好的方法是给函数加属性(C#/Java),或者加修饰(C++中的__declspec(dllimport)/visibility('hidden')啥的, 能力比较有限),更加简洁明了。


[1] package的概念是一系列需要共同发布的library,比如boost,或者opencv;用户使用一个package,但并不一定要link所有该package中library, 可能只是link其中之一。
[2] 一个参考实现:https://gist.github.com/4133666
[3] http://en.wikipedia.org/wiki/Indirection

posted @ 2012-11-22 22:18  lzprgmr  阅读(589)  评论(1编辑  收藏  举报

黄将军