Чому кодування імен аргументів у назвах функцій не зустрічається частіше? [зачинено]


47

У чистому коді автор наводить приклад

assertExpectedEqualsActual(expected, actual)

проти

assertEquals(expected, actual)

колишній заявляв, що він більш чіткий, оскільки він усуває необхідність пам’ятати, куди йдуть аргументи, і можливе неправильне використання, яке випливає з цього. Тим не менш, я ніколи не бачив прикладу колишньої схеми іменування в жодному коді і не бачив останнього весь час. Чому кодери не приймають перше, якщо воно, як стверджує автор, чіткіше, ніж друге?


9
Я думаю, що це чудове питання для дискусії. Але не те, на що можна відповісти об'єктивною відповіддю. Тож це питання може бути закритим як на основі думки.
Ейфорія

54
Багато людей заперечують проти першої схеми іменування, оскільки вона надмірно багатослівна , далеко поза межами того, де це сприятиме ясності. Тим більше assertEquals(), що цей метод використовується сотні разів у кодовій базі, тому можна очікувати, що читачі ознайомляться з умовою один раз. Різні рамки мають різні умови (наприклад, (actual, expected) or an agnostic (ліворуч, праворуч) `), але, на мій досвід, це щонайменше незначне джерело плутанини.
амон

5
Оскільки виграш настільки малий, порівняно з його перевагами, що будь-який здоровий, певно, пішов би піти. Якщо ви хочете більш вільний підхід, вам слід спробувати assert(a).toEqual(b)(навіть якщо ІМО все ще є багатослівним), де ви можете скористатися кількома пов'язаними твердженнями.
Адріано Репетті

18
Як ми можемо знати, що фактичні та очікувані значення? Невже так і має бути assertExpectedValueEqualsActualValue? Але чекати, як ми пам'ятаємо , чи використовує він ==або .equalsчи Object.equals? Чи має бути assertExpectedValueEqualsMethodReturnsTrueWithActualValueParameter?
користувач253751

6
Зважаючи на те, що для цього конкретного методу порядок двох аргументів не має значення, здається, що це поганий приклад для вибору переваг цієї схеми іменування.
Стівен Рандс

Відповіді:


66

Тому що це більше набирати та більше читати

Найпростіша причина полягає в тому, що люди люблять вводити менше, а кодування цієї інформації означає більше вводити текст. Читаючи його, мені щоразу доводиться читати всю річ, навіть якщо я знайомий з тим, яким повинен бути порядок аргументів. Навіть якщо він не знайомий з порядком аргументів ...

Багато розробників використовують IDE

Ідентифікатори IDE часто забезпечують механізм перегляду документації для певного методу за допомогою наведення курсору чи за допомогою комбінації клавіш. Через це назви параметрів завжди під рукою.

Кодування аргументів вводить дублювання та сполучення

Імена параметрів повинні вже документувати, якими вони є. Записуючи імена у назві методу, ми дублюємо цю інформацію і в підписі методу. Ми також створюємо зв'язок між назвою методу та параметрами. Скажіть expectedі actualзаплутайте наших користувачів. Перехід від assertEquals(expected, actual)до assertEquals(planned, real)не вимагає зміни коду клієнта за допомогою функції. Перехід від assertExpectedEqualsActual(expected, actual)до assertPlannedEqualsReal(planned, real)означає неоднозначну зміну API. Або ми не змінюємо ім'я методу, що швидко стає заплутаним.

Використовуйте типи замість неоднозначних аргументів

Справжня проблема полягає в тому, що у нас є неоднозначні аргументи, які легко перемикаються, оскільки вони одного типу. Замість цього ми можемо використовувати нашу систему типу та наш компілятор для забезпечення правильного порядку:

class Expected<T> {
    private T value;
    Expected(T value) { this.value = value; }
    static Expected<T> is(T value) { return new Expected<T>(value); }
}

class Actual<T> {
    private T value;
    Actual(T value) { this.value = value; }
    static Actual<T> is(T value) { return new Actual<T>(value); }
}

static assertEquals(Expected<T> expected, Actual<T> actual) { /* ... */ }

// How it is used
assertEquals(Expected.is(10), Actual.is(x));

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


5
Добре, якщо ви використовуєте IDE, ви маєте назви параметрів у довідці на повітряній кулі; якщо ви не використовуєте його, запам'ятовування назви функції еквівалентно запам'ятовуванню аргументів, тому нічого не виходить.
Пітер - Відновіть Моніку

29
Якщо ви заперечуєте проти assertExpectedEqualsActual"тому, що це більше набирати та більше читати", то як ви можете виступати за це assertEquals(Expected.is(10), Actual.is(x))?
ruakh

9
@ruakh це не можна порівняти. assertExpectedEqualsActualвсе-таки вимагає від програміста дбайливості вказувати аргументи в потрібному порядку. assertEquals(Expected<T> expected, Actual<T> actual)Підпис використовується компілятором для забезпечення правильного використання, що є зовсім іншим підходом. Ви можете оптимізувати цей підхід для стислості, наприклад expect(10).equalsActual(x), але це не питання ...
Холгер

6
Також у цьому конкретному випадку (==) порядок аргументів фактично не має значення для кінцевого значення. Порядок має значення лише для побічного ефекту (повідомлення про помилку). Під час замовлення питань це може мати певний сенс. Наприклад, strcpy (dest, src).
Крістіан Н

1
Не можу погодитись більше, особливо з частиною щодо дублювання та з’єднання ... Якщо кожен раз, коли параметр функції змінює своє ім'я, ім'я функції також має змінюватися, вам доведеться простежити всі звичаї цієї функції та змінити їх також ... Це призвело б до завантаження змін для мене, моєї команди та всіх інших, що використовують наш код як залежність ...
mrsmn

20

Ви запитуєте про тривалу дискусію в програмуванні. Скільки багатослів’я добре? Як загальну відповідь, розробники виявили, що зайвого багатослівного іменування аргументів не варто.

Багатослівність не завжди означає більшу чіткість. Розглянемо

copyFromSourceStreamToDestinationStreamWithoutBlocking(fileStreamFromChoosePreferredOutputDialog, heuristicallyDecidedSourceFileHandle)

проти

copy(output, source)

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

Існує довга історія додавання багатослівності. Наприклад, є загальнопопулярна " угорська нотація ", яка дала нам чудові назви lpszName. Це, як правило, впало в сторону загального програмного забезпечення. Однак додавання символів в імена змінних - членів (наприклад , mNameчи m_Nameабо name_) продовжує мати популярність в деяких колах. Інші це повністю відкинули. Мені трапляється працювати над кодовою базою фізичного моделювання, чиї документи стилю кодування вимагають, щоб будь-яка функція, яка повертає вектор, повинна вказувати кадр вектора у виклику функції ( getPositionECEF).

Можливо, вас зацікавлять деякі мови, які популярні Apple. Objective-C включає назви аргументів як частину підпису функції (Функція [atm withdrawFundsFrom: account usingPin: userProvidedPin]записана в документації як withdrawFundsFrom:usingPin:. Ось назва функції). Свіфт прийняв подібний набір рішень, вимагаючи від вас ввести імена аргументів у функції call ( greet(person: "Bob", day: "Tuesday")).


13
Всі інші пункти в сторону, це було б набагато легше прочитати, якби вони copyFromSourceStreamToDestinationStreamWithoutBlocking(fileStreamFromChoosePreferredOutputDialog, heuristicallyDecidedSourceFileHandle)були написані copy_from_source_stream_to_destination_stream_without_blocking(file_stream_from_choose_preferred_output_dialog, heuristically_decided_source_file_handle). Подивіться, наскільки простіше це було ?! Це тому, що занадто легко пропустити невеликі зміни на середині цієї гумористичноїблагополучної слова, і потрібно більше часу, щоб визначити, де межі слова. Розгублені плутанини.
tchrist

1
Синтаксис obj-C withdrawFundsFrom: account usingPin: userProvidedPinфактично запозичений у SmallTalk.
joH1

14
@tchrist будьте обережні, будучи впевненим, що ви маєте рацію щодо тем святих воєн. Інша сторона не завжди помиляється.
Корт Аммон

3
@tchrist Addingunderscoresnakesthingseasiertoreadnotharderasyouseeманіпулює аргументом. Тут відповідь використовувала великі літери, які ви опускаєте. AddingCapitalizationMakesThingsEasyEnoughToReadAsYouCanSeeHere. По-друге, 9 разів з 10 ім’я ніколи не повинно перевищувати [verb][adjective][noun](де кожен блок необов’язковий), формат, який добре читається за допомогою простої великої літери:ReadSimpleName
Flater

5
@tchrist - наука вашого дослідження ( вільне повне текстове посилання ) просто показує, що програмісти, навчені використовувати стиль підкреслення, швидше читають стиль підкреслення, ніж випадок з верблюдом. Дані також показують, що різниця менша для більш досвідчених предметів (і більшість предметів, які є студентами, припускають, що навіть ті, ймовірно, не особливо досвідчені). Це не означає, що програмісти, які витратили більше часу на використання верблюда, також дадуть такий же результат.
Жуль

8

Автор "Чистого кодексу" вказує на законну проблему, однак запропоноване ним рішення є неелегантним. Зазвичай існують кращі способи вдосконалення незрозумілих назв методів.

Він має рацію, що assertEquals(з бібліотек тестових одиниць стилю xUnit) не дає зрозуміти, який аргумент очікуваний, а який фактичний. Це теж мене покусало! Багато тестових бібліотек відзначили проблему та ввели альтернативні синтаксиси, як-от:

actual.Should().Be(expected);

Або подібне. Що, безумовно, набагато чіткіше, assertEqualsале і набагато краще, ніж assertExpectedEqualsActual. І це також набагато більше композиційний.


1
Я анальний, і я дотримуюся рекомендованого порядку, але мені здається, що якщо я очікую, що результат fun(x)буде 5, то що може піти не так, якщо скасувати порядок - assert(fun(x), 5)? Як тебе вкусив?
emory

3
@emory Я знаю, що jUnit (принаймні) створює повідомлення про помилку зі значень expectedта actual, тому їх перетворення може призвести до неточності повідомлення . Але я згоден, що це звучить більш природно :)
joH1

@ joH1 мені здається слабким. невдалий код не вдасться і код передачі передасться чи ви, assert(expected, observed)чи assert(observed, expected). Кращим прикладом може бути щось на зразок locateLatitudeLongitude- якщо ви перевернете координати, це серйозно зіпсується.
emory

1
@emory Люди, які не переймаються розумними повідомленнями про помилки в одиничних тестах, тому мені доводиться мати справу з "Assert.IsTrue fail" у деяких старих кодових базах. Що надзвичайно весело налагоджувати. Але так, в цьому випадку проблема може бути не такою суттєвою (за винятком випадків, коли ми робимо нечіткі порівняння, де порядок аргументів взагалі має значення). Вільні твердження - це справді чудовий спосіб уникнути цієї проблеми, а також зробити спосіб коду більш виразним (і забезпечити набагато краще повідомлення про помилку для завантаження).
Voo

@emory: Повернення аргументу зробить повідомлення про помилки оманливими та надішле вас неправильним шляхом при налагодженні.
ЖакБ

5

Ви намагаєтеся прокласти шлях між Сциллою та Харибдісом до ясності, намагаєтесь уникнути марної багатослівності (також відомий як безцільний безлад), а також надмірної стислості (також відомий як криптовалюта).

Отже, ми повинні подивитися на інтерфейс, який ви хочете оцінити, спосіб зробити налагодження тверджень про те, що два об'єкти рівні.

  1. Чи є якась інша функція, на якій вона могла б враховувати арійність та ім'я?
    Ні, тому сама назва досить зрозуміла.
  2. Чи мають типи якесь значення?
    Ні, тому давайте їх ігнорувати. Ви це вже робили? Добре.
  3. Чи симетричний він у своїх аргументах?
    Майже, при помилці повідомлення ставить кожне представлення аргументів на своє місце.

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

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

Чи впорядковані аргументи відповідають умові?
Здається, так і є. Хоча це в кращому випадку здається слабкою умовою.

Таким чином, різниця виглядає досить незначною, і аргумент-порядок охоплюється достатньо сильною умовою, що будь-яке зусилля, щоб поставити його в коді-ім'я функції, має негативну корисність.


добре, що порядок може мати значення з jUnit, який будує конкретне повідомлення про помилку зі значень expectedта actual(принаймні, з Strings)
joH1

Я думаю, я покрив цю частину ...
Дедуплікатор

Ви згадували , але вважають: assertEquals("foo", "doo")видає повідомлення про помилку ComparisonFailure: expected:<[f]oo> but was:<[d]oo>... Перестановка значення будуть інвертувати сенс повідомлення, що звучить більш анти симетрична мені. Так само, як ви сказали, у розробника є інші показники для усунення помилки, але це може ввести IMHO в оману і зайняти трохи більше часу налагодження.
joH1

Думка про існування "конвенції" для доручень аргументів є смішною, враховуючи, що обидва табори (dest, src vs. src, dest) сперечаються з цього принаймні до тих пір, поки існує синтаксис AT&T проти Intel. І непотрібні повідомлення про помилки в одиничних тестах - це чума, яку слід викорінювати, а не виконувати. Це майже так само погано, як "Assert.IsTrue не вдалося" ("ей, вам доведеться виконати тест блоку як завгодно, щоб налагодити його, тому просто запустіть його ще раз і поставте там точку перелому", "ей, ви повинні так чи інакше подивитися на код, так просто перевірте, чи правильно замовлення ").
Voo

@Voo: Справа в тому, що "шкода" за те, що вона помиляється, є мізерною (логіка від цього не залежить, а утиліта повідомлень не порушена на значну міру), і при написанні IDE покаже вам ім'я параметрів і введіть все одно.
Дедуплікатор

3

Часто це не додає ніякої логічної ясності.

Порівняйте "Додати" з "AddFirstArgumentToSecondArgument".

Якщо вам потрібна перевантаження, яка, скажімо, додає три значення. Що було б більше сенсу?

Ще одне "Додати" з трьома аргументами?

або

"ДодатиFirstAndSecondAndThirdArgument"?

Назва методу повинна передавати його логічне значення. Він повинен сказати, що це робить. На мікрорівні сказання того, які кроки він робить, не полегшує читача. Назви аргументів нададуть додаткову деталізацію, якщо це потрібно. Якщо вам все-таки потрібна більш детальна інформація, код буде саме для вас.


4
Addпропонує комутативну операцію. ОП стосується ситуацій, коли наказ має значення.
Rosie F

У Swift ви, наприклад, зателефонуйте add (5, to: x) або add (5, plus: 7, to: x) або add (5, plus: 7, даючи: x), якщо ви визначите функцію add () відповідно.
gnasher729

Третю перевантаження слід назвати "
Сумою

@StringyJack Хм .. Сума не є інструкцією, це іменник, який робить її менш придатною для назви методу. Але якщо ви відчуваєте це так, і якщо ви хочете бути пуристом щодо цього, дві версії аргументів також повинні бути названі Sum. Якщо у вас був метод Add, він повинен мати один аргумент, який додається до самого екземпляра об'єкта (який повинен бути числовим або векторним типом). 2 або більше різновидів аргументів (як би ви їх не назвали) були б статичними. Тоді 3 або більше версій аргументу були б зайвими, і ми би реалізували оператор плюс: - |
Мартін Мейт

1
@Martin Чекайте чого? sumє ідеально кромулентним дієсловом . Особливо часто це словосполучення "підбити підсумки".
Voo

2

Я хотів би додати ще щось, на що натякають інші відповіді, але я не думаю, що це було згадано прямо:

@puck каже: "Все ще немає гарантії, що перший згаданий аргумент у назві функції справді є першим параметром".

@cbojar каже: "Використовуйте типи замість неоднозначних аргументів"

Проблема полягає в тому, що мови програмування не розуміють імен: вони просто трактуються як непрозорі, атомні символи. Отже, як і у коментарях до коду, не обов'язково існує кореляція між тим, що названа функцією, і як вона насправді функціонує.

Порівняйте assertExpectedEqualsActual(foo, bar)з деякими альтернативами (з цієї сторінки та з інших країн), наприклад:

# Putting the arguments in a labelled structure
assertEquals({expected: foo, actual: bar})

# Using a keyword arguments language feature
assertEquals(expected=foo, actual=bar)

# Giving the arguments different types, forcing us to wrap them
assertEquals(Expected(foo), Actual(bar))

# Breaking the symmetry and attaching the code to one of the arguments
bar.Should().Be(foo)

Усі вони мають більшу структуру, ніж багатослівна назва, що надає мові щось непрозоре для погляду. Визначення та використання функції також залежить від цієї структури, тому вона не може не синхронізуватись із тим, що робить реалізація (наприклад, ім'я чи коментар).

Коли я стикаюся з такою проблемою або передбачаю її, перш ніж я засмучусь на своєму комп’ютері, я спочатку замикаюся, щоб запитати, чи взагалі «справедливо» звинувачувати машину. Іншими словами, чи було дано машині достатньо інформації, щоб відрізнити те, що я хочу, від того, що я просив?

Виклик, як-от, assertEqual(expected, actual)має стільки ж сенсу, як assertEqual(actual, expected)нам легко змішати їх, а машина може оратись вперед і робити неправильну справу. Якщо ми використовуємо assertExpectedEqualsActualзамість цього, це може змусити нас менше помилитися, але це не дає більше інформації машині (вона не може зрозуміти англійську мову, а вибір імені не повинен впливати на семантику).

Те, що робить «структуровані» підходи більш кращими, як аргументи ключових слів, мічені поля, різні типи тощо, - це те, що додаткова інформація також читається машиною , тому ми можемо встановити на машині неправильні звички та допомогти нам робити все правильно. Справа assertEqualне надто погана, оскільки єдиною проблемою будуть неточні повідомлення. Більш зловісний приклад може бути String replace(String old, String new, String content), який легко сплутати з тим, String replace(String content, String old, String new)що має зовсім інше значення. Простим засобом було б взяти пару [old, new], яка б помилками викликала помилку негайно (навіть без типів).

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

Це також пов'язано з "булевою сліпотою", де ми обчислюємо купу булевих (або цифр тощо) в одній частині коду, але при спробі їх використання в іншій не зрозуміло, що вони насправді представляють, чи ми їх переплутали і т. д. Порівняйте це, наприклад, з різними переліками, які мають описові назви (наприклад, LOGGING_DISABLEDа не false) і які викликають повідомлення про помилку, якщо ми їх змішуємо.


1

оскільки це знімає необхідність пам’ятати, куди йдуть аргументи

Це насправді? Все ще немає гарантії, що перший згаданий аргумент у назві функції справді є першим параметром. Тож краще погляньте на це (або дозвольте вашому IDE це зробити) та залишайтеся з розумними іменами, ніж сліпо покладайтеся на досить дурне ім’я.

Якщо ви читаєте код, ви можете легко побачити, що відбувається, коли параметри названі такими, якими вони повинні бути. copy(source, destination)набагато простіше зрозуміти, ніж подібне copyFromTheFirstLocationToTheSecondLocation(placeA, placeB).

Чому кодери не приймають перше, якщо воно, як стверджує автор, чіткіше, ніж друге?

Оскільки існують різні точки зору на різні стилі, і ви можете знайти x авторів інших статей, які стверджують протилежне. Ви б з глузду намагалися стежити за тим, що хтось десь пише ;-)


0

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

copyFromSourceToDestination( // "...ahh yes, the source directory goes first"

Легко забути впорядкування аргументів у функціях та командах оболонки, і багато програмістів з цієї причини покладаються на функції IDE або довідники функцій. Наявність аргументів, описаних в імені, було б красномовним рішенням цієї залежності.

Однак після написання опис аргументів стає зайвим для наступного програміста, який повинен прочитати оператор, оскільки в більшості випадків будуть використовуватися названі змінні.

copy(sourceDir, destinationDir); // "...makes sense"

Напруженість цього переможе більшість програмістів, і мені особисто це легше читати.

EDIT: Як зазначав @Blrfl, параметри кодування не є "інтуїтивно зрозумілими", адже вам потрібно запам'ятати в першу чергу назву функції. Для цього потрібно шукати посилання функцій або отримати допомогу від IDE, яка, швидше за все, надасть інформацію про впорядкування параметрів.


9
Тож якщо я можу зіграти захисника диявола протягом хвилини: це інтуїтивно зрозуміло лише тоді, коли ти знаєш повне ім’я функції. Якщо ви знаєте , що є функція копіювання і ви не пам'ятаєте це copyFromSourceToDestinationабо copyToDestinationFromSource, ваш вибір знайти його шляхом проб і помилок або читання довідкового матеріалу. IDE, які можуть завершити часткові імена, є лише автоматизованою версією останнього.
Blrfl

@Blrfl Сенс дзвінка copyFromSourceToDestinationполягає в тому, що якщо ви вважаєте, що це так copyToDestinationFromSource, компілятор знайде вашу помилку, але якщо вона була викликана copy, вона не буде. Отримати параметри процедури копіювання невірно, тому що strcpy, strcat тощо встановлюють прецедент. І чи легше прочитати цей короткий? Чи створює об'єднання списків (listA, listB, listC) listA з listB & listC, чи читає списокA & listB і записує списокC?
Rosie F

4
@RosieF Якщо я не знаю, що означають аргументи, я б прочитав документацію, перш ніж писати код. Окрім того, навіть з більш багатослівними іменами функцій, ще є місце для інтерпретації того, що власне є порядком. Хтось холодно подивившись на код, не зможе зрозуміти, що ви встановили умову про те, що те, що в імені функції, відображає порядок аргументів. Їм все одно доведеться це знати достроково чи читати документи.
Blrfl

OTOH, призначенняDir.copy (sourceDir); // "... має більше сенсу"
Крістіан Н

1
@ KristianH Який напрямок dir1.copy(dir2)працює? Не маю уявлення. Про що dir1.copyTo(dir2)?
maaartinus
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.