|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
0 | E, v1 X, E7 M4 R前言:
# N5 Y2 o' a, b% S* m2 r: p5 r) M3 M$ n# i/ y5 z2 |( v% [9 Z
最近在学习《Unix编程艺术》。以前粗略的翻过,以为是介绍unix工具的。现在认真的看了下,原来是介绍设计原则的。它的核心就是第一章介绍的unix的哲学以及17个设计原则,而后面的内容就是围绕它来展开的。以前说过,要学习适合自己的资料,而判断是否适合的一个方法就是看你是否能够读得下去。我对这本书有一种相见恨晚的感觉。推荐有4~6年工作经验的朋友可以读一下。* `% M$ W7 D4 A; g3 P2 X2 _# {
$ ~# d4 u; O; x. y: Y正题:
1 b/ B0 ?) \& k9 ]
, Z: n$ S% o$ V- B7 l2 }作者在介绍Unix设计原则时,其中有一条为“表示原则:把知识叠入数据以求逻辑质朴而健壮”。结合之前自己的一些经验,我对这个原则很有共鸣,所以先学习了数据驱动编程相关的内容,这里和大家分享出来和大家一起讨论。" n+ Q H0 \: Q0 y
) t$ Y3 {, {& ~2 ?+ }9 F数据驱动编程的核心& q0 |9 _3 {7 V3 l5 x
& j4 r7 [3 k! v! d4 r数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长于处理数据。数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。
6 W1 P) s/ C4 y0 R1 c
0 W3 h$ W' Q0 g, A真的是这样吗?让我们来看一个示例。
. } m, N7 _3 ]1 n5 t
5 N: n8 M$ m2 M1 ~4 @假设有一个程序,需要处理其他程序发送的消息,消息类型是字符串,每个消息都需要一个函数进行处理。第一印象,我们可能会这样处理:
+ G' O. p6 c$ R G. Tvoid msg_proc(const char *msg_type, const char *msg_buf)
3 F' i0 s, C+ `& f: w{
7 u) D4 I& k: Z f# f: X7 D if (0 == strcmp(msg_type, "inivite"))
4 I3 G: l: h2 B& I2 z {
% z" O: f4 h# C2 h& j inivite_fun(msg_buf);
v1 X+ B; H% ^& Q( E8 T% |( { } 8 q3 c* W) K+ v; Q9 E- R
else if (0 == strcmp(msg_type, "tring_100")) . s# ]: p! y1 V. R0 T! @
{ 2 E2 W7 s2 L3 u4 j9 E
tring_fun(msg_buf); r, @3 J! R6 w' }- \9 _- V
} 1 ~/ w8 G$ Y' e1 T
else if (0 == strcmp(msg_type, "ring_180"))
0 n* V( O& d( d3 u% A { ; b* @% |. H8 ]7 n) E+ S: v
ring_180_fun(msg_buf); . f* `* D, n7 _1 y6 D' {
}
1 ?4 U; p" k' J. U3 }$ P else if (0 == strcmp(msg_type, "ring_181"))
% l% ^) @8 N) X2 B2 P( k {
! R* z/ _! g" t% R ring_181_fun(msg_buf);
5 g$ T; A2 o8 j/ C" x" H7 q } ! [/ ] j3 |+ ]* T$ f
else if (0 == strcmp(msg_type, "ring_182"))
" `- P% H. D2 m7 j7 L* z {
0 a6 ?) C. E$ [ ring_182_fun(msg_buf);
4 V3 j( u: Z! m( ^. O* @( ^4 P } ) g+ m" {7 P; z) e
else if (0 == strcmp(msg_type, "ring_183"))
) l2 g- ^0 p/ F, ~: n! v {
/ ?: |; u% M# g7 L @' ~ ring_183_fun(msg_buf); / t/ \- b3 j' V; a* b' K
}
J/ `7 g& v# L% _ else if (0 == strcmp(msg_type, "ok_200")) - ?5 P- t3 E2 V
{
" V% M# v' ` a ok_200_fun(msg_buf);
) d5 D- V/ M' K- q# t }
- }# S9 O/ |0 p( F% F) U" d/ {
* ]: |$ h$ y. c 。。。。。。 ( D- O3 G c* s7 R
else if (0 == strcmp(msg_type, "fail_486")) + a' a$ a' m( Y
{ 2 _# y% J1 v9 @7 \0 V( r0 d4 N
fail_486_fun(msg_buf);
- l+ b( J1 i' E, B }
6 q) q% d+ R1 C. P) H6 r4 b4 T else " T0 R q: r- u# e5 C: M1 ?2 g9 M" z
{ 4 i# x+ _2 ^% D+ O8 l( B" D) r8 H* C3 M
log("未识别的消息类型%s\n", msg_type);
/ y! c; u: i: l1 Y } $ Y6 n2 Q- k; M* {
} ' c* L' o9 j+ x
上面的消息类型取自sip协议(不完全相同,sip协议借鉴了http协议),消息类型可能还会增加。看着常常的流程可能有点累,检测一下中间某个消息有没有处理也比较费劲,而且,没增加一个消息,就要增加一个流程分支。& b& L' U7 ^! b5 O, I. S
5 }0 f( ?7 Q c; n按照数据驱动编程的思路,可能会这样设计: ' O# T% g( T" p; [' q
typedef void (*SIP_MSG_FUN)(const char *);# S9 t7 _, y5 _8 j0 Z0 s8 k* d# ?
* H3 a$ j2 f5 F& h
typedef struct __msg_fun_st
/ D% b5 u, \; q4 n6 M, V{ % y; c* Z2 b, m, D
const char *msg_type;//消息类型
/ w/ x* }) t* K+ I& n SIP_MSG_FUN fun_ptr;//函数指针
6 z4 p" z. F4 _: R+ m( ~2 h}msg_fun_st;* R5 k5 D% D/ f
! C! {+ ^$ G2 i( K/ q, I) omsg_fun_st msg_flow[] =
- A$ L+ D* D/ M9 c W8 V{
0 [* }# ]1 k5 X4 q {"inivite", inivite_fun},
- d9 g0 n+ G( Q& z9 m& Y( l3 v {"tring_100", tring_fun},
; Y& u* Q M+ j, f% i {"ring_180", ring_180_fun}, Y7 r! U t6 _( A, p
{"ring_181", ring_181_fun},
& G' F t% b) A: n0 A2 [ {"ring_182", ring_182_fun},
5 Y4 z* v. F0 p3 G; L {"ring_183", ring_183_fun},
5 I* y0 H2 I/ k% E1 Q2 I+ Z/ B {"ok_200", ok_200_fun},
/ m# X: T* \+ c- Q; \5 L! R
# `' q4 ]- Z& H& k 。。。。。。
2 [ h% }/ `$ l8 c {"fail_486", fail_486_fun} : F) q- R, i; [! x
};, X' Z0 l$ g. I* X6 s+ q
, n4 W4 z% L {: m( Q4 G, j: A( G3 c& t
void msg_proc(const char *msg_type, const char *msg_buf)
! ^8 S: F* M! D/ f! W{ ; r; P/ [4 `# |# W0 v, g0 S5 H" `% ^
int type_num = sizeof(msg_flow) / sizeof(msg_fun_st);
. h- U- I3 C$ G$ Q/ I5 u int i = 0;, T" h' }: H2 A9 {2 q
4 v0 G T/ l$ O4 c. n1 v
for (i = 0; i < type_num; i++) & h7 N9 k$ d4 \2 \( h
{
8 [* v) z' [* C( q$ z1 Y4 V if (0 == strcmp(msg_flow.msg_type, msg_type)) 7 r4 {0 S' N! g% X$ {+ x- D2 z; S3 z
{ 4 r8 _' s, E \7 ]9 @/ ?# j
msg_flow.fun_ptr(msg_buf);
% N0 N8 S; W+ w$ {. W* j6 A return ;
9 Y5 {2 c' `6 w1 E, l* S* H }
! W8 Z; o% ?* C4 i& w7 K) x } 3 V5 j- v' e* I. T
log("未识别的消息类型%s\n", msg_type);
0 \! x" \% n- n; E, J}
- b' `9 m- [# _4 O" v; Z* {
. r9 k0 F( Y# l, \. |" @* n5 D下面这种思路的优势:
& i/ u1 ?, K- a# `9 _7 Z* K/ _. N/ ?# I3 M0 ^5 C- y# d; \
1、可读性更强,消息处理流程一目了然。
$ i8 X0 \$ r& N+ {7 A( W' _* t4 A: C+ t, Z' l
2、更容易修改,要增加新的消息,只要修改数据即可,不需要修改流程。
8 G2 S# Z! ?8 s# k" b$ l1 w# y$ B$ Z4 ]6 a- O$ Y% `0 K9 u3 S: N5 U
3、重用,第一种方案的很多的else if其实只是消息类型和处理函数不同,但是逻辑是一样的。下面的这种方案就是将这种相同的逻辑提取出来,而把容易发生变化的部分提到外面。: E9 T2 ~+ W; ?. o
! Q- v$ o' V4 @. y0 ?5 Z% F' x1 w! ]
隐含在背后的思想:" Y2 g @' J( x* t
6 Q0 a0 Z4 h5 D很多设计思路背后的原理其实都是相通的,隐含在数据驱动编程背后的实现思想包括:) F# A2 i0 w% K% B! o/ t
& E4 p, k! N; E1、控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。
, j0 F1 P$ \" `: d+ G
1 Y" ~4 F" ?1 q' ~2、隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。" W( N* {# {9 _3 a* U9 ]
8 t. P" S) M" e8 o$ D- n& f$ |* Y3、机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理(后面想专门写一篇文章介绍下机制和策略)。
" n+ A0 J8 i/ }; K' w3 g' P, ]1 f5 Q+ Q0 {. E
数据驱动编程可以用来做什么:. F4 g. o# D- \$ {0 X
2 ^$ A& |* \4 W! D- b: w如上例所示,它可以应用在函数级的设计中。
( m; w0 k* \0 p0 U. D. `
; |/ z- M3 [6 n/ L. m# B同时,它也可以应用在程序级的设计中,典型的比如用表驱动法实现一个状态机(后面写篇文章专门介绍)。4 W0 d* n; J" w6 t8 }
9 P, [" B) i: D% E& i4 m
也可以用在系统级的设计中,比如DSL(这方面我经验有些欠缺,目前不是非常确定)。& h( {& ]# g! M/ [
( \$ r" x, e& e- i+ C& U它不是什么:, {( I, E, E0 h
. q; A& U/ K g
1、 它不是一个全新的编程模型:它只是一种设计思路,而且历史悠久,在unix/linux社区应用很多;
, T/ m; T g% t7 |" d& Q3 o3 R1 m1 |9 z
2、它不同于面向对象设计中的数据:“数据驱动编程中,数据不但表示了某个对象的状态,实际上还定义了程序的流程;OO看重的是封装,而数据驱动编程看重的是编写尽可能少的代码。”
+ A' I+ _7 O+ |; D- M0 b+ l( x; G5 r- R0 w; T3 e5 b4 p
书中的值得思考的话:
3 U% Y* f9 _# u" R. [) J2 x; n5 G# S
数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike
, w5 ~' g. M: B1 {" @
, z2 l0 p& w& N# c7 `* @程序员束手无策。。。。。只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks
* U. Q- B# q# I# v B8 [. c! B; h% m P6 \7 N
数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者。 |
|