Решение проблем, которые идут с двоичной функцией assertEquals (ожидается, актуально)

10

После многих лет ковбойского кодирования, я решил взять книгу о том, как писать код хорошего качества. Я читаю «Чистый код» Роберта Сесила Мартина. В главе 3 (функции) есть раздел о двоичных функциях. Вот выдержка из книги.

Даже очевидные двоичные функции вроде бы assertEquals(expected, actual)проблематичны. Сколько раз вы поместили фактическое, где ожидаемый должен быть? Два аргумента не имеют естественного порядка. Ожидаемый, фактический порядок - это соглашение, которое требует практики для изучения.

Автор делает убедительное замечание. Я работаю в машинном обучении и сталкиваюсь с этим все время. Например, все метрические функции в библиотеке sklearn (вероятно, наиболее используемая библиотека python в данной области) требуют, чтобы вы внимательно следили за порядком входных данных. В качестве примера sklearn.metrics.homogeneity_score принимает в качестве входных данных labels_trueи labels_pred. То, что эта функция делает не слишком уместно, уместно то, что если вы переключите порядок входов, ошибка не будет выдана. Фактически переключение входов эквивалентно использованию другой функции в библиотеке.

Однако в книге не говорится о разумном исправлении таких функций, как assertEquals. Я не могу придумать исправления для assertEqualsили для функций, с которыми я часто сталкиваюсь, как описано выше. Каковы хорошие практики для решения этой проблемы?

HBeel
источник

Ответы:

11

Хорошо знать о возможной проблеме, даже если ее не исправить - таким образом вы можете быть бдительными при чтении или написании такого кода. В этом конкретном примере вы просто привыкаете к порядку аргументов через некоторое время.

Существуют способы на уровне языка для предотвращения путаницы в отношении порядка параметров: именованные аргументы. К сожалению, это не поддерживается во многих языках с синтаксисом в стиле C, таких как Java или C ++. Но в Python каждый аргумент может быть именованным аргументом. Вместо вызова функции def foo(a, b)как foo(1, 2), мы можем сделать foo(a=1, b=2). Многие современные языки, такие как C #, имеют похожий синтаксис. Семейство языков Smalltalk наиболее полно использовало именованные аргументы: позиционных аргументов не существует, и все именовано. Это может привести к коду, который читается очень близко к естественному языку.

Более практичной альтернативой является создание API, которые имитируют именованные аргументы. Это могут быть свободные API или вспомогательные функции, которые создают естественный поток. assertEquals(actual, expected)Название сбивает с толку. Некоторые альтернативы, которые я видел:

  • assertThat(actual, is(equalTo(expected)))Оборачивая некоторые аргументы в вспомогательные типы, функции обертывания эффективно служат именами параметров. В конкретном случае утверждений модульного теста этот метод используется сопоставителями Hamcrest для предоставления расширяемой и составной системы утверждений. Недостатком здесь является то, что вы получаете много вложений и вам нужно импортировать много вспомогательных функций. Это мой метод в C ++.

  • expect(actual).to.be(expected): свободный API, где вы объединяете вызовы функций. Хотя это позволяет избежать дополнительного вложения, это не очень расширяемо. Хотя я нахожу, что беглые API-интерфейсы читаются очень хорошо, разработка хорошего беглого API-интерфейса обычно требует больших усилий из моего опыта, потому что вам нужно реализовать дополнительные классы для нетерминальных состояний в цепочке вызовов. Это усилие действительно окупается только в контексте автозаполнения IDE, который может предложить следующие разрешенные вызовы метода.

Амон
источник
4

Есть несколько способов избежать этой проблемы. Тот, который не заставляет вас менять метод, который вы вызываете:

Скорее, чем

assertEquals( 42, meaningOfLife() ); 

использование

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

Это выдвигает соглашение в открытую, где легко заметить, что они переключаются. Конечно, это не так просто написать, но легко прочитать.

Если вы можете изменить вызываемый метод, вы можете использовать систему набора для принудительного использования, которое легко читать.

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

Некоторые языки позволяют избежать этого, потому что они имеют именованные параметры:

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

Другие не так, чтобы вы имитировали их:

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

Все, что вы делаете, находит способ, который делает это очевидным, который является правильным при чтении. Не заставляй меня догадываться, что такое конвенция. Показать его мне.

candied_orange
источник