Os métodos que não são "funções puras" e que interagem com APIs ou hardware externos devem ser estáticos?

8

Ao ler sobre quando tornar um método estático ou não, vi um princípio geral, conforme resumido neste post , de que um método só deve ser estático se não modificar um estado e seu resultado depender apenas dos parâmetros fornecidos para isto. No entanto, a resposta mais votada nesta publicação afirma que métodos estáticos devem ser usados ​​sempre que possível. Muitas das respostas deste post dizem que é preciso fazer o que for mais lógico.

No meu caso, tenho ~ 15 métodos em uma classe de utilitário comum porque eles são usados ​​em vários locais e não fazem sentido como membro de qualquer modelo ou modelo de exibição (usando C # MVVM). Existem métodos nulos que interagem com vários componentes de hardware usando seus pacotes (por exemplo, National Instruments, clientes OPC, etc.). Também existem métodos que recebem alguma entrada, fazem um GET ou PUT em nossa API e retornam uma resposta - esses métodos usam HttpClient ou algo semelhante. Esses métodos não são operadores simples na entrada, como Math.Sqrt(), e não alteram os estados.

Então, esses métodos (e, neste caso, toda a classe) devem ser estáticos? Existe o benefício óbvio de ter uma classe estática com métodos estáticos: é seguro e rápido, porque você não precisa criar um objeto. Além disso, cada um desses métodos estáticos usa mais métodos e classes estáticos para interações de API e hardware. A única desvantagem que vejo é que eu teria que escrever testes de unidade com uma estrutura paga, como o TypeMock Isolator. O custo não é um problema se a resposta é zombar deles usando algo como o TypeMock Isolator ou algum serviço pago semelhante; portanto, se esse for o consenso, tudo bem. Eu só quero tomar uma decisão que seja bem dimensionada e deixe pouca dívida técnica para trás, à medida que criamos novos desenvolvedores e à medida que nosso projeto cresce.

Informe-me se precisar fornecer mais informações para tornar isso mais claro!

adjordan
fonte
3
"interagir com vários componentes de hardware", "Também existem métodos que recebem alguma entrada, fazem um GET ou PUT em nossa API e, em seguida, retornam uma resposta" -> estes são os exemplos filhos posteres de interação com o estado
Caleth
2
Para expandir o comentário de @ Caleth: Lembre-se de que "state" não se aplica apenas àqueles e zeros na memória. Se você chama uma função que move o braço de um robô, muda de estado - no mundo real - junto com todas as ramificações de fazê-lo.
Greg Burghardt
1
"seguro e rápido, porque você não precisa criar um objeto". No caso da API de rede, você certamente está criando muitos objetos dentro da chamada . E, em qualquer caso, o tempo para criar seu objeto é pequeno comparado à latência da rede.
user949300
O que você fará quando tiver mais de um mesmo componente de hardware? Copiar / colar todos os métodos?
usar o seguinte comando

Respostas:

15

Então, esses métodos (e, neste caso, toda a classe) devem ser estáticos?

Não. Ou pelo menos, você não deve usá-los diretamente como estática.

Se você está trabalhando com vários componentes de hardware, você está muito muito propensos a querer zombar deles, use os novos, e escolher qual deles você está usando. Isso tudo grita por ter uma interface (ou outra abstração), para que o resto do seu código possa imediatamente ignorar a existência de algum componente de hardware. E a estática na maioria dos idiomas joga muito mal com interfaces (e outros mecanismos de abstração).

Telastyn
fonte
1
Ou, pelo menos, é comum ter coleções de métodos estáticos que fazem as chamadas reais que falam com coisas externas, mas depois criar uma classe de API stateful sobre ela e expor essa classe como a maneira de fazer as coisas, e é essa classe que é ridicularizada em vez das interfaces subjacentes, ou seja, padrão de fachada.
Whatsisname
Obrigado pela resposta! Este parece ser o consenso, por isso vou aderir a este princípio daqui para frente.
adjordan 6/06/19
4

Os conselhos conflitantes que você leu todos fazem sentido de alguma maneira, mas todos eles fazem suposições (diferentes) ou frases ambíguas. É um daqueles "dizendo a mesma coisa com palavras diferentes" tipos de distinções.

um método só deve ser estático se não modificar um estado

Observe a ambiguidade de "modificar um estado". O exemplo a seguir viola essa regra (literalmente), mas preserva o espírito (figurativo) da regra:

public static void SetUserNameToBob(User u)
{
    u.Name = "Bob";
}

Isso modifica o estado do Userobjeto e, de fato, "modifica um estado".

No entanto, esse método não depende de um estado interno específico para decidir seu próprio fluxo lógico (por exemplo, u.Name = currentlySelectedDefaultName()seria uma violação, pois o nome selecionado é um estado selecionado). E acho que é isso que significa: nenhum estado interno é modificado.

[um método só deve ser estático se] seu resultado depende apenas dos parâmetros fornecidos a ele

Veja o item anterior, este está praticamente dizendo a mesma coisa. O que isso significa é que isto:

public static string currentlySelectedDefaultName; 
public static void SetUserNameToBob(User u)
{
    u.Name = currentlySelectedDefaultName;
}

não deve ser estático, pois o nome "padrão atual" é um estado e, portanto, não deve ser uma variável / método global.

Pense no que acontece se dois threads separados estiverem em execução: um deles quer usar o padrão "Bob", o outro quer usar o padrão "Jim". Eles acabarão brigando pelo valor, que pode criar problemas de depuração maciços e comportamento inesperado.
Se, no entanto, cada thread tiver seu próprio DefaultNameSetterobjeto, eles não lutarão pelo mesmo recurso.

No entanto, a resposta mais votada nesta publicação afirma que métodos estáticos devem ser usados ​​sempre que possível.

Isso é meio que impor uma regra por tentativa / falha. Podemos definir esse método como estático?

  • Sim =>bom! Mantenha assim!
  • Não =>Isso prova que o código depende de um valor não estático em algum lugar e, portanto, não deve ser tornado estático.

Para citar indiretamente Jeff Goldblum em Jurassic Park , você não deve discutir a necessidade de fazer algo apenas provando que isso pode ser feito.

Novamente, a abordagem não está necessariamente (ou sempre) errada, mas assume cegamente que os métodos já foram escritos para serem tão independentes do estado quanto logicamente possível, o que simplesmente não é o caso.

Mesmo se você se inscrever nessa abordagem metodológica, ainda poderá aplicá-la quando um projeto não estiver mais em desenvolvimento. Se o projeto ainda estiver em desenvolvimento, os métodos atuais podem ser espaços reservados para a futura implementação. Pode ser possível tornar Foo()estática hoje, mas não amanhã, se a lógica dependente do estado simplesmente ainda não tiver sido implementada.

Muitas das respostas neste post dizem que é preciso fazer o que for mais lógico.

Bem, eles não estão errados; mas isso não é apenas uma pequena reformulação de dizer "faça a coisa certa"? Esse não é um conselho realmente útil, a menos que você já saiba quando usar estática e quando evitá-la. É uma pegadinha 22.


Então, quando você deve usar estática?

Como você notou, nem todos concordam com a regra, ou pelo menos em como formular a regra. Vou adicionar outra tentativa aqui, mas esteja ciente de que isso está criando efetivamente outro padrão :

insira a descrição da imagem aqui

Tenha isso em mente.

A estática são verdades universais.

Esse é o objetivo de um espaço para nome global: coisas que estão corretas em toda a camada do aplicativo.

Há um argumento de declive escorregadio aqui. Alguns exemplos:

var myConfigKey = ConfigurationManager.AppSettings["myConfigKey"];

Este é um exemplo muito claro. A configuração do aplicativo implica inerentemente que a configuração é global para o aplicativo e, portanto, é garantido um método estatístico.

bool datesOverlap = DateHelper.HasOverlap(myDateA_Start, myDateA_End, myDateB_Start, myDateB_End);

Este método é universalmente correto. Não importa quais datas você está comparando. O método não se importa com o significado das datas. Sejam datas de emprego, datas de contrato, ... não importa para o algoritmo do método.

Observe a semelhança semântica entre contextual e guiada pelo estado . Ambos os tipos se referem a um estado "não universal". Isso prova que as diferenças contextuais, portanto, dependem do estado e não são adequadas para serem tornadas estáticas.

var newPersonObject = Person.Create();

Esta é uma verdade universal. O mesmo processo de criação é usado em todo o aplicativo.

No entanto, esta linha pode ficar embaçada:

var newManager = Person.CreateManager();
var newJanitor = Person.CreateJanitor();

Do ponto de vista técnico, nada mudou. Gerente (e zeladores) são criados da mesma maneira em todo o aplicativo. No entanto, isso criou sutilmente um estado (gerente / zelador), que diminui lenta mas firmemente o quão universal é realmente a verdade.

Isso pode ser feito? Do ponto de vista técnico; sim .
Isso deveria ser feito? É uma questão de saber se você está discutindo o princípio puro ou se leva em consideração que é necessário fazer compromissos para não se esforçar sem sentido pela perfeição lógica. Então, eu diria que se não criar um problema maior do que o problema pretende resolver .

À medida que as opções se expandem (gerentes, zeladores, contadores, vendedores, ...), o problema aumenta. Para um problema suficientemente grande, é desejável um padrão de fábrica .

Se você tiver apenas duas opções e não tiver motivos para suspeitar que a lista de opções aumentará, é possível argumentar que os métodos de criação estáticos são suficientes. Alguns podem discordar, e eu também entendo o que eles querem dizer. Mas eu costumo ser prático e não excessivamente perfeccionista em minha abordagem.

Flater
fonte
3

No entanto, a resposta mais votada neste post afirma que métodos estáticos devem ser usados ​​sempre que possível ...

Métodos estáticos que afetam o estado ao qual estão diretamente acoplados são uma péssima idéia. Eles levam ao estado global - "ação assustadora à distância" - questões, código de espaguete e similares. Eles são difíceis de testar, depurar e manter. Minha leitura da resposta mais votada não encoraja nada disso, então está tudo bem.

Então, esses métodos (e, neste caso, toda a classe) devem ser estáticos?

Depende. Vamos dar um exemplo dos " métodos nulos que interagem com vários componentes de hardware usando seus pacotes ". Esses métodos podem ser estáticos, mas apenas se forem dissociados desses pacotes. Uma maneira de conseguir isso é passar esses pacotes como parâmetros. Deseja testar o método sem o hardware? Fácil, passe um pacote fictício e zombado que registra ações, em vez de acessar o hardware através do parâmetro O mesmo se aplica aos métodos HTTP; desde que o meio real de acessar a API seja transmitido como parâmetro, também.

Mas, você realmente está conseguindo algo dessa maneira ao criar uma instância e injetar esses pacotes, classes HTTP etc. no momento da construção? Provavelmente não. Claro, eles são métodos estáticos, portanto você não precisa de uma instância. Mas você tem a complexidade adicional de precisar expor todos esses pacotes, etc., por todo o código, a fim de passá-los como parâmetros. Muitas vezes, é muito mais fácil criar uma única instância e distribuí-la.

David Arno
fonte
Você está dizendo para repassar uma única instância de qualquer classe "UtilityMethods.cs" que eu faço? Atualmente, estou usando injeção de dependência com SimpleIoC, que eu acho que é a mesma idéia.
adjordan 6/06/19
2
@adjordan, de fato. Se você usa DI puro (do homem pobre) ou uma estrutura de IoC para ajudar na injeção, é com você. O pinciple permanece o mesmo: é mais fácil passar apenas uma instância do IUtilityMethodsque passar várias instâncias que precisam ser passadas para funções estáticas.
David Arno
1

O que recomendo e o que estou usando em minha equipe é usar métodos / classes estáticos, se houver uma necessidade real. Não deve ser a primeira opção para usar métodos / classes estáticos. Tem que ser uma boa razão para isso. Muitos desenvolvedores vêem que a opção estática é mais rápida e, portanto, mais barata. Mas isso é talvez no começo. O custo é criar testes de unidade mais complicados e sem a capacidade de usar contratos (interfaces) com várias implementações. Para os métodos / classes que interagem com o hardware. Estes não devem ser estáticos. Primeiro e para a maioria, não há razão. Segundo, é necessário zombar dessas classes em testes de unidade para obter um código de teste mais simples. Terceiro, é possível substituir a implementação no futuro.

Arafat Hegazy
fonte
0

As funções estáticas são mais desafiadoras quando você deseja permitir o acesso múltiplo em paralelo.

Por exemplo, é perfeitamente razoável, e provavelmente uma boa idéia, permitir que várias chamadas HTTP sejam executadas ao mesmo tempo. Com um objeto de instância, você pode facilmente manter um estado diferente para essas chamadas - os cabeçalhos, códigos de resultado, buffers, etc.

Com um único método estático, você pode criar uma instância interna para manter esse estado. Ou você sincroniza tudo (estou usando a terminologia Java, YMMV), que é suscetível a erros. Ou use uma fila. De qualquer forma, sem um trabalho de pés sofisticado, você perde a capacidade de correr em paralelo. Se uma chamada HTTP atolar devido a uma falha na rede, todas as outras terão que esperar.

O mesmo para o seu hardware - em alguns casos, pode fazer sentido permitir o acesso em paralelo.

Por esses motivos, discordo da resposta de 8 anos que você citou.

user949300
fonte
4
Uma função estática não deve conter nenhum estado.
Pieter B
1
@ Pieter B É claro (exceto um contador). No exemplo HTTP, seria necessário criar um objeto MyHTTPHelper por chamada para manter o estado. Nesse ponto, provavelmente é mais simples fazer com que o chamador original crie um objeto, não use um método estático.
user949300