В Чистом коде автор приводит пример
assertExpectedEqualsActual(expected, actual)
против
assertEquals(expected, actual)
с первым утверждалось, что это более ясно, потому что это устраняет необходимость помнить, куда идут аргументы и возможное неправильное использование, которое проистекает из этого. Тем не менее, я никогда не видел пример первой схемы именования в каком-либо коде и все время вижу последнюю. Почему кодеры не принимают первое, если оно, как утверждает автор, яснее второго?
clean-code
EternalStudent
источник
источник
assertEquals()
, этот метод используется сотни раз в кодовой базе , так что можно ожидать , что читатели ознакомятся с конвенцией один раз. Разные фреймворки имеют разные соглашения (например,(actual, expected) or an agnostic
(слева, справа) `), но, по моему опыту, это не более, чем незначительный источник путаницы.assert(a).toEqual(b)
(даже если IMO это все еще излишне многословно), где вы можете зацепить несколько связанных утверждений.assertExpectedValueEqualsActualValue
? Но подождите, как мы помним, использует ли он==
или.equals
илиObject.equals
? Должно ли это бытьassertExpectedValueEqualsMethodReturnsTrueWithActualValueParameter
?Ответы:
Потому что это больше печатать и больше читать
Самая простая причина в том, что людям нравится меньше печатать, а кодирование этой информации означает больше печатания. Когда я читаю его, мне каждый раз приходится читать все это, даже если я знаю, каким должен быть порядок аргументов. Даже если не знаком с порядком аргументов ...
Многие разработчики используют IDE
Среды IDE часто предоставляют механизм для просмотра документации по данному методу при наведении курсора или с помощью сочетания клавиш. Из-за этого имена параметров всегда под рукой.
Кодирование аргументов вводит дублирование и связь
Имена параметров должны уже документировать, что они есть. Записывая имена в имени метода, мы дублируем эту информацию и в сигнатуре метода. Мы также создаем связь между именем метода и параметрами. Говорят
expected
иactual
сбивают с толку наших пользователей. Переход отassertEquals(expected, actual)
кassertEquals(planned, real)
не требует изменения клиентского кода с помощью функции. Переход отassertExpectedEqualsActual(expected, actual)
кassertPlannedEqualsReal(planned, real)
означает серьезное изменение в API. Или мы не меняем имя метода, что быстро сбивает с толку.Используйте типы вместо неоднозначных аргументов
Реальная проблема в том, что у нас есть неоднозначные аргументы, которые легко переключаются, потому что они одного типа. Вместо этого мы можем использовать нашу систему типов и наш компилятор для обеспечения правильного порядка:
Это может быть применено на уровне компилятора и гарантирует, что вы не сможете получить их обратно. Подходя с другой стороны, это, по сути, то, что библиотека Hamcrest делает для тестов.
источник
assertExpectedEqualsActual
«потому что это больше печатать и больше читать», то как вы можете защищатьassertEquals(Expected.is(10), Actual.is(x))
?assertExpectedEqualsActual
все еще требует от программиста заботливости указывать аргументы в правильном порядке.assertEquals(Expected<T> expected, Actual<T> actual)
Подпись используется компилятором для обеспечения правильного использования, что является совершенно иным подходом. Вы можете оптимизировать этот подход для краткости, напримерexpect(10).equalsActual(x)
, но это был не вопрос ...Вы спрашиваете о давних дебатах в программировании. Сколько многословия это хорошо? Как общий ответ, разработчики обнаружили, что лишняя многословность именования аргументов не стоит.
Многословие не всегда означает большую ясность. Рассмотреть возможность
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")
).источник
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, и требуется больше времени, чтобы выяснить, где находятся границы слова. Сокрушение смущает.withdrawFundsFrom: account usingPin: userProvidedPin
фактически заимствован из SmallTalk.Addingunderscoresnakesthingseasiertoreadnotharderasyousee
манипулирует аргументом. В ответе здесь используется заглавная буква, которую вы опускаете.AddingCapitalizationMakesThingsEasyEnoughToReadAsYouCanSeeHere
, Во-вторых, 9 раз из 10 имя никогда не должно выходить за пределы[verb][adjective][noun]
(где каждый блок является необязательным), формат, который хорошо читается с помощью простой заглавной буквы:ReadSimpleName
Автор «Чистого кода» указывает на законную проблему, но его предложенное решение довольно не элегантно. Обычно есть лучшие способы улучшить неясные имена методов.
Он прав, что
assertEquals
(из библиотек модульных тестов стиля xUnit) не дает понять, какой аргумент является ожидаемым, а какой - действительным. Это также укусило меня! Многие библиотеки модульных тестов отметили проблему и ввели альтернативные синтаксисы, такие как:Или похожие. Что, конечно, намного понятнее,
assertEquals
но и намного лучше, чемassertExpectedEqualsActual
. И это также намного более сложным.источник
fun(x)
будет 5, что может пойти не так, если поменять порядок -assert(fun(x), 5)
? Как это укусило тебя?expected
иactual
, поэтому обращение их может привести к получению сообщения, не являющегося точным. Но я согласен, что это звучит более естественно, хотя :)assert(expected, observed)
или нетassert(observed, expected)
. Лучшим примером будет что-то вродеlocateLatitudeLongitude
- если вы измените координаты, это серьезно испортит.Вы пытаетесь направить свой путь между Сциллой и Харибдой к ясности, пытаясь избежать бесполезного многословия (также известного как бесцельное бродяжничество), а также чрезмерной краткости (также известной как загадочная краткость).
Итак, мы должны взглянуть на интерфейс, который вы хотите оценить, способ сделать отладочные утверждения, что два объекта равны.
Нет, поэтому само название достаточно понятно.
Нет, поэтому давайте их проигнорируем. Вы уже сделали это? Хороший.
Почти при ошибке сообщение помещает каждое представление аргументов в свое собственное место.
Итак, давайте посмотрим, имеет ли это небольшое различие какое-либо значение и не охватывается существующими строгими соглашениями.
Является ли предполагаемая аудитория неудобной, если аргументы непреднамеренно меняются местами?
Нет, разработчики также получают трассировку стека, и им все равно нужно тщательно изучить исходный код, чтобы исправить ошибку.
Даже без полной трассировки стека позиция утверждений решает этот вопрос. И если даже этого не хватает, и из сообщения, которое есть, это не очевидно, это максимально удваивает возможности.
Соответствует ли порядок аргументов соглашению?
Кажется, так и есть. Хотя это кажется в лучшем случае слабым соглашением.
Таким образом, разница выглядит довольно незначительной, и порядок аргументов покрывается достаточно строгим соглашением о том, что любая попытка поместить его в имя функции имеет отрицательную полезность.
источник
expected
иactual
(по крайней мере, со строками)assertEquals("foo", "doo")
выдает сообщение об ошибкеComparisonFailure: expected:<[f]oo> but was:<[d]oo>
... Перестановка значения будут инвертировать смысл сообщения, что звучит более анти симметричная мне. В любом случае, как вы сказали, у dev есть другие индикаторы для устранения ошибки, но это может ввести в заблуждение IMHO и занять немного больше времени для отладки.Часто это не добавляет никакой логической ясности.
Сравните «Добавить» с «AddFirstArgumentToSecondArgument».
Если вам нужна перегрузка, скажем, добавляет три значения. Что будет иметь больше смысла?
Еще один «Добавить» с тремя аргументами?
или же
"AddFirstAndSecondAndThirdArgument"?
Название метода должно передавать его логическое значение. Он должен сказать, что он делает. Сообщение на микроуровне о том, какие шаги необходимо предпринять, не облегчает читателю. Имена аргументов предоставят дополнительную информацию, если это необходимо. Если вам нужно больше подробностей, код будет прямо для вас.
источник
Add
предлагает коммутативную операцию. ФП касается ситуаций, когда порядок имеет значение.sum
это совершенно громоздкий глагол . Это особенно часто встречается в фразе «подвести итог».Я хотел бы добавить кое-что еще, на что намекают другие ответы, но я не думаю, что было упомянуто явно:
@puck говорит: «До сих пор нет гарантии, что первый упомянутый аргумент в имени функции действительно является первым параметром».
@cbojar говорит: «Используйте типы вместо неоднозначных аргументов»
Проблема в том, что языки программирования не понимают имен: они просто рассматриваются как непрозрачные атомарные символы. Следовательно, как и в комментариях к коду, не обязательно существует какая-либо корреляция между тем, как называется функция, и тем, как она на самом деле работает.
Сравните
assertExpectedEqualsActual(foo, bar)
с некоторыми альтернативами (с этой страницы и в других местах), например:Все они имеют больше структуры, чем подробное имя, что дает языку нечто непрозрачное для взгляда. Определение и использование функции также зависит от этой структуры, поэтому она не может быть не синхронизирована с тем, что делает реализация (например, имя или комментарий).
Когда я сталкиваюсь с такой проблемой или предугадываю ее, прежде чем я в отчаянии кричу на свой компьютер, сначала я задаюсь вопросом: «Справедливо» ли вообще обвинять машину? Другими словами, было ли дано машине достаточно информации, чтобы отличить то, что я хотел, от того, что я просил?
Подобный вызов
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
) и которые вызывают сообщение об ошибке, если мы их перепутали.источник
Это правда? Все еще нет гарантии, что первый упомянутый аргумент в имени функции действительно является первым параметром. Так что лучше найдите его (или позвольте вашей IDE сделать это) и оставайтесь с разумными именами, чем вслепую полагайтесь на довольно глупое имя.
Если вы читаете код, вы должны легко увидеть, что происходит, когда параметры названы так, как они должны быть.
copy(source, destination)
это гораздо легче понять, чем что-то подобноеcopyFromTheFirstLocationToTheSecondLocation(placeA, placeB)
.Потому что существуют разные точки зрения на разные стили, и вы можете найти x авторов других статей, которые утверждают обратное. Вы бы сошли с ума, пытаясь следовать всему, что кто-то где-то пишет ;-)
источник
Я согласен, что кодирование имен параметров в имена функций делает написание и использование функций более интуитивным.
Порядок аргументов в функциях и командах оболочки легко забыть, и по этой причине многие программисты полагаются на функции IDE или ссылки на функции. Наличие аргументов, описанных в имени, было бы красноречивым решением этой зависимости.
Однако после написания описание аргументов становится избыточным для следующего программиста, который должен прочитать оператор, поскольку в большинстве случаев будут использоваться именованные переменные.
Краткость этого перевесит большинство программистов, и мне лично легче читать.
РЕДАКТИРОВАТЬ: Как отметил @Blrfl, параметры кодирования не так уж и интуитивно понятны, так как вам нужно запомнить имя функции. Это требует поиска ссылок на функции или получения справки из IDE, которая в любом случае, вероятно, предоставит информацию о порядке расположения параметров.
источник
copyFromSourceToDestination
или нетcopyToDestinationFromSource
, ваши решения находят ее методом проб и ошибок или читая справочный материал. Среды IDE, которые могут заполнять частичные имена, являются просто автоматической версией последнего.copyFromSourceToDestination
том, что если вы думаетеcopyToDestinationFromSource
, что компилятор найдет вашу ошибку, но если она была вызванаcopy
, то не будет. Неправильно получить параметры подпрограммы копирования, так как strcpy, strcat и т. Д. Создают прецедент. И кратко ли легче читать? Создает ли mergeLists (listA, listB, listC) listA из listB & listC или читает listA & listB и записывает listC?dir1.copy(dir2)
работает? Без понятия. Как насчетdir1.copyTo(dir2)
?