Il codice è simile a un "naufragio del treno" (in violazione della legge di Demeter)?

23

Navigando attraverso un codice che ho scritto, ho trovato il seguente costrutto che mi ha fatto riflettere. A prima vista, sembra abbastanza pulito. Sì, nel codice effettivo il metodo getLocation() ha un nome leggermente più specifico che descrive meglio esattamente quale posizione ottiene.

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

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

Tuttavia , secondo la legge di Demeter come citato in Clean Code , un metodo f nella classe C dovrebbe chiamare solo metodi su C , oggetti creati o passati come argomenti a f e oggetti tenuti in variabili di istanza di C . Qualunque cosa al di là di questo (l'ultimo toString() nel mio caso particolare sopra, a meno che tu non consideri un oggetto temporaneo creato come risultato dell'invocazione del metodo stesso, nel qual caso l'intera Legge sembra essere discutibile) non è consentito.

Esiste un ragionamento valido per cui una chiamata come sopra, dati i vincoli elencati, dovrebbe essere scoraggiata o addirittura non consentita? O sono semplicemente troppo pignolo?

Se dovessi implementare un metodo URLToString() che chiama semplicemente toString() su un oggetto URL (come quello restituito da getLocation() ) passato ad esso come parametro, e restituisce il risultato, potrei avvolgere il getLocation() chiama per ottenere esattamente lo stesso risultato; efficacemente, vorrei solo spostare la conversione di un passo verso l'esterno. Questo in qualche modo lo renderebbe accettabile? (Mi sembra per me, intuitivamente, che non dovrebbe fare alcuna differenza in entrambi i modi, dal momento che tutto ciò che fa è spostare le cose un po '. Tuttavia, seguendo la lettera della Legge di Demetra come citato, sarebbe accettabile, dal momento che poi opererei direttamente su un parametro per una funzione.)

Farebbe qualche differenza se si trattasse di qualcosa di leggermente più esotico di chiamare toString() su un tipo standard?

Quando rispondi, tieni presente che alterare il comportamento o l'API del tipo di cui fa la variabile service non è pratico. Inoltre, per amor di discussione, diciamo che anche alterare il tipo di ritorno di getLocation() è impraticabile.

    
posta a CVn 21.09.2011 - 15:43
fonte

4 risposte

33

Il problema qui è la firma di setLocation . È digitato in modo stringato .

Per elaborare: Perché dovrebbe aspettarsi String ? Un String rappresenta qualsiasi tipo di dati testuali . Può potenzialmente essere tutto tranne che una posizione valida.

In realtà, questo pone una domanda: che cos'è una posizione? Come si sa I senza guardare nel codice? Se fosse un URL di quanto ne saprei molto di più su ciò che questo metodo si aspetta.
Forse avrebbe più senso che fosse una classe personalizzata Location . Ok, non lo saprei all'inizio, che cos'è, ma a un certo punto (probabilmente prima di scrivere this.configuration.getLocation() vorrei prendere un minuto per capire di cosa tratta questo metodo).
Certo, in entrambi i casi ho bisogno di cercare un altro posto per capire, cosa è previsto. Tuttavia, nel secondo caso, se capisco, che cos'è Location , posso usare la tua API, nel primo caso, se ho capito, che cos'è String (che può essere previsto), non lo faccio ancora sapere cosa si aspetta la tua API.

Nello scenario improbabile, che una posizione sia qualsiasi tipo di dati testuali la reinterpreterei a qualsiasi tipo di dati, che ha una rappresentazione testuale . Dato che Object ha un metodo toString , potresti andare con quello, sebbene ciò richieda un vero e proprio sfiducia dai client del tuo codice.

Dovresti anche considerare che stai parlando di Java, che ha pochissime funzionalità di design. Questo è ciò che ti costringe a chiamare effettivamente toString alla fine.
Se prendi C # per esempio, che è anche tipizzato staticamente, allora potresti effettivamente omettere quella chiamata da definizione del comportamento per un cast implicito .
In 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 obiettare che l'ultima chiamata a toString è meno una chiamata, piuttosto che solo il rumore generato dalla richiesta di Java di esplicitezza. Si sta chiamando un metodo, che ha qualsiasi oggetto Java, quindi non si codifica effettivamente alcuna conoscenza su una "unità distante" e quindi non si viola il Principio della Minima Conoscenza. Non c'è modo, a prescindere da cosa restituisca getLocation , che non abbia un metodo toString .

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 un linguaggio, che non abbia nemmeno enumerazioni ... stato lì).

    
risposta data 21.09.2011 - 17:25
fonte
20

La legge di Demeter è una linea guida del design, non una legge da seguire religiosamente.

Se ritieni che le tue classi siano abbastanza disgiunte che non c'è nulla di sbagliato nella riga this.configuration.getLocation() , specialmente se, come dici tu, non è pratico modificare altre parti dell'API.

Sono abbastanza sicuro che il cliente sarà perfettamente felice anche se farai a pezzi la legge di Demeter, purché tu rispetti ciò che vuole e in tempo. Quale non è una scusa per fare software cattivo, ma un promemoria per essere pragmatico nello sviluppo del software.

    
risposta data 21.09.2011 - 16:10
fonte
2

L'unica cosa che posso pensare 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 Demeter è una regola empirica - è bello seguirla, ma non romperti le spalle inutilmente.

    
risposta data 21.09.2011 - 17:21
fonte
2

Seguire rigorosamente la legge di Demeter 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 ciò che dovresti fare quando hai una scelta, ma a volte non hai scelta.

    
risposta data 21.09.2011 - 17:50
fonte

Leggi altre domande sui tag