当只有少量内容需要更改时,如何避免复制粘贴代码块?

时间:2011-09-06 06:23:45

标签: c++

我很难说出来,但我说有一些功能:

a = a + b;
printf("HI THERE");
b = a + c;

(假装这是30行而不是3行)。现在说我想稍后再做同样的事情,除了打印“HI THERE”我打印“HO THERE”。我目前所做的是复制粘贴此函数的所有行只是为了将HI THERE更改为HO THERE。对于大型代码块,这不是很优雅。

我知道一个解决方案可能就像:

adder_function(1);
adder_function(2);

void adder_fuction(int input) {
   a = a + b;
   printer_function(input);
   b = a + c;
}

void printer_function(int input) {
   if (input == 1) printf("HI THERE");
   if (input == 2) printf("HO THERE");
}

但对于更复杂的代码块,这似乎也不够优雅。有什么想法可以提供更好的解决方案吗?

编辑:只是为了展示我正在做什么,这里是有问题的代码(你可以看到除了.input和.output以及printf语句之外几乎没有任何变化):

found=line.find("INPUTS");
if (found == 0) {
    inputfound = true;
    found = line.find_first_of(":");

    if (found == string::npos) { 
        printf("BAD NETLIST INPUT DECLARATION\n\r"); 
        exit(1);
    }

    found = line.find_first_not_of("\n\t ",found+1);

    if (found == string::npos) {
        printf("BAD NETLIST INPUT DECLARATION\n\r");
        exit(1);
    }
    else {
        temp_node_name += line[found];
        for (i = found+1; i < line.size(); i++) {
            if ( isalnum(line[i]) || isspace(line[i]) ) {
                if ( isalnum(line[i]) )
                    temp_node_name += line[i];
                if ( isspace(line[i]) || i == line.size() - 1 ) {
                    if (!temp_node_name.empty()) {
                        if (determine_uniqueness(temp_node_name)) {
                            nodes.push_back(dummy_node);
                            nodes.at(id_counter).name_in_netlist = temp_node_name;
                            nodes.at(id_counter).input = true;
                            temp_node_name.erase();
                            id_counter++;
                        }
                    }
                }
            }
            else {
                printf("BAD NETLIST INPUT DECLARATION\n\r");
                exit(1);
            }
        }
    }
    printf("NETLIST INPUT DECLARATION OK\n\r");
    continue;
}

单独代码块被复制

found=line.find("OUTPUTS");
if (found == 0){
    outputfound = true;
    found = line.find_first_of(":");

    if (found == string::npos) {
        printf("BAD NETLIST OUTPUT DECLARATION\n\r"); 
        exit(1);
    }

    found = line.find_first_not_of("\n\t ",found+1);
    if (found == string::npos) {
        printf("BAD NETLIST OUTPUT DECLARATION\n\r");
        exit(1);
    }
    else {
        temp_node_name += line[found];
        for (i = found+1; i < line.size(); i++) {
            if ( isalnum(line[i]) || isspace(line[i]) ) {
                if ( isalnum(line[i]) )
                    temp_node_name += line[i];
                if ( isspace(line[i]) || i == line.size() - 1 ) {
                    if (!temp_node_name.empty()) {
                        if (determine_uniqueness(temp_node_name)) {
                            nodes.push_back(dummy_node);
                            nodes.at(id_counter).name_in_netlist = temp_node_name;
                            **nodes.at(id_counter).output = true;**
                            temp_node_name.erase();
                            id_counter++;
                        }
                    }
                }
            }
            else {
                printf("BAD NETLIST OUTPUT DECLARATION\n\r");
                exit(1);
            }
        }
    }
    printf("NETLIST OUTPUT DECLARATION OK\n\r");
    continue;
}

9 个答案:

答案 0 :(得分:2)

制作函数当然是重用代码的好方法。如果在多个位置重复使用相同的代码,则可以使用它来创建一个函数。如果您发现有很多函数在做类似的事情,也许您可​​以将它们组合成一个具有一些额外参数的函数。在您给出的示例中,您可以执行以下操作:

void adder_function(const char * message) {
    a = a + b;
    printf("%s", message);
    b = a + c;
}

int main() {
    adder_function("HI THERE");
    adder_function("HO THERE");
}

我建议您尽量避免在源代码中添加幻数(例如1和2)。如上所述,最好使用#defines或完全避免这种情况。

答案 1 :(得分:2)

遵循单一责任原则,您应该将您的代码分解为较小的部分。在您的示例中,您将UI代码(printf)与计算逻辑混合在一起。避免这样做。

由于您使用普通C代码(无C ++)进行讨论,因此很难为您提供使用常见OOP模式的更灵活设计的示例。但对于您的问题,一个非常简单的第一个解决方案是从计算中分离输出内容:

void ShowMessage(const char* msg);
int Calculate(int a, int b);

int DoTheHiThereCalculation()
{
  int c = Calculate(1, 2);
  ShowMessage("Hi THERE");
  return c;
}

答案 2 :(得分:1)

由于这里提出的各种简单解决方案似乎没有用,我会提出一把大锤(警告:谨慎使用

OO语言中有两种设计模式可以处理模式重复:

在您的情况下,我建议您以依赖注入的方式尝试Strategy模式。

struct Strategy {
  virtual void setFoo(Foo& foo, int i) = 0;
  virtual void setBar(Bar& bar, int i) = 0;
};

struct A: Strategy {
  virtual void setFoo(Foo& foo, int i) { foo.a = i; }
  virtual void setBar(Bar& bar, int i) { bar.a = i; }
};

struct B: Strategy {
  virtual void setFoo(Foo& foo, int i) { foo.b = i; }
  virtual void setBar(Bar& bar, int i) { bar.b = i; }
};

void doSomething(Foo& foo, Bar& bar, Strategy& strategy) {
  for (size_t i = 0; i < 10; ++i) {
    //
    strategy.setFoo(foo, i);
    //
    for (size_t j = 0; j < 10; ++j) {
      //
      strategy.setBar(bar, j);
      //
    }
  }
}

您现在可以使用doSomethingA策略调用B。例如:

int main(int argc, char* argv[]) {
  if (argc == 1) { std::cerr << "usage: %prog STRAT\n"; return 1; }

  Foo foo;
  Bar bar;

  char const strat = argv[1][0];
  switch(strat) {
  case 'a': case 'A': {
    A a;
    doSomething(foo, bar, a);
    return 0;
  }
  case 'b': case 'B': {
    B b;
    doSomething(foo, bar, b);
    return 0;
  }
  default:
    std::cerr << "Unknown Strategy: " << strat << ", pick A or B\n";
    return 2;
  }
}

答案 3 :(得分:1)

我迟到了,所以我只会解决你的更新代码。

首先,我看到您将错误消息打印到stdout(通过printf),然后调用exit。为什么?您应该将错误(可能是调试)消息打印到stderrfprintf(stderr, ...)perror,但如果库例程失败并设置perrorerrno会更好})。此外,由于这是C ++(不是普通的C),您可能希望使用iostream而不是*printf。它更多是C ++ - ish(尽管,主要是C程序员,我更喜欢*printf)。

由于两个代码块之间的唯一区别是第一个包含INPUT而第二个包含OUTPUT的字符串,我建议将所有这些内容填充到一个函数中,以及其他必要的参数(无论line可能是其中之一),采用名为bool is_input的参数。第一次调用时,is_input应为true,第二次应为false

然后,在两个代码块中,您可以将printf行更改为:

fprintf(stderr, "BAD NETLIST %s DECLARATION\n", is_input ? "INPUT" : "OUTPUT");

fprintf(stderr, "NETLIST %s DECLARATION OK\n", is_input ? "INPUT" : "OUTPUT");

然后为成员修改写:

(is_input ? nodes[id_counter].input : nodes[id_counter]) = true

(注意,没有必要连续两次使用.at具有相同的索引 - 如果它抛出异常一次,它将不会到达第二个调用,如果它没有抛出第一个时间它不会抛出第二个。它可能不会是一个巨大的加速,但这是重要的想法?)

最后,如果您需要设置外部变量inputfoundoutputfound,请在您的函数中添加bool &found参数,并将其设置为true功能。第一次调用它时,传递inputfound,第二次传递outputfound

现在你的两个代码块是相同的,可以放在一个函数中,并调用两次(一次使用"INPUT",一次使用"OUTPUT")。容易。

将来,每当您发现自己编写的代码块与另一个代码块非常相似时,请停止重写(或复制并粘贴)。将代码块全部复制到一个新函数中,并通过调用新函数替换原始块。现在,您可以根据需要重复使用该代码块。

答案 4 :(得分:0)

const char* STR_TABLE[N] =
{
  "HI THERE",
  "HO THERE",
  ...
};


printf(STR_TABLE[n]);

答案 5 :(得分:0)

根据主题入门评论

  

。如果我想做一些像node_struct.XXXXX = 2; XXXXXX可以是一个任意字符串,我该怎么做呢?

就你使用c / c ++而言,你可以使用宏。 (看here

#define NODE_MEMBER(x) node_struct.##x
......
NODE_MEMBER(a)
NODE_MEMBER(b)

答案 6 :(得分:0)

尝试将(在2个函数中)更改的内容作为参数传递给函数(这是唯一接受这些不同参数的函数)。这样,你可以在一个函数中完成这两件事。

答案 7 :(得分:0)

最简单的情况:如果只是改变中间操作的参数,只需将它们作为参数传递给函数(普通C就足够了):

void adder_fuction(const char *text) {
    a = a + b;
    puts(text);
    b = a + c;
}
int main() {
    adder_function("HI THERE");
    adder_function("HO THERE");
}

现在如果不是,你会想传递一个功能。有很多方法。第一种是传递函数指针(普通C仍然足够):

void adder_function(void (*callback)()) {
    a = a + b;
    callback();
    b = a + c;
}
void callback1() {
    puts("HI THERE");
}
void callback2() {
    puts("HO THERE");
}
int main() {
    adder_function(&callback1);
    adder_function(&callback2);
}

您当然可以使用参数和返回值进行回调。 E.g:

void adder_function(int (*callback)(const char *)) {
    a = a + b;
    c = c + callback("HI THERE");
    b = a + c;
}
int callback1(const char *) ...

现在,普通C的最后一个选项是使用预处理器。它应该被认为是一个丑陋的黑客,但是如果你在普通的C中做一些非常通用的东西它确实有它的位置:

#define define_adder_function(name, code) \
     name() { \
         a = a + b; \
         code; \
         b = a + c; \
     }

由于C宏不卫生,它允许以下内容:

define_adder_function(adder_function1, c = 22) // NO semicolon here. Using preprocessor does have it's quirks
int main() {
    adder_function1();
}

现在C ++允许一些额外的选项:

struct Callback {
    virtual void operator()(const char *) = 0;
};
void adder_function(Callback &cb) {
    a = a + b;
    cb("foo");
    b = a + c;
}
int main() {
    struct Cb : Callback {
        void operator()(const char *arg) { printf("Foo %s\n", arg); }
    } cb;
    adder_function(cb);
}

如果你有C ++ 11 std::function,可以是编译器中的C ++ 0x / C ++ 11支持,C ++ TR1(作为std::tr1::function)或者来自Boost({{1}你可以避免手动声明虚拟基类。

boost::function

避免虚拟基类的其他方法,这次也是虚拟调度(代码是编译两次的代码)是(适用于C ++ 98):

#include <functional>
void adder_function(std::function<void (const char *)> cb) {
    a = a + b;
    cb("foo");
    b = a + c;
}
void callback1(const char *arg) { puts(arg); }
int main() {
    struct { // You can have local structures, even with methods, but not local functions
        void operator()(const char *arg) { printf("Foo %s\n", arg); }
    } callback2;
    adder_function(&callback1); // std::function should accept either function or functor
    adder_function(callback2);
}

最后如果你在编译器中有C ++ 11支持(无法在库中执行此操作,这是新语法),您可以将最后两个与新的lambda语法结合使用:

template <typename Callback>
void adder_function(Callback cb) {

    a = a + b;
    cb("foo");
    b = a + c;
}
void callback1(const char *arg) { puts(arg); }
int main() {
    struct Callback2 { // Anonymous types can't be used in templates before C++11.
        void operator()(const char *arg) { printf("Foo %s\n", arg); }
    } callback2;
    adder_function(&callback1); // template also accepts either function pointer or functor
    adder_function(callback2);
}

答案 8 :(得分:0)

如果你不介意,我会重构它以打破责任。我将解析与程序流分开,该程序流决定在解析输入时要做什么。在封闭函数中,您将只调用帮助程序,并根据返回(或异常)确定要显示的错误,是否遇到错误,或者要设置的变量。这将从解析网表中删除重复(解析对于输入和输出完全相同,唯一的变化是要显示的错误消息以及是否设置输入/输出标志。标志的设置可以在外部完成,如果你的解析器返回一系列名称,那么外部代码可以构造节点并根据阶段设置标志。你的过程。