I metodi dovrebbero essere sempre chiusi per la modifica?

1

Diciamo che ho un metodo chiamato 'functionA' che viene chiamato da un servizio e che esegue un singolo pezzo di funzionalità, è facile da testare in quanto è una cosa sola.

Se qualche mese dopo arriva un nuovo requisito che richiede che venga chiamato un altro metodo 'functionB' direttamente dopo che 'functionA' è stata eseguita. Qual è l'approccio migliore per aggiungere functionB al mio codice.

per chiamare "functionB" alla fine del metodo "functionA":

functionA() {
    //do something..

    functionB();
}

functionB() {
    //do somehting...
}

Il problema è che quando I unit test functionA chiamerà functionB che sarà anche parte del test (quindi ora è più di un test di integrazione).

Oppure rifattora il codice con un nuovo metodo 'functionX' che chiama 'functionA' e quindi 'functionB'?

functionX() {
    functionA();
    functionB();
}

functionA() {
    //do something..   
}

functionB() {
    //do somehting...
}

Posso testare individualmente entrambi i metodi, ma ora ho modificato l'API.

Vedo il primo approccio così tanto nel codice legacy, è la migliore prassi per rendere sempre i metodi chiusi per la modifica o ci sono esempi quando è OK utilizzare il primo approccio?

    
posta user86834 29.03.2013 - 12:25
fonte

3 risposte

6

La mia regola generale è che quando si adatta un disegno, si dovrebbe mirare a rendere il design come si farebbe se si inizia da zero. Non rendere il tuo design non ottimale ora a causa delle caratteristiche di alcuni progetti precedenti. L'unico risultato sarà un gran casino di una base di codice.

Il fatto che sia necessario modificare questa funzione indica che si è verificato un problema con il progetto originale. Ha fatto un pessimo lavoro di adattamento ai requisiti. Idealmente, non dovresti modificare il tuo codice per adattarlo ai requisiti. (È un obiettivo un po 'irraggiungibile, ma è comunque l'obiettivo).

Quindi hai imparato qualcosa su come i requisiti tendono a cambiare. Usando questa conoscenza, come avrebbe progettato questo pezzo di codice per rendere più facile fare quel tipo di cambiamenti in futuro? Come puoi evitare di rompere i test unitari, facilitare l'implementazione?

Naturalmente, il tuo esempio è troppo vago per sapere quale sia l'approccio migliore. Ma direi: non aver paura di modificare la funzione, ma tieni presente che vuoi produrre progetti che non richiedono la modifica per usi futuri.

La situazione è leggermente diversa se si sta scrivendo il codice che altri progetti useranno. Per loro qualsiasi cambiamento di funzionalità, anche solo la correzione dei bug, può causare problemi. C'è bisogno di essere preso. Tuttavia, se è solo il tuo codice, non penso che questo sia un problema.

    
risposta data 29.03.2013 - 17:27
fonte
2

Ecco alcune situazioni in cui il primo approccio sarebbe stato sufficiente.

  • Se il "nuovo requisito" è un cambiamento nei dettagli dell'implementazione, cioè causato da una risposta a un'altra modifica di codice o al cambio di contratto di codice da qualche altra parte.

    • In questo caso, la modifica deve essere "fatturata" all'origine originale di tale modifica.
  • Se in effetti si tratta di una correzione di bug, ovvero se functionA avrebbe dovuto sempre chiamare functionB dovevano essere riscritti da zero.

    • Il bug fixing è un'eccezione esplicitamente consentita a questo principio.

In ogni caso, è utile ricordare la motivazione originale per questo principio:

  • Per evitare di causare cambiamenti di comportamento nei client esistenti che non hanno bisogno / aspettano il nuovo comportamento.
  • Ridurre lo sforzo (tramite test) di ristabilire che il cambio di codice non introduce errori nel sistema generale.

E ricorda anche che l'OCP viene tipicamente applicato durante la progettazione della classe.

Tipicamente, l'OCP influenza il design della classe incoraggiando i progettisti a pensare ai punti di estensione di un insieme di classi e renderli espliciti nel contratto o nell'interfaccia del codice.

Da questo modo di pensare, ci si potrebbe chiedere: è possibile che alcuni clienti abbiano preferito functionA a chiamare functionB e nel frattempo alcuni altri client hanno preferito diversamente? Ciò avrebbe richiesto un interruttore a levetta (nel senso più semplice) o un punto di estensione (nel senso OOP).

    
risposta data 29.03.2013 - 20:58
fonte
1

Una stretta aderenza a OCP implicherebbe che un nuovo oggetto con l'interfaccia functionA() con il comportamento corretto venga creato e sostituito per questo oggetto. Se l'ereditarietà o le interfacce sono utilizzate per garantire la sostituibilità (LSP) dipende dal sapore di OCP che preferisci, ma le idee sono le stesse.

I test dell'unità per il vecchio oggetto non cambierebbero e dovresti scrivere nuovi test unitari per il nuovo oggetto.

Se stai trovando difficile creare un nuovo oggetto che abbia tutto il comportamento necessario per sostituirlo per questo oggetto, ciò significa che i tuoi oggetti hanno troppe responsabilità (SRP).

Quanto rigorosamente vuoi applicare l'OCP (e gli altri principi SOLID) qui è una decisione che devi prendere.

    
risposta data 29.03.2013 - 21:20
fonte

Leggi altre domande sui tag