如何更改方法中的不需要的行为

时间:2016-05-25 21:41:29

标签: c++

我们有一些课程:

struct HttpRequest {
    std::string url;
};

struct HttpResponse {
    int          status_code;
    std::string  status_text;
    std::string  content;
};

struct HttpClient {
    void execute(HttpRequest const& req, HttpResponse& resp);
};

size_t
appender(void *contents, size_t size, size_t nmemb, void *userp)
{
    auto& ct = * reinterpret_cast<std::string*>(userp);
    size_t bytes = size * nmemb;

    ct.append(reinterpret_cast<char*>(contents), bytes);
    return bytes;
}

void HttpClient::execute(HttpRequest const& req, HttpResponse& resp)
{
    auto curl = curl_easy_init();

    curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, appender);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp.content);
    // header callback etc.

    CURLcode code = curl_easy_perform(curl);

    curl_easy_cleanup(curl);
    if (code != CURLE_OK) {
        throw std::runtime_error("curl fail");
    }

    return; // enjoy your HttpResponse
}

我们这样使用它们:

bool fatal(std::runtime_error const& rte) {
    return rte.what()[0] == 'F'; // if it starts with F, it's fatal
    // not the actual implementation :)
}

const int retries = 7;

int do_something(std::string const& url)
{
    HttpClient client;
    HttpRequest req{ url };

    for (int r = 0; r < retries; ++r) try {
        HttpResponse resp;

        client.execute(req, resp);
        //
        if (resp.status_code / 100 == 2) { // a 2xx success code
            return std::stoi(resp.content);
        }
        else { // maybe a 4xx client error or 5xx server error
            throw std::runtime_error("HTTP error");
        }
    }
    catch (std::runtime_error const& e) {
        if (fatal(e))
            throw;
        else
            continue; // retry
    }

    // ran out of retries
    throw std::runtime_error("out of retries");
}

一切都很好。 但是,有人实施了不同的模式:

int worker(HttpRequest const& req, HttpResponse& resp)
{
    HttpClient client;
    for (int r = 0; r < retries; ++r) try {
        client.execute(req, resp);
        //
        if (resp.status_code / 100 == 2) { // a 2xx success code
            return std::stoi(resp.content);
        }
        else { // maybe a 4xx client error or 5xx server error
            throw std::runtime_error("HTTP error");
        }
    }
    catch (std::runtime_error const& e) {
        if (fatal(e))
            throw;
        else
            continue; // retry
    }

    // ran out of retries
    throw std::runtime_error("out of retries");
}

int do_something_else(std::string const& url)
{
    HttpRequest req{ url };
    HttpResponse resp;
    return worker(req, resp);
}

现在我们发现我们在HttpClient::execute中有不受欢迎的行为:如果HttpResponse在输入时不为空(例如,我们得到503服务不可用,重试,并获得200 OK),{{1只是附加到现有内容。这不是我们想要的:我们已经对503响应采取了行动,我们不希望将200回复​​的内容附加到503回复的任何内容中。

我们浏览了我们的代码,发现了几个与execute类似且只有一个do_something的用法。我们从未需要(偶然)能力附加到现有的响应中。

问题是,我们如何解决这个问题,并确保我们不再遇到意外情况?我可以看到几个选项:

  • 保持原样;在do_something_else内手动清除HttpResponse;记录行为(不理想;人们并不总是阅读文档:)。
  • 更改do_something_else以清除输入时的HttpResponse。我不喜欢悄悄地改变同一界面背后的行为(加上我们需要改变单元测试)。
  • 向HttpClient添加一个包装器成员,清除HttpResponse,然后调用原始HttpClient::execute。这样做的好处是,如果我们想要附加行为,它就可用了。缺点是人们可能会无意中得到附加行为。
  • 与上述相同,但将现有的一个重命名为例如HttpClient::execute并调用新的execute_append(同样的界面背后的不同行为)。
  • execute更改为仅接受HttpRequest并按值返回HttpResponse。因为在execute中始终创建了一个新的HttpResponse,所以追加不是问题。在执行HTTP时,NRVO之后的任何性能损失都会因毫秒到秒的延迟而相形见绌。真正的缺点是我们有几个单元测试,其中execute是通过googlemock嘲笑的。如果我们更改签名,我们将不得不更改所有模拟,并且每个进行SetArgReferee的期望都必须更改为返回。

有什么建议吗?

1 个答案:

答案 0 :(得分:4)

HttpClient::execute的工作似乎是发送HTTP请求,并返回回复。

如果是这样,那么将HttpResponse对象作为参数传递给它是没有意义的。

execute()应该只使用req参数,然后返回HttpResponse

现代C ++ 11编译器将优化大部分返回非POD对象的开销,这要归功于移动语义,为您留下干净,明确的代码,没有潜在的副作用。