本文转载自微信公众号「程序喵大人」,作者程序喵大人。转载本文请联系程序喵大人公众号。
之前我曾经在知乎写过一篇回答,详细介绍了if-else的效率问题。
过多的if-else不仅导致程序运行效率低下,而且导致代码圈复杂度过高。如果大家有使用过静态代码分析工具,就会知道圈复杂度是衡量代码质量的一项重要的指标,圈复杂度越高,代码出现bug的可能性也越大。
我们可能刚开始写的代码很简洁,只有一个if-else分支,但由于需求的叠加和各种错误处理,我们有时候不得已要多加几个if-else,久而久之就发现,满屏的if-else,令你极其讨厌自己写的代码。
至于如何消灭if-else,可谓八仙过海各显神通,这里介绍几种常见的方法:
巧用表结构:一般如果某些条件可存储,可以考虑把条件存起来用于去掉if-else,例如:
- long long func() {
- const unsigned ARRAY_SIZE = 50000;
- int data[ARRAY_SIZE];
- const unsigned DATA_STRIDE = 256;
- for (unsigned c = 0; c < ARRAY_SIZE; ++c) data[c] = std::rand() % DATA_STRIDE;
- long long sum = 0;
- for (unsigned c = 0; c < ARRAY_SIZE; ++c) {
- if (data[c] >= 128) sum += data[c];
- }
- return sum;
- }
可以通过表结构去掉代码中的if分支
- long long func() {
- const unsigned ARRAY_SIZE = 50000;
- int data[ARRAY_SIZE];
- const unsigned DATA_STRIDE = 256;
- int lookup[DATA_STRIDE];
- for (unsigned c = 0; c < DATA_STRIDE; ++c) {
- lookup[c] = (c >= 128) ? c : 0;
- }
- for (unsigned c = 0; c < ARRAY_SIZE; ++c) data[c] = std::rand() % DATA_STRIDE;
- long long sum = 0;
- for (unsigned c = 0; c < ARRAY_SIZE; ++c) {
- sum += lookup[data[c]];
- }
- return sum;
- }
使用switch-case替换if-else:一般情况下switch-case比if-else效率高一些,而且逻辑也更清晰,例如:
- void func() {
- if (a == 1) {
- ...
- } else if (a == 2) {
- ...
- } else if (a == 3) {
- ...
- } else if (a == 4) {
- ...
- } else {
- ...
- }
- }
try-catch替换:if-else很多情况下都用于错误处理,如果我们使用try-catch处理错误,是不是就可以消灭if-else了呢,拿数值运算代码举例:
- class Number {
- public:
- friend Number operator+ (const Number& x, const Number& y);
- friend Number operator- (const Number& x, const Number& y);
- friend Number operator* (const Number& x, const Number& y);
- friend Number operator/ (const Number& x, const Number& y);
- // ...
- };
最简单的可以这样调用:
- void f(Number x, Number y) {
- // ...
- Number sum = x + y;
- Number diff = x - y;
- Number prod = x * y;
- Number quot = x / y;
- // ...
- }
但是如果需要处理错误,例如除0或者数值溢出等,函数得到的就是错误的结果,调用者需要做处理。
先看使用错误码的方式:
- class Number {
- public:
- enum ReturnCode {
- Success,
- Overflow,
- Underflow,
- DivideByZero
- };
- Number add(const Number& y, ReturnCode& rc) const;
- Number sub(const Number& y, ReturnCode& rc) const;
- Number mul(const Number& y, ReturnCode& rc) const;
- Number div(const Number& y, ReturnCode& rc) const;
- // ...
- };
- int f(Number x, Number y)
- {
- // ...
- Number::ReturnCode rc;
- Number sum = x.add(y, rc);
- if (rc == Number::Overflow) {
- // ...code that handles overflow...
- return -1;
- } else if (rc == Number::Underflow) {
- // ...code that handles underflow...
- return -1;
- } else if (rc == Number::DivideByZero) {
- // ...code that handles divide-by-zero...
- return -1;
- }
- Number diff = x.sub(y, rc);
- if (rc == Number::Overflow) {
- // ...code that handles overflow...
- return -1;
- } else if (rc == Number::Underflow) {
- // ...code that handles underflow...
- return -1;
- } else if (rc == Number::DivideByZero) {
- // ...code that handles divide-by-zero...
- return -1;
- }
- Number prod = x.mul(y, rc);
- if (rc == Number::Overflow) {
- // ...code that handles overflow...
- return -1;
- } else if (rc == Number::Underflow) {
- // ...code that handles underflow...
- return -1;
- } else if (rc == Number::DivideByZero) {
- // ...code that handles divide-by-zero...
- return -1;
- }
- Number quot = x.div(y, rc);
- if (rc == Number::Overflow) {
- // ...code that handles overflow...
- return -1;
- } else if (rc == Number::Underflow) {
- // ...code that handles underflow...
- return -1;
- } else if (rc == Number::DivideByZero) {
- // ...code that handles divide-by-zero...
- return -1;
- }
- // ...
- }
再看使用异常处理的方式:
- void f(Number x, Number y)
- {
- try {
- // ...
- Number sum = x + y;
- Number diff = x - y;
- Number prod = x * y;
- Number quot = x / y;
- // ...
- }
- catch (Number::Overflow& exception) {
- // ...code that handles overflow...
- }
- catch (Number::Underflow& exception) {
- // ...code that handles underflow...
- }
- catch (Number::DivideByZero& exception) {
- // ...code that handles divide-by-zero...
- }
如果有更多的运算,或者有更多的错误码,异常处理的优势会更明显。
提前return:对于某些错误处理可以考虑提前return,直接看代码:
- void func(A *a) {
- if (a) {
- ...
- } else {
- log_error(...);
- return;
- }
- }
适当情况下通过反转if条件就可以删除掉else分支。
合并分支表达式:有些情况下可以通过合并表达式来消除if-else,例如:
- void func() {
- if (a < 20) return;
- if (b > 30) return;
- if (c < 18) return;
- }
可以改为
- void func() {
- if (a < 20 || b > 30 || c < 18) return;
- }
策略模式:熟悉设计模式的同学可能都知道,一般代码中if-else过多,那就可以考虑使用策略模式啦,例如:
- enum class CalOperation {
- add,
- sub
- };
- int NoStragegy(CalOperation ope) {
- if (ope == CalOperation::add) {
- std::cout << "this is add operation" << std::endl;
- } else if (ope == CalOperation::sub) {
- std::cout << "this is sub operation" << std::endl;
- } // 如何将来需要增加乘法或者除法或者其它运算,还需要增加if-else
- return 0;
- }
这种if-else可以通过策略模式进行消除:
- #ifndef __CALCULATION__
- #define __CALCULATION__
- #include <iostream>
- class Calculation {
- public:
- Calculation() {}
- virtual ~Calculation() {}
- virtual void operation() { std::cout << "base operation" << std::endl; }
- };
- #endif
- #ifndef __ADD__
- #define __ADD__
- #include "calculation.h"
- class Add : public Calculation {
- void operation() override { std::cout << "this is add operation" << std::endl; }
- };
- #endif
- #ifndef __SUB__
- #define __SUB__
- #include "calculation.h"
- class Sub : public Calculation {
- void operation() override { std::cout << "this is sub operation" << std::endl; }
- };
- #endif
- int Stragegy() {
- Calculation *cal = new Add();
- cal->operation();
- delete cal;
- Calculation *cal2 = new Sub(); // 这里将来都可以用工厂模式改掉,不会违反开放封闭原则
- cal2->operation();
- delete cal2;
- return 0;
- }
将来如果有乘法除法和其它运算规则,只需要再加一个继承基类的子类即可。方便扩展,且遵循设计原则。
职责链模式:职责链模式尽管不能消灭if-else,但它可以用于改良if-else,使其更灵活,例如:
- #include <iostream>
- using std::cout;
- void func(int num) {
- if (num >= 0 && num <= 10) {
- cout << "0-10 \n";
- } else if (num > 10 && num <= 20) {
- cout << "10-20 \n";
- } else if (num > 20 && num <= 30) {
- cout << "20-30 \n";
- } else if (num > 30 && num <= 40) {
- cout << "30-40 \n";
- } else if (num > 40 && num <= 50) {
- cout << "40-50 \n";
- } else if (num > 50 && num <= 60) {
- cout << "50-60 \n";
- } else {
- cout << "not handle \n";
- }
- }
- int main() {
- func(25);
- func(43);
- return 0;
- }
可以考虑改为下面的形式:
- #include <iostream>
- using std::cout;
- struct Handle {
- virtual void process(int num) {}
- };
- struct Handle1 : public Handle {
- Handle1(Handle *processor) : processor_(processor) {}
- void process(int num) override {
- if (num >= 0 && num <= 10) {
- cout << "0-10 \n";
- } else {
- processor_->process(num);
- }
- }
- Handle *processor_;
- };
- struct Handle2 : public Handle {
- Handle2(Handle *processor) : processor_(processor) {}
- void process(int num) override {
- if (num >= 10 && num <= 20) {
- cout << "10-20 \n";
- } else {
- processor_->process(num);
- }
- }
- Handle *processor_;
- };
- struct Handle3 : public Handle {
- Handle3(Handle *processor) : processor_(processor) {}
- void process(int num) override {
- if (num >= 20 && num <= 30) {
- cout << "20-30 \n";
- } else {
- cout << "not handle \n";
- }
- }
- Handle *processor_;
- };
- int main() {
- Handle *handle3 = new Handle3(nullptr);
- Handle *handle2 = new Handle2(handle3);
- Handle *handle1 = new Handle2(handle2);
- handle1->process(24);
- handle1->process(54);
- return 0;
- }
- 三目运算符:某些简单情况下可以使用三目运算符消灭if-else,例如:
- int func(int num) {
- if (num > 20) return 1;
- else return 0;
- }
三目运算符:某些简单情况下可以使用三目运算符消灭if-else,例如:
- int func(int num) {
- if (num > 20) return 1;
- else return 0;
- }
可以改为:
- int func(int num) {
- return num > 20 ? 1 : 0;
- }
这样是不是代码也更清晰了一些。
else-if消除:有时候有些人写的代码确实就是这样,例如:
- int func(int num) {
- int ret = 0;
- if (num == 1) {
- ret = 3;
- } else if (num == 2) {
- ret = 5;
- } else {
- ret = 6;
- }
- return ret;
- }
是不是可以考虑改为:
- int func(int num) {
- if (num == 1) return 3;
- if (num == 2) return 5;
- return 6;
- }