代码模块化(二)

在按照上一篇的做法将代码重构之后,发现虽然模块之间的依赖关系虽然明确了,
但是使用方式2的模块太多了, 方式2有一个弊端就是,对于模块间的引用没有天然的限制,如果不小心使用了某个模块却没有调用此模块的init函数,在某些情况下也是能够正常运行的。

因此我决定将除了log还要config这种几乎所有模块都会用到的模块使用方式2外,其他的模块全部使用方式1来实现。在实现之初我觉得除了那些最基础的模块外完全采用方式1来实现应该是一个完美的树形,但是在实现过程中发现将模块化的组织为一个完美的树形有着种种障碍,同层模块之间的交互几乎不可能全部在上层模块中解决,在这一个月中我一直在各种架构书籍中去寻找答案, 得到的结论竟然是想将代码写成完美树形是不可能的.

我甚至于找到两个很常见的例子来推翻可以将程序组成完美树形的依据, 一个公司的结构与一个PC机的结构:

公司的结构其实理论上来看是个完美树形, 老板下面有一堆经理, 每个经理下又有一堆组长. 但是仔细思考后就会发现, 如果有什么事, 老板肯定不会去了解所有细节而是让所有经理来开会, 换句话说, 老板几乎不可能先去与A经理商议再将得到的结果与B经理商议. 肯定是招集所有经理来开会, 那么经理之间就会有直接交互,这是不可避免的, 由于同层人员有交互那么其依赖关系就不可能形成一个完美的树形

PC机主要的三大件CPU, 硬盘, 内存的交互仔细想来也并非我所想象的完美树状依赖关系, 如果CPU想将硬盘的内容读到内存里去, 只是与硬盘发送读命令, 向内存发送写命令, 其数据传输是需要内存与硬盘的总线来进行的, 并不经过cpu, 这同样与我想象中的硬盘与内存所有交互是通过cpu来进行并不相符.

在上面的实例中我得出一个结论, 上层代码会控制其直属下层代码之间进行交互,但是并不是所有的交互细节都可以通过上层模块来进行(当然同层模块之间的交互不能够形成循环依赖),在有些必要的情况下,两个同层模块间必须要留有接口用于交互。

经过不段斟酌,我认为代码模块之间的同层交互是这样的:
假设有三个模块module_a, module_b, module_c

module_a 属于上层模块, 其直属模块为module_b, module_c.
module_c与module_b处于同层, 但是有一个操作module_c 需要借助module_b来操作, 那么module_c的接口可能需要如下定义:


//module_c.h

struct module_c;
struct module_b;

struct module_c *module_c_create();
int module_c_free(struct module_c *m);

int module_c_do_something(struct module c *m, struct moduel_b *b, ...);

个人认为其实这种的同层模块间交互还是要尽量避免的, 如果两个模块之间有了接口依赖, 那么两个模块的耦合度就会增加, 其中一个模块就要对另一个块的接口有所了解, 对于维护和替换模块是不利的. 而如果去画依赖关系图的话, 也会增加一道依赖关系, 设计清晰度也会下降.



评论

  1. 看完此篇文章后,我内心陷入了深深的思索。
    46亿年前,地球形成,不知道什么时候生命出现,多么伟大的事情,生命本身已经是非常复杂的系统了,然而进化不满足,又出现了智商高度发达的人类。

    十一回家时,我对喂鸡有了一次观察,当我向鸡群撒食物的时候,总是强大的公鸡冲在最前面,而瘦弱的公鸡不敢抢第一口。
    然后我想,公鸡吃饭,当然是因为饿了,但是饿了公鸡有没有发现一条规律呢?那就是“它吃的越多它就能越强壮,它越强壮就能吃越多。”,我想公鸡们发现了“饿了->吃食->不饿”的规律,或者说这就是本能吧,但是公鸡应该没有发现“吃得多->强壮->吃更多”。
    一个瘦弱的公鸡面对一个强壮的公鸡的时候,或许大脑里会想“它好强壮啊,我打不过它,我少吃点就行了。”,然而却没有明白吃得少永远长不大的现象啊。
    而灵长类就进步的多,它们明白更多道理,而非仅仅是本能,再两只雄性黑猩猩的斗争中,黑猩猩经常攻击对方的睾丸,这说明什么?
    1.黑猩猩明白它是什么,黑猩猩因此推测别的黑猩猩也一样。
    2.黑猩猩明白抓破对方的睾丸意味着什么,它意图让对方丧失交配的功能,因而独享雌性。
    而人类战争中,一方总是杀掉一方的年轻人以及壮年人,但是却保留妇女,儿童,工匠,都是因为人类能观察自己。
    我那时明白了一个道理,“意识”是进化赋予灵长类的一个gift。
    而意识的出现又是因为什么呢?
    “动物因为对自己的观察而获得进步,群居让动物认识了别人也认识了自己。”
    “社会的进步来源于社会对自己的观察”

    所以我们观察我们的社会,以此想要获得智慧,事实上我们也获得了。

    人类原来应该是一个平面网络模型。
    社会把人类层次化,形成一个树形结构。

    计算机网络的发明者参照了邮局系统和电话系统,而邮局系统和电话系统也绝对不会是凭空出现。

    一项伟大的发明当然由伟大的人来发现,然而这个伟大的人也必然是通过观察社会而获得设计的智慧。

    我们设计软件时却说着“生产者”,“消费者”,“监督者”,“哨兵”,“服务器”,“客户端”,“攻击者”,“防御系统”这样的话,多么奇妙啊,我们直接就把人类产生的概念复制到计算机软件设计当中去。

    而软件开发人员从社会里获得了足够的智慧,他们提出了“框架”,“模式”,其中两个比较直观的模型被人类套用过来了。
    1.树模型,2.网络模型
    树模型是网络模型的一个简化,我们在网络上生成了树。

    你可以随手在纸上画几个圆圈代表社会上的人类,初始的时候,每个人的圈一样大,然后总会有人因为做出不同的贡献,有的人圈变得大一些,有的人变大更大些,把最大的圆圈和次大的联系,次大的再和次次大的联系,这样就构成了一个树,也是一个层次化设计,因此构成了国,省,市,县,乡,村。

    和人类的层级社会比较起来,软件设计中的树也够简单的,一个省长虽然在省里有举足轻重的地位,然而一个省长的离去基本上不会引起这个省普通人的生活的变化。在省长职位空缺的时期,副省长可以替代工作。而软件设计中一个节点的崩溃会导致下面的节点“无父可循”吧。

    什么是一个完美的树形结构呢?为什么把网络模型看成破坏完美的树形结构呢?综合来说,还是因为现在的软件开发人员懒,人类的认识总是从低级往高级发展,而现在我们明显发展到树形结构已经不足以解释我们的软件架构,而网络模型又太复杂,认识没有达到这个层级的地步。

    我虽然家乡在驻马店,按照完美树形模型,或者稍微宽松点的树形模型,我无法和一个临近的安徽人通话了。
    在完美树形模型中,我甚至不能姐姐说话,我需要先把说的话说给我父亲,我父亲再转给我姐姐。
    在稍微宽松的模型中,兄弟姐妹之间可以直接说话了,也就是你提出的模型,(Good,很漂亮)
    但是呢?假如要我一个安徽人通话呢?
    我们不属于一个村,不属于一个县,不属于一个市,不属于一个省,我们是一个树的两大分支的末尾节点,假如我们要通信的话,那是多么复杂啊?

    我注意到说道树结构,其实有两种,开发设计时的“树结构“,运行时的”树结构“,
    你所指的是开发设计时的树结构,而在模块之上的软件可能根本不是树形结构,当然有可能还是。

    下面讨论几个典型的树形结构吧,胡乱点评一番。
    1.进程树(运行时树结构)
    linux进程树是典型的运行时树结构,有意思的是,假如一个子进程失去其父亲的时候,会被交给pid 1线程。(当然有时候会随父进程结束而结束),pid1进程大概有一堆孩子吧。

    2.erlang otp结构(开发运行时树结构)
    我对erlang的otp结构没有更多的了解,但是它也是一个树形结构,某一节点崩溃后,它的子节点就交给了它的父节点。

    3.java & C# 类树(开发时树结构)
    典型的树结构,这个属于完美树形吧?所有的class都是一个class的后代。

    4.c++类树(开发时树结构)
    c++的类树不是一个完美树形,它可能继承于多个class倒有点像王庄结构。

    5.hadoop模型(运行时树结构)
    hadoop模型还是比较简单的,总共只有两层,一个manager,多个worker,manager分配任务给workers,并且监督workers干活,颇有一个包工队的概念,几个worker挂掉了根本不是问题,manager可以重启它,后者剩下的几个worker就能把活干完。那么问题来了,manager要是挂了呢?不要紧还有一个manager_backup。
    所以hadoop的模型也很乱,就像我们即用了git来保存源代码,又用复制文件夹来备份一样, 显得可笑了。
    据说腾讯的云端设计者,设计了一个三层模型,你想想,三层会比二层复杂多少。

    综上来说,我所说的好像都是运行时树结构,对于设计时的模块化,层次化,我的经验实在不多呀。

  2. @跑飞的程序员 因为程序中的模块不会坐火车:), 因此他们安徽人模块与驻马店模块的人想要通话就必然要打电话或写信, 那么方式是一样的, 电话传输线路肯定是先经过驻马店市交互机或路由器然后再转交给安徽市的换级或路由器, 再由安徽市的交换机一级一级去散转, 一直找到这个安徽人的电话中. 不管使用什么样的技术我觉得最起码在物理上是这样的, 你觉得呢? &_^

  3. 然而就像我开头所说的一样,我们智慧的获得来源于实践,来源于观察,然而总结,再实践,看看反馈如何。
    同层模块是不是需要通信,是不是要尽量避免,都来源于你的实践了。

  4. […] 经过一个月的再次反思, 我借用了少许面向对象的思想, 采用代码模块化(二)的方式重构了代码. 重构完成之后发现代码像是树形了, […]

发表评论