関数がパラメーターを変更しても大丈夫ですか


17

Linq To SQLをラップするデータレイヤーがあります。このデータレイヤーには、このメソッドがあります(簡略化されています)

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

変更を送信すると、レポート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; 
}

この質問は、単体テストを作成し、レポートパラメーターのIDプロパティが更新され、副作用の動作をモックするのが間違っていると感じた場合、コードの匂いがすることがはっきりしないことがわかったときに発生しました。


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は非常に明白であり、呼び出し側は渡された/変更されたオブジェクトを誤って使用しようとすることはできません。あなたのコードが何をしているのかにもよりますが、それは少し苦痛かもしれません。


Iraが必要ない場合、オブジェクトを返しません。それは余分な処理とメモリ(過剰)使用のように見えます。不要な場合は、無効または例外になります。そうでない場合は、DTOサンプルに投票します。
独立

これらすべての答えは、私たち自身の議論をかなり要約しています。これは本当の答えのない質問のようです。「はい、渡したのと同じオブジェクトを返しますが、APIが明確になります。」私たちの質問にはほぼ答えました。
ジョンペトラ

4

IMOこれは、変更の副作用が望ましい稀なインスタンスがある-あなたのレポートエンティティはIDを持っているので、すでにDTOの懸念を持っていると仮定することができ、間違いなくORMのがあり義務で、メモリレポートがあることを確実にするにはエンティティは、データベース表現オブジェクトと同期します。


6
+1-DBにエンティティを挿入した後、IDがあると予想されます。エンティティがそれなしで戻ってきたらもっと驚くでしょう。
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を変更しても驚くことではなく、確認は簡単です。アプリケーション全体のすべてのメソッドがレポートのIDを変更するかどうかを疑問に思うことは容易ではありません。

また、おそらくIDまったく属していてはならないとも主張Reportします。長い間有効なIDが含まれていないため、挿入前と挿入後に動作が異なる2つの異なるオブジェクトが実際にあります。「前の」オブジェクトは挿入できますが、取得、更新、または削除することはできません。「後」オブジェクトは正反対です。1つにはIDがあり、もう1つにはありません。それらの表示方法は異なる場合があります。表示されるリストは異なる場合があります。関連するユーザー権限は異なる場合があります。どちらも英語の意味では「レポート」ですが、非常に異なっています。

一方、コードは1つのオブジェクトで十分なほど単純な場合がありますが、コードがでこぼこしているかどうかを考慮する必要がありますif (validId) {...} else {...}


0

いいえ、大丈夫です!他の方法がない手続き型言語でのみパラメータを変更するプロシージャに適しています。OOP言語では、オブジェクト、この場合はレポート(report.generateNewId()のようなもの)で変更メソッドを呼び出します。

この場合、メソッドは2つのことを行うため、SRPを破壊します。dbにレコードを挿入し、新しいIDを生成します。メソッドはinsertRecord()と呼ばれるため、呼び出し側はメソッドが新しいIDを生成することも知ることができません。


3
えーと... db.Reports.InsertOnSubmit(report)オブジェクトのchangeメソッドを呼び出すとどうなりますか?
スティーブンC

それは大丈夫です...それは避ける必要がありますが、この場合、LINQ to SQLはパラメータの変更を行っているので、OPがフープジャンプクローン効果なしでそれを回避することはできません(それはSRPの独自の違反です)。
テラスティン

@StephenCレポートオブジェクトでそのメソッドを呼び出す必要があると言っていました。この場合、同じオブジェクトをパラメーターとして渡す意味はありません。
m3th0dman

@Telastyn私は一般的なケースで話していました。良い慣行を100%尊重することはできません。彼の特定のケースでは、5行のコードから最適な方法を推測することはできません
...-m3th0dman
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.