如何在没有大量重复检查代码的情况下处理函数中多个可能的早期 returns
How to handle multiple possible early returns from function without lots of duplicate checking code
鉴于我不想在此代码中使用异常,我该如何删除重复的:
if (rc != SQLITE_OK) {
return rc;
}
在下面这个函数中的大多数语句之后检查?
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
if (db_ == nullptr) {
return SQLITE_ERROR;
}
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
if(rc != SQLITE_OK) {
return rc;
}
// loop thru each parameter, calling bind for each one
rc = bind_fields(stmt, fields);
if(rc != SQLITE_OK) {
return rc;
}
// loop thru each where parameter, calling bind for each one
rc = bind_where(stmt, where);
if (rc != SQLITE_OK) {
return rc;
}
return step_and_finalise(stmt);
}
您可以使用 lambda 和 if-initializer
表达式来清理代码,但您仍然需要 return 错误代码:
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
if (db_ == nullptr) {
return SQLITE_ERROR;
}
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
const auto validate_sql = [](int rc) -> bool {
return rc != SQLITE_OK;
};
if(int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL); validate_sql(rc)) {
return rc;
}
// loop thru each parameter, calling bind for each one
if(int rc = bind_fields(stmt, fields); validate_sql(rc)) {
return rc;
}
// loop thru each where parameter, calling bind for each one
if(int rc = bind_where(stmt, where); validate_sql(rc)) {
return rc;
}
return step_and_finalise(stmt);
}
借助外部工作,可以编写 monadic 版本。
伪代码如下所示:
expected<void, RC> doStuff( arg1, arg2 ){
auto a = op1(arg1, arg2 );
return a->*op2(arg2)->*op3(arg1);
}
或
expected<void,RC> doStuff(arg1,arg2){
auto a = co_await op1(arg1);
return op2( co_await op3(arg2), co_await op4(arg1, arg2) );
}
但是函数外的样板远远超过函数内删除的样板。
最后,有人提议添加零成本例外。
如果您希望控制流不需要您键入控制流,那么这些和宏是关于您的选项的。
对于预期的情况,想法是预期的错误,当您对其执行 ->*
时,不会调用该操作,而只是通过错误状态。
这是一种没有异常的手动异常形式,但是没有办法自动化样板文件,嗯,在使用点或包装特定于操作集的代码中有更多样板文件。
协程有不可避免的分配开销(好吧,你的代码无法避免它;理论上编译器可以),以及一些最神秘的底层代码。同样,在这里我们试图伪造类似语法的异常而没有异常; co_await 出现错误时,将其传递给 return 值。
TL;DR 是“不,你无法摆脱那个控制流样板”。
首先,如果出现问题,您就是在泄漏 sqlite3_stmt
。您需要在 return
之前调用 sqlite3_finalize()
。
至于您的问题,您可以将重复的 if..return
包装在预处理器宏中,例如:
#define CHECK_RC(rc) \
if (rc != SQLITE_OK) \
{ \
sqlite3_finalize(stmt); \
return rc; \
}
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
if (db_ == nullptr) {
return SQLITE_ERROR;
}
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
CHECK_RC(rc);
// loop thru each parameter, calling bind for each one
rc = bind_fields(stmt, fields);
CHECK_RC(rc);
// loop thru each where parameter, calling bind for each one
rc = bind_where(stmt, where);
CHECK_RC(rc);
return step_and_finalise(stmt);
}
或者:
#define CHECK_RC(op) \
{ \
int rc = op; \
if (rc != SQLITE_OK) \
{ \
sqlite3_finalize(stmt); \
return rc; \
} \
}
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
if (db_ == nullptr) {
return SQLITE_ERROR;
}
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
CHECK_RC(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL));
// loop thru each parameter, calling bind for each one
CHECK_RC(bind_fields(stmt, fields));
// loop thru each where parameter, calling bind for each one
CHECK_RC(bind_where(stmt, where));
return step_and_finalise(stmt);
}
或者,您可以重新编写代码以不使用多个 return
,例如:
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
int rc;
if (db_ == nullptr) {
rc = SQLITE_ERROR;
}
else {
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
if (rc == SQLITE_OK) {
// loop thru each parameter, calling bind for each one
rc = bind_fields(stmt, fields);
if (rc == SQLITE_OK) {
// loop thru each where parameter, calling bind for each one
rc = bind_where(stmt, where);
if (rc == SQLITE_OK) {
rc = step_and_finalise(stmt);
stmt = NULL;
}
}
sqlite3_finalize(stmt);
}
}
return rc;
}
或者,你可以抛出异常,例如:
void check_rc(int rc)
{
if (rc != SQLITE_OK)
throw rc;
}
void check_ptr(void* ptr)
{
if (ptr == nullptr)
check_rc(SQLITE_ERROR);
}
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
try {
check_ptr(db_);
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
check_rc(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL));
try {
// loop thru each parameter, calling bind for each one
check_rc(bind_fields(stmt, fields));
// loop thru each where parameter, calling bind for each one
check_rc(bind_where(stmt, where));
return step_and_finalise(stmt);
}
catch (const int) {
sqlite3_finalize(stmt);
throw;
}
}
catch (const int errCode) {
return errCode;
}
}
在这样的线性操作序列中,我的处理方式与大多数人略有不同:
我不检查上一步是否成功,而是检查我是否具备下一步的先决条件。
int sqlite::update(
const std::string& table_name,
const std::vector<column_values>& fields,
const std::vector<column_values>& where
) {
sqlite3_stmt* stmt = NULL;
int rc = SQLITE_ERROR;
if (db_ != nullptr) {
const std::string sql = update_helper(table_name, fields, where);
rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
}
if (rc == SQLITE_OK) {
// loop thru each parameter, calling bind for each one
rc = bind_fields(stmt, fields);
}
if (rc == SQLITE_OK) {
// loop thru each where parameter, calling bind for each one
rc = bind_where(stmt, where);
}
if (rc == SQLITE_OK) {
rc = step_and_finalise(stmt);
}
return rc;
}
不可否认,这看起来与原始代码没有太大区别。当每个步骤的先决条件是前面的所有步骤都必须成功时,通常会出现这种情况。在其他示例中,检查看起来并不那么多余。
优点是这与早期 return 解决方案一样线性,没有早期 return 有时引起的混乱和复杂化。在成功的情况下,它不会进行不必要的比较。然而,它没有通常作为早期 return 的替代品出现的深度嵌套。
代价是一个错误将涉及一些冗余检查。但除非错误非常普遍,否则这是一个很小的成本。优化器可能会通过为您创建早期的 return 来消除它们。
这个版本的优点是没有方法外的影响
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
auto check = [](int rc) { if (rc != SQLITE_OK) throw rc; };
try
{
check(db_ == nullptr ? SQLITE_ERROR : SQLITE_OK);
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
check(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL));
// loop thru each parameter, calling bind for each one
check(bind_fields(stmt, fields));
// loop thru each where parameter, calling bind for each one
check(bind_where(stmt, where));
return step_and_finalise(stmt);
}
catch (const int rc)
{
return rc;
}
}
鉴于我不想在此代码中使用异常,我该如何删除重复的:
if (rc != SQLITE_OK) {
return rc;
}
在下面这个函数中的大多数语句之后检查?
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
if (db_ == nullptr) {
return SQLITE_ERROR;
}
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
if(rc != SQLITE_OK) {
return rc;
}
// loop thru each parameter, calling bind for each one
rc = bind_fields(stmt, fields);
if(rc != SQLITE_OK) {
return rc;
}
// loop thru each where parameter, calling bind for each one
rc = bind_where(stmt, where);
if (rc != SQLITE_OK) {
return rc;
}
return step_and_finalise(stmt);
}
您可以使用 lambda 和 if-initializer
表达式来清理代码,但您仍然需要 return 错误代码:
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
if (db_ == nullptr) {
return SQLITE_ERROR;
}
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
const auto validate_sql = [](int rc) -> bool {
return rc != SQLITE_OK;
};
if(int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL); validate_sql(rc)) {
return rc;
}
// loop thru each parameter, calling bind for each one
if(int rc = bind_fields(stmt, fields); validate_sql(rc)) {
return rc;
}
// loop thru each where parameter, calling bind for each one
if(int rc = bind_where(stmt, where); validate_sql(rc)) {
return rc;
}
return step_and_finalise(stmt);
}
借助外部工作,可以编写 monadic 版本。
伪代码如下所示:
expected<void, RC> doStuff( arg1, arg2 ){
auto a = op1(arg1, arg2 );
return a->*op2(arg2)->*op3(arg1);
}
或
expected<void,RC> doStuff(arg1,arg2){
auto a = co_await op1(arg1);
return op2( co_await op3(arg2), co_await op4(arg1, arg2) );
}
但是函数外的样板远远超过函数内删除的样板。
最后,有人提议添加零成本例外。
如果您希望控制流不需要您键入控制流,那么这些和宏是关于您的选项的。
对于预期的情况,想法是预期的错误,当您对其执行 ->*
时,不会调用该操作,而只是通过错误状态。
这是一种没有异常的手动异常形式,但是没有办法自动化样板文件,嗯,在使用点或包装特定于操作集的代码中有更多样板文件。
协程有不可避免的分配开销(好吧,你的代码无法避免它;理论上编译器可以),以及一些最神秘的底层代码。同样,在这里我们试图伪造类似语法的异常而没有异常; co_await 出现错误时,将其传递给 return 值。
TL;DR 是“不,你无法摆脱那个控制流样板”。
首先,如果出现问题,您就是在泄漏 sqlite3_stmt
。您需要在 return
之前调用 sqlite3_finalize()
。
至于您的问题,您可以将重复的 if..return
包装在预处理器宏中,例如:
#define CHECK_RC(rc) \
if (rc != SQLITE_OK) \
{ \
sqlite3_finalize(stmt); \
return rc; \
}
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
if (db_ == nullptr) {
return SQLITE_ERROR;
}
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
CHECK_RC(rc);
// loop thru each parameter, calling bind for each one
rc = bind_fields(stmt, fields);
CHECK_RC(rc);
// loop thru each where parameter, calling bind for each one
rc = bind_where(stmt, where);
CHECK_RC(rc);
return step_and_finalise(stmt);
}
或者:
#define CHECK_RC(op) \
{ \
int rc = op; \
if (rc != SQLITE_OK) \
{ \
sqlite3_finalize(stmt); \
return rc; \
} \
}
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
if (db_ == nullptr) {
return SQLITE_ERROR;
}
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
CHECK_RC(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL));
// loop thru each parameter, calling bind for each one
CHECK_RC(bind_fields(stmt, fields));
// loop thru each where parameter, calling bind for each one
CHECK_RC(bind_where(stmt, where));
return step_and_finalise(stmt);
}
或者,您可以重新编写代码以不使用多个 return
,例如:
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
int rc;
if (db_ == nullptr) {
rc = SQLITE_ERROR;
}
else {
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
if (rc == SQLITE_OK) {
// loop thru each parameter, calling bind for each one
rc = bind_fields(stmt, fields);
if (rc == SQLITE_OK) {
// loop thru each where parameter, calling bind for each one
rc = bind_where(stmt, where);
if (rc == SQLITE_OK) {
rc = step_and_finalise(stmt);
stmt = NULL;
}
}
sqlite3_finalize(stmt);
}
}
return rc;
}
或者,你可以抛出异常,例如:
void check_rc(int rc)
{
if (rc != SQLITE_OK)
throw rc;
}
void check_ptr(void* ptr)
{
if (ptr == nullptr)
check_rc(SQLITE_ERROR);
}
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
try {
check_ptr(db_);
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
check_rc(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL));
try {
// loop thru each parameter, calling bind for each one
check_rc(bind_fields(stmt, fields));
// loop thru each where parameter, calling bind for each one
check_rc(bind_where(stmt, where));
return step_and_finalise(stmt);
}
catch (const int) {
sqlite3_finalize(stmt);
throw;
}
}
catch (const int errCode) {
return errCode;
}
}
在这样的线性操作序列中,我的处理方式与大多数人略有不同:
我不检查上一步是否成功,而是检查我是否具备下一步的先决条件。
int sqlite::update(
const std::string& table_name,
const std::vector<column_values>& fields,
const std::vector<column_values>& where
) {
sqlite3_stmt* stmt = NULL;
int rc = SQLITE_ERROR;
if (db_ != nullptr) {
const std::string sql = update_helper(table_name, fields, where);
rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL);
}
if (rc == SQLITE_OK) {
// loop thru each parameter, calling bind for each one
rc = bind_fields(stmt, fields);
}
if (rc == SQLITE_OK) {
// loop thru each where parameter, calling bind for each one
rc = bind_where(stmt, where);
}
if (rc == SQLITE_OK) {
rc = step_and_finalise(stmt);
}
return rc;
}
不可否认,这看起来与原始代码没有太大区别。当每个步骤的先决条件是前面的所有步骤都必须成功时,通常会出现这种情况。在其他示例中,检查看起来并不那么多余。
优点是这与早期 return 解决方案一样线性,没有早期 return 有时引起的混乱和复杂化。在成功的情况下,它不会进行不必要的比较。然而,它没有通常作为早期 return 的替代品出现的深度嵌套。
代价是一个错误将涉及一些冗余检查。但除非错误非常普遍,否则这是一个很小的成本。优化器可能会通过为您创建早期的 return 来消除它们。
这个版本的优点是没有方法外的影响
int sqlite::update(const std::string& table_name, const std::vector<column_values>& fields, const std::vector<column_values>& where) {
auto check = [](int rc) { if (rc != SQLITE_OK) throw rc; };
try
{
check(db_ == nullptr ? SQLITE_ERROR : SQLITE_OK);
const std::string sql = update_helper(table_name, fields, where);
sqlite3_stmt* stmt = NULL;
check(sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, NULL));
// loop thru each parameter, calling bind for each one
check(bind_fields(stmt, fields));
// loop thru each where parameter, calling bind for each one
check(bind_where(stmt, where));
return step_and_finalise(stmt);
}
catch (const int rc)
{
return rc;
}
}