Как избежать дублирования кода в похожих методах, которые во многих местах отличаются небольшими количествами? - PullRequest
0 голосов
/ 17 марта 2020

У меня есть очень управляемая данными программа, которая содержит различные типы сущностей, имеющих очень похожие структуры и различающихся только в определенных c местах.

Например, у каждой сущности есть имя, которое можно изменить. Вот два примера методов , чтобы продемонстрировать, как методы могут быть похожими:

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 или что-то подобное), учитывая, что логика c должна быть там все равно закодирована. Это только переместит проблему на один шаг вверх.

Следует отметить, что это один из более коротких методов. Объекты также можно перемещать, удалять, создавать и т. Д. c. у всех из которых есть подобный код - иногда 30+ строк.

Как избежать дублирования кода различных структур с семантически равными полями / свойствами? не решает мою проблему. Этот вопрос в основном задает вопрос «как сделать наследование, когда наследования не существует», тогда как мой код борется с коллективизацией очень похожих логик c в наименьший общий знаменатель. Черты или обычные реализации не решат мою проблему, так как код все еще будет существовать - он будет перемещен только куда-нибудь еще.

Как бы вы go отнеслись к дедупликации этого кода?

Я больше ищу рекомендации, чем кто-то, кто пишет мой код для меня. Вот несколько возможных решений:

  1. использовать макросы, а затем просто использовать что-то вроде entity_rename_impl!(args)

  2. использовать вспомогательный метод с другим параметром для каждой указанной c вещи, которая может отличаться от функции к функции

  3. , не пытайтесь абстрагировать весь метод, а вместо этого сосредоточьтесь на написании вспомогательных функций для более мелких вещей, так что методы могут дублировать, но это очень мало кода, который абстрагируется в другом месте

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(())
}

1 Ответ

0 голосов
/ 17 марта 2020

Я обычно решаю эту проблему, используя дженерики. Пусть звонящий выберет подходящий тип.

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

impl Entity for Blueprint {
    // ...
}

pub fn rename<T>(/* ... */)
where
    T: Entity,
{
    // ...
}
...