È corretto per una funzione modificare un parametro?


17

Abbiamo un livello dati che avvolge Linq To SQL. In questo datalayer abbiamo questo metodo (semplificato)

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

In caso di invio delle modifiche, l'ID del report viene aggiornato con il valore nel database che restituiamo.

Dal lato chiamante sembra così (semplificato)

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

Guardando il codice, ID è stato impostato all'interno della funzione InsertReport come una sorta di effetto collaterale, quindi ignoriamo il valore restituito.

La mia domanda è: dovrei fare affidamento sull'effetto collaterale e fare qualcosa del genere invece.

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

o dovremmo impedirlo

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

forse anche

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

Questa domanda è stata sollevata quando abbiamo creato un test unitario e abbiamo scoperto che non è davvero chiaro che la proprietà ID dei parametri del report venga aggiornata e che per deridere il comportamento degli effetti collaterali si sentisse sbagliato, un odore di codice se lo si desidera.


2
Ecco a cosa serve la documentazione API.

Risposte:


16

Sì, è OK e abbastanza comune. Può essere non ovvio, come hai scoperto.

In generale, tendo ad avere metodi di persistenza che restituiscono l'istanza aggiornata dell'oggetto. Questo è:

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

Sì, stai restituendo lo stesso oggetto che hai passato, ma rende l'API più chiara. Non è necessario per il clone - se qualcosa che causerà confusione se, come nel codice originale, il chiamante continua a utilizzare l'oggetto in cui è passato.

Un'altra opzione è quella di utilizzare un DTO

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

In questo modo l'API è molto ovvia e il chiamante non può accidentalmente provare a utilizzare l'oggetto passato / modificato. A seconda di cosa sta facendo il tuo codice, può essere un po 'una seccatura.


Non restituirei l'oggetto se Ira non fosse necessario. Sembra un'elaborazione aggiuntiva e un uso eccessivo della memoria. Vado per un vuoto o un'eccezione, se non di necessità. Altrimenti, voto per il tuo campione DTO.
Indipendente il

Tutte queste risposte riassumono praticamente le nostre discussioni. Questa sembra essere una domanda senza una vera risposta. La tua affermazione "Sì, stai restituendo lo stesso oggetto che hai passato, ma rende l'API più chiara." praticamente ha risposto alla nostra domanda.
John Petrak,

4

IMO si tratta di un raro caso in cui è auspicabile l'effetto collaterale del cambiamento: poiché l'entità del report ha un ID, si potrebbe già presumere che abbia le preoccupazioni di un DTO e, probabilmente, esiste l' obbligo ORM di garantire che il report in memoria l'entità viene mantenuta in sincronia con l'oggetto di rappresentazione del database.


6
+1: dopo aver inserito un'entità nel DB, ci si aspetterebbe che abbia un ID. Sarebbe più sorprendente se l'entità tornasse senza di essa.
MattDavey,

2

Il problema è che senza documentazione non è affatto chiaro cosa stia facendo il metodo e soprattutto perché stia restituendo un numero intero.

La soluzione più semplice sarebbe quella di utilizzare un nome diverso per il tuo metodo. Qualcosa di simile a:

int GenerateIdAndInsert(Report report)

Tuttavia, ciò non è abbastanza inequivocabile: se, come in C #, l'istanza reportdell'oggetto viene passata per riferimento, sarebbe difficile sapere se l' reportoggetto originale è stato modificato o se il metodo lo ha clonato e modificato solo il clone. Se si sceglie di modificare l'oggetto originale, sarebbe meglio nominare il metodo:

void ChangeIdAndInsert(Report report)

Una soluzione più complicata (e forse meno ottimale) è quella di riformattare pesantemente il codice. Che dire:

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

Di solito i programmatori si aspettano che solo i metodi di istanza di un oggetto possano modificarne lo stato. In altre parole, non sono sorpreso se report.insert()cambia l'ID del rapporto ed è facile da controllare. Non è facile chiedersi per ogni metodo nell'intera applicazione se cambia l'ID del report o meno.

Direi anche che forse IDnon dovrebbe nemmeno appartenere Reportaffatto. Poiché non contiene un ID valido per così tanto tempo, hai davvero due oggetti diversi prima e dopo l'inserimento, con comportamenti diversi. L'oggetto "before" può essere inserito, ma non può essere recuperato, aggiornato o eliminato. L'oggetto "dopo" è esattamente l'opposto. Uno ha un ID e l'altro no. Il modo in cui vengono visualizzati potrebbe essere diverso. Gli elenchi in cui compaiono potrebbero essere diversi. Le autorizzazioni utente associate possono essere diverse. Sono entrambi "rapporti" nel senso inglese della parola, ma sono molto diversi.

D'altra parte, il tuo codice potrebbe essere abbastanza semplice da rendere sufficiente un oggetto, ma è qualcosa da considerare se il tuo codice è pieno if (validId) {...} else {...}.


0

No non va bene! È opportuno che una procedura modifichi un parametro solo nei linguaggi procedurali, dove non esiste altro modo; nelle lingue OOP chiama il metodo di modifica sull'oggetto, in questo caso sul rapporto (qualcosa come report.generateNewId ()).

In questo caso il tuo metodo fa 2 cose, quindi rompe SRP: inserisce un record nel db e genera un nuovo ID. Il chiamante non è in grado di sapere che il tuo metodo genera anche un nuovo ID poiché è semplicemente chiamato insertRecord ().


3
Ehm ... cosa succede se db.Reports.InsertOnSubmit(report)chiama il metodo di modifica sull'oggetto?
Stephen C,

Va bene ... dovrebbe essere evitato, ma in questo caso LINQ to SQL sta facendo la modifica dei parametri, quindi non è come se l'OP potesse evitarlo senza l'effetto clone del salto del cerchio (che è la sua stessa violazione di SRP).
Telastyn,

@StephenC Stavo dicendo che dovresti chiamare quel metodo sull'oggetto report; in questo caso non ha senso passare lo stesso oggetto di un parametro.
m3th0dman,

@Telastyn Stavo parlando nel caso generale; le buone pratiche non possono essere rispettate al 100%. Nel suo caso particolare nessuno può dedurre qual è il modo migliore da 5 righe di codice ...
m3th0dman
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.