Considere el siguiente método:
public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
}
Y la siguiente llamada:
var ids = ReturnEmployeeIds(true);
Para un desarrollador nuevo en el sistema, sería bastante difícil adivinar lo que true
hizo. Lo primero que debe hacer es pasar el mouse sobre el nombre del método o ir a la definición (ninguna de las cuales es una gran tarea en lo más mínimo). Pero, por razones de legibilidad, ¿tiene sentido escribir:
var ids = ReturnEmployeeIds(includeManagement: true);
¿Hay algún lugar que, formalmente, discuta si especificar explícitamente parámetros opcionales cuando el compilador no lo necesita?
El siguiente artículo analiza ciertas convenciones de codificación: https://msdn.microsoft.com/en-gb/library/ff926074.aspx
Algo similar al artículo anterior sería genial.
c#
coding-style
readability
parameters
JᴀʏMᴇᴇ
fuente
fuente
boolean
argumentos de método, y cómo?Respuestas:
Yo diría que en el mundo de C #, una enumeración sería una de las mejores opciones aquí.
Con eso, se vería obligado a explicar lo que está haciendo y cualquier usuario vería lo que está sucediendo. También es seguro para futuras extensiones;
Entonces, en mi opinión aquí:
enum> opcional enum> opcional bool
Editar: debido a una discusión con LeopardSkinPillBoxHat a continuación con respecto a una
[Flags]
enumeración que en este caso específico podría ser una buena opción (ya que estamos hablando específicamente de incluir / excluir cosas), en su lugar propongo usarloISet<WhatToInclude>
como parámetro.Es un concepto más "moderno" con varias ventajas, principalmente debido al hecho de que encaja en la familia de colecciones LINQ, pero que también
[Flags]
tiene un máximo de 32 grupos. La desventaja principal de usarISet<WhatToInclude>
es cuán mal se admiten los conjuntos en la sintaxis de C #:Parte de esto podría ser mitigado por funciones auxiliares, tanto para generar el conjunto como para crear "conjuntos de atajos" como
fuente
enum
enfoque, pero no soy un gran fanático de la mezclaInclude
y,Exclude
al mismoenum
tiempo, es más difícil comprender lo que está sucediendo. Si desea que esto sea realmente extensible, puede convertirlo en un[Flags]
enum
, hacerlos todosIncludeXxx
y proporcionar un valorIncludeAll
establecido enInt32.MaxValue
. Luego puede proporcionar 2ReturnEmployeeIds
sobrecargas, una que devuelve a todos los empleados y otra que toma unWhatToInclude
parámetro de filtro que puede ser una combinación de indicadores de enumeración.[Flags]
es una reliquia y solo debe usarse por motivos heredados o de rendimiento. Creo que aISet<WhatToInclude>
es un concepto mucho mejor (desafortunadamente C # carece de algo de azúcar sintáctico para que sea agradable de usar).Flags
enfoque porque permite que la persona que llama paseWhatToInclude.Managers | WhatToInclude.Cleaners
y el bit a bit o expresa exactamente cómo la persona que llama lo procesará (es decir, gerentes o limpiadores). Azúcar sintáctico intuitivo :-)Es discutible si este es un "buen estilo", pero en mi humilde opinión, este no es un mal estilo y es útil, siempre y cuando la línea de código no sea "demasiado larga". Sin parámetros con nombre, también es un estilo común introducir una variable explicativa:
Ese estilo es probablemente más común entre los programadores de C # que la variante de parámetro con nombre, ya que los parámetros con nombre no formaban parte del lenguaje anterior a la Versión 4.0. El efecto sobre la legibilidad es casi el mismo, solo necesita una línea de código adicional.
Entonces, mi recomendación: si cree que usar una enumeración o dos funciones con nombres diferentes es "excesivo", o si no desea o no puede cambiar la firma por un motivo diferente, continúe y use el parámetro con nombre.
fuente
includeManagement
como localconst
y evitar el uso de memoria adicional.Si encuentra código como:
puedes garantizar que lo que hay dentro
{}
será algo así como:En otras palabras, el booleano se usa para elegir entre dos cursos de acción: el método tiene dos responsabilidades. Esto está prácticamente garantizado un olor a código . Siempre debemos esforzarnos por garantizar que los métodos solo hagan una cosa; solo tienen una responsabilidad. Por lo tanto, argumentaría que ambas soluciones son el enfoque equivocado. El uso de parámetros opcionales es una señal de que el método está haciendo más de una cosa. Los parámetros booleanos también son un signo de esto. Tener ambos definitivamente debería hacer sonar las alarmas. Por lo tanto, lo que debe hacer es refactorizar los dos conjuntos diferentes de funcionalidad en dos métodos separados. Dé nombres a los métodos que aclaren lo que hacen, por ejemplo:
Esto mejora la legibilidad del código y garantiza que siga mejor los principios de SOLID.
EDITAR Como se ha señalado en los comentarios, el caso aquí es que estos dos métodos probablemente tendrán una funcionalidad compartida, y esa funcionalidad compartida probablemente estará mejor envuelta en una función privada que necesitará un parámetro booleano para controlar algún aspecto de lo que hace. .
En este caso, ¿se debe proporcionar el nombre del parámetro, aunque el compilador no lo necesite? Sugeriría que no hay una respuesta simple a eso y que se necesita un juicio caso por caso:
¿El archivo fuente y los métodos son pequeños y fáciles de entender? En caso afirmativo, entonces el parámetro nombrado probablemente equivale a ruido. Si no, puede ser muy útil. Entonces aplique la regla de acuerdo a las circunstancias.
fuente
public List<Guid> ReturnEmployeeIds(bool includeManagement) { calculateSomethingHereForBothBranches; if (includeManagement) return something else return something else }
que en el código real, dividirlo simplemente en dos métodos probablemente significará que esos dos métodos necesitarán el resultado del primer cálculo como parámetro.Si verdad. El código autodocumentado es algo hermoso. Como han señalado otras respuestas, hay formas de eliminar la ambigüedad de la llamada al
ReturnEmployeeIds
usar una enumeración, diferentes nombres de métodos, etc. Sin embargo, a veces no se puede evitar el caso, pero no desea ir y usar ellos en todas partes (a menos que te guste la verbosidad de visual basic).Por ejemplo, puede ayudar a aclarar una llamada pero no necesariamente otra.
Un argumento con nombre puede ser útil aquí:
No agrega mucha claridad adicional (voy a adivinar que esto está analizando una enumeración):
En realidad, puede reducir la claridad (si una persona no entiende qué capacidad tiene una lista):
O donde no importa porque estás usando buenos nombres de variables:
No tengo miedo. Sin embargo, abordemos ambas partes: la única vez que necesita usar explícitamente parámetros con nombre es porque el compilador requiere que lo haga (generalmente es para eliminar la ambigüedad de la declaración). Aparte de eso, puede usarlos cuando lo desee. Este artículo de MS tiene algunas sugerencias sobre cuándo puede usarlas, pero no es particularmente definitivo https://msdn.microsoft.com/en-us/library/dd264739.aspx .
Personalmente, creo que los uso con mayor frecuencia cuando estoy creando atributos personalizados o creando un nuevo método donde mis parámetros pueden cambiar o reordenarse (los atributos con nombre b / c se pueden enumerar en cualquier orden). Además, solo los uso cuando proporciono un valor estático en línea, de lo contrario, si paso una variable, trato de usar buenos nombres de variables.
En última instancia, se trata de preferencias personales. Por supuesto, hay algunos casos de uso en los que tiene que usar parámetros con nombre, pero después de eso debe hacer una llamada de juicio. OMI: utilícelo en cualquier lugar que crea que ayudará a documentar su código, reducirá la ambigüedad o le permitirá proteger las firmas de métodos.
fuente
Si el parámetro es obvio por el nombre (completo) del método y su existencia es natural para lo que se supone que el método para hacerlo, debe no utiliza la sintaxis de parámetro con nombre. Por ejemplo,
ReturnEmployeeName(57)
es bastante obvio que57
es la identificación del empleado, por lo que es redundante anotarlo con la sintaxis del parámetro con nombre.Con
ReturnEmployeeIds(true)
, como dijiste, a menos que mires la declaración / documentación de la función, es imposible saber quétrue
significa, por lo que debes usar la sintaxis del parámetro con nombre.Ahora, considere este tercer caso:
La sintaxis del parámetro con nombre no se usa aquí, pero dado que le estamos pasando una variable
ReturnEmployeeIds
, y esa variable tiene un nombre, y ese nombre significa lo mismo que el parámetro al que se lo estamos pasando (a menudo es el caso, pero ¡no siempre!): no necesitamos la sintaxis del parámetro con nombre para comprender su significado del código.Por lo tanto, la regla es simple: si puede comprender fácilmente con solo la llamada al método lo que se supone que significa el parámetro, no use el parámetro con nombre. Si no puede, eso es una deficiencia en la legibilidad del código, y probablemente quiera arreglarlos (no a cualquier precio, por supuesto, pero generalmente quiere que el código sea legible). La corrección no tiene que nombrarse parámetros: también puede poner el valor en una variable primero, o usar los métodos sugeridos en las otras respuestas, siempre que el resultado final facilite la comprensión de lo que hace el parámetro.
fuente
ReturnEmployeeName(57)
hace que sea inmediatamente obvio que esa57
es la identificación.GetEmployeeById(57)
por otro lado ...ReturnEmployeeName
para demostrar casos en los que, incluso sin mencionar explícitamente el parámetro en el nombre del método, es bastante razonable esperar que la gente se dé cuenta de lo que es. En cualquier caso, es digno de mención queReturnEmployeeName
, a diferencia , no se puede renombrar razonablementeReturnEmployeeIds
algo que indique el significado detrás de los parámetros.Sí, creo que es buen estilo.
Esto tiene más sentido para las constantes booleanas y enteras.
Si está pasando una variable, el nombre de la variable debe aclarar el significado. Si no es así, debe darle un mejor nombre a la variable.
Si está pasando una cadena, el significado a menudo es claro. Al igual que si veo una llamada a GetFooData ("Oregon"), creo que un lector puede adivinar que el parámetro es un nombre de estado.
No todas las cadenas son obvias, por supuesto. Si veo GetFooData ("M"), a menos que conozca la función, no tengo idea de lo que significa "M". Si se trata de algún tipo de código, como M = "empleados de nivel gerencial", entonces probablemente deberíamos tener una enumeración en lugar de un literal, y el nombre de la enumeración debería estar claro.
Y supongo que una cadena podría ser engañosa. Tal vez "Oregon" en mi ejemplo anterior se refiere, no al estado, sino a un producto o un cliente o lo que sea.
Pero GetFooData (verdadero) o GetFooData (42) ... eso podría significar cualquier cosa. Los parámetros con nombre son una forma maravillosa de hacer que se documente automáticamente. Los he usado exactamente para ese propósito en varias ocasiones.
fuente
No hay razones súper claras de por qué usar este tipo de sintaxis en primer lugar.
En general, trate de evitar tener argumentos booleanos que a distancia puedan parecer arbitrarios.
includeManagement
(muy probablemente) afectará en gran medida el resultado. Pero el argumento parece que tiene "poco peso".Se ha discutido el uso de una enumeración, no solo parecerá que el argumento "tiene más peso", sino que también proporcionará escalabilidad al método. Sin embargo, esta podría no ser la mejor solución en todos los casos, ya que su
ReturnEmployeeIds
método tendrá que escalar junto alWhatToInclude
-enum (vea la respuesta de NiklasJ ). Esto podría darte un dolor de cabeza más tarde.Considere esto: si escala el
WhatToInclude
-enum, pero no elReturnEmployeeIds
-metodo. Entonces podría arrojar unArgumentOutOfRangeException
(mejor de los casos) o devolver algo completamente no deseado (null
o vacíoList<Guid>
). Lo que en algunos casos puede confundir al programador, especialmente siReturnEmployeeIds
está en una biblioteca de clases, donde el código fuente no está disponible.Se supondrá que
WhatToInclude.Trainees
funcionará si loWhatToInclude.All
hace, ya que los alumnos son un "subconjunto" de todos.Esto (por supuesto) depende de cómo
ReturnEmployeeIds
se implemente.En los casos en que se pueda pasar un argumento booleano, intento dividirlo en dos métodos (o más, si es necesario), en su caso; uno podría extraer
ReturnAllEmployeeIds
,ReturnManagementIds
yReturnRegularEmployeeIds
. Esto cubre todas las bases y se explica por sí mismo a quien lo implemente. Esto tampoco tendrá el problema de "escala" mencionado anteriormente.Dado que solo hay dos resultados para algo que tiene un argumento booleano. Implementar dos métodos requiere muy poco esfuerzo adicional.
Menos código, rara vez es mejor.
Con esto dicho, no son algunos casos en los que expresamente se declara el argumento mejora la legibilidad. Considerar por ejemplo.
GetLatestNews(max: 10)
.GetLatestNews(10)
es todavía bastante auto-explicativo, la declaración explícita demax
ayuda aclarará cualquier confusión.Y si absolutamente debe tener un argumento booleano, qué uso no puede inferirse simplemente leyendo
true
ofalse
. Entonces diría que:.. es absolutamente mejor y más legible que:
Como en el segundo ejemplo,
true
podría significar absolutamente cualquier cosa . Pero trataría de evitar este argumento.fuente