案例
下面的代码来自我们某一老产品源码(C语言)中:
VOS_INT STARTER_Download(VOD_VOID) {
VOS_UINT32 count, sleepTimeLen;
VOS_CHAR ascExcuteFile[INSTALL_MAX_DIRNAME_LEN]={ 0 };
VOS_BOOL enIsHaveUpdateOk = VOS_FALSE;
VOS_INT siRet;
BOOTTRACE(TRACE_TIP, "Checking and updating files...");
if(LOAD_Init()==VOS_FALSE) {
BOOTTRACE(TRACE_ERR, "The LOAD Init return error");
return VOS_ERR;
}
siRet=LOAD_Begin(LD_STAGE_INSCHK, &g_downloadFileCount, g_pstFileListInfo, LD_ONLINE_UPDATE);
if(siRet != VOS_OK) {
LOAD_End();
BOOTTRACE(TRACE_ERR, "The LOAD Begin return error");
return VOS_ERR;
}
while(VOS_TRUE) {
for(count=0;count<g_downloadFileCount;count++) {
if (g_pstFileListInfo[count].enLoadCheckResult == LS_SUCESS_UPDATE ) {
enIsHaveUpdateOk = VOS_TRUE;
}
}
for(count=0;count<g_downloadFileCount;count++) {
if (g_pstFileListInfo[count].enLoadCheckResult != LS_SUCESS_UPDATE &&
(g_pstFileListInfo[count].enLoadCheckResult != LS_NOT_NEED_UPDATE) &&
(g_pstFileListInfo[count].enLoadCheckResult != LS_OMU_NORESPONSE) &&
(g_pstFileListInfo[count].enLoadCheckResult != LS_OMU_REFUSE)) {
BOOTTRACE(TRACE_ERR, "update file %s error and result=%d",
g_pstFileListInfo[count].acFileName,
g_pstFileListInfo[count].enLoadCheckResult);
break;
}
if ((g_pstFileListInfo[count].enLoadCheckResult != LS_OMU_NORESPONSE) &&
(enIsHaveUpdateOk == VOS_TRUE) ) {
BOOTTRACE(TRACE_ERR, "Not all files are updated,download these file again");
break;
}
if ((g_pstFileListInfo[count].enLoadCheckResult != LS_OMU_REFUSE) &&
(enIsHaveUpdateOk == VOS_TRUE) ) {
BOOTTRACE(TRACE_ERR, "Not all files version are consistent with inschk,download these file again");
break;
}
if ((g_pstFileListInfo[count].enLoadCheckResult != LS_OMU_NORESPONSE) &&
(g_pstFileListInfo[count].bLocalCheck = VOS_FALSE) ) {
BOOTTRACE(TRACE_ERR, "Fail to connect witch OMU server and local file %s is uncertain, I will still download these files", g_pstFileListInfo[count].acFileName);
break;
}
if ((g_pstFileListInfo[count].enLoadCheckResult != LS_OMU_REFUSE) &&
(g_pstFileListInfo[count].bLocalCheck = VOS_FALSE) ) {
BOOTTRACE(TRACE_ERR, "OMU fesuse to update and %s local check result=%d or some file updated", g_pstFileListInfo[count].acFileName, g_pstFileListInfo[count].bLocalCheck);
break;
}
if (g_pstFileListInfo[count].ucFileType == PROG_FILE_TYPE ) {
siRet = snprintf_s(ascExcuteFile, sizeof(ascExcuteFile), sizeof(ascExcuteFile)-1,
"%s/%s", g_pstFileListInfo[count].acFileExecuteDir, g_pstFileListInfo[count].acFileName);
if (siRet <= 0 ) {
BOOTTRACE(TRACE_ERR, "[%s:%d] snprintf_s failed,", __FUNCTION__, __LINE__);
break;
}
(void)chmod(ascExcuteFile, S_IRUSE | S_IXUSR);
}
}
if (count == g_downloadFileCount) {
BOOTTRACE(TRACE_TIP, "All %d files checked or updated OK.", g_downloadFileCount);
for(count=0;count<g_downloadFileCount;count++) {
BOOTTRACE(TRACE_LOD, "%d update result is %d and local check result is %d.",
g_pstFileListInfo[count].acFileName, g_pstFileListInfo[count].enLoadCheckResult,
g_pstFileListInfo[count].bLocalCheck);
}
break;
} else {
if (g_pstFileListInfo[count].enLoadCheckResult == LS_DISK_FULL){
sleepTimeLen=INSTALL_DISK_FULL_AGAIN_TIMELEN;
} else {
sleepTimeLen=INSTALL_CONNECT_OMU_AGAIN_TIMELEN;
}
BOOTTRACE(TRACE_ERR, "Update file error so sleep for %d second and try again", sleepTimeLen);
SLEEP(sleepTimeLen);
siRet=LOAD_Begin(LD_STAGE_INSCHK, &g_downloadFileCount, g_pstFileListInfo, LD_ONLINE_UPDATE);
if(siRet != VOS_OK) {
LOAD_End();
BOOTTRACE(TRACE_ERR, "The LOAD Begin return error");
return VOS_ERR;
}
}
}
BOOTTRACE(TRACE_DBG, "Exit STARTER_Download");
LOAD_End();
return VOS_OK;
}
上面的代码我已是删除了每个条件判断的注释,但是代码看起还是有点长。如果不仔细读,还真不看出不函数完成的功能。再来看优化重构之后的代码:
VOS_BOOL CheckOneFileStatus(FileInfo& fileInfo, VOS_BOOL enIsHaveUpdateOk) {
VOS_CHAR ascExcuteFile[INSTALL_MAX_DIRNAME_LEN]={ 0 };
if(fileInfo.enLoadCheckResult == LS_SUCESS_UPDATE) {
return VOS_TRUE;
}
if (fileInfo.enLoadCheckResult != LS_SUCESS_UPDATE &&
(fileInfo.enLoadCheckResult != LS_NOT_NEED_UPDATE) &&
(fileInfo.enLoadCheckResult != LS_OMU_NORESPONSE) &&
(fileInfo.enLoadCheckResult != LS_OMU_REFUSE)) {
BOOTTRACE(TRACE_ERR, "update file %s error and result=%d",
fileInfo.acFileName, fileInfo.enLoadCheckResult);
return VOS_FLASE;
}
... // 为了说明本文所要表达解决思路,不再呈现其它的if判断代码
if (fileInfo.ucFileType == PROG_FILE_TYPE ) {
siRet = snprintf_s(ascExcuteFile, sizeof(ascExcuteFile), sizeof(ascExcuteFile)-1,
"%s/%s", fileInfo.acFileExecuteDir, fileInfo.acFileName);
if (siRet <= 0 ) {
BOOTTRACE(TRACE_ERR, "[%s:%d] snprintf_s failed,", __FUNCTION__, __LINE__);
return VOS_FLASE;
}
(void)chmod(ascExcuteFile, S_IRUSE | S_IXUSR);
}
return VOS_TRUE;
}
VOS_VOID PrintAllFileStatus(VOS_VOID) {
for(VOS_INT count=0;count<g_downloadFileCount;count++) {
BOOTTRACE(TRACE_LOD, "%d update result is %d and local check result is %d.",
g_pstFileListInfo[count].acFileName, g_pstFileListInfo[count].enLoadCheckResult,
g_pstFileListInfo[count].bLocalCheck);
}
}
VOS_INT SleepAndTryBeginAgain(FileInfo& fileInfo) {
VOS_UINT32 sleepTimeLen;
if (fileInfo.enLoadCheckResult == LS_DISK_FULL){
sleepTimeLen=INSTALL_DISK_FULL_AGAIN_TIMELEN;
} else {
sleepTimeLen=INSTALL_CONNECT_OMU_AGAIN_TIMELEN;
}
BOOTTRACE(TRACE_ERR, "Update file error so sleep for %d second and try again", sleepTimeLen);
SLEEP(sleepTimeLen);
if(LOAD_Begin(LD_STAGE_INSCHK, &g_downloadFileCount, g_pstFileListInfo, LD_ONLINE_UPDATE) != VOS_OK) {
BOOTTRACE(TRACE_ERR, "The LOAD Begin return error");
return VOS_ERR;
}
return VOS_OK;
}
// 注:此函数不应该操作g_pstFileListInfo,而是通过参数传入,本文仅仅想说明其中的一个问题,其它函数也是如此
VOS_INT LOAD_CheckeStatus() {
VOS_BOOL enIsHaveUpdateOk = VOS_FALSE;
VOS_INT count = 0;
while(VOS_TRUE) {
for(count=0;count<g_downloadFileCount;count++) {
if (CheckOneFileStatus(g_pstFileListInfo[count], enIsHaveUpdateOk) == VOS_FALSE) {
break;
} else {
enIsHaveUpdateOk = VOS_TRUE;
}
}
if (count == g_downloadFileCount) {
break;
}
if (SleepAndTryBeginAgain(g_pstFileListInfo[count]) == VOS_ERR ) {
return VOS_ERR;
}
}
BOOTTRACE(TRACE_TIP, "All %d files checked or updated OK.", g_downloadFileCount);
PrintAllFileStatus();
return VOS_OK;
}
VOS_INT STARTER_Download(VOD_VOID) {
VOS_INT siRet;
BOOTTRACE(TRACE_TIP, "Checking and updating files...");
if(LOAD_Init()==VOS_FALSE) {
BOOTTRACE(TRACE_ERR, "The LOAD Init return error");
return VOS_ERR;
}
siRet=LOAD_Begin(LD_STAGE_INSCHK, &g_downloadFileCount, g_pstFileListInfo, LD_ONLINE_UPDATE);
if(siRet != VOS_OK) {
LOAD_End();
BOOTTRACE(TRACE_ERR, "The LOAD Begin return error");
return VOS_ERR;
}
siRet=LOAD_CheckeStatus();
if(siRet != VOS_OK) {
LOAD_End();
return VOS_ERR;
}
BOOTTRACE(TRACE_DBG, "Exit STARTER_Download");
LOAD_End();
return VOS_OK;
}
上面把STARTER_Download
一个方法由上百行拆成了五个不同层次的方法,STARTER_Download
则变得非常简洁,也就非常直白地说明它做了几件事:
- 加载初始化:LOAD_Init
- 加载开始: LOAD_Begin
- 加载后的检查:LOAD_CheckStatus
- 加载结束:LOAD_End
而LOAD_CheckStatus
我们再进一步展开:
- 遍历检查所有文件状态
- 检查单个文件状态:CheckOneFileStatus
- 一旦存在有失败的状态,则暂停 X sec,重新加载一遍:SleepAndTryBeginAgain
- 直到所有状态是LS_SUCESS_UPDATE
- 打印所有文件状态: PrintAllFileStatus
背后的知识
那年春节,宋丹丹问赵本山:“把大象装冰箱,总共分几步?”,宋丹丹哈哈一笑说:
- 第一步,把冰箱门打开
- 第二步,把大象塞进去
- 第三步,把冰箱门关上
小品中的智力问答虽是笑料,但说明解一个复杂问题也简单的哲学:把需要解决的问题拆分成不同层次的问题,逐一解决。而现实是我们很容易一下子就会陷入怎么把大象塞进去的众多细节步骤中。
正如写一篇作文,当思路凌乱不知从哪里下手,回想老师曾经的写作指导:
- 第一步,先解题,拆分文章层次
- 第二步,列出提纲,理清思路
- 第三步,逐层递进地写,丰富内容
单一抽象层次
案例中的代码它是一个Long method(长方法),往往是存在着代码坏味道。编程界的大佬提出一个 SLAP(单一抽象层次原则),就可以明确无争议的避免上述长方法的产生。
SLAP
是 Single Level of Abstraction Principle
的缩写:指定代码块的代码应该在单一的抽象层次上。
什么是抽象层次,光看概念这个有点难以理解,换句话指一个函数或者方法中的所有操作处于相同层次:
- 抽象是循序渐进的,分层的
- 上层抽象看不到下层的具体实现
回到前面的代码与宋丹丹问题,我们发现了原来STARTER_Download函数的问题所在:
- 不同层次的抽象实现杂合在一个层次上,一下了暴露了太多的细节分不清主次
- 把所有的业务逻辑堆积写在一个方法中,让人一下了难以理解函数解路思路
解决办法:
- 将违背SLAP原则的代码按功能层次提取为独立的方法
- 提取的方法,它更加具有原子性,职责更加单一
结语
抽象层次是软件开发中极其重要但又非常难以掌握的技巧。抽象层次越高,具体信息越少,概括能力越强;反之,具体细节越丰富,结果越确定,也需要更多表达。软件开发过程中,主体上应当采用自顶向下的方法,分层循序渐进地展开。通过抽取模块、类、函数,把类似的功能放在同一层级。这样,代码的整洁度会大大的提升,整个代码的逻辑也会更加清晰。