代码即数据,数据即代码(1)-把难以变更的代码变成易于变更的数据
相信对于这个标题,用过lisp的朋友一定不陌生,本来也是准备了一大堆理论要讲,想了想还是直接举例子比较好。
就举最近产品提的一个产品需求吧,简单描述一下:
- 对于不同的第三方应用,有不同的频率限制。没有配置则使用默认值
- 对于不同的第三方应用,在不同的时间段,有不同的频率限制。没有配置则使用默认值
公司内部都是用C++,当时第一点想到的肯定是配置一个xml文件,里面配置上这些参数,在进程启动的时候,用tinnyxml或者其他xml解析器把xml解析成C++可以辨识的数据结构。
我们来看一下这个xml配置有多复杂:
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 | <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的代码(没有写太复杂,只是示例而已):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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++封装类代码:
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | #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调用代码:
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 | #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解析类的时候,又在为产品需求变更而抱怨不已的时候,有没有想过能不能把这一切简化呢?
原创文章,版权所有。转载请注明:转载自Vimer的程序世界 [ http://www.vimer.cn ]
本文链接地址: http://www.vimer.cn/?p=2328
哈,我之前用 C 写过一个 Linux 下的文件访问重定向的库,配置部分就是用的 Lua~
[回复]
Dante 回复:
八月 19th, 2011 at 12:04 上午
嗯嗯,可惜公司对新语言的接受太过保守,否则真想废掉ini,直接用lua不就得了,还不用再写代码解析。。
[回复]
Ace 回复:
九月 5th, 2011 at 11:37 下午
从这一点来说 还是创业公司适合hacker们啊 哈哈
[回复]
好文,我顺便做一个小小的补充
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)类型吗?
我第一次成功运行这样的组合,初学者,见笑了;
感谢博主。
[回复]
楼主贴的是最终代码吗?貌似没有lua_close()
[回复]
Dante 回复:
二月 23rd, 2012 at 7:42 下午
感谢提醒!貌似忘记写了。。
[回复]
[...] 二.lua解析器的加入 老博友应该都看过我之前写的一篇博客: 代码即数据,数据即代码(1)-把难以变更的代码变成易于变更的数据,而这一次的需求也非常符合使用脚本的原则: [...]