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

68

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

Vamos assumir o seguinte:

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

Então, por exemplo, como devem ser nomeados esses dois? Parsere ConcreteParser? Parsere ParserImplementation?

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

public class Parser : IParser {
     // Implementations
}

Ou devo ignorar essa sugestão em casos de implementação única como essa?

Vinko Vrsalovic
fonte
30
Isso não torna menos uma religião. Você tem que procurar razões, não apenas alguém X diz Y.
Mike Dunlavey
35
@ MikeDunlavey E essa é a razão da pergunta!
Vinko Vrsalovic 23/08/16
14
Se houver um corpo de código existente (que você não vai reescrever), fique com qualquer convenção que ele use. Se for um código novo, use o que a maioria das pessoas que estiver trabalhando nele estiver mais feliz com / costumava usar. Somente se nenhum dos itens acima se aplicar, deve se tornar uma questão filosófica.
TripeHound 23/08
9
A regra de maioria do @TripeHound nem sempre é a melhor opção. Se há razões pelas quais algumas coisas são melhores que outras, uma única pessoa pode convencer o resto da equipe a defender uma causa com base nessas razões. Apenas submeter-se cegamente à maioria, sem avaliar as coisas, leva à estagnação. Vejo pelas respostas que, para este caso concreto, não há uma boa razão para remover o Is, mas isso não invalida ter perguntado em primeiro lugar.
Vinko Vrsalovic 23/08/16
35
O livro "Tio Bob" concentra-se em JAVA, não em C #. A maioria dos paradigmas é igual ao C #, mas algumas convenções de nomenclatura diferem (também observe os nomes das funções lowerCamelCase em Java). Ao escrever em C #, use as convenções de nomenclatura C #. De qualquer forma, siga o ponto principal de seu capítulo sobre nomeação: nomes legíveis que comunicam o significado do item!
Bernhard Hiller

Respostas:

191

Enquanto muitos, incluindo o "tio Bob", recomendam não usar Icomo prefixo para interfaces, isso é uma tradição bem estabelecida com o C #. Em termos gerais, deve ser evitado. Mas se você estiver escrevendo C #, realmente deve seguir as convenções desse idioma e usá-las. Não fazer isso causará grande confusão com qualquer pessoa familiarizada com C # que tente ler seu código.

David Arno
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
maple_shaft
9
Você não fornece base para "Em termos gerais, isso deve ser evitado". Em Java, eu devo ser evitado. Em C #, isso deve ser adotado. Outros idiomas seguem suas próprias convenções.
Básico
4
O princípio "em termos gerais" é simples: um cliente deve ter o direito de nem saber se está falando com uma interface ou uma implementação. Ambos os idiomas entenderam errado. C # errou na convenção de nomenclatura. Java entendeu errado o bytecode. Se eu alternar uma implementação para uma interface com o mesmo nome em Java, tenho que recompilar todos os clientes, mesmo que o nome e os métodos não tenham mudado. Não é um "escolha seu veneno". Acabamos de fazer errado. Aprenda com isso se estiver criando um novo idioma.
candied_orange
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

Este último tem vários problemas:

  1. Algo é apenas uma implementação do ISomething, mas é aquele com o nome genérico, como se fosse algo especial.
  2. MockSomething parece derivar de Something, mas implementa ISomething. Talvez devesse se chamar MockISomething, mas nunca vi isso na natureza.
  3. Refatorar é mais difícil. Se Something for uma classe agora, e você descobrir mais tarde que precisa introduzir uma interface, essa interface deverá ter o mesmo nome, porque deve ser transparente para os clientes se o tipo é uma classe concreta, uma classe abstrata ou uma interface . Alguma coisa quebra isso.
wallenborn
fonte
59
Por que você não seguiu o padrão bem estabelecido que o C # fornece? Que benefício isso concede que excede o custo de confundir e irritar todos os programadores de C # que seguem você?
Robert Harvey
6
@wallenborn Tudo bem, mas, realisticamente, você geralmente tem apenas uma única implementação legítima de uma interface, porque é comum usar interfaces para simulação em testes de unidade. Eu não quero colocar Defaultou Realou Implem tantos dos meus nomes de classe
Ben Aaronson
4
Eu concordo com você, mas se você viajar por essa estrada, terá que lutar contra todos os ponteiros já feitos para C #.
precisa
3
Não vejo problema em ter uma classe instável com o mesmo nome de uma interface se a grande maioria das instâncias que implementam a interface for essa classe. Por exemplo, muito código precisa de uma implementação direta de uso geral IList<T>e realmente não se importa como é armazenado internamente; ser capaz de apenas remover Ie ter um nome de classe utilizável parece melhor do que ter que saber magicamente (como em Java) que o código que deseja uma implementação comum List<T>deve ser usado ArrayList<T>.
Supercat
2
@ArtB Algumas razões. Primeiro, não há linguagem em que a convenção seja um bom marcador curto na classe CFoo : Foo. Portanto, essa opção não está realmente disponível. Dois, você tem uma inconsistência estranho, em seguida, porque algumas classes teria o marcador e outros se não, dependendo se há pode haver outras implementações da mesma interface. CFoo : Foomas SqlStore : Store. Esse é um problema que tenho Impl, que é essencialmente apenas um marcador mais feio. Talvez se houvesse uma linguagem com a convenção de sempre adicionando um C(ou qualquer outro), que pode ser melhor do queI
Ben Aaronson
27

Não se trata apenas de convenções de nomenclatura. O C # não suporta herança múltipla, portanto, esse uso herdado da notação húngara tem um pequeno benefício, embora útil, em que 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

Robbie Dee
fonte
7
É importante notar que o Java cuidadosamente evita esse problema usando palavras-chave diferentes para herança de classe e implementação interface, ou seja inheritsvs implements.
Konrad Rudolph
6
@KonradRudolph você quer dizer extends.
Pare de prejudicar Monica
2
@Andy O valor agregado é que podemos evitar o problema da notação húngara, mas preservar toda a clareza apresentada nesta resposta.
Konrad Rudolph
2
Realmente não deveria haver nenhuma confusão; Se você herdar uma classe, DEVE estar em primeiro lugar na lista, antes da lista de interfaces implementadas. Tenho certeza de que isso é imposto pelo compilador.
Andy
11
@ Andy ... se você herdar uma classe, DEVE ser a primeira da lista Isso é ótimo, mas sem o prefixo, você não saberia se estivesse lidando com algumas interfaces ou uma classe base e uma interface ...
Robbie Dee
15

Não não não.

As convenções de nomenclatura não são uma função dos seus fãs do tio 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 de sua loja. Em outras palavras, o primeiro na base de código define o padrão.

Só então você decide. Depois disso, apenas seja consistente. Se alguém começar a ser inconsistente, tire 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 Ivir um prefixo, estou bem, independentemente do idioma. Se eu vejo um em cada interface, estou bem. Se às vezes vejo uma, alguém vai pagar.

Se você se encontra no contexto rarefeito de definir esse precedente para sua base de código, recomendo que você considere isso:

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

Se você esgueirar-se Icom esse 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. Talvez não. Como não o chama newde qualquer maneira, não faz diferença. Como cliente, eu não sei. Eu 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 o cliente. Se não houver Itradição, talvez nem o renomeie. O que pode ser um problema, porque agora precisamos recompilar o cliente, porque, embora a fonte não se importe com o binário, 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, é uma boa razão para se importar. Mas, pelo amor aos brinquedos de mesa, não afirme que devemos fazer isso cegamente, porque é assim que é feito em algum idioma. Ou tem uma razão muito boa ou não se importa.

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

candied_orange
fonte
6
A consistência é fundamental, mas em uma linguagem de tipo estaticamente e com ferramentas modernas de refatoração é bastante fácil e seguro alterar nomes. Portanto, se o primeiro desenvolvedor fez uma má escolha em nomear, você não precisa viver com ele para sempre. Mas você pode alterar a nomeação em sua própria base de código, mas não pode alterá-la na estrutura ou nas bibliotecas de terceiros. Como o prefixo I (goste ou não) é uma convenção estabelecida em todo o mundo .net, é melhor seguir a convenção do que não segui-la.
precisa saber é o seguinte
Se você estiver usando C #, verá esses Is. Se o seu código os usar, você os verá em todos os lugares. Se o seu código não os estiver usando, você os verá no código da estrutura, levando exatamente ao mix que você não deseja.
Sebastian Redl
8

Há uma pequena diferença entre Java e C # que é relevante aqui. Em Java, cada membro é virtual por padrão. Em C #, todos os membros são selados por padrão - exceto os 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, nomeará a classe Parser; se você achar que precisa de várias implementações, altere a classe para uma interface com o mesmo nome e renomeie a implementação concreta para algo descritivo.

Em C #, a principal suposição é que, quando você obtém uma classe (o nome não começa com I), essa é a classe que você deseja. Lembre-se, isso não chega nem perto de 100% de precisão - um contra-exemplo típico seria de classes como Stream(que realmente deveriam ter sido uma interface ou algumas interfaces), e todo mundo tem suas próprias diretrizes e experiências de outros idiomas. Há também outras exceções, como o Basesufixo bastante usado para denotar uma classe abstrata - assim como em uma interface, você sabe que o tipo deve ser polimórfico.

Há também um bom recurso de usabilidade ao deixar o nome sem prefixo I para a funcionalidade relacionada a essa interface sem ter que recorrer a transformar a interface em 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 Enumerablecomo repositório de métodos que se aplicam a essa interface. Isso é desnecessário em Java, onde as interfaces também podem conter implementações de métodos.

Por fim, o Iprefixo é amplamente usado no mundo C # e, por extensão, no mundo .NET (como a maioria do código .NET é escrita em C #, faz sentido seguir as diretrizes C # para a maioria das 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ões desnecessárias - não é como omitir o prefixo que tornará seu código melhor :)

Suponho que o raciocínio do tio Bob fosse algo assim:

IBananaé a noção abstrata de banana. Se pode haver qualquer classe de implementação que não teria um nome melhor do que isso Banana, a abstração é totalmente sem sentido e você deve descartar a interface e usar apenas uma classe. Se houver um nome melhor (digamos, LongBananaou AppleBanana), não há razão para não usar Bananacomo o nome da interface. Portanto, o uso do Iprefixo significa que você possui uma abstração inútil, o que dificulta a compreensão do código sem nenhum benefício. E como o OOP estrito fará com que você sempre codifique contra interfaces, o único lugar onde você não veria o Iprefixo em um tipo seria em um construtor - ruído bastante inútil.

Se você aplicar isso à sua IParserinterface de amostra , poderá ver claramente que a abstração está inteiramente no território "sem sentido". Ou há algo específico sobre a implementação concreta de um analisador (por exemplo JsonParser, XmlParser...), ou você deve usar apenas uma classe. Não existe "implementação padrão" (embora em alguns ambientes isso realmente faça sentido - principalmente COM), existe 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 Iprefixo-, mantenha-o. Faça uma anotação mental sempre que vir código class Something: ISomething- isso significa que alguém não é muito bom em seguir o YAGNI e criar abstrações razoáveis.

[1] - Tecnicamente, isso não é mencionado especificamente no artigo de Liskov, mas é um dos fundamentos do artigo original da POO e, na minha leitura de Liskov, ela não contestou isso. Em uma interpretação menos rigorosa (a adotada pela maioria das linguagens OOP), isso significa que qualquer código que utilize um tipo público destinado a substituição (por exemplo, não final / selado) deve funcionar com qualquer implementação em conformidade desse tipo.

Luaan
fonte
3
Nitpick: o LSP não diz que todo tipo deve ser não final. Ele apenas diz que, para tipos que são não-final, os consumidores devem ser capazes de usar subclasses de forma transparente. - E, claro, Java também tem tipos finais.
Konrad Rudolph
@KonradRudolph Adicionei uma nota de rodapé sobre isso; Obrigado pelo feedback :)
Luaan
4

Então, por exemplo, como devem ser nomeados esses dois? Analisador e ConcreteParser? Parser e ParserImplementation?

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

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

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


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

A convenção ISomething apresenta nomes de cruft / bad. Dito isto, o cruft não é real *), a menos que as convenções e práticas usadas na escrita do código não sejam mais reais; se a convenção diz "use ISomething", é melhor usá-lo, em conformidade (e funcionar melhor) com outros desenvolvedores.


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

utnapistim
fonte
2

Como as outras respostas mencionam, prefixar seus nomes de interface Ifaz parte das diretrizes de codificação da estrutura .NET. Como as classes concretas são mais frequentemente interagidas do que as 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, IParserpara a interface e Parsera implementação padrão.

Porém, no caso de Java e linguagens similares, onde as interfaces são preferidas em vez de classes concretas, o Tio Bob está certo em que Ideve ser removido e as interfaces devem ter os nomes mais simples - Parserpara a interface do analisador, por exemplo. Mas, em vez de um nome baseado em função 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
}

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

TheHansinator
fonte
5
As interfaces também são preferidas em C #. Quem lhe disse que era preferível usar tipos concretos no dotnet?
precisa
@RubberDuck Está escondido em algumas das suposições que o idioma impõe a você. Por exemplo, o fato de que os membros são sealedpor padrão e você deve fazê-los explicitamente virtual. Ser virtual é o caso especial em C #. Obviamente, como a abordagem recomendada para escrever código mudou ao longo dos anos, muitas relíquias do passado precisavam permanecer para compatibilidade :) O ponto principal é que, se você obtiver uma interface em C # (que começa com I), você saberá que é totalmente tipo abstrato; se você obtiver uma não interface, a suposição típica é que esse é o tipo que você deseja, que o LSP seja condenado.
Luaan 24/08/16
11
Os membros são selados por padrão, para que seja explícito o que os herdeiros podem usar sobre @Luaan. É certo que às vezes é um PITA, mas não tem nada a ver com uma preferência por tipos concretos. O virtual certamente não é um caso especial em C #. Parece que você teve a infelicidade de trabalhar em uma base de código C # escrita por amadores. Lamento que tenha sido sua experiência com o idioma. É melhor lembrar que o desenvolvedor não é o idioma e vice-versa.
precisa saber é o seguinte
@RubberDuck Hah, eu nunca disse que não gostei. Eu prefiro muito, porque a maioria das pessoas não entende a indireção . Mesmo entre programadores. Selado por padrão é melhor na minha opinião. E quando eu comecei com o C #, todo mundo era amador, incluindo a equipe .NET :) No "verdadeiro POO", tudo deveria ser virtual - todo tipo deve ser substituível por um tipo derivado, capaz de substituir qualquer comportamento . Mas "real OOP" foi projetado para especialistas, não as pessoas que mudaram de substituição de lâmpadas para a programação porque é menos trabalho para mais dinheiro :)
Luaan
Não ... Não. Não é assim que o "verdadeiro POO" funciona. Em Java, você deve reservar um tempo para selar métodos que não devem ser substituídos, não deixando como o Oeste Selvagem, onde qualquer um pode superar tudo. LSP não significa que você pode substituir o que quiser. LSP significa que você pode substituir com segurança uma classe por outra. Smh
RubberDuck
-8

Você está certo de que esta convenção I = interface quebra as (novas) regras

Antigamente, você prefixava tudo com seu tipo intCounter boolFlag etc.

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

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


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}
Ewan
fonte
11
Novo? velho? Espere, pensei que programação fosse mais racionalidade do que exagero. Ou isso era nos velhos tempos?
3
Não é tudo sobre tornando-se convenções e blogs sobre como eles 'melhorar readablity'
Ewan
8
Acredito que você esteja pensando na notação húngara, na qual prefixou nomes com tipos. Mas não havia tempo em que você escrevesse algo como intCounterou boolFlag. Esses são os "tipos" errados. Em vez disso, você escreveria pszName(ponteiro para uma sequência terminada em NUL contendo um nome), cchName(contagem de caracteres na sequência "nome"), xControl(coordenada x do controle), fVisible(sinalizador indicando que algo está visível), pFile(ponteiro para um arquivo), hWindow(identificador para uma janela) e assim por diante.
Cody Gray
2
Ainda há muito valor nisso. Um compilador não detectará o erro ao adicionar xControla cchName. Ambos são do tipo int, mas possuem semânticas muito diferentes. Os prefixos tornam essas semânticas óbvias para um humano. Um ponteiro para uma string terminada em NUL se parece exatamente com um ponteiro para uma string no estilo Pascal para um compilador. Da mesma forma, o Iprefixo para interfaces surgiu na linguagem C ++, na qual interfaces são realmente apenas classes (e, na verdade, para C, onde não existe classe ou interface no nível da linguagem, é apenas uma convenção).
Cody Gray
4
@CodyGray Joel Spolsky escreveu um bom artigo, Fazendo o código errado parecer errado sobre a notação húngara e as maneiras pelas quais ela foi (mal) entendida. Ele tem uma opinião semelhante: o prefixo deve ser do tipo de nível superior, não do tipo de armazenamento de dados brutos. Ele usa a palavra "tipo", explicando: "Estou usando a palavra tipo de propósito, porque Simonyi erroneamente usou a palavra tipo em seu artigo, e gerações de programadores entenderam mal o que ele quis dizer".
21416 Joshua Taylor