Un codice del genere è un "disastro ferroviario" (in violazione della Legge di Demetra)?


23

Scorrendo il codice che ho scritto, mi sono imbattuto nel seguente costrutto che mi ha fatto pensare. A prima vista, sembra abbastanza pulito. Sì, nel codice attuale il getLocation()metodo ha un nome leggermente più specifico che descrive meglio quale posizione ottiene.

service.setLocation(this.configuration.getLocation().toString());

In questo caso, serviceè una variabile di istanza di tipo noto, dichiarata all'interno del metodo. this.configurationderiva dall'essere passato al costruttore della classe ed è un'istanza di una classe che implementa un'interfaccia specifica (che impone un getLocation()metodo pubblico ). Quindi, this.configuration.getLocation()è noto il tipo restituito dell'espressione ; specificamente in questo caso, è un java.net.URL, mentre service.setLocation()vuole un String. Poiché i due tipi String e URL non sono direttamente compatibili, è necessaria una sorta di conversione per adattarsi al piolo quadrato nel foro rotondo.

Tuttavia , secondo la Legge di Demetra, come citato nel codice pulito , un metodo f nella classe C dovrebbe chiamare solo i metodi su C , oggetti creati da o passati come argomenti di f , e oggetti tenuti nelle variabili di istanza C . Tutto ciò che va oltre (il finale toString()nel mio caso particolare sopra, a meno che non si consideri un oggetto temporaneo creato come risultato dell'invocazione del metodo stesso, nel qual caso l'intera Legge sembra essere discutibile) è vietato.

Esiste un ragionamento valido per cui una chiamata come quella sopra, dati i vincoli elencati, dovrebbe essere scoraggiata o addirittura vietata? O sto solo diventando troppo pignolo?

Se dovessi implementare un metodo URLToString()che chiama semplicemente toString()un URLoggetto (come quello restituito da getLocation()) passato ad esso come parametro e restituisce il risultato, potrei avvolgere la getLocation()chiamata in esso per ottenere esattamente lo stesso risultato; efficacemente, sposterei la conversione di un passo verso l'esterno. Ciò in qualche modo lo renderebbe accettabile? (Mi sembra , intuitivamente, che non dovrebbe fare alcuna differenza in entrambi i modi, poiché tutto ciò che fa è spostare le cose un po '. Tuttavia, andando dalla lettera della Legge di Demetra come citata, sarebbe accettabile, dal momento che io opererebbe quindi direttamente su un parametro per una funzione.)

Farebbe differenza se si trattasse di qualcosa di leggermente più esotico rispetto toString()a un tipo standard?

Quando rispondi, tieni presente che alterare il comportamento o l'API del tipo di cui serviceè variabile la variabile non è pratico. Inoltre, per ragioni di argomento, supponiamo che anche l'alterazione del tipo di ritorno getLocation()sia impraticabile.

Risposte:


34

Il problema qui è la firma di setLocation. È tipizzato in modo rigoroso .

Elaborare: perché dovrebbe aspettarsi String? A Stringrappresenta qualsiasi tipo di dati testuali . Può potenzialmente essere tutt'altro che una posizione valida.

In realtà, ciò pone una domanda: che cos'è un luogo? Come faccio io so senza guardare nel codice? Se fosse un URLche ne saprei molto di più su ciò che questo metodo si aspetta.
Forse avrebbe più senso essere una classe personalizzata Location. Ok, all'inizio non saprei di che si tratta, ma ad un certo punto (probabilmente prima di scrivere this.configuration.getLocation()mi prenderei un minuto per capire cosa restituisce questo metodo).
Certo, in entrambi i casi ho bisogno di cercare altrove per capire cosa ci si aspetta. Tuttavia, in quest'ultimo caso, se capisco, cos'è un Location, posso usare la tua API, nel primo caso, se capisco, cos'è un String(cosa prevedibile), non so ancora cosa si aspetta l'API.

Nell'improbabile scenario che un luogo sia qualsiasi tipo di dati testuali, lo reinterpreterei con qualsiasi tipo di dato che abbia una rappresentazione testuale . Dato il fatto, che Objectha un toStringmetodo, potresti seguirlo, anche se questo richiede un bel salto di fiducia da parte dei clienti del tuo codice.

Inoltre, dovresti considerare che stai parlando di Java, che ha pochissime caratteristiche in base alla progettazione. Questo è ciò che ti obbliga a chiamare toStringalla fine.
Se prendi C # per esempio, che è anche tipicamente statico, allora potresti effettivamente omettere quella chiamata definendo il comportamento per un cast implicito .
Nei linguaggi tipizzati dinamicamente, come Objective-C, non hai nemmeno bisogno della conversione, perché finché il valore si comporta come una stringa, tutti sono felici.

Si potrebbe sostenere che l'ultima chiamata a toStringè meno una chiamata, in realtà solo il rumore generato dalla richiesta di esplicitazione di Java. Stai chiamando un metodo che ha qualsiasi oggetto Java, quindi non codifichi effettivamente alcuna conoscenza su una "unità distante" e quindi non violi il Principio della Minima Conoscenza. Non c'è modo, non importa cosa getLocationritorni, che non abbia un toStringmetodo.

Ma per favore, non usare le stringhe, a meno che non siano davvero la scelta più naturale (o a meno che tu non stia usando una lingua, che non ha nemmeno enumerazioni ... state lì).


Tenderei ad essere d'accordo con te, ma ha già detto che non può modificare l'API di servizio.
jhocking

1
@jhocking: non ha detto di non poterlo fare. Ha detto che non è pratico. Non sono d'accordo. Cercare di applicare le migliori pratiche al codice che aggira i difetti nella progettazione dell'API ha davvero senso solo se tali soluzioni alternative sono l'unica opzione possibile. Tuttavia, qui l'impostazione dell'API è l'opzione migliore. Rimuovere le carenze è sempre preferibile aggirarle.
back2dos,

bene +1 comunque per "è meno una chiamata che in realtà solo il rumore generato dalla richiesta di esplicito di Java"
jhocking

1
Darei questo +1 anche se solo per "digitato in modo". Nel mio codice, provo a passare tipi che esprimono il più possibile significato / intento, ma quando si lavora con librerie e API di terze parti, a volte si è bloccati usando ciò che gli autori di quell'API hanno deciso. E IIRC, una posizione può effettivamente essere "qualsiasi tipo di dati testuali", ma per l'implementazione su cui sto lavorando, una posizione non URL è totalmente priva di significato.
un CVn il

1
Per quanto riguarda la modifica dell'API; questo è un software per computer, quindi è praticamente sempre possibile cambiare le cose. (Se non altro puoi sempre scrivere uno strato di astrazione.) Tuttavia, a volte farlo comporta troppi sforzi sia a breve che a lungo termine per essere giustificabili. Quindi, potrebbe essere perfettamente possibile apportare tali modifiche, ma ancora poco pratico.
un CVn il

21

La legge di Demetra è una linea guida di progettazione, non una legge da seguire religiosamente.

Se ritieni che le tue classi siano abbastanza disaccoppiate di quanto non ci sia nulla di sbagliato nella linea, this.configuration.getLocation()soprattutto se, come dici, non è pratico cambiare altre parti dell'API.

Sono abbastanza sicuro che il cliente sarà perfettamente felice anche se farai a pezzi la Legge di Demetra, purché tu fornisca ciò che vuole e in tempo. Che non è una scusa per creare software dannoso, ma un promemoria per essere pragmatico durante lo sviluppo di software.


1
Poiché this.configurationè una variabile di istanza di un tipo di un'interfaccia nota, chiamare su di essa un metodo definito da quell'interfaccia sembra andare bene anche secondo un'interpretazione rigorosa. Sì, lo so che è una linea guida, proprio come KISS, SOLID, YAGNI e così via. Esistono pochissime "leggi" (in senso giuridico) nello sviluppo generale del software.
un CVn del

4
+1 per essere pragmatico ;-)
Treb

1
Non penso che la Legge del Dissennatore dovrebbe applicarsi anche in un caso come questo: la configurazione è fondamentalmente un contenitore. In ogni API su cui abbia mai lavorato, guardare all'interno dei container è un comportamento normale e previsto.
Loren Pechtel,

2

L'unica cosa che mi viene in mente di non scrivere codice come questo è cosa succede se this.configuration.getLocation()restituisce null? Dipende dal codice circostante e dal pubblico di destinazione che utilizza questo codice. Ma come dice Marco - la legge di Demetra è una regola empirica - è bene seguirla, ma non spezzarti la schiena facendo inutilmente.


Se this.configuration.getLocation()restituisse null, allora probabilmente (a) non saremmo mai arrivati ​​così lontano, o (b) nel frattempo è successo qualcosa di catastrofico, nel qual caso vorrei che il codice fallisse. Quindi, sebbene questo sia sicuramente un punto valido in generale, in questo caso particolare è abbastanza sicuro dire che non si applica. Inoltre, ciò che circonda tutto questo e molto altro è un gestore di eccezioni appositamente progettato per gestire questo tipo di guasti imprevisti.
un CVn il

2

Seguire rigorosamente la Legge di Demetra significherebbe che dovresti implementare un metodo nell'oggetto di configurazione come:

function getLocationAsString() {
  return getLocation().toString();
}

ma personalmente non mi preoccuperei perché questa è una situazione così piccola, e comunque le tue mani sono un po 'legate a causa dell'API che non puoi cambiare. Le regole di programmazione riguardano cosa dovresti fare quando hai una scelta, ma a volte non hai una scelta.


1
Questo è lo stesso suggerito qui: c2.com/cgi/wiki?TrainWreck "Crea un metodo che rappresenti il ​​comportamento desiderato e dica al cliente cosa fare. Questo segue il principio" dillo, non chiedere ".
heltonbiker
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.