Pensamentos e melhores práticas sobre classes estáticas e membros [fechado]

11

Estou muito curioso quanto a pensamentos e melhores práticas do setor em relação a membros estáticos ou classes estáticas inteiras. Existe alguma desvantagem nisso ou ela participa de algum antipadrão?

Eu vejo essas entidades como "classes / membros de utilidade", no sentido de que elas não exigem ou exigem instanciar a classe para fornecer a funcionalidade.

Quais são os pensamentos gerais e as melhores práticas da indústria sobre isso?

Veja o exemplo abaixo de classes e membros para ilustrar o que estou referenciando.

// non-static class with static members
//
public class Class1
{
    // ... other non-static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

// static class
//
public static class Class2
{
    // ... other static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

Agradeço antecipadamente!

Thomas Stringer
fonte
1
O artigo do MSDN sobre classes estáticas e métodos estáticos fornece um tratamento muito bom.
Robert Harvey
1
@ Robert Harvey: Embora o artigo que você referenciou seja útil, ele não fornece muito em termos de práticas recomendadas e quais são algumas das armadilhas ao usar classes estáticas.
Bernard

Respostas:

18

Em geral, evite a estática - especialmente qualquer tipo de estado estático.

Por quê?

  1. A estática causa problemas com a simultaneidade. Como existe apenas uma instância, ela é compartilhada naturalmente entre a execução simultânea. Esses recursos compartilhados são prejudiciais à programação simultânea e geralmente não precisam ser compartilhados.

  2. A estática causa problemas no teste de unidade. Qualquer estrutura de teste de unidade que valha a pena executa testes simultaneamente. Então você encontra o número 1. Pior ainda, você complica seus testes com todo o material de instalação / desmontagem, e com os truques em que você se encaixa para tentar "compartilhar" o código de instalação.

Há muitas coisas pequenas também. As estáticas tendem a ser inflexíveis. Você não pode fazer interface com eles, não pode substituí-los, não pode controlar o tempo de sua construção, não pode usá-los bem em genéricos. Você não pode realmente versão deles.

Certamente há usos da estática: valores constantes funcionam muito bem. Métodos puros que não se encaixam em uma única classe podem funcionar muito bem aqui.

Mas, em geral, evite-os.

Telastyn
fonte
Estou curioso quanto à incapacidade de concorrência, você quer dizer. Você pode expandir isso? Se você quer dizer que os membros podem ser acessados ​​sem segregação, isso faz todo o sentido. Seus casos de uso de estática são o que eu normalmente os uso: valores constantes e métodos de pura / utilidade. Se esse é o melhor caso de uso e 99% para estática, isso me dá um nível de conforto. Além disso, marque +1 na sua resposta. Ótima informação.
Thomas Stringer
@ThomasStringer - apenas que quanto mais pontos de contato entre threads / tarefas, mais oportunidades para problemas de simultaneidade e / ou mais perda de desempenho durante a sincronização.
Telastyn
Portanto, se um encadeamento está acessando um membro estático no momento, outros encadeamentos precisam esperar até que o encadeamento proprietário libere o recurso?
Thomas Stringer
@ThomasStringer - talvez, talvez não. A estática é (quase na maioria dos idiomas) diferente de qualquer outro recurso compartilhado a esse respeito.
Telastyn
@ Thomashoinger: Infelizmente, é realmente pior do que isso, a menos que você marque o membro volatile. Você simplesmente não obtém garantias do modelo de memória sem dados voláteis, portanto, alterar uma variável em um encadeamento pode não refletir imediatamente ou de maneira alguma.
Phoshi 19/03/14
17

Se a função é "pura", não vejo problemas. Uma função pura opera apenas nos parâmetros de entrada e fornece um resultado com base nisso. Não depende de nenhum estado global ou contexto externo.

Se eu olhar para o seu próprio exemplo de código:

public class Class1
{
    public static string GetSomeString()
    {
        // do something
    }
}

Esta função não aceita nenhum parâmetro. Portanto, provavelmente não é puro (a única implementação pura dessa função seria retornar uma constante). Suponho que este exemplo não seja representativo do seu problema real, estou apenas apontando que essa provavelmente não é uma função pura.

Vamos dar um exemplo diferente:

public static bool IsOdd(int number) { return (number % 2) == 1; }

Não há nada errado com esta função ser estática. Podemos até transformar isso em uma função de extensão, permitindo que o código do cliente se torne ainda mais legível. As funções de extensão são basicamente apenas um tipo especial de funções estáticas.

Telastyn menciona corretamente a simultaneidade como um problema potencial com membros estáticos. No entanto, como essa função não usa o estado compartilhado, não há problemas de simultaneidade aqui. Mil threads podem chamar essa função simultaneamente sem problemas de simultaneidade.

Na estrutura do .NET, os métodos de extensão já existem há algum tempo. O LINQ contém muitas funções de extensão (por exemplo, Enumerable.Where () , Enumerable.First () , Enumerable.Single () , etc.). Nós não vemos isso tão ruim, não é?

O teste de unidade geralmente pode se beneficiar quando o código usa abstrações substituíveis, permitindo que o teste de unidade substitua o código do sistema por um teste duplo. As funções estáticas proíbem essa flexibilidade, mas isso é principalmente importante nos limites da camada de arquitetura, onde queremos substituir, por exemplo, uma camada de acesso a dados real por uma camada de acesso a dados falsa .

No entanto, ao escrever um teste para um objeto que se comporte de maneira diferente, dependendo se algum número é ímpar ou par, não precisamos realmente substituir a IsOdd()função por uma implementação alternativa. Da mesma forma, não vejo quando precisamos fornecer uma Enumerable.Where()implementação diferente para fins de teste.

Então, vamos examinar a legibilidade do código do cliente para esta função:

Opção a (com a função declarada como um método de extensão):

public void Execute(int number) {
    if (number.IsOdd())
        // Do something
}

Opção b:

public void Execute(int number) {
    var helper = new NumberHelper();
    if (helper.IsOdd(number))
        // Do something
}

A função estática (extensão) torna o primeiro trecho de código muito mais legível e a legibilidade é muito importante; portanto, use funções estáticas sempre que apropriado.

Pete
fonte