Ridurre l'effetto ripple nel mio codice

2

Voglio scoprire quale parte del codice "allentato" non riesce a capire.

Supponiamo di avere un oggetto Person im utilizzando sia il mio codice client sia il codice server. Se cambio un certo campo in Person dal tipo int a int[] . Quello che ho trovato è che ci sarà un enorme effetto a catena nel mio codice quasi ovunque io abbia usato il campo o la classe causati da quella modifica.

Di quale principio non sono a conoscenza / aderente?

    
posta BiGGZ 16.02.2017 - 21:03
fonte

5 risposte

12

Come molte altre cose nello sviluppo del software, il grado di accoppiamento nei sistemi software è un compromesso tra obiettivi concorrenti.

Considera questo metodo di costruzione che accetta un Cliente come parametro.

public CustomerProcessor(Customer customer)

Come puoi vedere, sei strettamente legato alla classe Cliente. Puoi iniettarlo da qualche altra parte, ma sei limitato a quel tipo specifico.

Ora considera questo costruttore:

public CustomerProcessor(ICustomer customer)

Ora stiamo specificando un'interfaccia invece di una classe. Possiamo passare qualsiasi oggetto desideriamo a questo metodo di costruzione, purché tale oggetto sia conforme all'interfaccia ICustomer (incluso un mock o stub, a scopo di test).

E se lo facessimo?

public CustomerProcessor(string customer)

Cosa c'è in customer ? Una stringa JSON? Una stringa XML? Solo un nome? Potrebbe essere qualsiasi cosa. Il modo in cui viene gestito dipende dal codice presente nell'oggetto CustomerProcessor , ma se il tuo oggetto CustomerProcessor è stato effettivamente passato in qualche altra classe come ICustomerProcessor , potresti avere una famiglia di CustomerProcessors che potrebbe capire quale sia il formato e analizza correttamente la stringa automaticamente, inclusa una modifica da int a int[] . Ora sei ancora più sciolto.

Che cosa ti costa? Sicurezza del tipo in fase di compilazione, ovviamente. Quello, e significativamente aumentata la complessità. Ti costa anche le prestazioni.

Quindi, perché mai dovresti farlo? Bene, ci sono casi d'uso validi. Prendi in considerazione un sistema di sensori. Ogni sensore ha un valore o un insieme di valori che emette. Esistono centinaia di tipi di sensori diversi. Scriveresti un corso per ogni nuovo? Alcuni sistemi come questo passano semplicemente tali informazioni come stringhe lungo un bus dati. Ogni stringa ha un tipo di sensore incorporato, in modo che il sensore possa essere identificato correttamente e i suoi dati vengano decodificati correttamente dalla stringa.

Per quanto riguarda il tuo effetto a catena, la modifica di un membro di un tipo dovrebbe influire solo sul codice che effettivamente tocchi il tuo int . Quando decidi di passare da int a int[] , ti impegni a refactoring di tutto il codice nel tuo sistema che tocca int . Questo è by-design. Ha più a che fare con i sistemi di tipo che con il grado di accoppiamento tra i moduli software; dal momento che stai cambiando la tua dichiarazione fondamentale su cosa sia un cliente, il sistema di tipi si lamenterà quando il tuo vecchio codice non soddisfa le nuove aspettative.

    
risposta data 16.02.2017 - 21:50
fonte
6

Se stai esponendo il tipo specifico di variabili di istanza di un oggetto, non stai facendo molto per mezzo di nascondere le informazioni e / o incapsulamento . Questi sono gli approcci principali per ridurre al minimo l'effetto a catena nel codice modulare e orientato agli oggetti.

Filosoficamente, gli oggetti non sono solo una o record tipo di dati. Hanno lo scopo di nascondere le loro rappresentazioni di stato interne. Il loro stato è idealmente esposto solo attraverso metodi e interfacce ben definite.

Tanto per la teoria. In pratica, gli oggetti sono spesso usati come strutture di fantasia. Linguaggi come JavaScript, Perl e Python danno accesso diretto a valori e attributi di istanza, e gli sviluppatori spesso usano quell'accesso diretto, evitando le virtù di progetti puri e altamente disaccoppiati per codice di codice inferiore e giri di progettazione più veloci. Iniziano anche a programmare in anticipo, senza architetture chiare. Alcune metodologie Agile incoraggiano attivamente questo ethos di rush-right-in-and-start-prototyping (si veda ad esempio YAGNI ).

Queste cose non sono necessariamente cattive. In effetti, a conti fatti, sono linguaggi popolari ed efficaci, sono utilizzati con molto successo nel campo e Agile ha aiutato a sistemare alcune cose che erano sbagliate con i metodi pre-Agile. Ma, mentre ci sono dei vantaggi, ci sono anche veri e propri compromessi per "programmare in anticipo" e "fare la cosa più semplice che possa funzionare". Scoprendo che hai scelto il tipo o la struttura di dati errati - nel tuo caso, uno% scalare% co_de in cui avevi bisogno di un composto int - e poi dover riordinare le modifiche in tutto il codice man mano che rendi comprensibile la tua nuova comprensione. .questo, questo è il prezzo da pagare.

In pratica, questo non è un cambiamento nel tipo di dati che le tradizionali risposte OO puriste potrebbero davvero aiutare. Se avessi un metodo che restituisce un int[] che in seguito avrebbe restituito un int , beh, ciò richiederebbe cambiamenti a catena attraverso il tuo codice tanto quanto cambiare una variabile di istanza. Getter e setter e tutto il messaggio che passa sotto il sole non avrebbero evitato quell'effetto a catena. Il concetto alla base dei dati è cambiato e l'altro codice dovrà cambiare per rispecchiarlo.

Una maggiore riflessione sull'architettura dei dati in primo piano potrebbe averti detto che avevi effettivamente bisogno di un elenco non di un singolo valore. È necessario trovare un equilibrio tra Agile e Architects. Ma qualsiasi modalità di sviluppo che lodi sia la prototipazione esplorativa che il refactoring sperimenterà alcuni cambiamenti a catena. Pensare in anticipo e impiegare una buona incapsulamento può ridurre e mitigare, ma non eliminare, riscrivere il codice come refactoring.

    
risposta data 16.02.2017 - 22:04
fonte
4

Potresti violare il principio Separazione dei dubbi : link

Considera alcuni dei seguenti aspetti relativi al tuo codice e al design generale della tua applicazione:

  1. Stai duplicando funzionalità sul tuo client e server? cioè c'è davvero c'è bisogno di inviare quel particolare dato / oggetto sul tuo canale di comunicazione?

  2. Ti ritrovi spesso a violare la legge di Demeter nel tuo codice?

  3. Quanto bene il tuo codice aderisce al Single Responsibility Principle (SRP); in particolare per le classi o i metodi che usano quel int[] ? Ci sono classi o metodi che dipendono da quel campo nonostante facciano un sacco di cose che non lo coinvolgono?

    • Correlati: altri principi SOLID: link
  4. Il server espone i suoi dati interni inutilmente? In tal caso, valutare se sia possibile creare un'interfaccia ridotta utilizzando DTO separati (oggetti Data Transfer) o per creare un'interfaccia più granulare.

  5. Per quanto riguarda l'interfaccia client / server, è spesso preferibile avere una separazione tra "Comandi" e "Query"

    • Correlato: link
    • In poche parole, CQRS consiste semplicemente nel separare quelle istruzioni che potrebbero essere CRUD o cambiare lo stato di qualcosa, lontano dalle istruzioni di "sola lettura", come "GetData" o " QueryData ' (cioè istruzioni per server che ti aspetteresti di essere' pure 'senza effetti collaterali).
  6. Il tuo cliente sta facendo troppo? In particolare se il client è un'applicazione GUI, potrebbe essere una buona idea mantenerlo il più leggero possibile e mirare a ridurre al minimo la logica lato client.

  7. Quanto è modulare la tua applicazione in generale? La tua applicazione è costituita prevalentemente da una manciata di classi di tipo "coltellino svizzero" che fanno un sacco misto di cose ?

    • Hai metodi lunghi che combinano molte cose diverse? (logica, gestione degli errori, gestione delle eccezioni, persistenza, creazione di oggetti, pulizia delle risorse, convalida ..)
    • Hai classi con più metodi che in realtà non "appartengono" insieme (ad esempio, i metodi per leggere / scrivere i dati generalmente non appartengono alle stesse classi dei metodi che eseguono la validazione o i calcoli su quei dati)

In generale: - Considerare i requisiti per l'applicazione nel suo complesso e cercare opportunità per il refactoring e l'astrazione di comportamenti diversi l'uno dall'altro.

  • Guarda i requisiti per gli indizi sui nomi delle classi. Una classe Person non sembra molto utile perché è più simile al nome di un'entità (ad esempio qualcosa che rappresenta i dati dell'applicazione). Le classi che rappresentano il comportamento potrebbero essere più utili se si riferiscono al requisito che soddisfano, ad es. PersonSerialiser , PersonValidator , PersonSalaryCalculator , ecc.

Non c'è un proiettile d'argento per ridurre l'accoppiamento: il refactoring del codice esistente non è un compito rapido; se sei già in una situazione in cui determinati campi sono sparsi ovunque, cerca opportunità per incapsulare quel campo e ridurne l'esposizione.

A volte è possibile avere un refactoring abbastanza localizzato, ma se si soffre di un "codice spaghetti" acuto si potrebbe finire per ritrovarsi in una tana del coniglio che riprende grandi pezzi dell'applicazione.

    
risposta data 16.02.2017 - 21:56
fonte
3

L'accoppiamento lento riguarda la riduzione al minimo delle dipendenze tecniche. Hai reso i tuoi server e moduli client indipendenti gli uni dagli altri dando loro i propri dati e rendendoli inconsapevoli l'uno dell'altro. Se modifichi il tuo modello di dominio problematico, avrà effetto sia sul server che sul client e avrai più lavoro da fare di quanto avresti avuto con un sistema strettamente accoppiato perché avrebbe usato il codice condiviso. Non c'è paradosso qui.

L'accoppiamento lento è buono nel senso che rompere una parte non romperà le altre parti. Ma richiede un protocollo. E se lo cambi, tutto cadrà dopo tutto e / o avrà bisogno di rielaborazione.

    
risposta data 16.02.2017 - 21:55
fonte
2

In generale dovresti essere più bravo a nascondere gli interni di Person il resto del codice non dovrebbe sapere cosa sta succedendo all'interno. Questo è l'incapsulamento. Lascia che l'oggetto Person faccia il lavoro, "tell do not ask".

Ma se il resto del codice ha bisogno di accedere alle informazioni in un campo o il valore restituito da un metodo e questo viene modificato da un valore semplice a un elenco, allora ci sarà un effetto a catena, questo è ciò che accade quando si refactoring , non può essere aiutato.

Ma progettando meglio e non saltando la pistola e piazzando un campo / metodo per es. un numero di telefono nella classe'Person 'prima che tu abbia deciso se c'è un numero di telefono o un elenco di numeri di telefono (che sarebbe YAGNI (non avrai bisogno di esso)) puoi ridurre l'effetto considerevole.

Ma il refactoring si verificherà sempre e può essere un bel problema.

    
risposta data 16.02.2017 - 21:49
fonte

Leggi altre domande sui tag