单一职责原则:
通常的定义是只专注于做一件事和仅有一个引起它变化的原因。对于接口、实现、函数级别往往我们比较容易关注单一职责,大家谈的也比较多,但对于返回值、参数可能不会有太多的人关注。但往往就是这些不符合单一职责原则的设计可能导致一些很难发现的BUG。看看下面这段代码:
pBuf = (byte*)realloc( pBuf, size); if( pbBuf != NULL ) { TODO... }
可能很多人一眼看上去并没有什么问题,先让我们看看这个库函数的定义:
函数简介 原型:extern void *realloc(void *mem_address, unsigned int newsize); 语法:指针名=(数据类型*)realloc(要改变内存大小的指针名,新的大小)。 功能:先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,
先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域,同时
返回新分配的内存区域的首地址。即重新分配存储器块的地址。 返回值:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
正常情况下pBuf是新空间的地址没有任何问题,但我们考虑下如果分配失败了呢,pBuf会被赋值成NULL,pBuf原指向的地址空间就没有指针指向了,造成了内存泄露。这种问题往往很难定位。熟悉realloc机制的人可能对这个问题很不屑,认为高手不会犯这些错误。但我们可以想下有没有办法设计一个好的接口让菜鸟也写出不会出错的代码呢。假设这个库函数的接口是这样的呢:
函数简介 原型:extern flag realloc(void **ppMem_address, unsigned int newsize); 语法:返回值 =(数据类型*)realloc(要改变内存大小的指针名,新的大小)。 返回值:如果重新分配成功则返回指True(ppMem_address保存新分配空间地址),否则返回False(ppMem_address保存老空间地址)。
相信任何一个使用这个接口的人都会写出下面的代码:
if( True == realloc( &pBuf, size)) { TODO... }else
{
TODO...
}
为什么有人会犯pBuf = (byte*)realloc( pBuf, size);这种错误?因为他只关注了realloc返回值是一个地址,没有关注该返回值还有错误识别的功能,换句话来说这个库函数的返回值不具备单一职责,导致了可能的错误使用。如果使用改进后的接口,因为返回值只有一个判断分配成功与否的功能,相信没有人还会用错。
我们再仔细看看我们新的接口,总觉得似乎有什么地方还是不对,看到void **ppMem_address可能要想一下明白,这个参数既是入参又是出参,它承担了原始地址的输入和新地址的输出,这不又违反了单一职责吗?好吧我们再改进一下:
函数简介 原型:extern flag realloc(void *pIn_Mem_address,void **ppOut_Mem_address, unsigned int newsize); 语法:返回值 =(数据类型*)realloc(要改变内存大小的指针名,新的内存指针名,新的大小)。 返回值:如果重新分配成功则返回指True,否则返回False。
现在这个接口就算一个初次看到的人也应该大概知道什么意思,相信也不会写出什么带BUG的代码,因为函数的参数、返回值都具有单一的功能,通过返回值来判断分配成功与否,通过出参来获取地址。一切看起来都很清晰。
在C库中还有很多类似的函数,如果当初的设计人员能多考虑单一职责,也许现在的系统中就会少了很多隐藏的BUG,接口永远是给别人使用的,一定要把使用者当成傻瓜,也许才能设计出好的接口。