Risolvere i problemi che derivano dalla funzione diadica assertEquals (previsto, effettivo)


10

Dopo anni di programmazione da cowboy, ho deciso di prendere un libro su come scrivere un codice di buona qualità. Sto leggendo Clean Code di Robert Cecil Martin. Nel capitolo 3 (funzioni) c'è una sezione sulle funzioni diadiche. Ecco un estratto dal libro.

Anche ovvie funzioni diadiche come assertEquals(expected, actual)sono problematiche. Quante volte hai messo l'attuale dove dovrebbe essere previsto? I due argomenti non hanno un ordinamento naturale. L'ordinamento atteso ed effettivo è una convenzione che richiede pratica per imparare.

L'autore fa un punto convincente. Lavoro nell'apprendimento automatico e mi imbatto sempre in questo. Ad esempio, tutte le funzioni metriche nella libreria sklearn (probabilmente la libreria python più utilizzata nel campo) richiedono di stare attenti all'ordine degli input. Ad esempio sklearn.metrics.homogeneity_score prende come input labels_truee labels_pred. Ciò che questa funzione non è troppo rilevante, ciò che è rilevante è che se si cambia l'ordine degli ingressi non verrà generato alcun errore. In effetti, la commutazione degli ingressi equivale all'utilizzo di un'altra funzione nella libreria.

Tuttavia, il libro non prosegue dicendo una soluzione ragionevole per funzioni come assertEquals. Non riesco a pensare a una correzione per assertEqualso per funzioni che mi capita spesso di trovare come descritto sopra. Quali sono le buone pratiche per risolvere questo problema?

Risposte:


11

È bene essere consapevoli di un possibile problema anche quando non c'è soluzione, in questo modo si può essere vigili durante la lettura o la scrittura di tale codice. In questo esempio specifico, dopo un po 'ti abitui all'ordine degli argomenti.

Esistono modi a livello di lingua per prevenire qualsiasi confusione sull'ordine dei parametri: argomenti denominati. Questo purtroppo non è supportato in molti linguaggi con sintassi in stile C come Java o C ++. Ma in Python, ogni argomento può essere un argomento denominato. Invece di chiamare una funzione def foo(a, b)come foo(1, 2), possiamo fare foo(a=1, b=2). Molti linguaggi moderni come C # hanno una sintassi simile. La famiglia di lingue Smalltalk ha preso gli argomenti più lontani: non ci sono argomenti posizionali e tutto è nominato. Questo può portare a un codice che legge molto vicino al linguaggio naturale.

Un'alternativa più pratica è creare API che simulano argomenti con nome. Possono essere API fluide o funzioni di supporto che creano un flusso naturale. Il assertEquals(actual, expected)nome è confuso. Alcune alternative che ho visto:

  • assertThat(actual, is(equalTo(expected))): avvolgendo alcuni argomenti in tipi di helper, le funzioni di wrapping servono effettivamente come nomi di parametro. Nel caso specifico delle asserzioni di unit test, questa tecnica viene utilizzata dai matcher di Hamcrest per fornire un sistema di asserzioni estensibile e compostabile. Lo svantaggio qui è che si ottiene un sacco di annidamento e è necessario importare molte funzioni di supporto. Questa è la mia tecnica preferita in C ++.

  • expect(actual).to.be(expected): un'API fluente in cui la funzione di stringa chiama insieme. Mentre questo evita ulteriori nidificazioni, questo non è molto estensibile. Mentre trovo che le API fluenti leggano molto bene, la progettazione di una buona API fluente tende a fare molti sforzi nella mia esperienza, perché è necessario implementare classi aggiuntive per gli stati non terminali nella catena di chiamate. Questo sforzo paga davvero solo nel contesto di un IDE di completamento automatico che può suggerire le successive chiamate di metodo consentite.


4

Esistono diversi metodi per evitare questo problema. Uno che non ti costringe a cambiare il metodo che chiami:

Piuttosto che

assertEquals( 42, meaningOfLife() ); 

Uso

expected = 42;
actual = meaningOfLife();
assertEquals(expected, actual);

Questo costringe la convenzione a uscire dove è facile individuarli mentre vengono scambiati. Certo non è così facile da scrivere ma è facile da leggere.

Se è possibile modificare il metodo chiamato, è possibile utilizzare il sistema di digitazione per forzare l'utilizzo che è facile da leggere.

assertThat( meaningOfLife(), is(42) );

Alcune lingue ti consentono di evitare questo perché hanno parametri denominati:

assertEquals( expected=42, actual=meaningOfLife() );

Altri no, quindi li simuli:

assertEquals().expected(42).actual( meaningOfLife() );

Qualunque cosa tu faccia, trova un modo per renderlo ovvio, che è corretto quando letto. Non farmi indovinare qual è la convenzione. Mostramelo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.