web-dev-qa-db-ja.com

関数がパラメータを変更しても問題ありませんか

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

コードを見ると、一種の副作用としてInsertReport関数内にIDが設定されており、戻り値は無視されます。

私の質問は、副作用に頼って代わりにこのようなことをするべきかどうかです。

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プロパティが更新されることと、副作用の動作を模擬するためにコードが悪臭を帯びていることは明らかではありません。

17
John Petrak

はい、それは問題なく、かなり一般的です。しかし、あなたが発見したように、それは自明ではないかもしれません。

一般に、永続化タイプのメソッドはオブジェクトの更新されたインスタンスを返す傾向があります。あれは:

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が非常にわかりやすくなり、呼び出し元が、渡されたオブジェクトや変更されたオブジェクトを誤って使用してしまうことがなくなります。ただし、コードの実行内容によっては、少し面倒な場合もあります。

16
Ben Hughes

IMOこれは、変更の副作用が望まれるまれな例です。レポートエンティティにIDがあり、DTOの懸念があると見なされている可能性があるため、ORM obligationインメモリレポートエンティティがデータベース表現オブジェクトと常に同期していることを確認してください。

4
StuartLC

問題は、ドキュメンテーションがないと、メソッドが何をしているか、特に整数を返す理由がはっきりしないことです。

最も簡単な解決策は、メソッドに別の名前を使用することです。何かのようなもの:

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を変更するかどうかは簡単ではありません。

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

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

2
Karl Bielefeldt

いいえ、それは大丈夫ではありません!他の方法がない手続き言語でのみパラメータを変更する手続きに適しています。 in OOP言語はオブジェクトの変更メソッド、この場合はレポート(report.generateNewId()など)を呼び出します)。

この場合、メソッドは2つの処理を実行するため、SRPが機能しなくなります。つまり、dbにレコードを挿入し、新しいIDを生成します。メソッドは単にinsertRecord()と呼ばれるため、呼び出し元はメソッドが新しいIDを生成することを知ることができません。

0
m3th0dman