Usar muitos métodos estáticos é ruim?


97

Eu tendo a declarar como estáticos todos os métodos em uma classe quando essa classe não exige o controle dos estados internos. Por exemplo, se eu precisar transformar A em B e não depender de algum estado interno C que pode variar, crio uma transformação estática. Se houver um estado interno C que desejo poder ajustar, adiciono um construtor para definir C e não uso uma transformação estática.

Eu li várias recomendações (incluindo sobre StackOverflow) para NÃO usar métodos estáticos, mas ainda não consigo entender o que há de errado com a regra prática acima.

Essa é uma abordagem razoável ou não?

Respostas:


152

Existem dois tipos de métodos estáticos comuns:

  • Um método estático "seguro" sempre dará a mesma saída para as mesmas entradas. Ele não modifica globais e não chama nenhum método estático "inseguro" de nenhuma classe. Essencialmente, você está usando um tipo limitado de programação funcional - não tenha medo deles, eles estão bem.
  • Um método estático "inseguro" altera o estado global, ou proxies para um objeto global, ou algum outro comportamento não testável. Esses são retrocessos para a programação procedural e devem ser refatorados se possível.

Existem alguns usos comuns de estática "insegura" - por exemplo, no padrão Singleton - mas esteja ciente de que, apesar de qualquer nome bonito que você chame, você está apenas alterando variáveis ​​globais. Pense bem antes de usar estática insegura.


Este foi exatamente o problema que eu tive que resolver - o uso, ou melhor, o uso indevido de objetos Singleton.
overslacked

Obrigado por essa excelente resposta. Minha pergunta é: se os singletons são passados ​​como parâmetros para os métodos estáticos, isso torna o método estático inseguro?
Tony D

1
Os termos "função pura" e "função impura" são nomes dados na programação funcional ao que você chama de estática "segura" e "insegura".
Omnimike,

19

Um objeto sem qualquer estado interno é algo suspeito.

Normalmente, os objetos encapsulam o estado e o comportamento. Um objeto que apenas encapsula o comportamento é estranho. Às vezes, é um exemplo de peso leve ou peso - mosca .

Outras vezes, é um projeto procedural feito em uma linguagem de objeto.


6
Eu ouço o que você está dizendo, mas como algo como um objeto Math pode encapsular qualquer coisa além de comportamento?
JonoW,

10
Ele apenas disse suspeito, não errado, e ele está absolutamente certo.
Bill K de

2
@JonoW: A matemática é um caso muito especial em que existem muitas funções sem estado. Claro, se você estiver fazendo programação funcional em Java, terá muitas funções sem estado.
S.Lott de

13

Esta é realmente apenas uma continuação da grande resposta de John Millikin.


Embora possa ser seguro tornar estáticos métodos sem estado (que são basicamente funções), às vezes pode levar a um acoplamento difícil de modificar. Considere que você tem um método estático como tal:

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

Que você chama de:

StaticClassVersionOne.doSomeFunkyThing(42);

O que é muito bom e muito conveniente, até que você encontre um caso em que precise modificar o comportamento do método estático e descubra que está fortemente vinculado a ele StaticClassVersionOne. Possivelmente, você poderia modificar o código e não teria problema, mas se houvesse outros chamadores dependentes do comportamento antigo, eles precisarão ser contabilizados no corpo do método. Em alguns casos, esse corpo de método pode ficar muito feio ou impossível de manter se tentar equilibrar todos esses comportamentos. Se você dividir os métodos, pode ter que modificar o código em vários lugares para levá-lo em conta, ou fazer chamadas para novas classes.

Mas considere se você criou uma interface para fornecer o método e a deu aos chamadores, agora quando o comportamento precisa mudar, uma nova classe pode ser criada para implementar a interface, que é mais limpa, mais facilmente testada e mais sustentável, e isso é dado aos chamadores. Nesse cenário, as classes de chamada não precisam ser alteradas ou mesmo recompiladas, e as mudanças são localizadas.

Pode ser ou não uma situação provável, mas acho que vale a pena considerar.


5
Eu argumento que este não é apenas um cenário provável, mas torna a estática um último recurso. A estática também torna o TDD um pesadelo. Onde quer que você use o estático, você não pode simular, você tem que saber qual é a entrada e a saída para testar uma classe não relacionada. Agora, se você alterar o comportamento do estático, seus testes em classes não relacionadas que usam esse estático serão interrompidos. Além disso, torna-se uma dependência oculta que você não pode passar para o construtor para notificar os desenvolvedores de uma dependência potencialmente importante.
DanCaveman

6

A outra opção é adicioná-los como métodos não estáticos no objeto de origem:

ou seja, mudando:

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

para dentro

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

no entanto, em muitas situações isso não é possível (por exemplo, geração de código de classe regular de XSD / WSDL / etc), ou tornará a classe muito longa, e os métodos de transformação podem muitas vezes ser uma verdadeira dor para objetos complexos e você apenas os deseja em sua própria classe separada. Sim, tenho métodos estáticos em classes de utilitários.


5

Classes estáticas funcionam, desde que sejam usadas nos lugares certos.

A saber: Métodos que são métodos 'folha' (eles não modificam o estado, eles simplesmente transformam a entrada de alguma forma). Bons exemplos disso são coisas como Path.Combine. Esses tipos de coisas são úteis e tornam a sintaxe mais concisa.

Os problemas que tenho com a estática são numerosos:

Em primeiro lugar, se você tiver classes estáticas, as dependências serão ocultadas. Considere o seguinte:

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 }       
}

Olhando para TextureManager, você não pode dizer quais etapas de inicialização devem ser executadas olhando para um construtor. Você deve mergulhar na classe para encontrar suas dependências e inicializar as coisas na ordem correta. Nesse caso, ele precisa que o ResourceLoader seja inicializado antes de ser executado. Agora aumente esse pesadelo de dependência e provavelmente você pode adivinhar o que vai acontecer. Imagine tentar manter o código onde não há uma ordem explícita de inicialização. Compare isso com injeção de dependência com instâncias - nesse caso, o código nem mesmo compilará se as dependências não forem atendidas!

Além disso, se você usar estática que modifica o estado, é como um castelo de cartas. Você nunca sabe quem tem acesso a quê, e o design tende a se parecer com um monstro espaguete.

Finalmente, e tão importante, o uso da estática vincula um programa a uma implementação específica. O código estático é a antítese do design para testabilidade. Testar códigos crivados de estática é um pesadelo. Uma chamada estática nunca pode ser trocada por um duplo de teste (a menos que você use estruturas de teste projetadas especificamente para simular tipos estáticos), portanto, um sistema estático faz com que tudo o que o usa seja um teste de integração instantâneo.

Resumindo, a estática é boa para algumas coisas e para pequenas ferramentas ou código descartável, eu não desencorajaria seu uso. No entanto, além disso, eles são um pesadelo sangrento para manutenção, bom design e facilidade de teste.

Aqui está um bom artigo sobre os problemas: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/


4

O motivo pelo qual você é avisado sobre os métodos estáticos é que usá-los perde uma das vantagens dos objetos. Os objetos são destinados ao encapsulamento de dados. Isso evita que efeitos colaterais inesperados aconteçam, o que evita bugs. Os métodos estáticos não têm dados encapsulados * e, portanto, não obtêm esse benefício.

Dito isso, se você não tiver nenhum uso de dados internos, eles podem ser usados ​​e um pouco mais rápidos de executar. Porém, certifique-se de não tocar em dados globais neles.

  • Algumas linguagens também possuem variáveis ​​de nível de classe que permitiriam o encapsulamento de dados e métodos estáticos.

4

Essa parece ser uma abordagem razoável. O motivo pelo qual você não deseja usar muitas classes / métodos estáticos é que você acaba se afastando da programação orientada a objetos e indo mais para o reino da programação estruturada.

No seu caso, em que você está simplesmente transformando A em B, digamos que tudo o que estamos fazendo é transformar o texto para partir

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

Então, um método estático faria sentido.

No entanto, se você está invocando esses métodos estáticos em um objeto com frequência e tende a ser exclusivo para muitas chamadas (por exemplo, a maneira como você os usa depende da entrada), ou se faz parte do comportamento inerente do objeto, seja sábio torná-lo parte do objeto e manter um estado dele. Uma maneira de fazer isso seria implementá-lo como uma interface.

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()
}

Edit: Um bom exemplo de grande uso de métodos estáticos são métodos auxiliares html em Asp.Net MVC ou Ruby. Eles criam elementos html que não estão vinculados ao comportamento de um objeto e, portanto, são estáticos.

Edição 2: Programação funcional alterada para programação estruturada (por algum motivo, fiquei confuso), adereços a Torsten por apontar isso.


2
Não acho que usar métodos estáticos se qualifique como programação funcional, então estou supondo que você quer dizer programação estruturada.
Torsten Marek de

3

Recentemente, refatorei um aplicativo para remover / modificar algumas classes que foram inicialmente implementadas como classes estáticas. Com o tempo, essas classes adquiriram muito e as pessoas continuaram marcando as novas funções como estáticas, já que nunca havia uma instância flutuando.

Portanto, minha resposta é que as classes estáticas não são inerentemente ruins, mas pode ser mais fácil começar a criar instâncias agora e ter que refatorar mais tarde.


3

Eu consideraria um cheiro de design. Se você está usando métodos estáticos, provavelmente não tem um design OO muito bom. Não é necessariamente ruim, mas como acontece com todos os cheiros, me faria parar e reavaliar. Isso sugere que você pode ser capaz de fazer um design OO melhor, ou que talvez você deva ir na outra direção e evitar totalmente OO para este problema.


2

Eu costumava ir e voltar entre uma classe com um monte de métodos estáticos e um singleton. Ambos resolvem o problema, mas o singleton pode ser substituído com muito mais facilidade por mais de um. (Os programadores sempre parecem tão certos de que haverá apenas 1 de alguma coisa e eu me vi errado o suficiente para desistir completamente dos métodos estáticos, exceto em alguns casos muito limitados).

De qualquer forma, o singleton lhe dá a capacidade de posteriormente passar algo para a fábrica para obter uma instância diferente e isso muda o comportamento de todo o seu programa sem refatorar. Mudar uma classe global de métodos estáticos em algo com diferentes dados de "apoio" ou um comportamento ligeiramente diferente (classe filha) é um grande problema.

E os métodos estáticos não têm vantagens semelhantes.

Então, sim, eles são ruins.


1

Contanto que nenhum estado interno entre em ação, tudo bem. Observe que geralmente se espera que os métodos estáticos sejam seguros para threads, portanto, se você usar estruturas de dados auxiliares, use-as de maneira segura para threads.


1

Se você sabe que nunca precisará usar o estado interno de C, tudo bem. No entanto, caso isso mude no futuro, você precisará tornar o método não estático. Se não for estático para começar, você pode simplesmente ignorar o estado interno se não precisar dele.


1

Se for um método utilitário, é bom torná-lo estático. Guava e Apache Commons são construídos sobre este princípio.

Minha opinião sobre isso é puramente pragmática. Se for o código do seu aplicativo, os métodos estáticos geralmente não são a melhor coisa a se ter. Os métodos estáticos têm sérias limitações de teste de unidade - eles não podem ser facilmente simulados: você não pode injetar uma funcionalidade estática simulada em algum outro teste. Normalmente, você também não pode injetar funcionalidade em um método estático.

Portanto, na lógica do meu aplicativo, geralmente tenho pequenas chamadas de método do tipo utilitário estático. Ie

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

um dos benefícios é que não testo esses métodos :-)


1

Bem, não existe bala de prata, é claro. Classes estáticas são adequadas para pequenos utilitários / ajudantes. Mas usar métodos estáticos para programação de lógica de negócios é certamente um mal. Considere o seguinte 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;
        }
    }

Você vê uma chamada de método estático para a ItemsProcessor.SplitItem(newItem);causa do cheiro

  • Você não tem uma dependência explícita declarada e se você não se aprofundar no código, pode ignorar o acoplamento entre sua classe e o contêiner de método estático
  • Você não pode testar BusinessServiceisolando-o ItemsProcessor(a maioria das ferramentas de teste não simula classes estáticas) e isso torna o teste de unidade impossível. Sem testes de unidade == baixa qualidade

0

Os métodos estáticos geralmente são uma escolha ruim, mesmo para código sem estado. Em vez disso, faça uma classe singleton com esses métodos que é instanciada uma vez e injetada nas classes que desejam usar os métodos. Essas classes são mais fáceis de simular e testar. Eles são muito mais orientados a objetos. Você pode envolvê-los com um proxy quando necessário. A estática torna o OO muito mais difícil e não vejo razão para usá-la em quase todos os casos. Não 100%, mas quase tudo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.