rust - 如何避免在许多点上存在少量差异的类似方法中的代码重复?
问题描述
我有一个非常数据驱动的程序,其中包含不同类型的实体,这些实体具有非常相似的结构,并且仅在特定位置有所不同。
例如,每个实体都有一个可以更改的名称。这里有两个示例方法来演示这些方法的相似之处:
pub fn rename_blueprint(
&mut self,
ctx: &mut Context,
db_handle: &Transaction,
blueprint_id: Uuid,
new_name: &str,
) -> Result<(), DataError> {
ctx.debug(format!(
"Renaming blueprint {} to {}",
blueprint_id, new_name
));
self.assert_blueprint_exists(ctx, db_handle, blueprint_id)?;
let mut stmt = db_handle
.prepare("UPDATE `blueprints` SET `name` = ? WHERE `id` == ?")
.on_err(|_| ctx.err("Unable to prepare update statement"))?;
let changed_rows = stmt
.execute(params![new_name.to_string(), blueprint_id])
.on_err(|_| ctx.err("Unable to update name in database"))?;
if changed_rows != 1 {
ctx.err(format!("Invalid amount of rows changed: {}", changed_rows));
return Err(DataError::InvalidChangeCount {
changes: changed_rows,
expected_changes: 1,
});
}
ctx.blueprint_renamed(blueprint_id, new_name);
Ok(())
}
pub fn rename_attribute(
&mut self,
ctx: &mut Context,
db_handle: &Transaction,
attribute_id: Uuid,
new_name: &str,
) -> Result<(), DataError> {
ctx.debug(format!(
"Renaming attribute {} to {}",
attribute_id, new_name
));
self.assert_attribute_exists(ctx, db_handle, attribute_id)?;
let mut stmt = db_handle
.prepare("UPDATE `attributes` SET `name` = ? WHERE `id` == ?")
.on_err(|_| ctx.err("Unable to prepare update statement"))?;
let changed_rows = stmt
.execute(params![new_name.to_string(), attribute_id])
.on_err(|_| ctx.err("Unable to update name in database"))?;
if changed_rows != 1 {
ctx.err(format!("Invalid amount of rows changed: {}", changed_rows));
return Err(DataError::InvalidChangeCount {
changes: changed_rows,
expected_changes: 1,
});
}
ctx.attribute_renamed(attribute_id, new_name);
Ok(())
}
对于 5-11 种实体类型,现在需要存在具有几乎相同代码的相同方法。我通常可以只用Blueprint
其他实体类型的名称替换,它会工作的。然而,这似乎是一个非常愚蠢的解决方案。
同样,编写一个接受所有相关字符串、方法等的辅助方法来调用它似乎同样愚蠢。
我不相信我什至可以通过传递一些“策略”或其他间接帮助器(EntityRenamer
或类似的东西)来避免这种情况,因为无论如何逻辑都需要在那里编码。它只是将问题向前推进了一步。
应该提到的是,这是较短的方法之一。实体也可以被移动、删除、创建等。所有这些都有相似的代码——有时长达 30 多行。
如何避免具有语义相同的字段/属性的不同结构的代码重复?不能解决我的问题。这个问题基本上是问“当不存在继承时如何进行继承”,而我的代码正在努力将非常相似的逻辑集体化为最低公分母。特征或通用实现不会解决我的问题,因为代码仍然存在 - 它只会被移动到其他地方。
您将如何去重复删除此代码?
与为我编写代码的人相比,我更在寻找指导方针。一些可能的解决方案可能是:
使用宏,然后使用类似的东西
entity_rename_impl!(args)
对每个可能因函数而异的特定事物使用具有不同参数的辅助方法
不要试图抽象整个方法,而是专注于为较小的东西编写辅助函数,这样方法可能会重复,但在其他地方抽象的代码很少
MCVE(游乐场):
#![allow(unused)]
pub struct Transaction {}
impl Transaction {
pub fn execute_sql(&self, sql: &str) -> i32 {
// .. do something in the database
0
}
pub fn bind_id(&self, id: Uuid) {}
}
#[derive(Clone, Copy)]
pub struct Uuid {}
impl std::fmt::Display for Uuid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "mockup")
}
}
pub fn assert_blueprint_exists(blueprint_id: Uuid) {}
pub fn track_blueprint_rename(id: Uuid, new_name: String) {}
pub fn assert_attribute_exists(blueprint_id: Uuid) {}
pub fn track_attribute_rename(id: Uuid, new_name: String) {}
pub fn rename_blueprint(
db_handle: &Transaction,
blueprint_id: Uuid,
new_name: &str,
) -> Result<(), String> {
println!("Renaming blueprint {} to {}", blueprint_id, new_name);
assert_blueprint_exists(blueprint_id);
db_handle.bind_id(blueprint_id);
let changed_rows = db_handle.execute_sql("UPDATE `blueprints` SET `name` = ? WHERE `id` == ?");
if changed_rows != 1 {
println!("Invalid amount of rows changed: {}", changed_rows);
return Err("Invalid change count in blueprint rename".to_string());
}
track_blueprint_rename(blueprint_id, new_name.to_string());
Ok(())
}
pub fn rename_attribute(
db_handle: &Transaction,
attribute_id: Uuid,
new_name: &str,
) -> Result<(), String> {
println!("Renaming attribute {} to {}", attribute_id, new_name);
assert_attribute_exists(attribute_id);
db_handle.bind_id(attribute_id);
let changed_rows = db_handle.execute_sql("UPDATE `attributes` SET `name` = ? WHERE `id` == ?");
if changed_rows != 1 {
println!("Invalid amount of rows changed: {}", changed_rows);
return Err("Invalid change count in attribute rename".to_string());
}
track_attribute_rename(attribute_id, new_name.to_string());
Ok(())
}
解决方案
我通常解决这类问题的方法是使用泛型。让调用者选择合适的类型。
trait Entity {
fn desc(&self) -> String;
}
impl Entity for Blueprint {
// ...
}
pub fn rename<T>(/* ... */)
where
T: Entity,
{
// ...
}