มันตกลงสำหรับฟังก์ชั่นในการปรับเปลี่ยนพารามิเตอร์


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; 
}

คำถามนี้เกิดขึ้นเมื่อเราสร้างการทดสอบหน่วยและพบว่ามันไม่ชัดเจนจริง ๆ ว่าคุณสมบัติ 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 จึงมีความชัดเจนมากและผู้โทรไม่สามารถลองและใช้วัตถุที่ผ่าน / ปรับเปลี่ยนโดยไม่ตั้งใจได้ ทั้งนี้ขึ้นอยู่กับสิ่งที่รหัสของคุณกำลังทำอยู่อาจเป็นความเจ็บปวดเล็กน้อย


ฉันจะไม่คืนสินค้าหากไอราไม่ต้องการ ดูเหมือนว่าการประมวลผลพิเศษและการใช้หน่วยความจำ (เกิน) ฉันไปเป็นโมฆะหรือข้อยกเว้นถ้าไม่ต้องการ อื่นฉันลงคะแนนสำหรับตัวอย่าง DTO ของคุณ
อิสระ

คำตอบทั้งหมดเหล่านี้ค่อนข้างมากสรุปการอภิปรายของเราเอง นี่ดูเหมือนจะเป็นคำถามที่ไม่มีคำตอบจริง คำสั่งของคุณ "ใช่คุณกำลังส่งคืนวัตถุเดียวกันกับที่คุณทำ แต่มันทำให้ API ชัดเจนขึ้น" ค่อนข้างตอบคำถามของเรา
John Petrak

4

IMO นี่เป็นอินสแตนซ์ที่หายากซึ่งมีผลข้างเคียงของการเปลี่ยนแปลงที่เป็นที่ต้องการ - เนื่องจากเอนทิตีรายงานของคุณมี ID ที่สามารถสันนิษฐานได้ว่ามีข้อกังวลของ DTO และมีภาระหน้าที่ ORM เพื่อให้แน่ใจว่ารายงานในหน่วยความจำ เอนทิตีถูกเก็บไว้ในซิงค์กับวัตถุที่เป็นตัวแทนฐานข้อมูล


6
+1 - หลังจากใส่เอนทิตีในฐานข้อมูลคุณคาดว่าจะมี 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ไม่ควรเป็นของReportเลย เนื่องจากมันไม่มีรหัสที่ถูกต้องมาเป็นเวลานานคุณจึงมีวัตถุที่แตกต่างกันสองรายการก่อนและหลังการแทรกด้วยพฤติกรรมที่แตกต่างกัน สามารถแทรกวัตถุ "ก่อนหน้า" ได้ แต่ไม่สามารถเรียกคืนอัปเดตหรือลบได้ วัตถุ "after" ตรงข้ามแน่นอน หนึ่งมี ID และอื่น ๆ ไม่ได้ วิธีการแสดงอาจแตกต่างกัน รายการที่ปรากฏอาจแตกต่างกัน สิทธิ์ผู้ใช้ที่เกี่ยวข้องอาจแตกต่างกัน พวกเขาทั้งสองเป็น "รายงาน" ในความหมายของคำภาษาอังกฤษ แต่พวกเขาแตกต่างกันมาก

ในทางกลับกันโค้ดของคุณอาจจะง่ายพอที่วัตถุหนึ่งจะพอเพียง แต่มันเป็นสิ่งที่ควรพิจารณาหากโค้ดของคุณมีการรวมกันเป็นif (validId) {...} else {...}กลุ่ม


0

ไม่มันไม่เป็นไร! เหมาะสำหรับโพรซีเดอร์เพื่อแก้ไขพารามิเตอร์เฉพาะในภาษาเชิงโพรซีเดอร์ซึ่งไม่มีวิธีอื่น ในภาษา OOP เรียกวิธีการเปลี่ยนแปลงบนวัตถุในกรณีนี้ในรายงาน (บางอย่างเช่น report.generateNewId ())

ในกรณีนี้วิธีการของคุณทำ 2 สิ่งดังนั้น SRP จะหยุดพัก: มันแทรกเร็กคอร์ดใน db และสร้าง id ใหม่ ผู้เรียกไม่สามารถรู้ได้ว่าวิธีการของคุณยังสร้างรหัสใหม่เพราะมันเรียกว่า insertRecord ()


3
เอ่อ ... จะเกิดอะไรขึ้นถ้าdb.Reports.InsertOnSubmit(report)เรียกวิธีการเปลี่ยนแปลงบนวัตถุ?
สตีเฟ่นซี

ไม่เป็นไร ... ควรหลีกเลี่ยง แต่ในกรณีนี้ LINQ ไปยัง SQL กำลังทำการแก้ไขพารามิเตอร์ดังนั้นจึงไม่เหมือน OP ที่สามารถหลีกเลี่ยงปัญหานี้ได้โดยไม่ต้องมีการข้ามการโคลนแบบกระโดดห่วง (ซึ่งเป็นการละเมิด SRP ของตัวเอง)
Telastyn

@StephenC ฉันกำลังบอกว่าคุณควรเรียกวิธีการนั้นในวัตถุรายงาน; ในกรณีนี้ไม่มีความหมายในการส่งผ่านวัตถุเดียวกันเป็นพารามิเตอร์
m3th0dman

@Telastyn ฉันพูดในกรณีทั่วไป; แนวปฏิบัติที่ดีไม่สามารถเคารพได้ 100% โดยเฉพาะในกรณีของเขาไม่มีใครสามารถอนุมานได้ว่าเป็นวิธีที่ดีที่สุดจาก 5 บรรทัดของรหัส ...
m3th0dman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.