Por que todos os métodos não são virtuais ou por que cada classe não possui pelo menos uma interface?


8

Essa é uma pergunta mais filosófica, que aborda a plataforma .NET, mas talvez seja útil também para outros idiomas. Estou fazendo muitos testes de unidade e, especialmente, quando estou usando componentes de terceiros com os quais frequentemente luto. No .NET, há uma enorme reivindicação ao design de componentes para a escolha de qual método deve ser virtual ou não. Por um lado, é o uso de componentes (o que faz sentido ser virtual), por outro, é a mockability de componentes . Você pode usar o Shims para simular componentes de terceiros, mas isso geralmente leva a um design e complexidade ruins. Como me lembro da discussão sobre tornar todos os métodos virtuais (o JAVA o tem desde o início), o .NET era sobre desempenho. Mas ainda é o problema? Por que todos os métodos do .NET não são virtuais ou por que cada classe não possui pelo menos uma interface?


1
Ah, sim, porque depois de alguns minutos alguém me votou (minha primeira vez), eu gostaria de saber o porquê.
Anton Kalcik

Suponho que a pessoa que votou para encerrar esta questão como "muito ampla" também tenha votado contra você.
David Arno

1
"Programadores Stack Exchange é um site de perguntas e respostas para programadores profissionais interessados ​​em perguntas conceituais sobre desenvolvimento de software" Entendi algo errado?
Anton Kalcik

5
Costumo concordar que esta questão é muito ampla. "Por que cada classe não tem um virtual?" Por que deveria? Você mencionou o teste. Talvez você possa melhorar a pergunta alterando-a para "por que essa classe (a classe .net mais irritante) não tem esses métodos marcados como virtuais?" No entanto, você pode descobrir que a resposta é "Ah, sim. Eles realmente não anteciparam a explosão do TDD ao projetar o .NET 1.0"
perfeccionista

4
Se você deseja torná-lo amplo, porque você está interessado apenas em opiniões, o assunto é fora de tópico por dois motivos: muito amplo e baseado em opiniões .
Jörg W Mittag

Respostas:


7

Como Anders diz , em parte se trata de desempenho e, em parte, de bloquear projetos mal-pensados ​​para reduzir o escopo de problemas causados ​​posteriormente por pessoas que herdam cegamente coisas que não foram projetadas para serem herdadas.

O desempenho parece óbvio - embora a maioria dos métodos não seja notada no hardware moderno, um getter virtual pode causar um impacto notável, mesmo no hardware moderno que depende cada vez mais de instruções facilmente previstas no pipeline da CPU.

Agora, o motivo de tornar tudo virtual por padrão parece ser motivado apenas por uma coisa: estruturas de teste de unidade. Parece-me que este é um motivo fraco, em que estamos alterando o código para se adequar à estrutura, em vez de projetá-la adequadamente.

Talvez o problema seja com as estruturas usadas aqui, elas devem melhorar para permitir a substituição de funções em tempo de execução por calços injetados, em vez de criar código que faça isso (com todos os hacks para contornar métodos privados e não virtuais, para não mencionar funções estáticas e de terceiros)

Portanto, a razão pela qual os métodos não são virtuais por padrão é como Anders disse, e qualquer desejo de torná-los virtuais para se adequar a alguma ferramenta de teste de unidade está colocando o carrinho à frente do cavalo, o design adequado supera as restrições artificiais.

Agora você pode mitigar tudo isso usando interfaces ou reformulando toda a sua base de código para usar componentes (ou microsserviços) que se comunicam por meio da passagem de mensagens em vez de chamadas de método com fio direto. Se você aumentar a superfície de uma unidade para que o teste seja um componente e esse componente seja totalmente independente, você não precisará realmente parafusá-lo para testá-lo.


Eu não poderia concordar mais.
Robert Harvey

@gbjbaanb Eu não entendi a última frase "Se você aumentar a superfície de uma unidade para testar como um componente, e esse componente é totalmente independente, você realmente não precisa se ferrar com ele para testar a unidade. "
Anton Kalcik 31/05

Excelente resposta (melhor que a minha, sinto-me :)
David Arno

1
@AntonKalcik Quero dizer, se você tem uma caixa preta, ela não deve ter dependências que precisam de zombaria. Ou seja, você testa testando entradas e vendo o que sai. Se sua caixa é pequena e você a projetou para ser dissociada de outros serviços, isso pode acontecer facilmente. O problema é que uma classe não é desagrupável de outras classes com muita facilidade e, portanto, exige zombaria. Se você pensa em uma unidade como um componente (uma DLL talvez, ou um microsserviço) em vez de uma classe, você pode testar dessa maneira.
Gbjbaanb

Em relação ao desempenho: a CPU pode prever facilmente a chamada de método virtual (se for sempre a mesma). O grande problema é que é muito mais difícil para o compilador JIT incorporar chamadas de método virtual, o que impede outras otimizações.
precisa

6

Existem dois elementos na sua pergunta, então tentarei resolvê-los por vez:

  1. Por que os métodos .NET não são virtuais por padrão?

    A herança, na prática, tem muitos problemas . Portanto, se for para ser usado como parte de um design, deve ser cuidadosamente considerado. Ao tornar os métodos não virtuais por padrão, a teoria é que incentiva o desenvolvedor a pensar em herança ao projetar uma classe. Se isso funciona na prática é outra questão, é claro.

  2. Por que nem todas as classes implementam uma interface?

    Design deficiente é a resposta simples. Apesar de serem comumente reconhecidas como boas práticas por muito tempo, infelizmente muitas pessoas ainda não projetam seus sistemas com DI e TDD em mente.

A combinação de classes que não são totalmente herdáveis ​​e que não são facilmente ridicularizadas cria uma "tempestade perfeita" de código difícil de testar. Até chegarmos ao dia em que todos usam o DI e o princípio de design para interface, não há muito que possa ser feito para aliviar a dor.


3
"Design deficiente é a resposta simples" - costumo concordar, mas há exceções. Veja objetos de transferência de dados ou objetos de registro. Para objetos simples de linguagem POCO / POJO / insert aqui, não faz sentido para eles implementar um interfacejust porque. Qual o valor de ter um interfacepara o que é essencialmente uma tupla glorificada? (Embora talvez usando um POJO / isto POCO é uma falha de projeto)
Dan Pantry

2
@AntonKalcik, a equipe de C # adota uma abordagem muito conservadora "nunca introduza uma mudança de ruptura" ao idioma e tornar as interfaces obrigatórias seria definitivamente uma mudança de ruptura. Então, suspeito que você não verá isso adicionado ao c #.
David Arno

3
@ DanPantry, objetos de dados simples raramente precisam de zombaria, portanto, normalmente não precisam de interfaces.
David Arno

2
Em C♯, as interfaces definem Objetos, as classes definem Tipos de Dados Abstratos. (É um equívoco comum que as classes sejam usadas em C♯ para escrever código orientado a objetos. Muito pelo contrário: o código OO em CO nunca pode usar classes ou estruturas como tipos, apenas interfaces. Assim que você usa classes como tipos , você não está mais fazendo OO.) Portanto, eu não chamaria de não usar interfaces um erro de design. É uma opção de design: ADTs e Objetos têm pontos fortes e fracos. Você deve escolher o que faz mais sentido em seu domínio específico.
Jörg W Mittag

5
@ JörgWMittag: As soon as you use classes as types, you are no longer doing OO - Classes são tipos. O que?? A herança de classe normal é OO, não importa o que você diga. Você não decide mais que não é OO só porque DI é toda a raiva agora.
Robert Harvey
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.