有时,在某些情况下,简单代码块的重复是不可避免的。为了说明,使用此示例代码:
注意:此代码仅用于说明目的,实际代码更大更复杂。它也可能包含错误,但这个问题的重点不是那个。
switch(cmd) {
case CMD_BLOCK_READ:
if(current_user != key) {
ERROR("Access violation - invalid key!");
res = CR_ACCESS_DENIED;
break;
}
if(current_state < STATE_BUSY) {
WARN("Command %s is not allowed in this state!", cmd_name[cmd]);
res = CR_NOT_PERMITTED;
break;
}
if(ioctl(fd, HPI_CTL_BR) != 0) {
WARN("Handshake failed (%s). Aborted!", strerror(errno));
res = CR_TIME_OUT;
goto post_resp;
}
if(block_read(id) != 0) {
ERROR("Failed to read %d block (%s)! Aborted!", id, strerror(errno));
res = CR_FAIL;
goto send_nop;
}
res = CR_SUCCESS;
break;
case CMD_BLOCK_WRITE:
if(current_user != key) {
ERROR("Access violation - invalid key!");
res = CR_ACCESS_DENIED;
break;
}
if(current_state < STATE_BUSY) {
WARN("Command %s is not allowed in this state!", cmd_name[cmd]);
res = CR_NOT_PERMITTED;
break;
}
if(ioctl(fd, HPI_CTL_BR) != 0) {
WARN("Handshake failed (%s). Aborted!", strerror(errno));
res = CR_TIME_OUT;
goto post_resp;
}
if(block_write(id) != 0) {
ERROR("Failed to write %d block - %s. Command aborted!", id, strerror(errno));
res = CR_FAIL;
goto send_nop;
}
res = CR_SUCCESS;
break;
case CMD_REQ_START:
if(current_state < STATE_READY) {
WARN("Command %s is not allowed in this state!", cmd_name[cmd]);
res = CR_NOT_PERMITTED;
break;
}
state = STATE_BUSY;
if(ioctl(fd, HPI_CTL_BR) != 0) {
WARN("Handshake failed (%s). Aborted!", strerror(errno));
res = CR_TIME_OUT;
goto send_nop;
}
if(block_read(id) != 0) {
ERROR("Failed to read %d block (%s)! Aborted!", id, strerror(errno));
res = CR_FAIL;
goto post_resp;
}
res = CR_SUCCESS;
break;
}
/* The remaining 28 or so similar commands */
}
如您所见,由于细微差别和广泛使用break/goto语句,无法使用函数或内联。我通常做的是定义一些宏:
/* NOTE: DO NOT USE these macros outside of Big Switch */
#define CHECK_KEY(_key) \
if(current_user != (_key)) \
{ \
ERROR("Access violation!"); \
res = CR_ACCESS_DENIED; \
break; \
}
#define CHECK_STATE(_state) \
if(current_state < _state) \
{ \
WARN("Command %s is not allowed in this state!", cmd_name[cmd]); \
res = CR_NOT_PERMITTED; \
break; \
}
#define HANDSHAKE(_fail) \
if(ioctl(fd, CTL_BR) != 0) \
{ \
WARN("Handshake failed (%s). Aborted!", strerror(errno)); \
res = CR_TIME_OUT; \
goto _fail; \
}
#define BLOCK_READ(_id, _fail) \
if(block_read((int)(_id))!= 0) \
{ \
ERROR("Failed to read %d block (%s)! Aborted!", (int)_id, strerror(errno)); \
res = CR_FAIL; \
goto _fail; \
}
#define BLOCK_WRITE(_id, _fail) \
if(block_write((int)(_id)) != 0) \
{ \
ERROR("Failed to write %d block - %s. Aborted!", (int)_id, strerror(errno)); \
res = CR_FAIL; \
goto _fail; \
}
..并使用它们编写相同的代码。代码变得更小并且(可以说)更具可读性:
switch(cmd)
{
case CMD_BLOCK_READ:
CHECK_KEY(key);
CHECK_STATE(STATE_BUSY);
HANDSHAKE(post_resp);
BLOCK_READ(id, send_nop);
res = CR_SUCCESS;
break;
case CMD_BLOCK_WRITE:
CHECK_KEY(key);
CHECK_STATE(STATE_BUSY);
HANDSHAKE(post_resp);
BLOCK_WRITE(id, send_nop);
res = CR_SUCCESS;
break;
case CMD_REQ_START:
{
CHECK_STATE(STATE_READY);
state = STATE_BUSY;
HANDSHAKE(send_nop);
BLOCK_READ(id, post_resp);
res = CR_SUCCESS;
break;
}
/* The remaining 28 or so similar commands */
<..>
代码看起来更像某种脚本语言,而不是好的旧C语言,而且非常丑陋,但为了可读性,我愿意牺牲它。
问题是你如何应对类似的情况?有哪些更优雅的解决方案和最佳实践?
PS我承认,在一般情况下,宏和goto语句是糟糕设计的标志,所以没有必要对它们有多邪恶或我的编程风格有多糟糕而大发雷霆。