¿Puede una cadena de método C#ser "demasiado larga"?
method-chaining (4)
Esto es considerado un olor de código por algunos y no por otros. Cada vez que vea lo siguiente:
Foo.getBar().getBlah().getItem().getName();
realmente deberías estar pensando, "¿qué es lo que realmente quiero?" En su lugar, tal vez su método debería contener una llamada de función:
String getName(Int _id, String _item)
{
return myBar.getName( _id, _item );
}
que luego delega hacia abajo, entre las clases. Luego, si algo cambia en una de sus clases en una actualización posterior, verá exactamente dónde ocurre y puede cambiarlo en una ubicación.
Naturalmente, no en términos de legibilidad, ya que siempre puede organizar los métodos separados en líneas separadas. Más bien, ¿es peligroso, por alguna razón, encadenar un número excesivamente grande de métodos? Utilizo el encadenamiento de métodos principalmente para ahorrar espacio al declarar variables individuales de un solo uso, y tradicionalmente utilizo métodos de retorno en lugar de métodos que modifican a la persona que llama. Excepto por los métodos de cuerdas, los encadeno sin piedad. En cualquier caso, a veces me preocupa el impacto de usar cadenas de métodos excepcionalmente largas en una sola línea.
Digamos que necesito actualizar el valor de un elemento basado en el nombre de usuario de alguien. Desafortunadamente, el método más corto para recuperar el usuario correcto es similar al siguiente.
SPWeb web = GetWorkflowWeb();
SPList list2 = web.Lists["Wars"];
SPListItem item2 = list2.GetItemById(3);
SPListItem item3 = item2.GetItemFromLookup("Armies", "Allied Army");
SPUser user2 = item2.GetSPUser("Commander");
SPUser user3 = user2.GetAssociate("Spouse");
string username2 = user3.Name;
item1["Contact"] = username2;
Todo lo que tiene un 2 o un 3 dura solo una llamada, por lo que podría condensarlo de la siguiente manera (lo que también me permite deshacerme de un superfluo 1):
SPWeb web = GetWorkflowWeb();
item["Contact"] = web.Lists["Armies"]
.GetItemById(3)
.GetItemFromLookup("Armies", "Allied Army")
.GetSPUser("Commander")
.GetAssociate("Spouse")
.Name;
Es cierto que parece mucho más largo cuando está todo en una línea y cuando tienes int.Parse(ddlArmy.SelectedValue.CutBefore(";#", false))
lugar de 3
. Sin embargo, esta es una de las longitudes promedio de estas cadenas, y puedo prever fácilmente algunos de los conteos excepcionalmente más largos. Excluyendo la legibilidad, ¿hay algo de lo que deba preocuparme por estas más de 10 cadenas de métodos? ¿O no hay daño en el uso de cadenas de método realmente muy largas?
La única preocupación que debe considerar al respecto (si ignora la legibilidad) es el manejo de recursos y el GC. Las preguntas que debe hacer son:
- ¿Son las llamadas que estoy haciendo devolver objetos que deben ser eliminados?
- ¿Estoy iniciando un montón de cosas dentro de un solo ámbito en lugar de ámbitos más pequeños a los que GC podría ser más reactivo? (GC está programado para ejecutarse cada vez que deja un ámbito, aunque la programación y cuando realmente se ejecuta son dos cosas diferentes :-p).
Pero en realidad ... si llama a un método por línea, o colóquelas todas juntas, todo termina siendo el mismo en IL (o casi el mismo).
Josh
La legibilidad es la mayor preocupación, pero a menudo eso no es un problema en absoluto.
También puede describir la sintaxis de consulta LINQ como (debajo de todo) exactamente tal configuración. Simplemente hace que se vea más bonita ;-p
Un problema posible es cuando necesita introducir cosas como using
o lock
; con la API fluida puede sentirse tentado a simplemente eliminar estos componentes, pero eso puede llevar a rarezas cuando se lanza una excepción.
El otro pensamiento posible es que es posible que desee tener un manejo de excepciones más granular alrededor de algunas de las llamadas; pero siempre puedes simplemente romper el flujo:
var foo = bar.MethodA().MethodB(...).MethodC();
try {
foo.MethodD();
} catch (SomeSpecificException) {
//something interesting
}
O incluso puedes hacer eso en un método de extensión para mantener la apariencia fluida:
bar.MethodA().MethodB(...).MethodC().MyExtensionMethodD();
donde MyExtensionMethodD
es el que agrega con un manejo especial (excepciones, bloqueos, uso, etc.).
No hay limitaciones técnicas sobre el tiempo que puede durar una cadena de métodos.
Sin embargo, tres áreas que pueden volverse problemáticas son la depuración , el manejo de excepciones y la eliminación de recursos .
La depuración se complica por el mismo hecho que hace que el encadenamiento sea tan elegante: la falta de variables temporales intermedias. Desafortunadamente, sin las variables temporales, la inspección de los resultados intermedios cuando la depuración se vuelve dolorosa.
El manejo de las excepciones se complica por el hecho de que no puede aislar las excepciones generadas de un método en comparación con otro. Normalmente, esto no es un problema si no puede hacer algo significativo en respuesta a la excepción, simplemente deje que se propague por la cadena de llamadas. Sin embargo, si se da cuenta en algún momento posterior de que necesita un manejo de excepciones, debe refactorizar la sintaxis de encadenamiento para poder insertar los controladores de prueba / captura adecuados.
Similar al manejo de excepciones es el caso de la disposición determinista de recursos. La forma más fácil en C # para lograr esto es con using()
- desafortunadamente, la sintaxis de encadenamiento lo excluye. Si está llamando a métodos que devuelven objetos desechables, probablemente sea una buena idea evitar el encadenamiento de la sintaxis para poder ser un buen "ciudadano de código" y disponer de esos recursos lo antes posible.
La sintaxis de encadenamiento de métodos se usa a menudo en APIs fluidas , donde permite que la sintaxis de su código refleje más de cerca la secuencia de operaciones que desea. LINQ es un ejemplo en .NET donde a menudo se ve una sintaxis fluida / en cadena.