2014-12-04 16 views
14

Voglio creare un metodo che concatena l'API in Go. In tutti gli esempi riesco a trovare che le operazioni concatenate sembrano sempre avere successo, cosa che non posso garantire. Cerco quindi di estenderli per aggiungere il valore di ritorno dell'errore.Vai al concatenamento del metodo e alla gestione degli errori

Se lo faccio come questo

package main 

import "fmt" 

type Chain struct { 
} 

func (v *Chain)funA() (*Chain, error) { 
    fmt.Println("A") 
    return v, nil 
} 
func (v *Chain)funB() (*Chain, error) { 
    fmt.Println("B") 
    return v, nil 
} 
func (v *Chain)funC() (*Chain, error) { 
    fmt.Println("C") 
    return v, nil 
} 

func main() { 
    fmt.Println("Hello, playground") 
    c := Chain{} 
    d, err := c.funA().funB().funC() // line 24 
} 

Il compilatore mi dice chain-err-test.go:24: multiple-value c.funA() in single-value context e non verrà compilato. Esiste un buon modo in modo che funcA, funcB e funcC possano segnalare un errore e interrompere quella catena?

+0

È possibile utilizzare il panico, ma ovviamente questo significa che dovrete recuperare su ciascun metodo o alla radice di esso. Puoi anche rendere l'oggetto 'Chain' statico con un errore e controllarlo per ciascun metodo. –

+0

@Not_a_Golfer true, ma mi chiedo se ci sia un buon modo idiomatico per farlo. Nel mio mondo le condizioni di errore sono ovunque e in alcuni luoghi (ad esempio, gli operatori come in http://www.golangpatterns.info/object-orientated/operators) tale concatenamento dovrebbe fornire una buona API. (il primo esempio di quella pagina non è una bella API - o hai un sacco di funzioni per tutti i tipi o un interruttore in cui i tipi errati non vengono rilevati dal compilatore) – johannes

+1

Ricordo una API migliore che include funzioni che restituiscono funzioni, come in http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis (fatto dopo http://commandcenter.blogspot.com.au/2014/01/self-referential-functions -e-design.html). Tuttavia non è esattamente "concatenare". – VonC

risposta

25

C'è un buon modo così funcA, funcB e funcC può segnalare un errore e fermare quella catena?

Sfortunatamente no, non esiste una buona soluzione al problema. I workaround sono sufficientemente complessi (aggiungendo nei canali di errore, ecc.) Che il costo supera il guadagno.

Il concatenamento del metodo non è un idioma in Go (almeno non per metodi che possono possibilmente errori). Questo non perché ci sia qualcosa di particolarmente sbagliato nelle catene di metodi, ma una conseguenza dell'idioma di restituire errori invece di farsi prendere dal panico. Le altre risposte sono soluzioni alternative, ma nessuna è idiomatica.

Posso chiedere, non è idiomatico concatenare metodi in Go a causa della conseguenza di errore di ritorno come facciamo in Go, o è più in generale una conseguenza di avere più ritorni di metodo?

Buona domanda, ma non è perché Go supporta più resi. Python supporta più ritorni, e Java può anche tramite una classe Tuple<T1, T2>; le catene di metodi sono comuni in entrambe le lingue. Il motivo per cui queste lingue possono farla franca è perché comunicano in modo idiomatico gli errori tramite eccezioni. Le eccezioni interrompono immediatamente la catena di metodi e passano al relativo gestore di eccezioni. Questo è il comportamento che gli sviluppatori di Go stavano specificamente cercando di evitare scegliendo invece di restituire gli errori.

+0

Risposta piacevole (+1). Posso chiedere, non è idiomatico mettere in catena i metodi in Go a causa della conseguenza dell'errore di ritorno come facciamo in Go, o è più in generale una conseguenza di avere più ritorni di metodo? – user1503949

+0

@ user1503949 Buona domanda. Vedi la mia modifica. – weberc2

+2

Grazie per questo; è un buon chiarimento. Se solo potessi inversione di voti due volte;) – user1503949

2

Se si ha il controllo sul codice e la firma funzione è identica si può scrivere qualcosa di simile:

func ChainCall(fns ...func() (*Chain, error)) (err error) { 
    for _, fn := range fns { 
     if _, err = fn(); err != nil { 
      break 
     } 
    } 
    return 
} 

playground

+0

Perché preoccuparsi di restituire l'oggetto '* Chain' originale? Non stai facendo nulla con questo. Ancora una volta, questa è una soluzione intelligente, ma è il tipo di intelligente che è meno leggibile rispetto alla scritta 'if err: = funX(); err! = nil {/ * fa qualcosa * /} 'per X = {A, B, C} rispettivamente. – weberc2

+0

@ weberc2 Principalmente perché ero troppo pigro per riscrivere il codice e volevo mostrare un esempio che avrebbe funzionato con il suo codice corrente;) – OneOfOne

-1

Che ne dite di questo approccio: creare una struttura che i delegati Chain e error e restituirlo al posto di due valori. es .:

package main 

import "fmt" 

type Chain struct { 
} 

type ChainAndError struct { 
    *Chain 
    error 
} 

func (v *Chain)funA() ChainAndError { 
    fmt.Println("A") 
    return ChainAndError{v, nil} 
} 

func (v *Chain)funB() ChainAndError { 
    fmt.Println("B") 
    return ChainAndError{v, nil} 
} 

func (v *Chain)funC() ChainAndError { 
    fmt.Println("C") 
    return ChainAndError{v, nil} 
} 

func main() { 
    fmt.Println("Hello, playground") 
    c := Chain{} 
    result := c.funA().funB().funC() // line 24 
    fmt.Println(result.error) 
} 
+1

Un errore qui non fermerà la catena dall'esecuzione, secondo la domanda originale. Ancora peggio, l'unico errore che conta è quello di 'funC()'; gli errori restituiti da 'funA()' e 'funB()' sono ignorati. – weberc2

+0

Che ne dici di accumulare gli errori in una sezione a scopo di indagine e, in ogni caso, considerare il risultato errato in quanto la sezione ha uno o più elementi? –

Problemi correlati