Resolvendo os problemas que acompanham a função diádica assertEquals (esperado, real)

10

Após anos de codificação de cowboys, decidi pegar um livro sobre como escrever código de boa qualidade. Estou lendo o Código Limpo, de Robert Cecil Martin. No capítulo 3 (funções), há uma seção sobre funções diádicas. Aqui está um trecho do livro.

Mesmo funções diádicas óbvias como assertEquals(expected, actual)são problemáticas. Quantas vezes você colocou o real onde deveria estar o esperado? Os dois argumentos não têm ordem natural. A ordenação real esperada é uma convenção que requer prática para aprender.

O autor faz um argumento convincente. Eu trabalho em aprendizado de máquina e me deparei com isso o tempo todo. Por exemplo, todas as funções métricas na biblioteca sklearn (provavelmente a biblioteca python mais usada no campo) exigem que você tenha cuidado com a ordem das entradas. Como exemplo, sklearn.metrics.homogeneity_score assume como entradas labels_truee labels_pred. O que essa função faz não é muito relevante, o que é relevante é que, se você alternar a ordem das entradas, nenhum erro será gerado. De fato, alternar as entradas é equivalente a usar outra função na biblioteca.

No entanto, o livro não diz uma correção sensata para funções como assertEquals. Não consigo pensar em uma correção para assertEqualsou para funções que frequentemente encontro como a descrita acima. Quais são as boas práticas para resolver esse problema?

HBeel
fonte

Respostas:

11

É bom estar ciente de um possível problema, mesmo quando não há solução - dessa forma, você pode ficar atento ao ler ou escrever esse código. Neste exemplo específico, você se acostuma à ordem dos argumentos depois de um tempo.

Existem maneiras no nível da linguagem para evitar qualquer confusão sobre a ordem dos parâmetros: argumentos nomeados. Infelizmente, isso não é suportado em muitas linguagens com sintaxe no estilo C, como Java ou C ++. Mas no Python, todo argumento pode ser um argumento nomeado. Em vez de chamar uma função def foo(a, b)como foo(1, 2), podemos fazer foo(a=1, b=2). Muitas linguagens modernas como o C # têm sintaxe semelhante. A família de idiomas Smalltalk levou os argumentos nomeados mais longe: não há argumentos posicionais e tudo é nomeado. Isso pode levar ao código que lê muito próximo à linguagem natural.

Uma alternativa mais prática é criar APIs que simulam argumentos nomeados. Podem ser APIs fluentes ou funções auxiliares que criam um fluxo natural. O assertEquals(actual, expected)nome é confuso. Algumas alternativas que eu já vi:

  • assertThat(actual, is(equalTo(expected))): agrupando alguns argumentos em tipos auxiliares, as funções de agrupamento servem efetivamente como nomes de parâmetros. No caso específico de asserções de teste de unidade, essa técnica é usada pelos matchers Hamcrest para fornecer um sistema de asserção extensível e composível. A desvantagem aqui é que você recebe muitos aninhamentos e precisa importar muitas funções auxiliares. Esta é minha técnica básica em C ++.

  • expect(actual).to.be(expected): uma API fluente na qual você agrupa chamadas de função. Embora isso evite aninhamentos extras, isso não é muito extensível. Embora eu ache que as APIs fluentes leem muito bem, projetar uma API fluente tende a exigir muito esforço em minha experiência, porque você precisa implementar classes adicionais para estados não terminais na cadeia de chamadas. Esse esforço realmente compensa no contexto de um IDE de preenchimento automático que pode sugerir as próximas chamadas de método permitidas.

amon
fonte
4

Existem vários métodos para evitar esse problema. Um que não o force a alterar o método que você chama:

Ao invés de

assertEquals( 42, meaningOfLife() ); 

Usar

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

Isso força a convenção a se abrir, onde é fácil identificá-los sendo trocados. Claro que não é tão fácil escrever, mas é fácil ler.

Se você pode alterar o método que está sendo chamado, pode usar o sistema de digitação para forçar o uso fácil de ler.

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

Alguns idiomas permitem evitar isso porque eles nomearam parâmetros:

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

Outros não, então você os simula:

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

O que quer que você encontre, encontrará uma maneira que a torne óbvia e correta quando lida. Não me faça adivinhar qual é a convenção. Mostre-me.

candied_orange
fonte