异常机制

记得学习C++之初就接触到, C++有异常机制, 可是我就是不知道为什么会有异常, 常常在想有返回值不是就够了, 就此问题我也问了很多人, 找了很多书, 可是只是有人给我说异常怎么用, 却就是不给我说异常为什么产生的, 当然有可能是我没精读过大多C++书籍导致的.

后来转向C之后, 这个问题也就被搁置了. 直到不久前, 用C的返回值写的很烦时(几乎每个函数调用都需要检查返回值), 又想到了C++的异常机制, 终于发现异常机制的方便之处.

使用异常几乎可以省掉大半的检查返回值的代码, 当然这并非不检查, 只是在语法上可以很掉很大的功夫, 这也难怪”C接口与实现”的作者去花那么大力气去实现一个异常库了.

自然语言的描述总是缺乏说服力, 机器的世界还是用机器语言来描述:


//C++ language

#include
int func1(int n)
{
if (n == -1)
throw 3;
printf("func1: successn");
return 0;
}
int func2(int n)
{
func1(n);
printf("func2: successn");
return 0;
}

int main(int argc, char * argv[])
{
try {
func2(-1);
} catch (int n) {
printf("exception:%dn", n);
}

return 0;
}

//C language
#include

int func1(int n)
{
if (n == -1)
return 3;
printf("func1: successn");

return 0;
}

int func2(int n)
{
int err;

err = func1(n);
if (err != 0)
return err;

printf("func2: successn");

return 0;
}

int main(int argc, char * argv[])
{
int err;

err = func2(-1);

if (err != 0)
printf("exception:%dn", err);

return 0;
}

比较C与C++的代码可以发现, 在没有使用异常的代码中(C代码), 每一个层调用, 每一个函数的返回值都要去判断, 函数体几乎要爆增一倍. 而使用异常的代码测可以在throw之后直接一路飞速回到最上层想要检查返回值的地方. 可以省下大规模的返回值比较代码, 我想这也许就是异常机制出现的意义吧.

btw, 其实异常机制就是语法糖, 只不过是很实用的语法糖.

代码模块化(二)

在按照上一篇的做法将代码重构之后,发现虽然模块之间的依赖关系虽然明确了,
但是使用方式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, ...);

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