У меня есть очень управляемая данными программа, которая содержит различные типы сущностей, имеющих очень похожие структуры и различающихся только в определенных 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 отнеслись к дедупликации этого кода?
Я больше ищу рекомендации, чем кто-то, кто пишет мой код для меня. Вот несколько возможных решений:
использовать макросы, а затем просто использовать что-то вроде entity_rename_impl!(args)
использовать вспомогательный метод с другим параметром для каждой указанной c вещи, которая может отличаться от функции к функции
, не пытайтесь абстрагировать весь метод, а вместо этого сосредоточьтесь на написании вспомогательных функций для более мелких вещей, так что методы могут дублировать, но это очень мало кода, который абстрагируется в другом месте
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(())
}