Problemas de nomeação: “ISomething” deve ser renomeado para “Something”? [fechadas]

67

O capítulo do tio Bob sobre nomes em Código Limpo recomenda que você evite codificações em nomes, principalmente em relação à notação húngara. Ele também menciona especificamente remover o prefixo I das interfaces, mas não mostra exemplos disso.

Vamos supor o seguinte:

  • O uso da interface é principalmente para obter testabilidade por meio da injeção de dependência
  • Em muitos casos, isso leva a ter uma única interface com um único implementador

Então, por exemplo, quais devem ser esses dois nomes? Parser e ConcreteParser ? Parser e ParserImplementation ?

public interface IParser {
    string Parse(string content);
    string Parse(FileInfo path);
}

public class Parser : IParser {
     // Implementations
}

Ou eu deveria ignorar essa sugestão em casos de implementação única como este?

    
por Vinko Vrsalovic 23.08.2016 / 11:01
fonte

8 respostas

191

Embora muitos, incluindo "Uncle Bob", recomendem não usar I como um prefixo para interfaces, isso é uma tradição bem estabelecida com o C #. Em termos gerais, deve ser evitado. Mas se você está escrevendo C #, você deve seguir as convenções da linguagem e usá-la. Não fazer isso causará enorme confusão com qualquer pessoa familiarizada com o C # que tente ler seu código.

    
por 23.08.2016 / 11:48
fonte
39

A interface é o conceito lógico importante, portanto, a interface deve conter o nome genérico. Então, eu prefiro ter

interface Something
class DefaultSomething : Something
class MockSomething : Something

do que

interface ISomething
class Something : ISomething
class MockSomething : ISomething

O último tem vários argumentos:

  1. Algo é apenas uma implementação do ISomething, mas é aquela com o nome genérico, como se fosse algo especial.
  2. MockSomething parece derivar de Something, mas implementa ISomething. Talvez devesse ser chamado MockISomething, mas nunca vi isso em estado selvagem.
  3. A refatoração é mais difícil. Se Algo é uma classe agora, e você só descobrir depois que precisa introduzir uma interface, essa interface deve ter o mesmo nome, porque deve ser transparente para os clientes se o tipo é uma classe concreta, uma classe abstrata ou uma interface . ISomething quebra isso.
por 23.08.2016 / 12:30
fonte
27

Isto não é apenas sobre convenções de nomenclatura. O C # não suporta múltiplas heranças, por isso este uso legado da notação húngara tem um benefício pequeno, embora útil, quando você está herdando de uma classe base e implementando uma ou mais interfaces. Então isso ...

class Foo : BarB, IBarA
{
    // Better...
}

... é preferível a isso ...

class Foo : BarB, BarA
{
    // Dafuq?
}

IMO

    
por 23.08.2016 / 16:55
fonte
15

Não, não, não.

As convenções de nomenclatura não são uma função do fandom do seu Uncle Bob. Eles não são uma função da linguagem, c # ou de outra forma.

Eles são uma função da sua base de código. Em uma extensão muito menor da sua loja. Em outras palavras, o primeiro na base de código define o padrão.

Só então você decide. Depois disso, seja consistente. Se alguém começar a ser inconsistente, retire sua arma nerf e faça com que atualizem a documentação até que se arrependam.

Ao ler uma nova base de código, se eu nunca ver um prefixo I , estou bem, independentemente do idioma. Se eu vejo um em cada interface, estou bem. Se eu às vezes vejo um, alguém vai pagar.

Se você se encontrar no contexto rarefeito de estabelecer esse precedente para sua base de código, peço que considere isso:

Como cliente, reservo-me o direito de não dar a mínima para o que estou falando. Tudo que eu preciso é um nome e uma lista de coisas que posso chamar contra esse nome. Aqueles não podem mudar a menos que eu diga. Eu possuo essa parte. O que eu estou falando, interface ou não, não é meu departamento.

Se você roubar um I nesse nome, o cliente não se importará. Se não houver I , o cliente não se importa. O que está falando pode ser concreto. Pode não ser. Como não chama new de qualquer forma, não faz diferença. Como o cliente, não sei. Não quero saber.

Agora, como um macaco de código olhando para esse código, mesmo em java, isso pode ser importante. Afinal, se eu tiver que alterar uma implementação para uma interface, posso fazer isso sem informar ao cliente. Se não houver uma tradição I , talvez eu nem a renomeie. O que pode ser um problema, porque agora temos que recompilar o cliente porque, mesmo que a fonte não se importe, o binário faz. Algo fácil de perder se você nunca mudou o nome.

Talvez isso não seja um problema para você. Talvez não. Se for, essa é uma boa razão para se importar. Mas, pelo amor dos brinquedos de mesa, não afirmemos que devemos fazer isso apenas cegamente, porque é assim que é feito em alguma língua. Ou tem uma boa razão ou não se importa.

Não se trata de viver com ela para sempre. É sobre não me dar porcaria sobre a base de código não estar de acordo com sua mentalidade particular, a menos que você esteja preparado para consertar o 'problema' em todos os lugares. A menos que você tenha uma boa razão, eu não deveria seguir o caminho da menor resistência à uniformidade, não venha a mim pregando conformidade. Eu tenho coisas melhores para fazer.

    
por 24.08.2016 / 03:37
fonte
8

Existe uma pequena diferença entre Java e C # que é relevante aqui. Em Java, cada membro é virtual por padrão. Em C #, cada membro é lacrado por padrão - exceto para membros da interface.

As suposições que acompanham isso influenciam a diretriz - em Java, todo tipo público deve ser considerado não final, de acordo com o Princípio de Substituição de Liskov [1]. Se você tiver apenas uma implementação, você nomeará a classe Parser ; Se você achar que precisa de múltiplas implementações, você apenas mudará a classe para uma interface com o mesmo nome e renomeará a implementação concreta para algo descritivo.

Em C #, a principal suposição é que, quando você recebe uma classe (o nome não começa com I ), essa é a classe que você deseja. Lembre-se, isso está longe de ser 100% preciso - um contra-exemplo típico seria classes como Stream (que realmente deveria ter sido uma interface, ou algumas interfaces), e todo mundo tem suas próprias diretrizes e origens de outros idiomas . Há também outras exceções, como o sufixo Base amplamente utilizado para denotar uma classe abstrata - assim como com uma interface, você sabe que o tipo deve ser polimórfico.

Há também um bom recurso de usabilidade ao deixar o nome não-prefixado para funcionalidade que se relaciona com essa interface sem ter que recorrer a tornar a interface uma classe abstrata (o que prejudicaria devido à falta de herança múltipla de classe em C #). Isso foi popularizado pelo LINQ, que usa IEnumerable<T> como interface e Enumerable como um repositório de métodos que se aplicam a essa interface. Isso é desnecessário em Java, onde as interfaces podem conter implementações de métodos também.

Por fim, o prefixo I é amplamente usado no mundo C # e, por extensão, no mundo .NET (já que a maior parte do código .NET é escrito em C #, faz sentido seguir as diretrizes C # para a maioria dos as interfaces públicas). Isso significa que você quase certamente estará trabalhando com bibliotecas e códigos que seguem essa notação, e faz sentido adotar a tradição para evitar confusão desnecessária - não é como omitir o prefixo tornará seu código melhor:)

Assumo que o raciocínio do tio Bob foi algo assim:

IBanana é a noção abstrata de banana. Se pode haver qualquer classe de implementação que não teria um nome melhor que Banana , a abstração é totalmente sem sentido, e você deve abandonar a interface e usar apenas uma classe. Se houver um nome melhor (por exemplo, LongBanana ou AppleBanana ), não há motivo para não usar Banana como o nome da interface. Portanto, usar o prefixo I significa que você tem uma abstração inútil, o que dificulta o entendimento do código sem nenhum benefício. E como a OOP restrita faz com que você sempre codifique em interfaces, o único lugar onde você não veria o prefixo I em um tipo estaria em um construtor - um ruído sem sentido.

Se você aplicar isso à sua interface IParser de amostra, poderá ver claramente que a abstração está inteiramente no território "sem sentido". Há algo específico sobre uma implementação concreta de um analisador (por exemplo, JsonParser , XmlParser , ...) ou você deve usar apenas uma classe. Não existe uma "implementação padrão" (embora em alguns ambientes, isso realmente faça sentido - notavelmente, COM), ou há uma implementação específica ou você deseja uma classe abstrata ou métodos de extensão para os "padrões". No entanto, em C #, a menos que sua base de código já omita o I -prefix, mantenha-o. Apenas faça uma anotação mental toda vez que você vir o código como class Something: ISomething - isso significa que alguém não é muito bom em seguir o YAGNI e construir abstrações razoáveis.

[1] - Tecnicamente, isso não é especificamente mencionado no artigo de Liskov, mas é uma das fundações do original OOP paper e na minha leitura de Liskov, ela não contestou isso. Em uma interpretação menos estrita (a adotada pela maioria das linguagens OOP), isso significa que qualquer código que use um tipo público destinado a substituição (ou seja, não final / selado) deve funcionar com qualquer implementação conforme desse tipo.

    
por 24.08.2016 / 11:17
fonte
4

So, for example, what should these two be named? Parser and ConcreteParser? Parser and ParserImplementation?

Em um mundo ideal, você não deve prefixar seu nome com uma mão curta ("eu ..."), mas simplesmente deixar o nome expressar um conceito.

Eu li em algum lugar (e não posso fornecer) a opinião de que esta convenção leva você a definir um conceito "IDollar" quando você precisa de uma classe "Dollar", mas a solução correta seria um conceito chamado simplesmente "Currency".

Em outras palavras, a convenção dá uma saída fácil para a dificuldade de nomear as coisas e a saída fácil é sutilmente errada .

No mundo real, os clientes do seu código (que podem ser apenas "você do futuro") precisam lê-lo mais tarde e estar familiarizado com ele o mais rápido possível (ou com o mínimo de esforço) ( dê aos clientes do seu código o que eles esperam obter.

A convenção ISomething introduz nomes inválidos / inválidos. Dito isso, a sujeira não é verdadeira *, a menos que as convenções e práticas usadas para escrever o código não sejam mais reais; se a convenção diz "use ISomething", então é melhor usá-lo, para se conformar (e trabalhar melhor) com outros desenvolvedores.

*) Eu posso dizer isso porque "cruft" não é um conceito exato de qualquer maneira:)

    
por 24.08.2016 / 12:02
fonte
2

Como as outras respostas mencionam, prefixar seus nomes de interface com I faz parte das diretrizes de codificação da estrutura .NET. Como as classes concretas são mais frequentemente interagidas com interfaces genéricas em C # e VB.NET, elas são de primeira classe em esquemas de nomenclatura e, portanto, devem ter os nomes mais simples - portanto, IParser para a interface e Parser para a implementação padrão .

Mas no caso de Java e linguagens semelhantes, onde as interfaces são preferidas em vez de classes concretas, o Uncle Bob está certo em que I deve ser removido e interfaces devem ter os nomes mais simples - Parser para sua interface de analisador , por exemplo. Mas, em vez de um nome baseado em função como ParserDefault , a classe deve ter um nome que descreva como o analisador é implementado :

public interface Parser{
}

public class RegexParser implements Parser {
    // parses using regular expressions
}

public class RecursiveParser implements Parser {
    // parses using a recursive call structure
}

public class MockParser implements Parser {
    // BSes the parsing methods so you can get your tests working
}

Isto é, por exemplo, como Set<T> , HashSet<T> e TreeSet<T> são nomeados.

    
por 23.08.2016 / 19:19
fonte
-8

Você está correto em dizer que essa convenção da interface I = quebra as (novas) regras

Antigamente você prefixava tudo com o tipo intCounter boolFlag etc.

Se você quiser nomear as coisas sem o prefixo, mas também evitar 'ConcreteParser', você pode usar namespaces

namespace myapp.Interfaces
{
    public interface Parser {...
}


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}
    
por 23.08.2016 / 11:11
fonte