¿Es suficiente distinguir los métodos solo por el nombre del argumento (no el tipo)?

36

¿Es suficiente distinguir los métodos solo por el nombre del argumento (no el tipo) o es mejor nombrarlo más explícitamente?

Por ejemplo T Find<T>(int id)vs T FindById<T>(int id).

¿Hay alguna buena razón para nombrarlo más explícitamente (es decir, agregar ById) en lugar de mantener solo el nombre del argumento?

Una razón por la que puedo pensar es cuando las firmas de los métodos son las mismas pero tienen un significado diferente.

FindByFirstName(string name) y FindByLastName(string name)

Konrad
fuente
44
Entonces, cuando sobrecarga Buscar para incluir T Find<T>(string name)o (int size)¿cómo planea resolver los problemas inevitables?
UKMonkey
3
@ Reino Unido ¿Qué problemas inevitables?
Konrad el
3
en el primer caso: si varias entradas tienen el mismo nombre, entonces tendría que cambiar la firma de la función; lo que significa que la gente probablemente se confundirá con lo que debe devolver; En el último caso, el argumento es el mismo y, por lo tanto, una sobrecarga ilegal. Usted comienza a nombrar la función con "byX" o crea un objeto para el argumento para que pueda tener el equivalente de sobrecarga con el mismo argumento. Cualquiera de los dos funciona bien para diferentes situaciones.
UKMonkey
2
@UKMonkey puede publicar una respuesta con algunos ejemplos de código si lo desea
Konrad
3
Los ID probablemente deberían ser un IDobjeto opaco y no solo un int. De ese modo, compruebe en tiempo de compilación que no utiliza una identificación para int o viceversa en alguna parte de su código. Y con eso puedes tener find(int value)y find(ID id).
Bakuriu el

Respuestas:

68

Claro que hay una buena razón para nombrarlo más explícitamente.

No es principalmente la definición del método la que debe explicarse por sí misma, sino el uso del método . Y mientras findById(string id)y find(string id)se explican por sí mismos, hay una gran diferencia entre findById("BOB")y find("BOB"). En el primer caso, usted sabe que el literal aleatorio es, de hecho, un Id. En el último caso, no está seguro: en realidad podría ser un nombre de pila o algo completamente diferente.

Kilian Foth
fuente
99
Excepto en el 99% de otros casos en los que tiene un nombre de variable o propiedad es un punto de referencia: findById(id)vs find(id). Puedes ir en cualquier dirección en esto.
Greg Burghardt el
16
@GregBurghardt: Un valor particular no necesariamente se denomina de la misma manera para un método y su llamador. Por ejemplo, considere double Divide(int numerator, int denominator)que se utiliza en un método: double murdersPerCapita = Divide(murderCount, citizenCount). No puede confiar en dos métodos que usan el mismo nombre de variable, ya que hay muchos casos en los que ese no es el caso (o cuando es el caso, es un mal nombre)
Flater
1
@Flater: Dado el código en la pregunta (encontrar cosas de algún tipo de almacenamiento persistente), imagino que podría llamar a este método con un "murderedCitizenId" o "citizenId" ... Realmente no creo que el argumento o los nombres de las variables sean ambiguo aquí Y honestamente, podría ir en cualquier dirección en esto. En realidad es una pregunta muy obstinada.
Greg Burghardt el
44
@GregBurghardt: No puede destilar una regla global de un solo ejemplo. La pregunta de OP es en general , no específica del ejemplo dado. Sí, hay algunos casos en los que tiene sentido usar el mismo nombre, pero también hay casos en los que no. De ahí esta respuesta, es mejor apuntar a la coherencia, incluso si no es necesario en un subconjunto de casos de uso.
Flater
1
Los parámetros de nombres resuelven la confusión después de que la confusión ya haya existido , ya que debe mirar la definición del método, mientras que los métodos con nombre explícito evitan por completo la confusión al tener el nombre presente en la llamada al método. Además, no puede tener dos métodos con el mismo nombre y tipo de argumento pero con un nombre de argumento diferente, lo que significa que necesitará nombres explícitos en ciertos casos de todos modos.
Darkhogg
36

Ventajas de FindById () .

  1. A prueba de futuro : si comenzar con Find(int), y luego tener que agregar otros métodos ( FindByName(string), FindByLegacyId(int), FindByCustomerId(int), FindByOrderId(int), etc), la gente como yo tienden a pasar las edades buscando FindById(int). No es realmente un problema si se puede y va a cambiar Find(int)a la FindById(int)vez que se hace necesario - el futuro es a prueba sobre estos si s.

  2. Más fácil de leer . Findestá perfectamente bien si la llamada parece record = Find(customerId);Sin embargo, FindByIdes un poco más fácil de leer si lo es record = FindById(AFunction());.

  3. La consistencia . Puede aplicar constantemente el patrón FindByX(int)/ en FindByY(int)todas partes, pero Find(int X)/ Find(int Y)no es posible porque entran en conflicto.

Ventajas de Find ()

  • BESO. Findes simple y directo, y junto operator[]con uno de los 2 nombres de funciones más esperados en este contexto. (Algunas alternativas populares que son get, lookupo fetch, dependiendo del contexto).
  • Como regla general, si tiene un nombre de función que es una sola palabra conocida que describe con precisión lo que hace la función, úsela. Incluso si hay un nombre de varias palabras más largo que es un poco mejor para describir lo que hace la función. Ejemplo: longitud frente a NumberOfElements . Hay una compensación, y dónde trazar la línea está sujeto a un debate en curso.
  • En general, es bueno evitar la redundancia. Si observamos FindById(int id), podemos eliminar fácilmente la redundancia cambiándola a Find(int id), pero hay una compensación: perdemos algo de claridad.

Alternativamente, puede obtener las ventajas de ambos utilizando Ids fuertemente tipados:

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

Implementación de Id<>: Escribiendo valores de ID en C #

Los comentarios aquí, así como en el enlace anterior, plantearon múltiples preocupaciones con respecto a las Id<Customer>que me gustaría abordar:

  • Preocupación 1: es un abuso de genéricos. CustomerIdy OrderIDson diferentes tipos ( customerId1 = customerId2;=> bueno, customerId1 = orderId1;=> malo), pero su implementación es casi idéntica, por lo que podemos implementarlos con copiar y pegar o con metaprogramación. Si bien hay un valor en una discusión sobre exponer u ocultar lo genérico, la metaprogramación es para qué sirven los genéricos.
  • Preocupación 2: No detiene errores simples. Es una solución en busca de un problema. El problema principal que se elimina mediante el uso de identificadores fuertemente tipados es el orden de argumento incorrecto en una llamada a DoSomething(int customerId, int orderId, int productId). Los ID fuertemente tipados también evitan otros problemas, incluido el que se le preguntó a OP.
  • Preocupación 3: realmente solo oscurece el código. Es difícil saber si se retiene una identificación int aVariable. Es fácil decir que se retiene un ID Id<Customer> aVariable, e incluso podemos decir que es un ID de cliente.
  • Preocupación 4: estos ID no son tipos fuertes, solo envoltorios. Stringes solo una envoltura byte[]. La envoltura, o encapsulación, no está en conflicto con la tipificación fuerte.
  • Preocupación 5: está sobre diseñado. Aquí está la versión mínima, aunque recomiendo agregar operator==y operator!=también, si no desea confiar exclusivamente en 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; }
}
Peter
fuente
10

Otra forma de pensar en esto es usar la seguridad de tipo del idioma.

Puede implementar un método como:

Find(FirstName name);

Donde FirstName es un objeto simple que envuelve una cadena que contiene el nombre y significa que no puede haber confusión en cuanto a lo que está haciendo el método, ni en los argumentos con los que se llama.

3DPrintScanner
fuente
44
No estoy seguro de cuál es su respuesta a la pregunta de los OP. ¿Recomienda usar un nombre como "Buscar" confiando en el tipo de argumento? ¿O recomienda utilizar dichos nombres solo cuando haya un tipo explícito para los argumentos y utilizar un nombre más explícito como "FindById" en otro lugar? ¿O recomienda introducir tipos explícitos para hacer un nombre como "Buscar" más factible?
Doc Brown
2
@DocBrown Creo que esto último, y me gusta. En realidad es similar a la respuesta de Peter, iiuc. El razonamiento, según tengo entendido, es doble: (1) Del tipo de argumento queda claro qué hace la función; (2) No puede cometer errores como string name = "Smith"; findById(name);.que es posible si utiliza tipos generales no descriptos.
Peter - Restablece a Mónica el
55
Si bien en general soy fanático de la seguridad del tipo de tiempo de compilación, tenga cuidado con los tipos de envoltorios. La introducción de clases de envoltura por razones de seguridad de tipo puede a veces complicar severamente su API si se hace en exceso. por ejemplo, todo el problema del "artista anteriormente conocido como int" que tiene winapi; a la larga, diría que la mayoría de las personas solo miran los interminables DWORD LPCSTRclones, etc. y piensan "es un int / string / etc.", llega al punto en el que pasas más tiempo apuntalando tus herramientas de las que realmente diseñas el código .
jrh
1
@jrh Cierto. Mi prueba de fuego para la introducción de tipos "nominales" (que difieren solo en el nombre) es cuando las funciones / métodos comunes no tienen ningún sentido en nuestro caso de uso, por ejemplo, a intmenudo se suman, multiplican, etc., lo que no tiene sentido para las ID; por lo que me gustaría hacer IDdistinto de int. Esto puede simplificar una API, al reducir lo que podemos hacer dado un valor (por ejemplo, si tenemos un ID, solo funcionará find, no por ejemplo, ageo highscore). Por el contrario, si nos encontramos convirtiendo mucho, o escribiendo la misma función / método (p find. Ej. ) Para varios tipos, es una señal de que nuestras distinciones son demasiado finas
Warbo
1

Voy a votar por una declaración explícita como FindByID ... El software debe ser creado para el cambio. Debe estar abierto y cerrado (SÓLIDO). Entonces, la clase está abierta para agregar un método de búsqueda similar como digamos FindByName ... etc.

Pero FindByID está cerrado y su implementación está probada por unidad.

No sugeriré métodos con predicados, esos son buenos a nivel genérico. ¿Qué sucede si en función del campo (ByID) tiene una metodología diferente?

BrightFuture1029
fuente
0

Me sorprende que nadie sugiera usar un predicado como el siguiente:

User Find(Predicate<User> predicate)

Con este enfoque, no solo reduce la superficie de su API, sino que también le da más control al usuario que la usa.

Si eso no es suficiente, siempre puede ampliarlo a sus necesidades.

Aybe
fuente
1
El problema es que es menos eficiente debido al hecho de que no puede aprovechar cosas como los índices.
Solomon Ucko