Bir fonksiyonun bir parametreyi değiştirmesi uygun mudur?


17

Linq To SQL'i saran bir veri katmanımız var. Bu veri katmanında bu yöntem var (basitleştirilmiş)

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

Gönderme değişikliklerinde, rapor kimliği daha sonra döndürdüğümüz veritabanındaki değerle güncellenir.

Arayan taraftan böyle görünüyor (basitleştirilmiş)

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

Koda bakıldığında ID, InsertReport işlevinin içinde bir tür yan etki olarak ayarlandı ve sonra dönüş değerini yok sayıyoruz.

Sorum şu, yan etkiye güvenmeli ve bunun gibi bir şey yapmalıyım.

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

ya da bunu engellemeliyiz

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

hatta belki

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

Bu soru, bir birim testi oluşturduğumuzda ve rapor parametreleri ID özelliğinin güncellendiğini ve yan etki davranışını alay etmenin yanlış hissettiğini, eğer isterseniz bir kod kokusunun gerçekten net olmadığını tespit ettiğimizde ortaya çıktı.


2
API belgeleri bunun içindir.

Yanıtlar:


16

Evet, sorun değil ve oldukça yaygın. Yine de, keşfettiğiniz gibi açık olmayabilir.

Genel olarak, kalıcılık türü yöntemleri nesnenin güncelleştirilmiş örneğini döndürme eğilimindedir. Yani:

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

Evet, ilettiğiniz nesnenin aynısını döndürüyorsunuz, ancak API'yi daha net hale getiriyor. Klonlamaya gerek yoktur - eğer orijinal kodunuzda olduğu gibi, arayan kişi geçtiği nesneyi kullanmaya devam ederse karışıklığa neden olacak bir şey varsa.

Başka bir seçenek de DTO kullanmaktır

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

Bu şekilde API çok açıktır ve arayan yanlışlıkla geçirilen / değiştirilmiş nesneyi kullanmayı deneyemez. Kodunuzun ne yaptığına bağlı olarak, biraz acı olabilir.


Ira'ya ihtiyaç duymazsa nesneyi iade etmem. Bu ekstra işleme ve bellek (aşırı) kullanımı gibi görünüyor. İhtiyaç duyulmuyorsa, bir boşluk veya istisna için gidiyorum. Yoksa, ben DTO örneğiniz için oy veriyorum.
Bağımsız

Tüm bu cevaplar hemen hemen kendi tartışmalarımızı özetliyor. Bu gerçek bir cevabı olmayan bir soru gibi görünüyor. "Evet, geçtiğinizle aynı nesneyi döndürüyorsunuz, ancak API'yı daha net hale getiriyor." sorumuzu hemen hemen yanıtladı.
John Petrak

4

IMO bu, değişikliğin yan etkisinin arzu edildiği nadir bir durumdur - Rapor varlığınızın bir kimliği olduğundan, DTO'nun endişelerini zaten taşıdığı varsayılabilir ve tartışmalı olarak bellek içi Raporun sağlanması için ORM yükümlülüğü vardır . varlık, veritabanı temsil nesnesiyle eşzamanlı tutulur.


6
+1 - DB'ye bir varlık ekledikten sonra bir kimliğe sahip olmasını beklersiniz . Varlığın onsuz geri gelmesi daha şaşırtıcı olurdu.
MattDavey

2

Sorun, dokümantasyon olmadan, yöntemin ne yaptığı ve özellikle neden bir tamsayı döndürdüğü açık değildir.

En kolay çözüm, yönteminiz için farklı bir ad kullanmak olacaktır. Gibi bir şey:

int GenerateIdAndInsert(Report report)

Yine de, bu yeterince açık değildir: eğer C # 'da olduğu gibi, reportnesnenin örneği referans olarak iletilirse, orijinal reportnesnenin değiştirilip değiştirilmediğini veya yöntemin onu klonlayıp değiştirip yalnızca klonu değiştirip değiştirmediğini bilmek zor olacaktır . Orijinal nesneyi değiştirmeyi seçerseniz, yöntemi adlandırmak daha iyi olur:

void ChangeIdAndInsert(Report report)

Daha karmaşık (ve belki de daha az optimal) bir çözüm, kodu büyük ölçüde yeniden düzenlemektir. Ne dersin:

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

Genellikle programcılar yalnızca bir nesnenin örnek yöntemlerinin durumunu değiştirmesini bekler. Başka bir deyişle, report.insert()raporun kimliğini değiştirdiğinde şaşırmadım ve kontrol edilmesi kolay. Raporun kimliğini değiştirip değiştirmediğini tüm uygulamadaki her yöntem için merak etmek kolay değildir.

Ayrıca belki IDde hiç ait olmaması gerektiğini savunuyorum Report. Bu kadar uzun bir süre geçerli bir kimlik içermediğinden, ekleme işleminden önce ve sonra iki farklı nesneniz var, farklı davranışlarla. "Önceki" nesnesi eklenebilir, ancak alınamaz, güncellenemez veya silinemez. "After" nesnesi tam tersidir. Birinin kimliği var, diğerinin yok. Görüntülenme şekilleri farklı olabilir. Göründükleri listeler farklı olabilir. İlişkili kullanıcı izinleri farklı olabilir. Her ikisi de kelimenin İngilizce anlamında "raporlardır", ancak çok farklıdırlar.

Öte yandan, kodunuz bir nesnenin yeterli olacağı kadar basit olabilir, ancak kodunuzun biberli olup olmadığı düşünülmelidir if (validId) {...} else {...}.


0

Hayır, sorun değil! Bir prosedürün, sadece başka bir yolun olmadığı prosedürel dillerde değiştirilmesi bir prosedür için uygundur; OOP dillerinde nesne üzerindeki değiştirme yöntemini, bu durumda raporda (report.generateNewId () gibi bir şey) çağırır.

Bu durumda yönteminiz 2 şey yapar, bu nedenle SRP'yi keser: db'ye bir kayıt ekler ve yeni bir kimlik oluşturur. Arayan, yönteminizin yeni bir kimlik oluşturduğunu bile bilmiyor çünkü basitçe insertRecord () olarak adlandırılıyor.


3
Hmm ... ya db.Reports.InsertOnSubmit(report)nesne üzerinde değişiklik yöntemini çağırırsa?
Stephen C

Tamam ... kaçınılmalıdır, ancak bu durumda LINQ to SQL parametre modifikasyonu yapıyor, bu yüzden OP, kasnak atlama klon etkisi olmadan (SRP'nin kendi ihlali olan) bundan kaçınabilecek gibi değil.
Telastyn

@StephenC Rapor nesnesindeki bu yöntemi çağırmanız gerektiğini söylüyordum; bu durumda, aynı nesneyi parametre ile geçirmenin bir anlamı yoktur.
m3th0dman

@Telastyn Genel davada konuşuyordum; iyi uygulamalara% 100 saygı gösterilemez. Özel durumunda hiç kimse 5 satır koddan en iyi yolu
çıkaramaz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.