如何避免类似方法的代码重复,这些方法在很多方面差异很小?

我有一个非常数据驱动的程序,其中包含不同类型的实体,这些实体的结构非常相似,只是在特定位置不同。

例如,每个实体都有一个可以更改的名称。 two example methods展示了这些方法可能如何相似:

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,attribute_id: Uuid,DataError> {
    ctx.debug(format!(
        "Renaming attribute {} to {}",attribute_id,new_name
    ));
    self.assert_attribute_exists(ctx,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: {}",});
    }
    ctx.attribute_renamed(attribute_id,new_name);
    Ok(())
}

对于5-11种更多类型的实体,现在需要使用具有几乎相同代码的相同方法。我通常可以将Blueprint替换为其他实体类型的名称,这一切都可以工作。但是,这似乎是一个愚蠢的解决方案。

同样,编写一个接受所有相关字符串,方法之类的帮助器方法似乎也很愚蠢。

我不认为我可以通过传入一些“策略”或其他间接帮助器(EntityRenamer或类似的东西)来避免这种情况,因为无论如何都需要在其中编码逻辑。只是将问题向上移动了。

应该指出,这是较短的方法之一。实体也可以移动,删除,创建等。所有实体都具有相似的代码-有时长30+行。

How to avoid code duplication of different structs with semantically equal fields/properties?无法解决我的问题。这个问题基本上是在问“当不存在继承时如何进行继承”,而我的代码却在努力将非常相似的逻辑集体化为最低公分母。特性或常见的实现方式不会解决我的问题,因为代码仍然存在-只能将其移动到其他地方。

您将如何去重复此代码?

我正在寻找指导方针,而不是为我编写代码的人。一些可能的解决方案可能是:

  1. 使用宏,然后使用类似entity_rename_impl!(args)

  2. 的宏
  3. 对于每个可能因函数而异的特定事物,使用具有不同参数的辅助方法

  4. 不要尝试抽象整个方法,而应该专注于为较小的事物编写辅助函数,以便这些方法可以重复,但是在其他地方抽象的代码很少

MCVE(playground):

#![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,String> {
    println!("Renaming blueprint {} to {}",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,String> {
    println!("Renaming attribute {} to {}",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(())
}
z298961 回答:如何避免类似方法的代码重复,这些方法在很多方面差异很小?

我通常解决此类问题的方法是使用泛型。让呼叫者选择适当的类型。

trait Entity {
    fn desc(&self) -> String;
}

impl Entity for Blueprint {
    // ...
}

pub fn rename<T>(/* ... */)
where
    T: Entity,{
    // ...
}
本文链接:https://www.f2er.com/2632937.html

大家都在问