Вирішення завдань, пов'язаних із затвердженням діадичної функції (рівності (очікувані, фактичні)


10

Після років кодування ковбоя я вирішив забрати книгу про те, як написати код хорошої якості. Я читаю чистий код Роберта Сесіля Мартіна. У главі 3 (функції) є розділ про діадичні функції. Ось уривок із книги.

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

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

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

Відповіді:


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() );

Що б ви не знаходили, ви знайдете спосіб, який дає зрозуміти, що правильно під час читання. Не змушуйте мене гадати, що таке конвенція. Покажи це мені.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.