La modifica di un parametro in arrivo è un antipattern? [chiuso]


60

Sto programmando in Java, e faccio sempre i convertitori in questo modo:

public OtherObject MyObject2OtherObject(MyObject mo){
    ... Do the conversion
    return otherObject;
}

Nel nuovo posto di lavoro lo schema è:

public void MyObject2OtherObject(MyObject mo, OtherObject oo){
    ... Do the conversion
}

Per me è un po 'puzzolente, dato che mi sono abituato a non modificare i parametri in arrivo. Questa modifica dei parametri in entrata è un antipattern o è OK? Ha alcuni gravi inconvenienti?


4
La risposta giusta per questo sarà specifica della lingua, poiché quelli in cui i parametri pass-by-value li trasformano effettivamente in variabili locali.
Blrfl,

1
Il secondo modello viene talvolta utilizzato come misura di efficienza per riutilizzare gli oggetti. Supponendo che oosia un oggetto passato al metodo, non un puntatore impostato su un nuovo oggetto. È questo il caso qui? Se questo è Java, probabilmente lo è, se è C ++ potrebbe non esserlo
Richard Tingle,

3
Sembra un'ottimizzazione prematura, aye. Si consiglia di utilizzare il primo modulo in generale, ma se si verifica un collo di bottiglia delle prestazioni, è possibile utilizzare il secondo modulo con un commento per giustificarlo . Un commento impedirebbe a te stesso e agli altri di guardare quel codice e di far apparire di nuovo la stessa domanda.
Appassionato il

2
Il secondo modello è utile quando anche la funzione restituisce un valore, ad esempio esito positivo / negativo. Ma non c'è nulla di veramente "sbagliato" in questo. Dipende davvero da dove vuoi creare l'oggetto.
GrandmasterB,

12
Non nominerei funzioni che implementano questi due diversi schemi allo stesso modo.
Casey,

Risposte:


70

Non è un antipasto, è una cattiva pratica.

La differenza tra un antipattern e una semplice cattiva pratica è qui: definizione anti-pattern .

Il nuovo stile di lavoro che mostri è una cattiva pratica , vestigia o tempi pre-OOP, secondo il Clean Code di zio Bob.

Gli argomenti sono naturalmente interpretati come input di una funzione.

Tutto ciò che ti costringe a controllare la firma della funzione equivale a un doppio take. È una pausa cognitiva e dovrebbe essere evitata. Nei giorni precedenti la programmazione orientata agli oggetti era talvolta necessario disporre di argomenti di output. Tuttavia, gran parte della necessità di argomenti di output scompare nei linguaggi OO


7
Non vedo perché la definizione che hai collegato precluda che questo viene considerato un anti-pattern.
Samuel Edwin Ward,

7
Suppongo sia perché la logica che "stiamo salvando CPU / mem non creando un nuovo oggetto, invece dovremmo riciclarne uno che abbiamo e passarlo come arg" è così ovviamente sbagliata per tutti quelli con un serio background OOP che questo potrebbe non costituisce mai un modello - quindi non c'è modo di considerarlo un anti-modello secondo la definizione ...
vaxquis,

1
Che dire del caso in cui un parametro di input è una grande raccolta di oggetti che devono essere modificati?
Steve Chambers,

Anche se preferisco anche l'opzione 1, cosa succede se OtherObjectè un'interfaccia? Solo il chiamante (si spera) sa quale tipo concreto vuole.
user949300,

@SteveChambers Penso che, in questo caso, il design della classe o del metodo sia errato e debba essere riformulato per evitarlo.
piotr.wittchen,

16

Citando il famoso libro di Robert C. Martin "Clean Code":

Gli argomenti di output dovrebbero essere evitati

Le funzioni dovrebbero avere un numero limitato di argomenti

Il secondo modello viola entrambe le regole, in particolare quella "argomento di output". In questo senso è peggio del primo modello.


1
Direi che viola il primo, molti argomenti significano codice disordinato, ma penso che due argomenti siano totalmente ok. Penso che la regola sul codice pulito significhi che se hai bisogno di 5 o 6 dati in entrata, vuoi fare troppo in un unico metodo, quindi dovresti rifattorizzare per aumentare la leggibilità del codice e l'ambito OOP.
CsBalazs Ungheria

2
comunque sospetto che questa non sia una legge severa, ma un forte suggerimento. So che non funzionerà con tipi primitivi, se si tratta di un modello diffuso in un progetto un junior otterrebbe idee per passare un int e aspettarsi che cambi come Oggetti.
CsBalazs Ungheria

27
Indipendentemente da quanto possa essere corretto il codice pulito, non penso che questa sia una risposta preziosa senza spiegare perché debbano essere evitati. Non sono comandamenti che scendono su una tavoletta di pietra, la comprensione è importante. Questa risposta potrebbe essere migliorata fornendo un riassunto del ragionamento nel libro e un riferimento al capitolo
Daenyth,

3
Bene, diavolo, se qualcuno lo ha scritto in un libro, deve essere un buon consiglio per ogni possibile situazione. Non c'è bisogno di pensare.
Casey,

2
@emodendroket Ma amico, è il ragazzo!
Pierre Arlaud,

15

Il secondo linguaggio può essere più veloce, perché il chiamante può riutilizzare una variabile su un ciclo lungo, anziché ogni iterazione che crea una nuova istanza.

Non lo userei generalmente, ma ad es. nella programmazione del gioco ha il suo posto. Ad esempio, guardare molte operazioni Vector3f di JavaMonkey consente di passare istanze che dovrebbero essere modificate e restituite come risultato.


12
bene, posso essere d'accordo se questo è il collo di bottiglia, ma ovunque io abbia lavorato, i colli di bottiglia sono di solito algoritmi con bassa efficienza, non clonazione o creazione di oggetti. Conosco meno dello sviluppo del gioco.
CsBalazs Ungheria

4
@CsBalazsHungary Credo che i problemi quando la creazione di oggetti è una preoccupazione tendano ad essere correlati alle allocazioni di memoria e alla garbage collection, che probabilmente sarebbe il prossimo livello di colli di bottiglia a seguito della complessità algoritmica (specialmente in un ambiente a memoria limitata, ad esempio smartphone e simili) .
JAB,

JMonkey è anche il luogo in cui l'ho visto molto poiché è spesso critico per le prestazioni. Non l'ho mai sentito chiamato "JavaMonkey" però
Richard Tingle il

3
@JAB: Il GC della JVM è stato progettato appositamente per il caso di oggetti effimeri. Grandi quantità di oggetti di breve durata sono banali da raccogliere e in molti casi l'intera generazione effimera può essere raccolta con una sola mossa puntatore.
Phoshi,

2
in realtà, se si deve creare un oggetto , non sarà efficace dal punto di vista delle prestazioni; se un blocco di codice deve essere fortemente ottimizzato per la velocità, è necessario utilizzare invece primitive e array nativi; per questo motivo, ritengo che l'affermazione contenuta in questa risposta non sia valida.
vaxquis,

10

Non penso che siano due pezzi equivalenti di codice. Nel primo caso, devi creare il file otherObject. È possibile modificare l'istanza esistente nel secondo. Entrambi hanno i suoi usi. L'odore di codice preferirebbe l'uno all'altro.


So che non sono equivalenti. Hai ragione, stiamo insistendo su queste cose, quindi farebbe la differenza. Quindi stai dicendo che nessuno di loro è antipattern, è solo una questione del caso d'uso?
CsBalazs Ungheria

@CsBalazsHungary Direi di sì. A giudicare dal piccolo codice che hai fornito.
Euforico,

Immagino che diventi un problema più serio se hai un parametro primitivo in arrivo, che ovviamente non verrà aggiornato, quindi come ha scritto @claasz: dovrebbe essere evitato a meno che non sia necessario.
CsBalazs Ungheria

1
@CsBalazsHungary In caso di argomento primitivo, la funzione non funzionerebbe nemmeno. Quindi hai un problema peggiore rispetto alla modifica degli argomenti.
Euforico,

Direi che un giovane sarebbe caduto nella trappola del tentativo di rendere un tipo primitivo un parametro di output. Quindi direi che il primo convertitore dovrebbe essere preferito, se possibile.
CsBalazs Ungheria

7

Dipende davvero dalla lingua.

In Java la seconda forma può essere un anti-pattern, ma alcune lingue trattano il passaggio di parametri in modo diverso. In Ada e VHDL per esempio, invece di passaggio per valore o per riferimento, i parametri possono avere modi in, outo in out.

È un errore modificare un inparametro o leggere un outparametro, ma eventuali modifiche a un in outparametro vengono restituite al chiamante.

Quindi le due forme in Ada (anche queste sono VHDL legali) lo sono

function MyObject2OtherObject(mo : in MyObject) return OtherObject is
begin
    ... Do the conversion
    return otherObject;
end MyObject2OtherObject;

e

procedure MyObject2OtherObject(mo : in MyObject; oo : out OtherObject) is
begin
    ... Do the conversion
    oo := ... the conversion result;
end MyObject2OtherObject;

Entrambi hanno i loro usi; una procedura può restituire più valori in più Outparametri mentre una funzione può restituire un solo risultato. Poiché lo scopo del secondo parametro è chiaramente indicato nella dichiarazione di procedura, non è possibile contestare questo modulo. Tendo a preferire la funzione per la leggibilità. ma ci saranno casi in cui la procedura è migliore, ad esempio dove il chiamante ha già creato l'oggetto.


3

Sembra davvero puzzolente e senza vedere più contesto è impossibile dirlo con certezza. Potrebbero esserci due ragioni per farlo, sebbene ci siano alternative per entrambi.

Innanzitutto, è un modo conciso di implementare la conversione parziale o di lasciare il risultato con valori predefiniti se la conversione fallisce. Cioè, potresti avere questo:

public void ConvertFoo(Foo from, Foo to) {
    if (can't convert) {
        return;
    }
    ...
}

Foo a;
Foo b = DefaultFoo();
ConvertFoo(a, b);
// If conversion fails, b is unchanged

Naturalmente, questo di solito viene gestito utilizzando le eccezioni. Tuttavia, anche se le eccezioni devono essere evitate per qualsiasi motivo, c'è un modo migliore per farlo: il modello TryParse è un'opzione.

Un altro motivo è che potrebbe essere dovuto a motivi puramente coerenti, ad esempio fa parte di un'API pubblica in cui questo metodo viene utilizzato per tutte le funzioni di conversione per qualsiasi motivo (come altre funzioni di conversione con più output).

Java non è bravo a gestire più output - non può avere parametri di solo output come alcuni linguaggi o avere più valori di ritorno come altri - ma ancora, potresti usare oggetti return.

Il motivo della coerenza è piuttosto scadente, ma purtroppo potrebbe essere il più comune.

  • Forse i poliziotti di stile sul tuo posto di lavoro (o la tua base di codice) provengono da un background non Java e sono stati riluttanti a cambiare.
  • Il tuo codice potrebbe essere stato una porta da una lingua in cui questo stile è più idiomatico.
  • La tua organizzazione potrebbe aver bisogno di mantenere la coerenza delle API in diverse lingue, e questo era lo stile con il minimo comune denominatore (è stupido ma succede anche per Google ).
  • O forse lo stile aveva più senso in un lontano passato e si trasformava nella sua forma attuale (per esempio, avrebbe potuto essere il modello TryParse ma un predecessore ben intenzionato ha rimosso il valore di ritorno dopo aver scoperto che nessuno lo controllava affatto).

2

Il vantaggio del secondo modello è che forza il chiamante ad assumere la proprietà e la responsabilità per l'oggetto creato. Non vi è dubbio se il metodo abbia creato l'oggetto o lo abbia ottenuto da un pool riutilizzabile. Il chiamante sa di essere responsabile della durata e dello smaltimento del nuovo oggetto.

Gli svantaggi di questo metodo sono:

  1. Non può essere utilizzato come metodo di fabbrica, il chiamante deve conoscere il sottotipo esatto di OtherObjectcui ha bisogno e costruirlo in anticipo.
  2. Non può essere utilizzato con oggetti che richiedono parametri nel loro costruttore se tali parametri provengono MyObject. OtherObjectdeve essere costruibile senza saperlo MyObject.

La risposta si basa sulla mia esperienza in c #, spero che la logica si traduca in Java.


Penso che con la responsabilità di cui parli, crei anche un ciclo di vita degli oggetti "più difficile da vedere". In un sistema di metodo complesso preferirei una modifica diretta di un oggetto piuttosto che iniziare a esaminare tutto il metodo che stiamo passando.
CsBalazs Ungheria

Questo era quello che stavo pensando. Un tipo primitivo di Iniezione
Lyndon White,

Un altro vantaggio è se OtherObject è astratto o un'interfaccia. La funzione non ha idea del tipo da creare, ma il chiamante potrebbe.
user949300,

2

Data la semantica libera, myObjectToOtherObjectpotrebbe significare anche che si spostano alcuni dati dal primo oggetto al secondo o che li si converte completamente di nuovo, il secondo metodo sembra più adeguato.

Tuttavia, se il nome del metodo fosse Convert(come dovrebbe essere se osserviamo la parte "... esegui la conversione"), direi che il secondo metodo non avrebbe alcun senso. Non converti un valore in un altro valore già esistente, IMO.

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.