Чи нормально функція зміни параметра


17

У нас є шар даних, який обертає Linq в SQL. У цьому шарі даних у нас є цей метод (спрощений)

int InsertReport(Report report)
{
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
    return report.ID; 
}

Після подання змін ідентифікатор звіту оновлюється значенням у базі даних, яку ми потім повертаємо.

З боку виклику це виглядає так (спрощено)

var report = new Report();
DataLayer.InsertReport(report);
// Do something with report.ID

Дивлячись на код, ID встановлюється всередині функції InsertReport як певний побічний ефект, і тоді ми ігноруємо повернене значення.

Моє запитання: чи варто покладатися на побічний ефект і робити щось подібне замість цього.

void InsertReport(Report report)
{
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
}

чи ми повинні цьому запобігати

int InsertReport(Report report)
{
    var newReport = report.Clone();
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport.ID; 
}

можливо, навіть

Report InsertReport(Report report)
{
    var newReport = report.Clone();
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport; 
}

Це питання виникло, коли ми створили одиничний тест і виявили, що не зовсім зрозуміло, що властивість ідентифікаторів параметрів звіту оновлюється і що для знущань над поведінкою побічного ефекту почувалося неправильним, якщо ви захочете кодом.


2
Це те, для чого призначена документація API.

Відповіді:


16

Так, це нормально і досить часто. Однак, як ви виявили, це може бути не очевидно.

Взагалі, я схильний, щоб методи постійного типу повертали оновлений екземпляр об'єкта. Тобто:

Report InsertReport(Report report)
{        
    db.Reports.InsertOnSubmit(report);
    db.SubmitChanges();
    return report; 
}

Так, ви повертаєте той самий об’єкт, що і ви передали, але це робить API зрозумілішим. Клону немає потреби - якщо що-небудь може спричинити плутанину, якщо, як і у вихідному коді, абонент продовжує використовувати об'єкт, який вони передали.

Ще один варіант - використовувати DTO

Report InsertReport(ReportDTO dto)
{
    var newReport = Report.Create(dto);
    db.Reports.InsertOnSubmit(newReport);
    db.SubmitChanges();
    return newReport; 
}

Таким чином API дуже очевидний, і абонент не може випадково спробувати використати переданий / змінений об'єкт. Залежно від того, що робить ваш код, це може бути трохи болем.


Я б не повернув об’єкт, якщо Іра не знадобиться. Це виглядає як додаткова обробка та використання пам'яті (понад). Я переходжу до порожнечі чи винятку, якщо це не потрібно. Ще, я голосую за ваш зразок DTO.
Незалежна

Усі ці відповіді в значній мірі підсумовують наші власні дискусії. Це, здається, питання без реальної відповіді. Ваше твердження "Так, ви повертаєте той самий об'єкт, що і ви передали, але це робить API зрозумілішим". майже відповів на наше запитання.
Джон Петрак

4

IMO - це рідкісний випадок, коли бажаний побічний ефект зміни - оскільки ваша організація звіту має ідентифікатор, вона вже може вважати, що вона має занепокоєння DTO, і, можливо, існує обов'язок ORM забезпечити виконання звіту в пам'яті сутність зберігається в синхроні з об'єктом представлення бази даних.


6
+1 - після вставки об’єкта в БД ви очікуєте, що він має ідентифікатор. Було б дивніше, якби суб’єкт повернувся без нього.
MattDavey

2

Проблема полягає в тому, що без документації взагалі не зрозуміло, що робить метод, і особливо, чому він повертає ціле число.

Найпростішим рішенням буде використовувати іншу назву для свого методу. Щось на зразок:

int GenerateIdAndInsert(Report report)

Однак це не є однозначно: якщо, як і в C #, екземпляр reportоб'єкта передається за посиланням, було б важко дізнатися, чи reportбув модифікований оригінальний об'єкт або якщо метод клонував його та змінив лише клон. Якщо ви вирішите змінити оригінальний об'єкт, було б краще назвати метод:

void ChangeIdAndInsert(Report report)

Більш складним (і, можливо, менш оптимальним) рішенням є сильне перероблення коду. Що стосовно:

using (var transaction = new TransactionScope())
{
    var id = this.Data.GenerateReportId(); // We need to find an available ID...
    this.Data.AddReportWithId(id, report); // ... and use this ID to insert a report.
    transaction.Complete();
}

2

Зазвичай програмісти очікують, що лише методи екземпляра об'єкта можуть змінити його стан. Іншими словами, я не здивований, якщо report.insert()змінить ідентифікатор звіту, і це легко перевірити. Зробити питання про кожен метод у всій програмі непросто, чи змінює він ідентифікатор звіту чи ні.

Я також зауважу, що, можливо, IDнавіть не слід належати Report. Оскільки він не містить дійсного ідентифікатора так довго, у вас дійсно є два різні об'єкти до та після вставки, з різною поведінкою. Об'єкт "до" можна вставити, але його неможливо отримати, оновити чи видалити. Об'єкт "після" - це прямо протилежне. У одного є ідентифікатор, а в іншого немає. Спосіб їх відображення може бути різним. Списки, в яких вони з’являються, можуть бути різними. Дозволи для асоційованих користувачів можуть бути різними. Вони обидва "доповіді" в англійському розумінні цього слова, але вони дуже різні.

З іншого боку, ваш код може бути досить простим, що одного об’єкта буде достатньо, але це щось, що слід врахувати, якщо ваш код переповнений if (validId) {...} else {...}.


0

Ні, це не нормально! Процедура підходить для зміни параметра лише в процедурних мовах, де іншого способу немає; в мовах OOP викликають метод зміни об'єкта, в цьому випадку на звіті (щось на зразок report.generateNewId ()).

У цьому випадку ваш метод робить 2 речі, отже, розбиває SRP: він вставляє запис у db і генерує новий ідентифікатор. Абонент не може знати, що ваш метод також генерує новий ідентифікатор, оскільки його просто називають insertRecord ().


3
Ermm ... що робити, якщо db.Reports.InsertOnSubmit(report)викликає метод зміни об’єкта?
Стівен C

Це добре ... цього слід уникати, але в цьому випадку LINQ до SQL робить модифікацію параметрів, тому це не так, як ОР може уникнути цього без ефекту клонування, що стрибає з обручем (що є власним порушенням SRP).
Теластин

@StephenC Я говорив, що ви повинні викликати цей метод на об’єкті звіту; у цьому випадку немає сенсу передавати той самий об’єкт, що і параметр.
m3th0dman

@Telastyn Я виступав у загальному випадку; передового досвіду не можна дотримуватися на 100%. У його конкретному випадку ніхто не може зробити висновок про те, що найкраще із 5 рядків коду ...
m3th0dman
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.