Quantcast
Channel: Planeta Código
Viewing all articles
Browse latest Browse all 2733

Koalite: Mitos y leyendas sobre métodos estáticos

$
0
0

El Aquellarre

En los últimos años he notado que cada vez utilizo más los métodos estáticos cuando desarrollo en C# (seguramente en su primo Java me pasaría lo mismo). No es que los busque de forma intencionada, sencillamente hay muchas ocasiones en las que me resulta la opción más natural.

Esto no sería tan extraño si no fuese porque durante mucho tiempo viví bajo la impresión (bastante generalizada) de que los métodos estáticos son evil. No es ni la primera ni la última vez que evoluciona mi opinión con respecto a algo, y siempre está bien analizar los motivos.

Cuándo utilizo métodos estáticos

Ya he dicho que no es algo que busque explícitamente, y de hecho en mi código conviven sin problemas partes en las que se utilizan más métodos estático, dando lugar a un código más funcional (o procedural), con partes mucho más orientadas a objetos.

Lo único que me lleva a decidir entre un estilo u otro es la comodidad a la hora de implementar (y testear, y mantener, etc.) una funcionalidad. Hay cosas que (creo) se modelan mejor desde un punto de vista de objetos, y otras desde un punto de vista de funciones.

Porque eso es lo que son los métodos estáticos: funciones en un lenguaje en el que no tienes más remedio que empaquetar todo en clases.

Hace diez años, Steve Yegge escribió un excelente post sobre verbos y sustantivos en el que se trataba este problema.

Cuando utilizamos un lenguaje como C# o Java en el que todo tiene que estar necesariamente dentro de una clase, acabamos diseñando a partir de sustantivos. Cada clase es un sustantivo. Tienes un Customer. Y un Order. Y un OrderProcessor, que sólo tiene un método, Process. Hay cosas que encajan muy bien en la filosofía de nombres, como Customer/ y Order, y otras que chirrían más, como el OrderProcessor.

Ante esto podemos mantenernos “dentro de los cánones establecidos por el lenguaje y la OOP”, o podemos revelarnos (muy poquito, tampoco nos engañemos) y asumir que parte de nuestro código es más sencillo y más bonito si lo modelamos directamente como funciones que, al final, se traducen en método estáticos.

En general, trato de evitar al máximo las clases que acaban en Manager, Service, Provider, Formatter, etc. Si puedo, aplico alguna de estas técnicas para pasar lógica hacia objetos con más peso, o intento convertirlos en método estáticos.

Por ejemplo, antes que tener esto:

public class OrderProcessor {
  public void ProcessAll(Order[] orders) {
    foreach (var order in orders)
      order.Process();
    DomainEvents.Raise(new OrderBatchProcessed(orders));
  }
}

Prefiero tener esto:

public class Order {
  // Todas las cosas que lleve un order

  public static void ProcessAll(Order[] orders) {
    foreach (var order in orders)
      order.Process();
    DomainEvents.Raise(new OrderBatchProcessed(orders));
  }
}

Si la clase Order llegase a crecer mucho, hay formas de tratar con clases grandes que también tienen sus ventajas.

Hay muchos escenarios en los que resulta cómodo recurrir a métodos estáticos.

En cuanto te alejas un poco del “dominio” (en el sentido más amplio de la palabra) de tu aplicación, y empiezas a necesitas funciones para convertir de un tipo de objeto a otro. O para formatear un objeto para mostrarlo en pantalla. O para serializarlo a XML o JSON de una manera específica.

Todo este tipo de operaciones suelen realizarse sin dependencias externas y son perfectos candidatos para implementar como métodos estáticos en lugar de colgarlos de alguna clase arbitraria solo para sentirte más OOP Master.

También suelo utilizarlos como fachadas para tratar con estado global. Es cierto que intento minimizar el estado global, pero a veces no queda más remedio que asumir la realidad, y tener una fachada estática para lanzar eventos de dominio, traducir recursos y cosas por el estilo ayuda a lidiar con él.

Cazando mitos

Muchos de los que piensan que los métodos estáticos son terribles acaban por crear objetos sin estado ni dependencias que contienen el método estático. Eso obliga a instanciar una clase para invocar un método que no depende para nada del resto de la clase.

¿Quién no ha visto este código en las típicas katas de TDD?

public class StringCalculator {
  public int Add(string numbers) {
    // Hacer algo con numbers 
  }
}

¿Por qué crear un clase como esa para tener dentro una función cuya salida depende únicamente de los parámetros de entrada?

Entre los argumentos que se usan para defender ese estilo está el de “las clases y métodos estáticos no son relamente orientación a objetos”.

Francamente, hoy en día no veo eso como un argumento válido. Para mi esto no va de poder aplicar al código una etiqueta de “100% OOP Certified” (o, para el caso, “100% FP Certified”), sino de sacarle partido a las herramientas que tenemos. Entiendo el argumento en un contexto didáctico, donde estás intentando aplicar al máximo un paradigma, pero en un entorno real hay que ser más pragmático.

También hay quien piensa que los métodos estáticos son más difíciles de testear, pero realmente no es así. De hecho, en el ejemplo anterior es (marginalmente) más sencillo testear StringCalculator.Add que new StringCalculator().Add y hay escenarios en los que la introducción de un método estático construido de la forma adecuada puede simplificar mucho la forma de testear, como explicaba en este post sobre cómo refactorizar tests unitarios para eliminar dependencias. Esa es una técnica que utilizo muy frecuentemente y me gusta mucho para aislar la parte que quiero testear de lo que no considero interesante. La clave para que algo sea fácil de testear no es si es estático o no, sino el tipo de dependencias que tiene y su relación con ellas.

En realidad, cuanto se habla de que los métodos estáticos son complicados de testear no es por el propio método estático: es por cómo testear aquello que depende del método estático. Normalmente cuando pasa es porque quieres (o necesitas) aislarte de la implementación real del método estático.

Si es porque consideras que los tests unitarios sólo pueden abarcar una clase y hay que aislarlos de todas sus dependencias, te invito a que reconsideres tu postura sobre qué constituye la unidad en un test unitario.

Si aun así no quieres invocar el método estático porque accede a recursos externos o es más lento, siempre tienes varias alternativas. Llegado el caso, podrías encapsular el método estático en una clase y usar esa clase como dependencia de lo que estás testeando. Yo no haría esto de forma especulativa, pero si fuera necesario refactorizar hacia ello es fácil. De todas formas, la solución más sencilla es inyectar el método estático como dependencia de lo que necesites testear.

Puedes pasar de esto:

public void SomeBigProcess() {
  var numbers = numberProvider.GetNumbers();
  var result = StringCalculator.Add(numbers);
  var mailBody = $"Result is {result}";
  mailSender.Send(mailBody);
}

A esto:

public void SomeBigProcess() {
  SomeBigProcess(StringCalculator.Add);
}

public void SomeBigProcess(Func<string, int> add) {
	var numbers = numberProvider.GetNumbers();
	var result = add(numbers);
	var mailBody = $"Result is {result}";
	mailSender.Send(mailBody);
}

Ahora tienes un método al que le puedes inyectar el método estático y testearlo como más te guste. Incluso en este caso es algo más fácil de testear que teniendo un StringCalculator porque en lugar de necesitar un mock/fake/stub, puedes crearte al vuelo al función que vas a usar en el test.

Algunos incluso podrían decir que no usar un método estático es un problema de eficiencia porque obliga a instanciar un objeto StringCalculator para nada y eso mete presión al GC. Vale, puede ser, pero no es mi caso. Es sumamente raro que tenga un código en el que me preocupe tanto el consumo de memoria y las pausas del GC como para que eso sea relavante.

Otro mito que arrastran los métodos estáticos es que no son seguros en un entorno multihebra.

Esta confusión puede venir motivada por el uso de variables estáticas. En un entorno multihebra, tener estado mutable estático es una receta segura para dolores de cabeza. Tenemos un punto global, del que dependen todas las hebras, que puede ser modificado por cualquiera y hace que las hebras queden acopladas entre si temporalmente (dependiendo de en qué orden se ejecuten y modifiquen el estado global, el resultado será uno u otro).

Sin embargo, cuando hablamos de métodos, este problema no existe. A menos que accedan a variables estáticas pero, claro, en ese caso da igual que el método lo sea o no: el problema es la variable, como veíamos antes.

Un método estático que depende sólo de sus parámetros de entrada es completamente seguro en un entorno multihebra. En realidad, es lo más seguro que puede haber porque no accede a estado global y ni siquiera accede a estado local a la instancia de la clase, puesto que no pertenece a ninguna instancia.

El problema es que, mal utilizados, los métodos estáticos suelen ir acompañados de variables estáticas y acaban dando lugar a situaciones un tanto desastrosas. Bueno, en realidad, mal utilizado, casi todo puede dar lugar a situaciones desastrosas.

Conclusión

Mezlar métodos estáticos (funciones) y clases no debería ser algo tan raro. A fin de cuentas, los que hayan programado en Javascript o Python estan acostumbrados a ello y seguro que han podido comprobar las ventajas de disponer de más herramientas a la hora de modelar la solución a un problema.

Parte de los problemas que se han atribuido tradicionalmente a los métodos estáticos son perfectamente salvable, y otros directamente no son problemas.

En mi opinión, parte del recelo hacia ellos viene de la época en la que la OOP no estaba tan extendida, había mucha gente que venía de programacíon estructurada clásica (C, Visual Basic), y se no se aprovechaban las características de los lenguajes orientados a objetos: seguían programando todo con funciones y estructuras de datos. No es que eso sea malo, pero cuando llevabas eso a un lenguaje que no estaba pensado para ello, y además lo hacía alguien sin experiencia, el resultado era catastrófico (todos hemos visto código así).

A día de hoy me gustaría pensar que no es tan necesario estar con el palo encima de la gente y que tenemos criterio suficiente para poder usar las herramientas que tenemos a nuestra disposición y sacarles partido. Tal vez estemos pegando a los monos equivocados.

Posts relacionados:

  1. Recopilatorio sobre modelos de dominio

Viewing all articles
Browse latest Browse all 2733