Почему кодирование имен аргументов в именах функций более распространено? [закрыто]

47

В Чистом коде автор приводит пример

assertExpectedEqualsActual(expected, actual)

против

assertEquals(expected, actual)

с первым утверждалось, что это более ясно, потому что это устраняет необходимость помнить, куда идут аргументы и возможное неправильное использование, которое проистекает из этого. Тем не менее, я никогда не видел пример первой схемы именования в каком-либо коде и все время вижу последнюю. Почему кодеры не принимают первое, если оно, как утверждает автор, яснее второго?

EternalStudent
источник
9
Я думаю, что это отличный вопрос для обсуждения. Но не то, что можно ответить объективным ответом. Так что этот вопрос может быть закрыт как основанный на мнении.
Эйфорическая
54
Многие люди будут спорить с первой схемой именования, потому что она чрезмерно многословна , далеко за пределами той точки, где она поможет ясности. Специально для assertEquals(), этот метод используется сотни раз в кодовой базе , так что можно ожидать , что читатели ознакомятся с конвенцией один раз. Разные фреймворки имеют разные соглашения (например, (actual, expected) or an agnostic (слева, справа) `), но, по моему опыту, это не более, чем незначительный источник путаницы.
Амон
5
Поскольку выигрыш настолько мал, по сравнению с его преимуществами, что любой здравомыслящий человек, вероятно, уйдет. Если вы хотите более гибкий подход, вы должны попробовать assert(a).toEqual(b)(даже если IMO это все еще излишне многословно), где вы можете зацепить несколько связанных утверждений.
Адриано Репетти
18
Как мы узнаем, что фактические и ожидаемые значения? Наверняка так и должно быть assertExpectedValueEqualsActualValue? Но подождите, как мы помним, использует ли он ==или .equalsили Object.equals? Должно ли это быть assertExpectedValueEqualsMethodReturnsTrueWithActualValueParameter?
user253751
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 делает для тестов.

cbojar
источник
5
Хорошо, если вы используете IDE, у вас есть имена параметров в всплывающей подсказке; если вы не используете его, запоминание имени функции эквивалентно запоминанию аргументов, поэтому ничего не получится в любом случае.
Питер - Восстановить Монику
29
Если вы возражаете assertExpectedEqualsActual«потому что это больше печатать и больше читать», то как вы можете защищать assertEquals(Expected.is(10), Actual.is(x))?
Руах
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:. Это имя функции). Swift принял аналогичный набор решений, требуя, чтобы вы указали имена аргументов в вызовах функций ( 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). Видишь, как легче было ?! Это потому, что слишком легко пропустить небольшие изменения на полпути через этот humungousunbrokenwordsalad, и требуется больше времени, чтобы выяснить, где находятся границы слова. Сокрушение смущает.
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. И это также намного более сложным.

JacquesB
источник
1
Я анальный, и я следую рекомендованному порядку, но мне кажется, что, если я ожидаю, что результат fun(x)будет 5, что может пойти не так, если поменять порядок - assert(fun(x), 5)? Как это укусило тебя?
Эмори
3
@emory Я знаю, что jUnit (по крайней мере) формирует сквозное сообщение об ошибке из значений expectedи actual, поэтому обращение их может привести к получению сообщения, не являющегося точным. Но я согласен, что это звучит более естественно, хотя :)
joH1
@ joH1 мне кажется это слабым. неудачный код потерпит неудачу, и передача кода пройдет независимо от того, делаете вы это assert(expected, observed)или нет assert(observed, expected). Лучшим примером будет что-то вроде locateLatitudeLongitude- если вы измените координаты, это серьезно испортит.
Эмори
1
@emory Люди, которых не волнуют разумные сообщения об ошибках в модульных тестах, - вот почему мне приходится иметь дело с «Assert.IsTrue fail» в некоторых старых базах кода. Что очень весело отлаживать. Но да, в этом случае проблема может быть не такой существенной (кроме случаев, когда мы проводим нечеткие сравнения, когда порядок аргументов обычно имеет значение). Свободные утверждения - действительно отличный способ избежать этой проблемы, а также сделать код более выразительным (и обеспечить гораздо более качественное сообщение об ошибке при загрузке).
Во
@emory: изменение аргумента приведет к тому, что сообщения об ошибках будут вводить в заблуждение, и при отладке отправит вас по неверному пути.
JacquesB
5

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

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

  1. Есть ли какая-либо другая функция, которая могла бы учитывать арность и имя?
    Нет, поэтому само название достаточно понятно.
  2. Типы имеют какое-либо значение?
    Нет, поэтому давайте их проигнорируем. Вы уже сделали это? Хороший.
  3. Это симметрично в своих аргументах?
    Почти при ошибке сообщение помещает каждое представление аргументов в свое собственное место.

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

Является ли предполагаемая аудитория неудобной, если аргументы непреднамеренно меняются местами?
Нет, разработчики также получают трассировку стека, и им все равно нужно тщательно изучить исходный код, чтобы исправить ошибку.
Даже без полной трассировки стека позиция утверждений решает этот вопрос. И если даже этого не хватает, и из сообщения, которое есть, это не очевидно, это максимально удваивает возможности.

Соответствует ли порядок аргументов соглашению?
Кажется, так и есть. Хотя это кажется в лучшем случае слабым соглашением.

Таким образом, разница выглядит довольно незначительной, и порядок аргументов покрывается достаточно строгим соглашением о том, что любая попытка поместить его в имя функции имеет отрицательную полезность.

Deduplicator
источник
ну, порядок может иметь значение с jUnit, который формирует конкретное сообщение об ошибке из значений expectedи actual(по крайней мере, со строками)
joH1
Я думаю, что я рассмотрел эту часть ...
Дедупликатор
вы упоминали , но считают: assertEquals("foo", "doo")выдает сообщение об ошибке ComparisonFailure: expected:<[f]oo> but was:<[d]oo>... Перестановка значения будут инвертировать смысл сообщения, что звучит более анти симметричная мне. В любом случае, как вы сказали, у dev есть другие индикаторы для устранения ошибки, но это может ввести в заблуждение IMHO и занять немного больше времени для отладки.
joH1
Идея, что существует «соглашение» для порядков аргументов, забавна, учитывая, что оба лагеря (dest, src vs. src, dest) спорили об этом, по крайней мере, до тех пор, пока существует синтаксис AT & T и Intel. А бесполезные сообщения об ошибках в модульных тестах - это чума, которую следует искоренять, а не приводить в исполнение. Это почти так же плохо, как «Assert.IsTrue fail» («эй, вы все равно должны выполнить модульный тест для его отладки, так что просто запустите его снова и поставьте точку останова», «эй, вы все равно должны посмотреть на код, так что просто проверьте правильность заказа ").
Во
@Voo: Дело в том, что «ущерб» от неправильного понимания является крошечным (логика от этого не зависит, и утилита сообщений не пострадает в значительной степени), и при написании IDE покажет вам имя параметра и все равно печатать.
Дедупликатор
3

Часто это не добавляет никакой логической ясности.

Сравните «Добавить» с «AddFirstArgumentToSecondArgument».

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

Еще один «Добавить» с тремя аргументами?

или же

"AddFirstAndSecondAndThirdArgument"?

Название метода должно передавать его логическое значение. Он должен сказать, что он делает. Сообщение на микроуровне о том, какие шаги необходимо предпринять, не облегчает читателю. Имена аргументов предоставят дополнительную информацию, если это необходимо. Если вам нужно больше подробностей, код будет прямо для вас.

Мартин Маат
источник
4
Addпредлагает коммутативную операцию. ФП касается ситуаций, когда порядок имеет значение.
Рози Ф
В Swift вы, например, вызываете add (5, to: x) или add (5, plus: 7, to: x) или add (5, plus: 7, давая: x), если вы определяете функцию add () соответственно.
gnasher729
Третья перегрузка должна называться «Сумма»
StingyJack
@StringyJack Хм .. Sum - это не инструкция, это существительное, которое делает его менее подходящим для имени метода. Но если вы так думаете, и если вы хотите быть пуристом по этому поводу, то версия с двумя аргументами также должна называться Sum. Если бы у вас был метод Add, он должен иметь один аргумент, который добавляется к самому экземпляру объекта (который должен быть числовым или векторным типом). 2 или более аргумента (как бы вы их ни называли) были бы статическими. Тогда 3 или более версии аргумента будут избыточными, и мы бы реализовали оператор плюс: - |
Мартин Маат
1
@ Мартин Подожди что? sumэто совершенно громоздкий глагол . Это особенно часто встречается в фразе «подвести итог».
Во
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) и которые вызывают сообщение об ошибке, если мы их перепутали.

Warbo
источник
1

потому что это устраняет необходимость помнить, куда идут аргументы

Это правда? Все еще нет гарантии, что первый упомянутый аргумент в имени функции действительно является первым параметром. Так что лучше найдите его (или позвольте вашей IDE сделать это) и оставайтесь с разумными именами, чем вслепую полагайтесь на довольно глупое имя.

Если вы читаете код, вы должны легко увидеть, что происходит, когда параметры названы так, как они должны быть. copy(source, destination)это гораздо легче понять, чем что-то подобное copyFromTheFirstLocationToTheSecondLocation(placeA, placeB).

Почему кодеры не принимают первое, если оно, как утверждает автор, яснее второго?

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

шайба
источник
0

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

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

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

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

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

Краткость этого перевесит большинство программистов, и мне лично легче читать.

РЕДАКТИРОВАТЬ: Как отметил @Blrfl, параметры кодирования не так уж и интуитивно понятны, так как вам нужно запомнить имя функции. Это требует поиска ссылок на функции или получения справки из IDE, которая в любом случае, вероятно, предоставит информацию о порядке расположения параметров.

Джош Тейлор
источник
9
Так что, если я смогу сыграть адвоката дьявола на минуту: это интуитивно понятно, когда вы знаете полное название функции. Если вы знаете, что есть функция копирования, и вы не помните, является ли она copyFromSourceToDestinationили нет copyToDestinationFromSource, ваши решения находят ее методом проб и ошибок или читая справочный материал. Среды IDE, которые могут заполнять частичные имена, являются просто автоматической версией последнего.
Blrfl
@Blrfl Смысл в copyFromSourceToDestinationтом, что если вы думаете copyToDestinationFromSource, что компилятор найдет вашу ошибку, но если она была вызвана copy, то не будет. Неправильно получить параметры подпрограммы копирования, так как strcpy, strcat и т. Д. Создают прецедент. И кратко ли легче читать? Создает ли mergeLists (listA, listB, listC) listA из listB & listC или читает listA & listB и записывает listC?
Рози Ф
4
@RosieF Если бы я не был уверен, что означают аргументы, я бы прочитал документацию, прежде чем писать код. Кроме того, даже с более подробными именами функций все еще есть место для интерпретации того, каков порядок на самом деле. Тот, кто холодно взглянет на код, не сможет понять, что вы установили соглашение о том, что то, что в имени функции отражает порядок аргументов. Им все равно придется знать об этом заранее или читать документы.
Blrfl
OTOH, destinationDir.copy (sourceDir); // «... имеет больше смысла»
Кристиан Х.
1
@KristianH В каком направлении dir1.copy(dir2)работает? Без понятия. Как насчет dir1.copyTo(dir2)?
Маартин