Có ổn không khi một chức năng sửa đổi một tham số


17

Chúng tôi có một lớp dữ liệu bao bọc Linq To SQL. Trong datalayer này, chúng ta có phương thức này (đơn giản hóa)

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

Khi gửi thay đổi, ID báo cáo được cập nhật với giá trị trong cơ sở dữ liệu mà sau đó chúng tôi trả lại.

Từ phía gọi, nó trông như thế này (đơn giản hóa)

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

Nhìn vào mã, ID đã được đặt bên trong hàm insertReport dưới dạng một loại hiệu ứng phụ, và sau đó chúng ta bỏ qua giá trị trả về.

Câu hỏi của tôi là, tôi có nên dựa vào tác dụng phụ và làm một cái gì đó như thế này thay thế.

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

hoặc chúng ta nên ngăn chặn nó

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

thậm chí có thể

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

Câu hỏi này được đặt ra khi chúng tôi tạo một bài kiểm tra đơn vị và thấy rằng nó không thực sự rõ ràng rằng thuộc tính ID tham số báo cáo được cập nhật và để chế giễu hành vi tác dụng phụ cảm thấy sai, bạn sẽ ngửi thấy mùi mã.


2
Đây là những gì tài liệu API dành cho.

Câu trả lời:


16

Vâng, nó ổn, và khá phổ biến. Nó có thể không rõ ràng, như bạn đã khám phá.

Nói chung, tôi có xu hướng có các phương thức kiểu kiên trì trả về thể hiện cập nhật của đối tượng. Đó là:

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

Có, bạn đang trả lại cùng một đối tượng như bạn đã truyền, nhưng nó làm cho API rõ ràng hơn. Không cần bản sao - nếu bất cứ điều gì sẽ gây nhầm lẫn nếu, như trong mã ban đầu của bạn, người gọi tiếp tục sử dụng đối tượng họ truyền vào.

Một lựa chọn khác là sử dụng DTO

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

Theo cách đó, API rất rõ ràng và người gọi không thể vô tình thử và sử dụng đối tượng được truyền vào / sửa đổi. Tùy thuộc vào những gì mã của bạn đang làm, nó có thể là một chút đau.


Tôi sẽ không trả lại đối tượng nếu không cần thiết. Đó trông giống như xử lý thêm và sử dụng bộ nhớ (hơn). Tôi đi một khoảng trống hoặc ngoại lệ, nếu không cần thiết. Khác, tôi bỏ phiếu cho mẫu DTO của bạn.
Độc lập

Tất cả những câu trả lời này khá nhiều tổng hợp các cuộc thảo luận của chúng ta. Đây dường như là một câu hỏi không có câu trả lời thực sự. Câu nói của bạn "Có, bạn đang trả lại cùng một đối tượng như bạn đã truyền vào, nhưng nó làm cho API rõ ràng hơn." khá nhiều trả lời câu hỏi của chúng tôi.
John Petrak

4

IMO đây là một trường hợp hiếm hoi mà tác dụng phụ của thay đổi là mong muốn - bởi vì thực thể Báo cáo của bạn đã có ID, nó có thể được coi là có mối quan tâm của DTO và có thể có nghĩa vụ ORM để đảm bảo rằng Báo cáo trong bộ nhớ thực thể được giữ đồng bộ với đối tượng biểu diễn cơ sở dữ liệu.


6
+1 - sau khi chèn một thực thể vào DB, bạn sẽ mong muốn nó có ID. Sẽ ngạc nhiên hơn nếu thực thể trở lại mà không có nó.
MattDavey

2

Vấn đề là không có tài liệu, nó không rõ phương thức đang làm gì và đặc biệt là tại sao nó lại trả về một số nguyên.

Giải pháp đơn giản nhất là sử dụng một tên khác cho phương thức của bạn. Cái gì đó như:

int GenerateIdAndInsert(Report report)

Tuy nhiên, điều này vẫn chưa đủ rõ ràng: nếu như trong C #, thể hiện của reportđối tượng được truyền bằng tham chiếu, sẽ rất khó để biết liệu reportđối tượng ban đầu có bị sửa đổi hay không, nếu phương thức nhân bản nó và chỉ sửa đổi bản sao. Nếu bạn chọn sửa đổi đối tượng ban đầu, tốt hơn là đặt tên cho phương thức:

void ChangeIdAndInsert(Report report)

Một giải pháp phức tạp hơn (và có thể ít tối ưu hơn) là tái cấu trúc mạnh mã. Thế còn:

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

Thông thường các lập trình viên mong đợi rằng chỉ có các phương thức cá thể của đối tượng có thể thay đổi trạng thái của nó. Nói cách khác, tôi không ngạc nhiên nếureport.insert() thay đổi id của báo cáo và thật dễ dàng để kiểm tra. Không dễ để tự hỏi đối với mọi phương thức trong toàn bộ ứng dụng nếu nó thay đổi id của báo cáo hay không.

Tôi cũng sẽ tranh luận rằng có lẽ IDthậm chí không nên thuộc về Reporttất cả. Vì nó không chứa id hợp lệ quá lâu, bạn thực sự có hai đối tượng khác nhau trước và sau khi chèn, với hành vi khác nhau. Đối tượng "trước" có thể được chèn, nhưng không thể truy xuất, cập nhật hoặc xóa. Đối tượng "sau" là hoàn toàn ngược lại. Một cái có id và cái kia thì không. Cách chúng được hiển thị có thể khác nhau. Các danh sách họ xuất hiện có thể khác nhau. Quyền người dùng liên kết có thể khác nhau. Cả hai đều là "báo cáo" theo nghĩa tiếng Anh của từ này, nhưng chúng rất khác nhau.

Mặt khác, mã của bạn có thể đủ đơn giản để một đối tượng đủ, nhưng đó là điều cần xem xét nếu mã của bạn bị tiêu if (validId) {...} else {...}.


0

Không, nó không ổn! Nó phù hợp với một thủ tục chỉ sửa đổi một tham số trong các ngôn ngữ thủ tục, trong đó không có cách nào khác; trong các ngôn ngữ OOP gọi phương thức thay đổi trên đối tượng, trong trường hợp này là trên báo cáo (một cái gì đó giống như báo cáo.generateNewId ()).

Trong trường hợp này, phương thức của bạn thực hiện 2 điều, do đó phá vỡ SRP: nó chèn một bản ghi trong db và nó tạo ra một id mới. Người gọi không thể biết rằng phương thức của bạn cũng tạo ra một id mới vì nó được gọi đơn giản là insertRecord ().


3
Ermm ... nếu db.Reports.InsertOnSubmit(report)gọi phương thức thay đổi trên đối tượng thì sao?
Stephen C

Không sao ... nên tránh, nhưng trong trường hợp này, LINQ to SQL đang thực hiện sửa đổi tham số để OP không thể tránh điều đó nếu không có hiệu ứng nhân bản nhảy vòng (đó là vi phạm SRP của chính nó).
Telastyn

@StephenC Tôi đã nói rằng bạn nên gọi phương thức đó trên đối tượng báo cáo; trong trường hợp này không có ý nghĩa gì trong việc truyền cùng một đối tượng như một tham số.
m3th0dman

@Telastyn Tôi đã nói trong trường hợp chung; thực hành tốt không thể được tôn trọng 100%. Trong trường hợp cụ thể của mình, không ai có thể suy ra cách tốt nhất từ ​​5 dòng mã là gì ...
m3th0dman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.