Rozwiązywanie problemów związanych z funkcją dyadyczną assertEquals (oczekiwana, rzeczywista)

10

Po latach kodowania kowbojskiego postanowiłem wybrać książkę o tym, jak napisać kod dobrej jakości. Czytam Clean Code autorstwa Roberta Cecila Martina. W rozdziale 3 (funkcje) znajduje się sekcja na temat funkcji dyadycznych. Oto fragment książki.

assertEquals(expected, actual)Problematyczne są nawet oczywiste funkcje dyadyczne . Ile razy umieściłeś rzeczywiste miejsce, gdzie powinno być oczekiwane? Te dwa argumenty nie mają naturalnego uporządkowania. Oczekiwane, rzeczywiste porządkowanie jest konwencją, która wymaga praktyki.

Autor podkreśla przekonujący punkt. Pracuję w uczeniu maszynowym i cały czas się z tym spotykam. Na przykład wszystkie funkcje metryczne w bibliotece sklearn (prawdopodobnie najczęściej używana biblioteka Pythona w tej dziedzinie) wymagają uważności na kolejność danych wejściowych. Jako przykład sklearn.metrics.homogeneity_score przyjmuje jako dane wejściowe labels_truei labels_pred. Ta funkcja nie jest zbyt istotna, ważne jest to, że jeśli zmienisz kolejność wejść, nie zostanie zgłoszony błąd. W rzeczywistości przełączanie wejść jest równoznaczne z użyciem innej funkcji w bibliotece.

Jednak książka nie mówi sensownej poprawki dla funkcji takich jak assertEquals. Nie mogę wymyślić poprawki dla assertEqualslub funkcji, które często spotykam, jak ta opisana powyżej. Jakie są dobre praktyki, aby rozwiązać ten problem?

HBeel
źródło

Odpowiedzi:

11

Warto zdawać sobie sprawę z możliwego problemu, nawet gdy nie ma poprawki - w ten sposób możesz zachować czujność podczas czytania lub pisania takiego kodu. W tym konkretnym przykładzie po prostu przyzwyczajasz się do kolejności argumentów.

Istnieją sposoby na poziomie języka, aby uniknąć nieporozumień dotyczących kolejności parametrów: nazwane argumenty. To niestety nie jest obsługiwane w wielu językach ze składnią w stylu C, taką jak Java lub C ++. Ale w Pythonie każdy argument może być argumentem nazwanym. Zamiast wywoływać funkcję def foo(a, b)as foo(1, 2), możemy to zrobić foo(a=1, b=2). Wiele współczesnych języków, takich jak C #, ma podobną składnię. Rodzina języków Smalltalk najdalej przyjęła nazwane argumenty: nie ma żadnych argumentów pozycyjnych i wszystko nazywa się. Może to prowadzić do kodu, który czyta się bardzo blisko języka naturalnego.

Bardziej praktyczną alternatywą jest tworzenie interfejsów API symulujących nazwane argumenty. Mogą to być płynne interfejsy API lub funkcje pomocnicze, które tworzą naturalny przepływ. assertEquals(actual, expected)Nazwa jest niejasna. Niektóre alternatywy, które widziałem:

  • assertThat(actual, is(equalTo(expected))): zawijając niektóre argumenty w typach pomocników, funkcje zawijania skutecznie służą jako nazwy parametrów. W konkretnym przypadku stwierdzeń z testów jednostkowych technika ta jest stosowana przez osoby dobierające Hamcrest w celu zapewnienia rozszerzalnego i składanego systemu asercji. Wadą jest to, że masz dużo zagnieżdżania i musisz zaimportować wiele funkcji pomocniczych. To jest moja technika w C ++.

  • expect(actual).to.be(expected): płynny interfejs API, w którym ciągi funkcji wywołuje się razem. Chociaż pozwala to uniknąć dodatkowego zagnieżdżania, nie jest to jednak bardzo rozszerzalne. Chociaż uważam, że płynne interfejsy API czytają bardzo dobrze, projektowanie dobrego płynnego interfejsu API zwykle wymaga dużego wysiłku, ponieważ musisz zaimplementować dodatkowe klasy dla stanów nieterminalnych w łańcuchu połączeń. Wysiłek ten naprawdę się opłaca tylko w kontekście autouzupełniania IDE, które może sugerować kolejne dozwolone wywołania metod.

amon
źródło
4

Istnieje kilka metod uniknięcia tego problemu. Taki, który nie zmusza cię do zmiany metody, którą wywołujesz:

Zamiast

assertEquals( 42, meaningOfLife() ); 

Posługiwać się

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

To wymusza konwencję na otwartej przestrzeni, gdzie łatwo jest zauważyć, jak się zmieniają. Jasne, że nie jest tak łatwo pisać, ale jest łatwy do odczytania.

Jeśli możesz zmienić wywoływaną metodę, możesz użyć systemu pisania, aby wymusić użycie, które jest łatwe do odczytania.

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

Niektóre języki pozwalają tego uniknąć, ponieważ nazwali parametry:

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

Inni tego nie symulują:

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

Cokolwiek zrobisz, znajdź sposób, który sprawi, że stanie się oczywiste, co jest poprawne po przeczytaniu. Nie każ mi zgadywać, jaka jest konwencja. Pokaż mi to.

candied_orange
źródło