¿Es malo usar muchos métodos estáticos?

97

Tiendo a declarar como estáticos todos los métodos de una clase cuando esa clase no requiere realizar un seguimiento de los estados internos. Por ejemplo, si necesito transformar A en B y no confío en algún estado interno C que puede variar, creo una transformación estática. Si hay un estado interno C que quiero poder ajustar, agrego un constructor para configurar C y no uso una transformación estática.

Leí varias recomendaciones (incluso en StackOverflow) para NO abusar de los métodos estáticos, pero aún no entiendo qué es lo que está mal con la regla general anterior.

¿Es un enfoque razonable o no?

Lolo
fuente

Respuestas:

152

Hay dos tipos de métodos estáticos comunes:

  • Un método estático "seguro" siempre dará la misma salida para las mismas entradas. No modifica globales y no llama a ningún método estático "inseguro" de ninguna clase. Esencialmente, está utilizando un tipo limitado de programación funcional; no tenga miedo de estos, están bien.
  • Un método estático "inseguro" muta el estado global, o hace proxy a un objeto global, o algún otro comportamiento no comprobable. Estos son retrocesos a la programación procedimental y deben refactorizarse si es posible.

Hay algunos usos comunes de la estática "insegura", por ejemplo, en el patrón Singleton, pero tenga en cuenta que, a pesar de los nombres bonitos que los llame, solo está mutando variables globales. Piense detenidamente antes de utilizar estática insegura.

John Millikin
fuente
Este era exactamente el problema que tenía que resolver: el uso, o más bien el mal uso, de los objetos Singleton.
excedido el
Gracias por esa excelente respuesta. Mi pregunta es, si los singletons se pasan como parámetros a los métodos estáticos, ¿eso hace que el método estático no sea seguro?
Tony D
1
Los términos "función pura" y "función impura" son nombres que se dan en programación funcional a lo que usted llama estática "segura" e "insegura".
Omnimike
19

Un objeto sin ningún estado interno es algo sospechoso.

Normalmente, los objetos encapsulan el estado y el comportamiento. Un objeto que solo encapsula el comportamiento es extraño. A veces es un ejemplo de peso ligero o mosca .

Otras veces, el diseño procedimental se realiza en un lenguaje de objetos.

S.Lott
fuente
6
Escucho lo que estás diciendo, pero ¿cómo puede algo como un objeto Math encapsular algo más que el comportamiento?
JonoW
10
Simplemente dijo sospechoso, no incorrecto, y tiene toda la razón.
Bill K
2
@JonoW: Math es un caso muy especial donde hay muchas funciones sin estado. Por supuesto, si está haciendo programación funcional en Java, tendrá muchas funciones sin estado.
S.Lott
13

En realidad, esto es solo una continuación de la gran respuesta de John Millikin.


Aunque puede ser seguro hacer que los métodos sin estado (que son prácticamente funciones) sean estáticos, a veces puede conducir a un acoplamiento que es difícil de modificar. Considere que tiene un método estático como tal:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

Que llamas como:

StaticClassVersionOne.doSomeFunkyThing(42);

Lo cual está muy bien, y es muy conveniente, hasta que se encuentra con un caso en el que tiene que modificar el comportamiento del método estático y descubre que está estrechamente vinculado a él StaticClassVersionOne. Posiblemente podría modificar el código y estaría bien, pero si hubiera otras personas que llamaran dependiendo del comportamiento anterior, deberán contabilizarse en el cuerpo del método. En algunos casos, el cuerpo del método puede volverse bastante feo o imposible de mantener si trata de equilibrar todos estos comportamientos. Si divide los métodos, es posible que deba modificar el código en varios lugares para tenerlo en cuenta o realizar llamadas a nuevas clases.

Pero considere si ha creado una interfaz para proporcionar el método y se la ha dado a las personas que llaman, ahora, cuando el comportamiento debe cambiar, se puede crear una nueva clase para implementar la interfaz, que es más limpia, más fácil de probar y más fácil de mantener. y eso, en cambio, se le da a quienes llaman. En este escenario, las clases que llaman no necesitan ser modificadas o incluso recompiladas, y los cambios están localizados.

Puede que sea una situación probable o no, pero creo que vale la pena considerarla.

Grundlefleck
fuente
5
Sostengo que este no es solo un escenario probable, esto hace que la estática sea un último recurso. La estática también hace que TDD sea una pesadilla. Dondequiera que use la estática, no puede simular, debe saber cuál es la entrada y la salida para probar una clase no relacionada. Ahora, si cambia el comportamiento de la estática, sus pruebas en clases no relacionadas que usan esa estática se rompen. Además, se convierte en una dependencia oculta que no puede pasar al constructor para notificar a los desarrolladores de una dependencia potencialmente importante.
DanCaveman
6

La otra opción es agregarlos como métodos no estáticos en el objeto de origen:

es decir, cambiando:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

dentro

public class Bar {
    ...
    public Foo transform() { ...}
}

sin embargo, en muchas situaciones esto no es posible (por ejemplo, generación de código de clase regular desde XSD / WSDL / etc.), o hará que la clase sea muy larga, y los métodos de transformación a menudo pueden ser un verdadero dolor para los objetos complejos y solo los desea en su propia clase separada. Así que sí, tengo métodos estáticos en clases de utilidad.

JeeBee
fuente
5

Las clases estáticas están bien siempre que se utilicen en los lugares correctos.

A saber: métodos que son métodos "hoja" (no modifican el estado, simplemente transforman la entrada de alguna manera). Buenos ejemplos de esto son cosas como Path.Combine. Este tipo de cosas son útiles y hacen que la sintaxis sea más tersa.

Los problemas que tengo con la estática son numerosos:

En primer lugar, si tiene clases estáticas, las dependencias están ocultas. Considera lo siguiente:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

Mirando TextureManager, no puede decir qué pasos de inicialización deben llevarse a cabo mirando un constructor. Debe profundizar en la clase para encontrar sus dependencias e inicializar las cosas en el orden correcto. En este caso, necesita que ResourceLoader se inicialice antes de ejecutarse. Ahora amplíe esta pesadilla de dependencia y probablemente pueda adivinar lo que sucederá. Imagínese tratando de mantener el código donde no hay un orden explícito de inicialización. Compare esto con la inyección de dependencias con instancias; en ese caso, el código ni siquiera se compilará si las dependencias no se cumplen.

Además, si usa estática que modifica el estado, es como un castillo de naipes. Nunca se sabe quién tiene acceso a qué, y el diseño tiende a parecerse a un monstruo espagueti.

Finalmente, e igualmente importante, el uso de estática vincula un programa a una implementación específica. El código estático es la antítesis del diseño para la prueba. Probar código plagado de estática es una pesadilla. Una llamada estática nunca se puede intercambiar por un doble de prueba (a menos que use marcos de prueba diseñados específicamente para simular tipos estáticos), por lo que un sistema estático hace que todo lo que lo usa sea una prueba de integración instantánea.

En resumen, las estáticas están bien para algunas cosas y para herramientas pequeñas o código desechable no desaconsejaría su uso. Sin embargo, más allá de eso, son una maldita pesadilla para el mantenimiento, el buen diseño y la facilidad de prueba.

Aquí hay un buen artículo sobre los problemas: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

Mark Simpson
fuente
4

La razón por la que se le advierte de los métodos estáticos es que su uso pierde una de las ventajas de los objetos. Los objetos están destinados a la encapsulación de datos. Esto evita que se produzcan efectos secundarios inesperados, lo que evita errores. Los métodos estáticos no tienen datos encapsulados *, por lo que no obtienen este beneficio.

Dicho esto, si no tiene uso de datos internos, están bien de usar y son un poco más rápidos de ejecutar. Sin embargo, asegúrese de no tocar datos globales en ellos.

  • Algunos lenguajes también tienen variables a nivel de clase que permitirían la encapsulación de datos y métodos estáticos.
Steve Rowe
fuente
4

Ese parece ser un enfoque razonable. La razón por la que no desea utilizar demasiadas clases / métodos estáticos es que termina alejándose de la programación orientada a objetos y más hacia el ámbito de la programación estructurada.

En su caso en el que simplemente está transformando A en B, diga que todo lo que estamos haciendo es transformar el texto para pasar de

"hello" =>(transform)=> "<b>Hello!</b>"

Entonces, un método estático tendría sentido.

Sin embargo, si está invocando estos métodos estáticos en un objeto con frecuencia y tiende a ser único para muchas llamadas (por ejemplo, la forma en que lo usa depende de la entrada), o es parte del comportamiento inherente del objeto, lo haría Sea prudente convertirlo en parte del objeto y mantener un estado del mismo. Una forma de hacerlo sería implementarlo como una interfaz.

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

Editar: Un buen ejemplo del gran uso de métodos estáticos son los métodos de ayuda html en Asp.Net MVC o Ruby. Crean elementos html que no están vinculados al comportamiento de un objeto y, por lo tanto, son estáticos.

Edición 2: se cambió la programación funcional a la programación estructurada (por alguna razón me confundí), apoyo a Torsten por señalar eso.

MunkiPhD
fuente
2
No creo que el uso de métodos estáticos califique como programación funcional, así que supongo que te refieres a la programación estructurada.
Torsten Marek
3

Recientemente refactoricé una aplicación para eliminar / modificar algunas clases que se habían implementado inicialmente como clases estáticas. Con el tiempo, estas clases adquirieron mucho y la gente siguió etiquetando las nuevas funciones como estáticas, ya que nunca hubo una instancia flotando.

Entonces, mi respuesta es que las clases estáticas no son inherentemente malas, pero podría ser más fácil comenzar a crear instancias ahora y luego tener que refactorizarlas más tarde.

holgado
fuente
3

Lo consideraría un olor a diseño. Si se encuentra utilizando principalmente métodos estáticos, probablemente no tenga un diseño de OO muy bueno. No es necesariamente malo, pero como con todos los olores, me haría detenerme y reevaluar. Sugiere que es posible que pueda hacer un mejor diseño de OO, o que tal vez debería ir en la otra dirección y evitar OO por completo para este problema.

patros
fuente
2

Solía ​​ir y venir entre una clase con un montón de métodos estáticos y un singleton. Ambos resuelven el problema, pero el singleton se puede reemplazar mucho más fácilmente con más de uno. (Los programadores siempre parecen tan seguros de que solo habrá 1 de algo y me equivoqué las veces suficiente para renunciar por completo a los métodos estáticos, excepto en algunos casos muy limitados).

De todos modos, el singleton te da la posibilidad de pasar algo más tarde a la fábrica para obtener una instancia diferente y eso cambia el comportamiento de todo tu programa sin refactorizar. Cambiar una clase global de métodos estáticos en algo con diferentes datos de "respaldo" o un comportamiento ligeramente diferente (clase secundaria) es un gran dolor de cabeza.

Y los métodos estáticos no tienen una ventaja similar.

Entonces sí, son malos.

Bill K
fuente
1

Siempre que no entre en juego el estado interno, está bien. Tenga en cuenta que, por lo general, se espera que los métodos estáticos sean seguros para subprocesos, por lo que si usa estructuras de datos auxiliares, utilícelas de manera segura para subprocesos.

Lucero
fuente
1

Si sabe que nunca necesitará utilizar el estado interno de C, está bien. Sin embargo, si eso alguna vez cambia en el futuro, deberá hacer que el método no sea estático. Si, para empezar, no es estático, puede simplemente ignorar el estado interno si no lo necesita.

Adam Crume
fuente
1

Si es un método de utilidad, es bueno hacerlo estático. Guava y Apache Commons se basan en este principio.

Mi opinión al respecto es puramente pragmática. Si es el código de su aplicación, los métodos estáticos generalmente no son lo mejor que puede tener. Los métodos estáticos tienen serias limitaciones de prueba unitaria; no se pueden burlar fácilmente: no se puede inyectar una funcionalidad estática simulada en otra prueba. Por lo general, tampoco puede inyectar funcionalidad en un método estático.

Entonces, en la lógica de mi aplicación, generalmente tengo pequeñas llamadas a métodos de utilidad estática. Es decir

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

uno de los beneficios es que no pruebo tales métodos :-)

Andrey Chaschev
fuente
1

Bueno, por supuesto que no existe una fórmula mágica. Las clases estáticas están bien para pequeñas utilidades / ayudantes. Pero el uso de métodos estáticos para la programación lógica empresarial es ciertamente malo. Considere el siguiente código

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

Ves una llamada de método estático a ItemsProcessor.SplitItem(newItem);It huele porque

  • No tiene una dependencia explícita declarada y si no profundiza en el código, puede pasar por alto el acoplamiento entre su clase y el contenedor de métodos estáticos
  • No puede probar BusinessServiceaislándolo de ItemsProcessor(la mayoría de las herramientas de prueba no simulan clases estáticas) y hace que las pruebas unitarias sean imposibles. Sin pruebas unitarias == baja calidad
Konstantin Chernov
fuente
0

Los métodos estáticos son generalmente una mala elección incluso para código sin estado. En su lugar, cree una clase singleton con estos métodos que se instancia una vez y se inyecta en las clases que desean usar los métodos. Estas clases son más fáciles de burlar y probar. Están mucho más orientados a objetos. Puede envolverlos con un proxy cuando sea necesario. Las estáticas hacen que OO sea mucho más difícil y no veo ninguna razón para usarlas en casi todos los casos. No al 100%, pero casi todos.

Juan
fuente