¿Uso del tiempo pasado (voz pasiva) para connotar la inmutabilidad? (Por ejemplo, Array.Transposed vs. Array.Transpose)

8

[Nota: El siguiente es un poco C # pesado en su sintaxis y convenciones, como citar las reglas de FxCop, porque este es mi fondo, pero la intención de esta discusión es ser independiente del lenguaje y los ejemplos de código utilizados generalmente deben considerarse como pseudocódigo. - Miguel]

Creo que todos estaríamos de acuerdo en que las clases generalmente deben diseñarse de modo que sus instancias de objeto sean inmutables siempre que sea práctico, y los resultados de llamar a los miembros de la clase deben estar libres de efectos secundarios, tanto como sea posible.

Sin embargo, las convenciones de nomenclatura históricas tienden a usar la voz activa: 'GetSomething', 'DoSomehing', etc. y esto es bueno porque establece claramente qué acción tomará el miembro de la clase.

Ahora estoy empezando a pensar, sin embargo, que los miembros a veces (o posiblemente a menudo) se benefician al ser nombrados en tiempo pasado para connotar que se devuelve un valor sin afectar el objeto referenciado.

Por ejemplo, para un método que transpone una matriz, los estilos de nomenclatura actuales probablemente lo llamarían 'Array.Transpose', pero si este miembro devuelve una nueva matriz, sin transponer el objeto al que se hace referencia, entonces creo que llamarlo 'Array.Transposed 'sería más preciso y lo hace más claro para el programador.

Cuando se trabaja con un método 'Array.Transpose ()', uno podría intentar accidentalmente usar código como el siguiente:

Array myArray = new Array(); 
myArray.Transpose();

Pero si el método 'Transponer' devuelve una nueva matriz sin afectar el objeto referenciado, entonces este código no hace nada, es decir, 'myArray' no se transpondría silenciosamente . (Supongo que si 'Transponer' fuera una propiedad, entonces el compilador entendería que lo anterior no es legal, pero las pautas de FxCop establecen que un miembro que realiza una conversión debería ser un método).

Pero si el método se nombrara en tiempo pasado como 'Array.Transposed', sería más claro para el usuario que se requiere la siguiente sintaxis:

Array myArray = new Array(); 
Array transposedArray = myArray.Transposed();

Otros nombres que creo que encajarían en esta categoría serían miembros denominados 'Resized' vs. 'Resize', 'Shifted' vs. 'Shift', etc. El tiempo presente se usaría para los miembros que afectaron el objeto referenciado, mientras que el el tiempo pasado se usaría para los miembros que devolvieron un nuevo valor sin afectar el objeto referenciado.

Otro enfoque que es más común actualmente es prefijar cada nombre con "Obtener" o "Para", pero creo que esto agrega un peso innecesario, mientras que el cambio al tiempo pasado comunica el significado de manera efectiva.

¿Tiene sentido para otros programadores aquí, o esta idea en tiempo pasado suena demasiado incómoda al leerla?

Editar:

Grandes discusiones a continuación, agradezco mucho a todos. A continuación, detallo algunos puntos que me gustaría resumir en base a sus comentarios y mi propio pensamiento revisado sobre esto.

Método de encadenamiento de sintaxis "fluidas"

Basado en las discusiones a continuación, el uso del tiempo pasado es probablemente de algún valor cuando se usa en una clase mutable para dejar en claro qué miembros afectan o no el estado interno del objeto referenciado.

Sin embargo, para las clases inmutables, que deberían ser la mayoría de los casos (uno esperaría), creo que el uso del tiempo pasado puede hacer que la sintaxis sea innecesariamente incómoda.

Por ejemplo, con "sintaxis fluidas" como las creadas por el método de encadenamiento, el uso del tiempo pasado haría que el código fuera menos legible. Por ejemplo, cuando se utiliza LINQ a través de C #, una cadena de método típica podría tener el siguiente aspecto:

dataSource.Sort().Take(10).Select(x => x.ToString);

Si se cambia al tiempo pasado, esto se volvería incómodo:

dataSource.Sorted().Taken(10).Selected(x => x.ToString);

Eso sí, dataSource.Sorted () tiene un significado más claro que dataSource.Sort (), porque las expresiones LINQ siempre crean una nueva enumeración sin afectar la fuente. Sin embargo, dado que somos plenamente conscientes de esto cuando usamos este paradigma, el uso del tiempo pasado es redundante y hace que la "fluidez" de leerlo sea incómoda sin una ganancia real.

Uso de propiedades versus métodos

Una cuestión clave que se menciona a continuación se refiere a las pautas de FxCop para el uso de métodos "Get" frente a las propiedades de solo lectura. Por ejemplo, la regla FxCop CA1024 .

Aunque no se menciona explícitamente en esa regla FxCop, el uso de los métodos "To" parecería aplicarse también a esta razón, ya que los métodos ToXXXXXX () son métodos de conversión. Y en el caso de ToArray (), el método es tanto un método de conversión como un método que devuelve una matriz.

Uso del tiempo pasado para propiedades booleanas

Algunos sugirieron que el tiempo pasado puede connotar un valor booleano, por ejemplo, una propiedad 'Éxito' o 'Conectado'. Creo que las siguientes réplicas que enfatizan el uso de un 'Is', 'Has' o 'Are' para propiedades y campos booleanos son correctas. El tiempo pasado también puede ayudar a este respecto, pero no tanto como usar un prefijo 'Is'. Siéntase libre de usar el tiempo pasado para las propiedades y campos booleanos, ayuda, pero creo que es aún más importante prefijar el nombre con 'Is', 'Has' o 'Are'.

Conclusiones generales

Para el tema de Tranpose () vs. Transposed (), creo que 'skizzy' lo hizo bien, sugiriendo que debería ser Transpose () para una acción mutante vs. GetTransposed () para un resultado no mutante. Esto lo hace 100% claro. Pero, de nuevo, creo que esto solo podría aplicarse para una clase mutuable, como una matriz.

Para las clases inmutables, especialmente donde podría usarse una sintaxis fluida de encadenamiento de métodos, entonces creo que esta sintaxis se volvería innecesariamente incómoda. Por ejemplo, compare:

var v = myObject.Transpose().Resize(3,5).Shift(2);

a:

var v = myObject.GetTransposed().GetResized(3,5).GetShifted(2);

Hmmm ... en realidad, incluso aquí parece dejar 100% claro que el resultado no afecta la fuente, pero no estoy seguro.

Supongo que para un paradigma como LINQ donde es bien conocido, el uso de "Get" y / o el tiempo pasado no es necesario, pero para una nueva API, puede tener algún valor, ¡al principio! Si la API contiene solo objetos inmutables, una vez que el usuario comprende esto, el uso de "Get" y / o el tiempo pasado solo reduce la legibilidad.

Así que creo que no hay respuestas fáciles aquí. ¡Supongo que uno debe pensar en la totalidad de su API y elegir con cuidado!

-- Miguel

Mike Rosenblum
fuente
Por supuesto, espero que solo olvidar el parens para la invocación de métodos (o los que son opcionales en el lenguaje de los ejemplos se encuentran, aunque eso es raro) y que no tenía la intención de ser estas propiedades ...
Hola Delnan, sí, es una propiedad y es internacional. Al ser una propiedad, es aún más claro que la propiedad 'Transponer' (o 'Transpuesta') devuelve un valor y no debería tener un efecto secundario. De hecho, el compilador debería recoger el uso incorrecto del código anterior, pero la idea aquí es tratar de ser aún más claro para el usuario, antes de que el compilador lo recoja.
Mike Rosenblum
Creo que es una muy mala idea usar propiedades para todo como crear un objeto completamente nuevo. Y para respaldarlo, MSDN dice que un método es preferible a una propiedad si "El método realiza una operación que consume mucho tiempo. El método es perceptiblemente más lento que el tiempo que lleva establecer u obtener el valor de un campo".
Buena fuente, muchas gracias por eso. Por lo tanto, mi ejemplo 'Transpuesto' podría haber sido ideal, ya que se tropieza con la regla CA1024 de FxCop porque este miembro (a) realiza una conversión y (b) devuelve una matriz. Dicho esto, la discusión sobre el tiempo presente frente al pasado todavía se aplica, ¡pero definitivamente usaré los métodos ToXXXXX () o GetXXXX () en lugar de las propiedades al devolver un resultado de conversión o matriz!
Mike Rosenblum
Creo que quieres decir que los objetos deberían ser inmutables, no clases. Las clases son generalmente siempre inmutables, al menos en lenguajes estáticamente tipados.
Nemanja Trifunovic

Respuestas:

7

El tiempo pasado suena un poco incómodo. Creo que pretendías que la palabra fuera un adjetivo.

Aquí está mi sugerencia para el problema que solicitó: en lugar de Transpose(), llámelo GetTransposed(). De esa manera, sabes que estás recibiendo algo y no estás modificando el objeto.

Sal
fuente
1
Creo que, al final, esta es realmente la respuesta "correcta". No voy a marcarlo como "correcto" porque (1) algunos otros, particularmente 'qes' y 'Developer Art' hicieron algunos comentarios realmente reflexivos, y (2) realmente no creo que haya un "derecho" responda a esto: esta es más una pregunta de "Wiki de la comunidad". (¿Tiene p.se ese concepto?)
Mike Rosenblum
1
+1 ¡Buena respuesta concisa! De esta manera Transpose(), todavía se puede interpretar como el ajuste del objeto dado. Quizás esto debería ser una construcción del lenguaje. :) object.Transpose()vs object:Transpose().
Steven Jeuris
Prefiero en AsTransposedlugar de los GetTransposedcasos en que las llamadas repetidas consecutivas, sin efectos secundarios, produzcan resultados semánticamente idénticos (que pueden o no ser el mismo objeto, pero no se puede mostrar que sean objetos diferentes sin usar cosas como ReferenceEqualso el equivalente) .
supercat
6

Tiene sentido de hecho.

Lo que estás hablando, en mi opinión, debe tomarse como una regla general al diseñar una API o una biblioteca. Solo entonces ofrecería un beneficio tangible si el sistema es consistente en todas las clases.

Otro problema es que, en muchos casos, a los usuarios de las interfaces no les importa cómo se logra el resultado.

¿Transponer () transpondrá el objeto actual? ¿Transpose () creará una nueva instancia transpuesta y le devolverá la referencia? ¿O tal vez se transponga una vez, guarde en caché los resultados en una copia y lo devuelva en llamadas posteriores sin afectar el objeto actual?

Los usuarios a menudo solo quieren obtener el resultado transpuesto, por lo que no muchos se preocuparán por la convención de nomenclatura que está proponiendo.

Pero por derecho propio, es una propuesta válida.


fuente
4

Estoy totalmente en desacuerdo. El tiempo pasado es incómodo y no se usa comúnmente (lo cual es razón suficiente en sí mismo para no preferirlo).

Si el método modificó el objeto sobre el que fue llamado, no debería volver a aparecer, esto deja en claro que modifica el objeto.

Del mismo modo, si el método no modifica el objeto sobre el que se llamó y en su lugar construye un nuevo objeto, el método devolverá un objeto del mismo tipo. Esta es una convención clara y ya común.

La firma del método es, en mi opinión, una convención más clara y mucho más comúnmente utilizada para indicar si el método devuelve un nuevo objeto o modifica a la persona que llama.

quentin-starin
fuente
Hola qus, sí, el enfoque en tiempo pasado no se usa comúnmente, por eso planteé la pregunta aquí. (Creo que tiene sentido, pero los precedentes cuentan mucho). Su punto sobre la firma del método es bueno, pero, desafortunadamente, es extremadamente sutil porque los compiladores generalmente no imponen que se use el resultado de un método . Por lo tanto, un compilador no pudo detectar el error de llamar a myObject.Resize () y no hacer uso del valor de retorno. Mi propuesta es que nombrar el método "Resized" haría más claro para el programador que result = myObject.Resized () es obligatorio.
Mike Rosenblum
1
Los compiladores ciertamente pueden advertir que el resultado de un método no se utiliza, que es más de lo que pueden hacer para cualquier comportamiento basado en el tiempo de un verbo en el idioma inglés utilizado en un identificador, por lo que es un punto no válido. Estoy afirmando que nombrar en tiempo pasado no lo aclarará porque: 1. No es una convención establecida. 2. Hay otras convenciones más establecidas 3. Habrá muchas palabras que serán excepcionalmente incómodas o completamente inválidas para tener en tiempo pasado, mientras que las firmas de métodos no sufrirán esa confusión.
quentin-starin
Claro que los compiladores podrían advertir esto, pero generalmente solo lo hacen para las propiedades. Los valores de retorno de los métodos que no se utilizan generalmente son ignorados por casi todos los compiladores en configuraciones estándar.
Mike Rosenblum
Dicho esto, cuanto más pienso en sus comentarios y la regla CA1024 de FxCop (msdn.microsoft.com/en-us/library/ms182181(VS.80).aspx), mis ejemplos son todas situaciones en las que se está produciendo una conversión y / o donde se devuelve una matriz. El resultado es que estos deberían usar un nombre de método GetXXXXX () o ToXXXXX (), y creo que esto es correcto. (Especialmente porque es la convención establecida.) Creo que nombrar el método en tiempo pasado, como en "GetTransposed ()", todavía es útil aquí, pero solo marginalmente.
Mike Rosenblum
3

Suena razonable, y hay precedentes: Python tiene ambos list.sort()y sorted(seq). El primero es un método, el otro es una función que actúa en cualquier iterable y devuelve una lista.

Adam Byrtek
fuente
¿list.sort () devuelve algo?
quentin-starin
No lo hace
Adam Byrtek
2

En la biblioteca de colecciones Scala, hay algunos métodos que están en tiempo pasado:

  • groupeddevuelve un iterador que itera sobre los elementos de una secuencia en fragmentos de nelementos
  • sorted devuelve una versión ordenada de la colección
  • updated devuelve una nueva versión de la colección con un elemento cambiado

Sin embargo, no estoy seguro de si fue una decisión consciente o si simplemente se quedaron sin nombres, porque la biblioteca de colección de Scala es muy rica. Por ejemplo, también hay groupByy sortBymétodos, y el updatemétodo es especial, porque el compilador reescribe la asignación

foo(bar) = baz

dentro

foo.update(bar, baz)

Las versiones anteriores de Scala sí se utilizaron updatepara colecciones inmutables, pero updatedevolver una nueva versión en lugar de mutar es muy incómodo, porque obviamente debe asignar el valor de retorno a algo, por ejemplo, "actualizar" una entrada en un mapa inmutable sería algo me gusta

val newMap = oldMap(key) = value // HUH ??!!??

Ahora es

val newMap = oldMap.updated(key -> value)

En un lenguaje con un buen sistema de tipos, los tipos generalmente le dirán si la rutina muta o no sus argumentos. En Haskell, por ejemplo, el tipo de versión inmutable sería algo así como

transpose :: Array -> Array

mientras que una versión mutable sería

transpose :: State Array
Jörg W Mittag
fuente
1

Cuando veo un método / propiedad en el tiempo pasado, inmediatamente asumo que devuelve un valor booleano. Su propuesta puede tener sentido lógicamente, pero parece extraño e incómodo. Si tengo que explicarle a alguien por qué tiene sentido, probablemente no sea una buena convención.

Ed S.
fuente
2
Es por eso que es una buena convención prefijar valores booleanos con "es". ; p Además, la razón por la que lo explica es porque no es una convención. Algunas respuestas a esta pregunta dependen demasiado de las convenciones existentes. Debe poder apartarse y juzgarlo sin una convención conocida.
Steven Jeuris
0

No soy un gran admirador de los nombres de variables en tiempo pasado para la inmutabilidad: es una convención algo inusual y creo que causaría confusión, especialmente para aquellas personas que no son particularmente fuertes en la gramática inglesa (y solo estoy bromeando a medias allí...)

Además, estas convenciones eliminan la oportunidad de usar el tiempo pasado para denotar algo más que creo que es más importante: una descripción del estado del objeto descrito, por ejemplo, "elementos procesados" le dice claramente que los elementos de una colección ya han sido procesado, que es información lógica importante para transmitir.

Una mejor idea: hacer que todo sea inmutable . Entonces no necesitas ninguna distinción lingüística elegante. Sabes que quieres :-)

mikera
fuente
1
Por eso es una buena convención prefijar valores booleanos con "es" y "tiene".
Steven Jeuris