我们有一些课程:
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::execute
。这样做的好处是,如果我们想要附加行为,它就可用了。缺点是人们可能会无意中得到附加行为。 HttpClient::execute
并调用新的execute_append
(同样的界面背后的不同行为)。execute
更改为仅接受HttpRequest并按值返回HttpResponse。因为在execute
中始终创建了一个新的HttpResponse,所以追加不是问题。在执行HTTP时,NRVO之后的任何性能损失都会因毫秒到秒的延迟而相形见绌。真正的缺点是我们有几个单元测试,其中execute
是通过googlemock嘲笑的。如果我们更改签名,我们将不得不更改所有模拟,并且每个进行SetArgReferee的期望都必须更改为返回。有什么建议吗?
答案 0 :(得分:4)
HttpClient::execute
的工作似乎是发送HTTP
请求,并返回回复。
如果是这样,那么将HttpResponse
对象作为参数传递给它是没有意义的。
execute()
应该只使用req
参数,然后返回HttpResponse
。
现代C ++ 11编译器将优化大部分返回非POD对象的开销,这要归功于移动语义,为您留下干净,明确的代码,没有潜在的副作用。