|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
+ H/ T% l! u6 I5 L
前言:3 C' c# b9 E6 ? D. x
8 T; v1 ?. |: ^ e) V% H9 }8 j, E; @2 \最近在学习《Unix编程艺术》。以前粗略的翻过,以为是介绍unix工具的。现在认真的看了下,原来是介绍设计原则的。它的核心就是第一章介绍的unix的哲学以及17个设计原则,而后面的内容就是围绕它来展开的。以前说过,要学习适合自己的资料,而判断是否适合的一个方法就是看你是否能够读得下去。我对这本书有一种相见恨晚的感觉。推荐有4~6年工作经验的朋友可以读一下。
* [; j; ?& C4 L% r! R5 L7 u
9 g% r' F- W& q3 L3 @7 n5 n正题:2 G3 w* b# i4 u' D5 c
8 v3 h& H# J' J作者在介绍Unix设计原则时,其中有一条为“表示原则:把知识叠入数据以求逻辑质朴而健壮”。结合之前自己的一些经验,我对这个原则很有共鸣,所以先学习了数据驱动编程相关的内容,这里和大家分享出来和大家一起讨论。
$ C' w5 m$ {2 [( W! x! x1 w( i: O- m7 q* A/ }+ n6 ~6 {
数据驱动编程的核心
6 i) |# B/ T5 [. b, ]
U* Q( H" Y+ u' @, `" B' l数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长于处理数据。数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。5 j9 Y. u3 H, A% ?8 e, c8 M9 |
0 J0 V1 q X3 A' M7 h1 ?. a7 L真的是这样吗?让我们来看一个示例。4 |& p% u# d( ], U$ w, e, Y
, g: Z3 z. Z2 o
假设有一个程序,需要处理其他程序发送的消息,消息类型是字符串,每个消息都需要一个函数进行处理。第一印象,我们可能会这样处理:
% [" q/ g6 }* m8 ]) Fvoid msg_proc(const char *msg_type, const char *msg_buf)
5 T% N8 x1 M) a! I. F{ 2 q) [) O3 Y% X8 b/ {! l
if (0 == strcmp(msg_type, "inivite"))
, u" n+ ~, U$ R$ y- Q3 ` { ! l' E/ F( ?8 ]; B
inivite_fun(msg_buf); 1 I( N# }6 A% b% o
}
! B& ]; f5 k1 _$ \% a) Y else if (0 == strcmp(msg_type, "tring_100")) / }- k0 c. s: [& v
{
$ T1 L9 Y- ]# d) I2 B+ r# ` tring_fun(msg_buf);
9 ^2 O) D' e& `3 A h } 3 a$ t+ [9 O6 E8 I7 b4 i0 w
else if (0 == strcmp(msg_type, "ring_180"))
) c" k5 o s5 G+ Q O { 6 `+ H( w' S, i
ring_180_fun(msg_buf); 8 E! _2 r1 {8 l% w8 e$ Y: {
}
$ U0 G) \, K% W3 T! C+ {, T/ L# J else if (0 == strcmp(msg_type, "ring_181")) 9 |# n8 x' p' @
{
' H' o( ?1 X" r ring_181_fun(msg_buf); ) H8 H* ~' X; J' }6 I. v8 D+ g
}
; @9 H7 h1 p" H& j- @7 y8 f else if (0 == strcmp(msg_type, "ring_182"))
2 n5 s* S" X8 ` {
+ Q z0 S! E! r ring_182_fun(msg_buf);
8 j( I0 x1 C. I' m } ' P6 j; q" J1 C. W
else if (0 == strcmp(msg_type, "ring_183"))
! a) U ]" P& ~ {
# c# V' R: X2 f6 L: d5 N ring_183_fun(msg_buf); 4 R6 G1 ^: J. w( ^: f
}
9 ~" _- T" D/ z- D else if (0 == strcmp(msg_type, "ok_200"))
, F2 p- U" f6 a( B$ K, ~5 f% I) @' ~; ~ { 9 \- D9 G( u& ^- e6 W
ok_200_fun(msg_buf); ! [" _1 Y/ Z- \0 `
}4 j1 C. B9 }4 |9 B1 ~2 F
# b$ q: Q2 U( ] R
。。。。。。 8 r9 y& V% a; L
else if (0 == strcmp(msg_type, "fail_486"))
! v/ y \* J( L% ]# \. G$ y { 2 p+ u( n6 p3 G
fail_486_fun(msg_buf); 4 j5 d0 _7 K) b W/ H/ V( c a& C
} 3 e! E' n, v. `
else 9 p9 D9 Q& g6 V5 H# |
{ 5 ~" G: Y8 [5 Y R- l
log("未识别的消息类型%s\n", msg_type); + C7 F6 P' ?# a- u z" f* J
}
, e" ]5 i! D/ l5 ? Q8 B) d J} % ^0 D' J3 ~+ J. W9 H9 [2 ^
上面的消息类型取自sip协议(不完全相同,sip协议借鉴了http协议),消息类型可能还会增加。看着常常的流程可能有点累,检测一下中间某个消息有没有处理也比较费劲,而且,没增加一个消息,就要增加一个流程分支。8 y1 V$ X+ l( x9 }3 ]
& }, l7 y1 |2 g" k: F4 a5 l
按照数据驱动编程的思路,可能会这样设计: " v' [# J; c6 e. ?+ U" G+ i
typedef void (*SIP_MSG_FUN)(const char *);
( Y. ~2 u% g! u; ]
" i# d( _: Q& `+ b! x* b7 K: `- t$ Atypedef struct __msg_fun_st / H( S8 H8 p3 S a. x
{ 1 U+ E r$ D/ l" ]4 [0 x$ w
const char *msg_type;//消息类型 ' y+ Y T" d, h* }( D* \! ]
SIP_MSG_FUN fun_ptr;//函数指针 * D" B' I2 Y8 }" b( c( M
}msg_fun_st;5 s6 P/ z9 i8 W) w" J
* o3 h9 [+ P/ R; j+ R
msg_fun_st msg_flow[] = , f I* j& g4 A6 b8 I- O( c- Q
{
) S2 S; ]6 n6 O& e4 j$ S# o {"inivite", inivite_fun}, ; C' t: g! r5 ~6 z* }
{"tring_100", tring_fun}, 4 C- j' _& c. ^& J, \
{"ring_180", ring_180_fun}, / [, c- K# e6 X2 C( F, e6 j
{"ring_181", ring_181_fun}, / T- }- X$ C9 g4 n
{"ring_182", ring_182_fun}, ) t0 ^( |! B' I* z! K9 I
{"ring_183", ring_183_fun},
5 |) F2 I5 }- F1 Z6 _0 _) d. w/ g {"ok_200", ok_200_fun},
) \# W4 G0 d9 K% k4 F& M
4 k+ b9 o/ J& V: J: d+ H 。。。。。。 + r' y; M; w# d& D8 s! t. ]
{"fail_486", fail_486_fun}
/ K! K9 V3 t ^4 L};
2 m2 E/ m+ b7 S7 X W b' N- s9 }/ c9 e6 w: [6 r2 j
void msg_proc(const char *msg_type, const char *msg_buf) * \4 J: `. e6 V0 b& S; e
{ ! Z0 V4 @# F1 l
int type_num = sizeof(msg_flow) / sizeof(msg_fun_st); g) a* U: ~! n0 l2 I$ Q+ n+ }8 ^
int i = 0;2 J+ m2 {# E$ Q8 G
7 y1 E) ?/ A1 C; |/ I' s. i for (i = 0; i < type_num; i++)
- t9 x% T: _4 a( a+ {: L { U' q$ p0 P: g' z' y: k# ~. u( F. Q8 i
if (0 == strcmp(msg_flow.msg_type, msg_type)) & K* V' L# X! z9 _
{ # B! x; P% L( A! N' m
msg_flow.fun_ptr(msg_buf); 1 w9 ?2 f& F; W J" |5 N2 g, r
return ; % [5 l( T4 @5 }! P
} - I0 ]) G' \3 F0 u" B- C: q0 J' V
} 6 {: G3 z" j- j# N# T
log("未识别的消息类型%s\n", msg_type); , k( X, U8 L! C2 ?) k% K
}
2 ?; s j) G6 g4 ~# {5 e4 G9 E: N" j# y
下面这种思路的优势:" @% ]7 X+ D- M0 l
~" `. P4 x% ^1 S) m X
1、可读性更强,消息处理流程一目了然。
% j1 F/ ^# o7 @3 t
, n1 F0 t) l$ i" y2、更容易修改,要增加新的消息,只要修改数据即可,不需要修改流程。- Q3 t9 ^9 v: L6 _. I9 O/ b
% B* J7 p3 x8 ?4 G; K" F
3、重用,第一种方案的很多的else if其实只是消息类型和处理函数不同,但是逻辑是一样的。下面的这种方案就是将这种相同的逻辑提取出来,而把容易发生变化的部分提到外面。( |8 z* P" p/ v( m* d9 g
1 @& }6 H( s; |1 @
隐含在背后的思想:1 {: v/ y/ r8 m8 d& Z6 l
$ q. ^9 _! y* @# v9 ~
很多设计思路背后的原理其实都是相通的,隐含在数据驱动编程背后的实现思想包括:
& N) o F0 r9 U* B ^& U& b
+ y7 |' B3 A, z$ r- [; Z# o) I1、控制复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。
/ i( [' n. T5 f" C( r9 v; d- H$ x4 d1 \/ K
2、隔离变化。像上面的例子,每个消息处理的逻辑是不变的,但是消息可能是变化的,那就把容易变化的消息和不容易变化的逻辑分离。: S/ O* Q! z! ~ Q0 F
4 r V' X' t9 z4 q
3、机制和策略的分离。和第二点很像,本书中很多地方提到了机制和策略。上例中,我的理解,机制就是消息的处理逻辑,策略就是不同的消息处理(后面想专门写一篇文章介绍下机制和策略)。
' C. l6 T+ j1 N% |9 Q2 ~; `' Z4 M! \
数据驱动编程可以用来做什么:
+ |5 n8 W' W% R u
- r% A( w3 E A: a如上例所示,它可以应用在函数级的设计中。
0 A8 o0 e2 V! E6 x8 M! N6 A: q+ V1 i! q
同时,它也可以应用在程序级的设计中,典型的比如用表驱动法实现一个状态机(后面写篇文章专门介绍)。
0 x0 D# o$ g a, @
+ N1 _/ u5 x: a4 u" {也可以用在系统级的设计中,比如DSL(这方面我经验有些欠缺,目前不是非常确定)。
/ N- p5 ^% q7 T3 b+ ^; ]$ H" F7 y* t7 X: q& o3 }$ n
它不是什么:
. _0 e' a: R1 y: Q/ ]6 O
, @; ^" o r( y6 ^1、 它不是一个全新的编程模型:它只是一种设计思路,而且历史悠久,在unix/linux社区应用很多;$ K& [. B: H& k9 l( |0 }
* x! c/ |3 \- L2、它不同于面向对象设计中的数据:“数据驱动编程中,数据不但表示了某个对象的状态,实际上还定义了程序的流程;OO看重的是封装,而数据驱动编程看重的是编写尽可能少的代码。”, h- n6 f0 g- M# B" L8 E- v
! Q4 ]" u% J, Y8 {8 n
书中的值得思考的话:
3 r) P ]: C* y% c' R3 M F7 G3 B: c3 p' Z) n0 T4 \0 N- ^
数据压倒一切。如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike M J, w" q# r9 }* K) F
. y" t% p" H, ^) T4 H7 `0 v
程序员束手无策。。。。。只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks
) t3 ]) O- e! ?: @ J% d
4 T" H7 i2 A% y; w" [' D数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者。 |
|