Metodo singolo con molti parametri e molti metodi che devono essere chiamati in ordine

15

Ho alcuni dati grezzi di cui ho bisogno per fare molte cose (spostarlo, ruotarlo, ridimensionarlo lungo un determinato asse, ruotarlo in una posizione finale) e non sono sicuro quale sia il modo migliore per farlo per mantenere il codice leggibilità. Da un lato, posso creare un singolo metodo con molti parametri (10+) per fare ciò di cui ho bisogno, ma questo è un incubo di lettura del codice. D'altra parte, potrei fare più metodi con 1-3 parametri ciascuno, ma questi metodi dovrebbero essere chiamati in un ordine molto specifico per ottenere il risultato corretto. Ho letto che è meglio che i metodi facciano una cosa e la facciano bene, ma sembra che abbia molti metodi che devono essere chiamati per aprire il codice per i bug difficili da trovare.

Esiste un paradigma di programmazione che potrei usare per minimizzare i bug e rendere il codice più facile da leggere?

    
posta tomsrobots 01.03.2015 - 02:15
fonte

4 risposte

20

Attenzione ai accoppiamento temporale . Tuttavia, questo non è sempre un problema.

Se è necessario eseguire i passaggi in ordine, ne consegue che la fase 1 produce alcuni oggetti richiesti per la fase 2 (ad esempio un flusso di file o un'altra struttura di dati). Solo questo richiede che la seconda funzione deve essere chiamata dopo la prima, non è nemmeno possibile chiamarli nell'ordine sbagliato accidentalmente.

Suddividendo le tue funzionalità in pezzi di dimensioni ridotte, ogni parte è più facile da capire e sicuramente più facile da testare in isolamento. Se disponi di una funzione da 100 linee di grandi dimensioni e di qualcosa nelle pause intermedie, in che modo il tuo test fallito ti dice cosa c'è che non va? Se uno dei cinque metodi di linea si rompe, il test dell'unità guastato ti indirizza immediatamente verso l'unico pezzo di codice che richiede attenzione.

Ecco come il codice complesso dovrebbe apparire:

public List<Widget> process(File file) throws IOException {
  try (BufferedReader in = new BufferedReader(new FileReader(file))) {
    List<Widget> widgets = new LinkedList<>();
    String line;
    while ((line = in.readLine()) != null) {
      if (isApplicable(line)) { // Filter blank lines, comments, etc.
        Ore o = preprocess(line);
        Ingot i = smelt(o);
        Alloy a = combine(i, new Nonmetal('C'));
        Widget w = smith(a);
        widgets.add(w);
      }
    }
    return widgets;
  }
}

In qualsiasi momento durante il processo di conversione dei dati grezzi in un widget finito, ciascuna funzione restituisce qualcosa richiesto dal passaggio successivo del processo. Non si può formare una lega dalle scorie, bisogna prima annusarla (purificarla). Non si può creare un widget senza il permesso appropriato (ad esempio acciaio) come input.

I dettagli specifici di ciascun passaggio sono contenuti in singole funzioni che possono essere testate: anziché testare l'intero processo di estrazione di rocce e creare widget, testare ogni passaggio specifico. Ora hai un modo semplice per assicurarti che se il tuo processo "crea widget" fallisce, puoi restringere il motivo specifico.

Oltre ai vantaggi del test e alla dimostrazione della correttezza, scrivere codice in questo modo è molto più facile da leggere. Nessuno può capire un elenco di parametri enorme . Scomposizione in piccoli pezzi e mostra cosa significa ogni pezzo: questo è grokkable .

    
risposta data 01.03.2015 - 07:24
fonte
10

L'argomento "deve essere eseguito in ordine" è discutibile dal momento che praticamente tutto il codice deve essere eseguito nell'ordine corretto. Dopotutto, non puoi scrivere su un file, quindi aprirlo e chiuderlo, vero?

Dovresti concentrarti su ciò che rende il tuo codice il più gestibile. Questo di solito significa scrivere le funzioni che sono piccole e facilmente comprensibili. Ogni funzione dovrebbe avere un unico scopo e non avere effetti collaterali imprevisti.

    
risposta data 01.03.2015 - 02:28
fonte
5

Vorrei creare un ImageProcesssor «(o qualsiasi altro nome si adatti al tuo progetto) e un oggetto di configurazione ProcessConfiguration , che contiene tutti i parametri necessari.

 ImageProcessor p = new ImageProcessor();

 ProcessConfiguration config = new processConfiguration().setTranslateX(100)
                                                         .setTranslateY(100)
                                                         .setRotationAngle(45);
 p.process(image, config);

All'interno del processore di immagini incapsula l'intero processo dietro un mehtod process()

public class ImageProcessor {

    public Image process(Image i, ProcessConfiguration c){
        Image processedImage=i.getCopy();
        shift(processedImage, c);
        rotate(processedImage, c);
        return processedImage;
    }

    private void rotate(Image i, ProcessConfiguration c) {
        //rotate
    }

    private void shift(Image i, ProcessConfiguration c) {
        //shift
    }
}

Questo metodo chiama i metodi di trasformazione nell'ordine corretto shift() , rotate() . Ogni metodo ottiene i parametri appropriati dalla ProcessConfiguration passata.

public class ProcessConfiguration {

    private int translateX;

    private int rotationAngle;

    public int getRotationAngle() {
        return rotationAngle;
    }

    public ProcessConfiguration setRotationAngle(int rotationAngle){
        this.rotationAngle=rotationAngle;
        return this;
    }

    public int getTranslateY() {
        return translateY;
    }

    public ProcessConfiguration setTranslateY(int translateY) {
        this.translateY = translateY;
        return this;
    }

    public int getTranslateX() {
        return translateX;
    }

    public ProcessConfiguration setTranslateX(int translateX) {
        this.translateX = translateX;
        return this;
    }

    private int translateY;

}

Ho usato interfacce fluide

public ProcessConfiguration setRotationAngle(int rotationAngle){
    this.rotationAngle=rotationAngle;
    return this;
}

che consente l'inizializzazione nifty (come visto sopra).

L'ovvio vantaggio, che incapsula i parametri necessari in un oggetto. Le tue firme dei metodi diventano leggibili:

private void shift(Image i, ProcessConfiguration c)

Si tratta di spostamento di un'immagine e i parametri dettagliati sono in qualche modo configurati .

In alternativa, puoi creare una ProcessingPipeline :

public class ProcessingPipeLine {

    Image i;

    public ProcessingPipeLine(Image i){
        this.i=i;
    };

    public ProcessingPipeLine shift(Coordinates c){
        shiftImage(c);
        return this;
    }

    public ProcessingPipeLine rotate(int a){
        rotateImage(a);
        return this;
    }

    public Image getResultingImage(){
        return i;
    }

    private void rotateImage(int angle) {
        //shift
    }

    private void shiftImage(Coordinates c) {
        //shift
    }

}

Una chiamata di metodo a un metodo processImage crea un'istanza di tale pipeline e rende trasparente cosa e in quale ordine viene eseguito: spostamento , rotazione

public Image processImage(Image i, ProcessConfiguration c){
    Image processedImage=i.getCopy();
    processedImage=new ProcessingPipeLine(processedImage)
            .shift(c.getCoordinates())
            .rotate(c.getRotationAngle())
            .getResultingImage();
    return processedImage;
}
    
risposta data 01.03.2015 - 10:01
fonte
3

Hai preso in considerazione l'utilizzo di qualche tipo di currying ? Immagina di avere una classe Processee e una classe Processor :

class Processor
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T1 a1, T2 a2)
    {
        // Process using a1
        // then process using a2
    }
}

Ora puoi sostituire la classe Processor di due classi Processor1 e Processor2 :

class Processor1
{
    private final Processee _processee;

    public Processor1(Processee p)
    {
        _processee = p;
    }

    public Processor2 process(T1 a1)
    {
        // Process using argument a1

        return new Processor2(_processee);
    }
}

class Processor2
{
    private final Processee _processee;

    public Processor(Processee p)
    {
        _processee = p;
    }

    public void process(T2 a2)
    {
        // Process using argument a2
    }
}

Puoi quindi chiamare le operazioni nel giusto ordine usando:

new Processor1(processee).process(a1).process(a2);

Puoi applicare questo modello più volte se ne hai più di due parametri. Puoi anche raggruppare gli argomenti come vuoi, ad esempio non lo fai è necessario che ogni metodo process richieda esattamente un argomento.

    
risposta data 01.03.2015 - 10:16
fonte

Leggi altre domande sui tag