Достаточно ли того, чтобы методы отличались только по имени аргумента (не по типу)?

36

Достаточно ли, чтобы методы отличались только по имени аргумента (не по типу) или лучше назвать его более явно?

Так , например T Find<T>(int id)против T FindById<T>(int id).

Есть ли веская причина называть его более явно (т.е. добавлять ById) вместо сохранения только имени аргумента?

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

FindByFirstName(string name) а также FindByLastName(string name)

Konrad
источник
4
Итак, когда вы перегружаете Find, чтобы включить T Find<T>(string name)или (int size)как вы планируете решить неизбежные проблемы?
UKMonkey
3
@ UKMonkey какие неизбежные проблемы?
Конрад
3
в первом случае: если несколько записей имеют одно и то же имя, вам придется изменить сигнатуру функции; это означает, что люди, вероятно, будут смущены тем, что это должно возвратиться; В последнем случае аргумент один и тот же - и, следовательно, недопустимая перегрузка. Вы либо начинаете именовать функцию с помощью «byX», либо создаете объект для аргумента, чтобы вы могли иметь эквивалент перегрузки с тем же аргументом. Либо хорошо работает в разных ситуациях.
UKMonkey
2
@UKMonkey вы можете опубликовать ответ с некоторыми примерами кода, если хотите
Конрад
3
Идентификаторы, вероятно, должны быть непрозрачным IDобъектом, а не просто int. Таким образом, получите проверку во время компиляции, что вы не используете id для int или наоборот в какой-то части вашего кода. И с этим вы можете иметь find(int value)и find(ID id).
Бакуриу

Ответы:

68

Конечно, есть веская причина назвать это более явно.

Прежде всего, это не само определение метода , а само использование метода . И хотя findById(string id)и find(string id)оба говорят сами за себя, существует огромная разница между findById("BOB")и find("BOB"). В первом случае вы знаете, что случайный литерал фактически является идентификатором. В последнем случае вы не уверены - на самом деле это может быть имя или что-то другое.

Килиан Фот
источник
9
За исключением случаев, в 99% других случаев , когда у вас есть имя переменной или свойство является точкой отсчета: findById(id)против find(id). Вы можете пойти по любому пути.
Грег Бургхардт
16
@GregBurghardt: конкретное значение не обязательно называется одинаково для метода и его вызывающего. Например, рассмотрим double Divide(int numerator, int denominator)используется в методе: double murdersPerCapita = Divide(murderCount, citizenCount). Вы не можете полагаться на два метода, использующих одно и то же имя переменной, так как существует множество случаев, когда это не так (или когда это так, называете это плохо)
Flater
1
@Flater: учитывая код в вопросе (поиск вещей из какого-то постоянного хранилища), я думаю, вы могли бы вызвать этот метод с помощью «murderedCitizenId» или «citizenId» ... Я действительно не думаю, что имена аргументов или переменных неоднозначно здесь. И, честно говоря, я мог пойти по этому пути. Это на самом деле очень самоуверенный вопрос.
Грег Бургхардт
4
@GregBurghardt: Вы не можете выделить глобальное правило из одного примера. Вопрос ОП в целом не является специфическим для данного примера. Да, в некоторых случаях использование одного и того же имени имеет смысл, но в некоторых случаях это не так. Отсюда этот ответ: лучше стремиться к согласованности, даже если это не требуется в подмножестве вариантов использования.
Флейтер
1
Параметры имен разрешают путаницу после того, как путаница уже существует , так как вам нужно взглянуть на определение метода, в то время как методы с явно заданными именами полностью избегают путаницы , если имя присутствует в вызове метода. Кроме того, у вас не может быть двух методов с одинаковым именем и типом аргумента, но с другим именем аргумента, что означает, что вам в любом случае понадобятся явные имена в определенных случаях.
Darkhogg
36

Преимущества FindById () .

  1. Будущее теплоизолирующие : Если начать с Find(int), а затем должны добавить другие методы ( FindByName(string), FindByLegacyId(int), FindByCustomerId(int), FindByOrderId(int), и т.д.), люди , как я , как правило , тратить возрастов ищут FindById(int). На самом деле не проблема, если вы можете и будете переходить Find(int)к тому времени, FindById(int)когда это станет необходимым, - будьте готовы в будущем, если s.

  2. Легче читать . Findотлично подходит, если вызов выглядит, record = Find(customerId);но FindByIdнемного легче для чтения, если это так record = FindById(AFunction());.

  3. Согласованность . Вы можете последовательно применять шаблон FindByX(int)/ FindByY(int)везде, но Find(int X)/ Find(int Y)это невозможно, поскольку они конфликтуют.

Преимущества Find ()

  • ПОЦЕЛУЙ. Findпрост и понятен, и наряду с operator[]этим он является одним из 2 наиболее ожидаемых имен функций в этом контексте. (Некоторые популярные варианты того get, lookupили fetch, в зависимости от контекста).
  • Как правило, если у вас есть имя функции, представляющее собой одно хорошо известное слово, которое точно описывает, что делает функция, используйте его. Даже если есть более длинное имя, состоящее из нескольких слов, которое немного лучше описывает, что делает функция. Пример: Длина против NumberOfElements . Существует компромисс, и вопрос о том, где провести черту, является предметом постоянных дебатов.
  • Как правило, хорошо избегать избыточности. Если мы посмотрим FindById(int id), мы можем легко удалить избыточность, изменив ее на Find(int id), но есть компромисс - мы теряем некоторую ясность.

В качестве альтернативы вы можете получить преимущества обоих , используя строго типизированные идентификаторы:

CustomerRecord Find(Id<Customer> id) 
// Or, depending on local coding standards
CustomerRecord Find(CustomerId id) 

Реализация Id<>: строго вводить значения идентификаторов в C #

Комментарии здесь, а также в ссылке выше, вызвали многократные опасения относительно того, Id<Customer>что я хотел бы рассмотреть:

  • Проблема 1: это злоупотребление дженериками. CustomerIdи OrderIDэто разные типы ( customerId1 = customerId2;=> хороший, customerId1 = orderId1;=> плохой), но их реализация почти идентична, поэтому мы можем реализовать их либо с помощью вставки копии, либо с помощью метапрограммирования. В то время как в обсуждении есть смысл показывать или скрывать универсальное, метапрограммирование - то, для чего предназначены универсальные.
  • Проблема 2: Это не останавливает простые ошибки. Это решение в поисках проблемы . Главная проблема, которая устраняется с помощью строго типизированных идентификаторов, - это неправильный порядок аргументов в вызове DoSomething(int customerId, int orderId, int productId). Строго набранные идентификаторы также предотвращают другие проблемы, включая тот, о котором спрашивал ОП.
  • Проблема 3: Это действительно просто затеняет код. Трудно сказать, если идентификатор удерживается int aVariable. Легко сказать, что идентификатор удерживается Id<Customer> aVariable, и мы можем даже сказать, что это идентификатор клиента.
  • Проблема 4: Эти идентификаторы не являются сильными типами, это просто оболочки. Stringэто просто обертка вокруг byte[]. Упаковка или инкапсуляция не противоречат строгой типизации.
  • Озабоченность 5: все закончено. Вот минимальная версия, хотя я рекомендую добавить operator==и operator!=как хорошо, если вы не хотите , чтобы полагаться исключительно на Equals:

,

public struct Id<T>: {
    private readonly int _value ;
    public Id(int value) { _value = value; }
    public static explicit operator int(Id<T> id) { return id._value; }
}
Питер
источник
10

Другой способ думать об этом - использовать безопасность типов языка.

Вы можете реализовать такой метод, как:

Find(FirstName name);

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

3DPrintScanner
источник
4
Не уверен, что ваш ответ на вопрос ОП. Вы рекомендуете использовать имя типа «Найти», полагаясь на тип аргумента? Или вы рекомендуете использовать такие имена только в том случае, если для аргументов есть явный тип, а в других местах использовать более явное имя, например «FindById»? Или вы рекомендуете вводить явные типы, чтобы сделать имя типа «Найти» более выполнимым?
Док Браун
2
@DocBrown Я думаю, что последнее, и мне это нравится. На самом деле это похоже на ответ Питера, iiuc. Как я понимаю, логическое обоснование двоякое: (1) из типа аргумента ясно, что делает функция; (2) Вы не можете делать ошибки, такие как., string name = "Smith"; findById(name);Что возможно, если вы используете неописуемые общие типы.
Питер - Восстановить Монику
5
Хотя в целом я фанат безопасности типов во время компиляции, будьте осторожны с типами-оболочками. Введение классов-оболочек для безопасности типов может иногда сильно усложнять ваш API, если оно выполняется в избытке. например, целая проблема «художника, ранее известная как int», которая есть у winapi; В DWORD LPCSTRконце концов, я бы сказал, что большинство людей просто смотрят на бесконечные клоны и т. д. и думают, что «это int / string / etc.», и это приводит к тому, что вы тратите больше времени на поддержку своих инструментов, чем на разработку кода. ,
августа
1
@jrh Правда. Мой лакмусовый тест для введения «номинальных» типов (различающихся только по названию) заключается в том, что общие функции / методы не имеют никакого смысла в нашем случае использования, например, ints часто суммируются, умножаются и т. Д., Что не имеет смысла для идентификаторов; так что я бы IDотличался от int. Это может упростить API, сузив то, что мы можем сделать, учитывая значение (например, если у нас есть ID, он будет работать только с find, например, ageили highscore). И наоборот, если мы много конвертируем или пишем одну и ту же функцию / метод (например find) для нескольких типов, это признак того, что наши различия слишком хороши
Warbo
1

Я буду голосовать за явное объявление, как FindByID .... Программное обеспечение должно быть построено для изменения. Он должен быть открытым и закрытым (ТВЕРДЫМ). Таким образом, класс открыт для добавления аналогичного метода поиска, например, скажем FindByName .. и т. Д.

Но FindByID закрыт, и его реализация проверена модулем.

Я не буду предлагать методы с предикатами, они хороши на общем уровне. Что делать, если на основе поля (ByID) у вас есть совершенно другая методология.

BrightFuture1029
источник
0

Я удивлен, что никто не предложил использовать предикат, такой как следующее:

User Find(Predicate<User> predicate)

При таком подходе вы не только уменьшаете поверхность своего API, но и даете больше контроля пользователю, использующему его.

Если этого недостаточно, вы всегда можете расширить его для своих нужд.

Aybe
источник
1
Проблема в том, что он менее эффективен из-за того, что не может использовать такие вещи, как индексы.
Соломон Уко,