相信对于这个标题,用过lisp的朋友一定不陌生,本来也是准备了一大堆理论要讲,想了想还是直接举例子比较好。 就举最近产品提的一个产品需求吧,简单描述一下:
- 对于不同的第三方应用,有不同的频率限制。没有配置则使用默认值
- 对于不同的第三方应用,在不同的时间段,有不同的频率限制。没有配置则使用默认值
公司内部都是用C++,当时第一点想到的肯定是配置一个xml文件,里面配置上这些参数,在进程启动的时候,用tinnyxml或者其他xml解析器把xml解析成C++可以辨识的数据结构。
我们来看一下这个xml配置有多复杂:
<freq_config interval="60" pt_relevant="1">
<app_list>
<app appid="0" max_day_load="360000">
<time_range_list>
<time_range begin_time="1" end_time="5" load_perc="0.1" />
<time_range begin_time="17" end_time="20" load_perc="0.5" />
</time_range_list>
<pt_config xy="0.5" qz="0.5" wb="0.5"></pt_config>
</app>
<app appid="10881" max_day_load="100000">
<time_range_list>
<time_range begin_time="1" end_time="5" load_perc="0.1" />
<time_range begin_time="17" end_time="20" load_perc="0.5" />
</time_range_list>
<pt_config xy="0.5" qz="0.5" wb="0.5"></pt_config>
</app>
<app appid="10883" max_day_load="360000">
<time_range_list>
<time_range begin_time="1" end_time="5" load_perc="0.5" />
<time_range begin_time="17" end_time="20" load_perc="0.5" />
</time_range_list>
<pt_config xy="0.5" qz="0.5" wb="0.5"></pt_config>
</app>
</app_list>
</freq_config>
这个配置可以说已经非常复杂了,而最重要的是,这种产品上的需求,说变就变,如果真的现有的配置格式满足不了新需求,那么只能变更了。做哪些变更呢?
- 配置文件格式
- 配置文件解析代码
- 数据结构代码
- 计算逻辑
- 重新编译发布
一个小小的产品需求变更居然会带来这么多修改量,这是很不合理的,况且需求变更从来都是最频繁的事情。 我们对这五个操作步骤考虑一下: 如果把计算逻辑从C++中剥离出来,放在脚本里,由脚本解析器作为一个通用的配置文件解析工具,那么1,2,3,4,5步就都不需要修改任何C++框架代码
好吧,我们已经得到答案了,其实C++框架只是想要一个频率限制的值而已,那么这个值究竟是怎么算出来的,就交给脚本去做吧。 而对脚本来说,xml什么的配置文件完全没有必要,因为在python,lua,lisp这样的脚本语言这里,数据和代码的界限已经越来越小(lisp的思想的数据=代码,实在是很有预见性的理论)。
OK,那我们来看一下实现,由于lua本身的小巧,这次的实现是使用了lua作为脚本(其实我更想用python,但是考虑到python的确有点太大了,不过如果想看C++中调用python的话,可以看这里:C,C++代码中调用python脚本,C,C++中调用python脚本(2)-高级应用) lua的代码(没有写太复杂,只是示例而已):
tb_app2limit = {
{100, 100},
{23, 1000}
}
function get_freq_limit(appid)
limit = -1
for k,v in pairs(tb_app2limit) do
if v[1] == appid then
limit = v[2]
break
end
end
return limit
end
C++封装类代码:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <iostream>
#include <memory>
#include <sstream>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
#include "lua_pub.h"
using namespace std;
#ifndef FREQCONF_ERROR
#define FREQCONF_ERROR(fmt, args...) \
snprintf(m_szErrMsg, sizeof(m_szErrMsg), "[%s][%d][%s]"fmt, \
__FILE__, __LINE__,__FUNCTION__, ##args)
#endif
struct StLuaFree
{
StLuaFree(lua_State* L)
{
m_L = L;
}
~StLuaFree()
{
if (m_L)
{
lua_close(m_L);
}
}
lua_State* m_L;
};
class CFreqConfig
{
public:
CFreqConfig(const string& filename = "freq_conf.lua") {
m_filename = filename;
}
virtual ~CFreqConfig() {}
int GetFreqLimit(uint32_t appid, int& limit)
{
const char lua_funcname[] = "get_freq_limit";
lua_State *L = lua_open();
//自动释放
StLuaFree st_lua_free(L);
luaL_openlibs(L);
if (luaL_loadfile(L, m_filename.c_str()) || lua_pcall(L, 0, 0, 0))
{
FREQCONF_ERROR("cannot run configuration file: %s", lua_tostring(L, -1));
return -1;
}
lua_getglobal(L, lua_funcname); /* function to be called */
lua_pushnumber(L, appid); /* push 1st argument */
/* do the call (1 arguments, 1 result) */
if (lua_pcall(L, 1, 1, 0) != 0)
{
FREQCONF_ERROR("error running function %s: %s", lua_funcname, lua_tostring(L, -1));
return -2;
}
/* retrieve result */
if (!lua_isnumber(L, -1))
{
FREQCONF_ERROR("function %s must return a number", lua_funcname);
return -3;
}
limit = (int)lua_tonumber(L, -1);
lua_pop(L, 1); /* pop returned value */
return 0;
}
const char * GetErrMsg()
{
return m_szErrMsg;
}
private:
string m_filename;
char m_szErrMsg[1024];
};
main调用代码:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <iostream>
#include <memory>
#include <sstream>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
#include "freq_config.h"
using namespace std;
int main(int argc, char **argv)
{
CFreqConfig conf("freq_conf.lua");
uint32_t appid = 23;
int limit;
int ret = conf.GetFreqLimit(appid, limit);
if (ret)
{
printf("GetFreqLimit fail.ret:%d,msg:%s\n", ret, conf.GetErrMsg());
return -1;
}
printf("limit:%d\n", limit);
return 0;
}
输出结果:
limit:1000
OK,整个逻辑就是这样。其实实现方式是很简单的,无非就是在C++中调用了lua脚本,但是大家在辛苦的用C/C++写着ini解析类,xml解析类的时候,又在为产品需求变更而抱怨不已的时候,有没有想过能不能把这一切简化呢?
依云 on #
哈,我之前用 C 写过一个 Linux 下的文件访问重定向的库,配置部分就是用的 Lua~
Reply
Dante on #
嗯嗯,可惜公司对新语言的接受太过保守,否则真想废掉ini,直接用lua不就得了,还不用再写代码解析。。
Reply
Ace on #
从这一点来说 还是创业公司适合hacker们啊 哈哈
Reply
fy on #
好文,我顺便做一个小小的补充
Mac OS X下
首先,安装lua
> sudo port install lua
其次,建立lua_pub.h头文件到当前目录,内容:
1 extern "C" {
2 #include "lua.h"
3 #include "lauxlib.h"
4 #include "lualib.h"
5 }
6
最后,编译运行
> g++ -I/opt/local/include/ -L/opt/local/lib/ -llua main.cpp -o main
> ./main
limit:1024
另外,lua可以这样写:
1 tb_app2limit = {
2 {100, 100},
3 {23, 1000},
4 {25668, 2^10}
5 }
有点意思;不过lua没有字典(dict)类型吗?
我第一次成功运行这样的组合,初学者,见笑了;
感谢博主。
Reply
ryanking on #
楼主贴的是最终代码吗?貌似没有lua_close()
Reply
Dante on #
感谢提醒!貌似忘记写了。。
Reply