命名问题:“ Isomething”是否应重命名为“ Something”?[关闭]


68

Bob叔叔在“ 干净代码”中有关名称的章节中建议您避免使用名称编码,主要是关于匈牙利表示法。他还特别提到I从接口中删除前缀,但没有显示此示例。

让我们假设以下内容:

  • 接口的使用主要是通过依赖注入实现可测试性
  • 在许多情况下,这导致与单个实施者具有单个接口

因此,例如,这两个应命名为什么?ParserConcreteParserParserParserImplementation

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

public class Parser : IParser {
     // Implementations
}

还是在这样的单一实现案例中忽略该建议?


30
那一点也不会使它成为一种宗教。你得找原因,不只是人X说Y.
麦克Dunlavey

35
@MikeDunlavey这就是问题的原因!
Vinko Vrsalovic

14
如果有一个现有的代码体(您不会重写),则请遵循其使用的任何约定。如果是新代码,请使用/过去最快乐的人。只有以上两种都不适用,才成为哲学问题。
TripeHound

9
@TripeHound多数规则并非始终是最佳选择。如果有某些事情比其他事情更好的理由,那么一个人可以说服团队的其他成员根据这些理由提出理由。只是盲目屈从于多数,而不通过导致停滞而评估事物。从答案中可以看出,对于这种具体情况,没有充分的理由删除Is,但这并不会使首先提出的要求无效。
Vinko Vrsalovic

35
“鲍勃叔叔”的书着重于JAVA,而不是C#。大多数范例与C#相同,但是某些命名约定有所不同(另请参见Java中的lowerCamelCase函数名称)。使用C#编写时,请使用C#命名约定。但是无论如何,请遵循他关于命名的一章的要点:易读的名称,传达项目的含义!
伯恩哈德·希勒

Answers:


191

尽管包括“鲍勃叔叔”在内的许多人都建议不要将I其用作接口的前缀,但这是C#的一种既定传统。一般而言,应避免这种情况。但是,如果您正在编写C#,则确实应该遵循该语言的约定并使用它。不这样做将与其他熟悉C#并试图读取您的代码的人产生巨大的困惑。


评论不作进一步讨论;此对话已转移至聊天
maple_shaft

9
您没有提供“一般而言,应避免使用”的依据。在Java中,应避免使用。在C#中应该接受它。其他语言遵循它们自己的约定。
2016年

4
“一般而言”的原理很简单:客户端应该有权甚至不知道它是在与接口还是在实现对话。两种语言都错了。C#的命名约定错误。Java弄错了字节码。如果在Java中将实现切换到具有相同名称的接口,则即使名称和方法没有更改,我也必须重新编译所有客户端。这不是“挑毒”。我们一直做错了。如果您要创建一种新的语言,请从中学到。
candied_orange

39

接口是重要的逻辑概念,因此,接口应带有通用名称。所以,我宁愿有

interface Something
class DefaultSomething : Something
class MockSomething : Something

interface ISomething
class Something : ISomething
class MockSomething : ISomething

后者有几个问题:

  1. 某物只是ISomething的一种实现,但它是具有通用名称的那个,好像它在某种程度上很特殊。
  2. MockSomething似乎源于Something,但实现了ISomething。也许应该将其命名为MockISomething,但我从未在野外看到它。
  3. 重构比较困难。如果Something现在是一个类,而您以后才发现必须引入一个接口,则该接口应命名为相同的接口,因为无论类型是具体类,抽象类还是接口,该接口对客户端都是透明的。ISomething打破了这一点。

59
您为什么不遵循C#提供的公认标准?这带来了什么好处,而这又超出了使您后面的每个C#程序员感到困惑和烦恼的代价?
罗伯特·哈维

6
@wallenborn很好,但是实际上您通常只有一个接口的合法实现,因为在单元测试中通常使用接口来实现可模拟性。我不想把DefaultRealImpl在这么多我的类名
本·阿伦森

4
我同意您的看法,但是如果您走这条路,您将不得不与为C#制造的每一个短绒棉战斗。
RubberDuck

3
如果实现该接口的绝大多数实例都是该类,则我不认为拥有与接口同名的可实例化类是没有问题的。例如,许多代码需要的直接通用实现,IList<T>而实际上并不在乎其内部存储方式。能够仅使用I并拥有可用的类名似乎比不得不神奇地知道(如Java中)想要普通实现的代码List<T>应该使用更好ArrayList<T>
超级猫

2
@ArtB几个原因。一种是,没有一种语言可以像在这样的类上将约定作为一个很好的简短标记CFoo : Foo。因此,该选项实际上并不可用。第二,您会感到奇怪的不一致,因为某些类将具有标记,而另一些则不会,这取决于是否可能有同一接口的其他实现。CFoo : Foo但是SqlStore : Store。这是我Impl遇到的一个问题,本质上这只是一个难看的标记。也许如果有一种习惯上总是添加一个C(或其他任何东西)的语言,那可能会更好I
Ben Aaronson

27

不仅仅是关于命名约定。C#不支持多重继承,因此在从基类继承并实现一个或多个接口的情况下,这种匈牙利符号的旧用法虽然有用,但仍具有很小的好处。所以这...

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

...比这更好...

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

海事组织


7
值得注意的是,Java通过为类继承和接口实现使用不同的关键字(即inheritsvs)巧妙地避免了此问题implements
康拉德·鲁道夫

6
@KonradRudolph你的意思是extends
OrangeDog

2
@Andy附加值是我们可以避免使用匈牙利符号法,但可以保留此答案中所有吹捧的清晰度。
康拉德·鲁道夫

2
确实不应有任何混乱;如果您继承一个类,则它必须在列表中的第一个,在实现接口的列表之前。我很确定这是由编译器强制执行的。
安迪

1
@Andy ...如果您继承一个类,则它必须在列表中排在首位。这很棒,但是如果没有前缀,您将不知道要处理的是几个接口还是基类和接口...
Robbie Dee

15

不不不。

命名约定不是Bob叔叔的狂热粉丝的功能。它们不是c#或其他语言的功能。

它们是您的代码库的功能。在您的商店范围内要小得多。换句话说,代码库中的第一个设置了标准。

只有这样,您才能决定。之后,请保持一致。如果某人开始变得前后矛盾,请拿走他们的nerf枪,让他们更新文档,直到他们悔改为止。

在阅读新的代码库时I,无论哪种语言,我都看不到前缀,这很好。如果我在每个界面上都看到一个,那很好。如果我有时看到一个,有人会付钱。

如果您发现自己处于为代码库设置此先例的稀缺环境中,请您考虑一下:

作为客户班,我保留不给我在说什么的权利。我所需要的只是一个名称和可以针对该名称调用的内容列表。除非我这样说,否则这些可能不会改变。我拥有那一部分。我正在谈论的内容(不是界面)不是我的部门。

如果您I以这个名字偷偷摸摸,客户将不在乎。如果没有I,则客户端不在乎。它在说什么可能是具体的。可能不会。由于new无论哪种方式都没有调用它,因此没有区别。作为客户,我不知道。我不想知道

现在,即使是在Java中,作为代码猴子查看该代码,这也可能很重要。毕竟,如果我必须将实现更改为接口,则可以在不通知客户端的情况下进行操作。如果没有I传统,我什至无法重命名。这可能是个问题,因为现在即使源不关心二进制文件,我们也必须重新编译客户端。如果您从未更改过名字,很容易错过。

也许这对您来说不是问题。也许不吧。如果是这样,那就是一个值得照顾的好理由。但是对于喜欢台式玩具的人来说,不要说我们应该盲目地这样做,因为这是某些语言的处理方式。要么有该死的正当理由,要么不在乎。

这不是要永远与它共存。除非您准备好在所有地方解决“问题”,否则不要给我关于不符合您的特定心态的代码库的废话。除非您有充分的理由,否则我不应该走最小程度的抵抗统一的道路,也不要来找我宣扬整合。我有更好的事情要做。


6
一致性是关键,但是使用静态类型的语言和现代的重构工具,更改名称非常容易且安全。因此,如果第一个开发人员在命名时做出了错误的选择,则不必永远忍受它。但是您可以在自己的代码库中更改命名,但是不能在框架或第三方库中更改命名。由于I前缀(不管喜欢与否)是.net世界中的既定惯例,因此最好遵循该惯例而不是不遵循它。
JacquesB

如果您使用的是C#,则会看到那些I。如果您的代码使用它们,则到处都会看到它们。如果您的代码未使用它们,则将从框架代码中看到它们,从而精确地导致不需要的混合。
塞巴斯蒂安·雷德尔

8

Java和C#之间存在一个微小的差异,在这里是相关的。在Java中,默认情况下,每个成员都是虚拟的。在C#中,默认情况下每个成员都是密封的-接口成员除外。

与此相关的假设会影响准则-在Java中,根据Liskov的“替代原则” [1],每个公共类型都应视为非最终类型。如果只有一个实现,则将其命名为class Parser。如果发现需要多个实现,则只需将类更改为具有相同名称的接口,然后将具体实现重命名为描述性名称。

在C#中,主要假设是当您获得一个类(名称不以开头I)时,这就是您想要的类。请注意,这远远不能达到100%的准确度-一个典型的反例就是类似的类Stream(它确实应该是一个接口或几个接口),并且每个人都有自己的使用其他语言的指南和背景。还有其他例外,例如使用相当广泛的Base后缀来表示抽象类-就像具有接口一样,您知道类型应该是多态的。

还有一个很好的可用性功能,它为与该接口相关的功能保留了非I前缀的名称,而不必诉诸使该接口成为抽象类(由于C#中缺少类多重继承,这会造成伤害)。这被LINQ所普及,它IEnumerable<T>用作接口以及Enumerable适用于该接口的方法的存储库。在Java中,接口也可以包含方法实现,这是不必要的。

最终,该I前缀在C#世界中广泛使用,并在.NET世界中得到扩展(由于到目前为止,大多数.NET代码都是用C#编写的,因此对于大多数公共接口,都应遵循C#准则)。这意味着您几乎可以肯定会使用遵循该符号的库和代码,并且采用这种传统来防止不必要的混乱是很有意义的-就像省略前缀不会使您的代码变得更好:)

我认为鲍勃叔叔的推理是这样的:

IBanana是香蕉的抽象概念。如果有任何实现类没有比更好的名称Banana,那么抽象是完全没有意义的,您应该删除接口并仅使用一个类。如果有一个更好的名称(例如LongBananaAppleBanana),则没有理由不使用它Banana作为接口的名称。因此,使用I前缀表示您有一个无用的抽象,这使代码更难理解,没有任何好处。而且由于严格的OOP总是让您针对接口进行编码,因此唯一看不到I类型前缀的地方就是构造函数-毫无意义。

如果将其应用于示例IParser接口,则可以清楚地看到抽象完全在“无意义”的范围内。无论是有一些具体的事情有关的具体实现解析器(例如JsonParserXmlParser...),或者你应该只使用一个类。没有“默认实现”这样的东西(尽管在某些环境中,这确实有意义-尤其是COM),或者有特定的实现,或者您想要“默认值”的抽象类或扩展方法。但是,在C#中,除非您的代码库已经省略了I-prefix,否则请保留它。每当您看到类似的代码时class Something: ISomething,都需要在脑子里记一下-这意味着有人不太擅长遵循YAGNI并构建合理的抽象。

[1]-从技术上讲,这在Liskov的论文中没有特别提及,但这是原始OOP论文的基础之一,在我阅读Liskov时,她没有对此提出质疑。在一种不太严格的解释中(大多数OOP语言采用的一种解释),这意味着使用打算用于替代(即非最终/密封)的公共类型的任何代码都必须与该类型的任何符合实现一起工作。


3
Nitpick:LSP并没有说每种类型都不应该是最终类型。它只是说,该类型非最终,消费者必须能够透明地使用子类。—当然,Java也具有最终类型。
康拉德·鲁道夫

@KonradRudolph我为此添加了一个脚注;感谢您的反馈:)
a安

4

因此,例如,这两个应命名为什么?解析器和ConcreteParser?解析器和ParserImplementation?

在理想的世界中,您不应在名字前面加上简写形式(“ I ...”),而应让名字表达一个概念。

我在某处读到(无法找到它)该ISomething约定使您在需要“ Dollar”类时可以定义“ IDollar”概念,但正确的解决方案是简单地称为“ Currency”的概念。

换句话说,约定使命名事物的难易程度变得容易,而容易犯错微妙的


在现实世界中,您的代码的客户端(可能只是“未来的您”)需要能够稍后阅读并尽可能快地(或不费吹灰之力)熟悉它(给客户端他们期望得到的代码)。

ISomething约定引入了临时名称/错误名称。话虽如此,但实际上并不是真正的*),除非编写代码所使用的约定和实践不再实际;如果约定说“使用ISomething”,那么最好使用它,使其与其他开发人员保持一致(并更好地工作)。


*)我可以这样说,因为“残酷”不是一个确切的概念:)


2

就像其他答案提到的那样,在I.NET框架的编码准则中会以前缀前缀您的接口名称。因为与C#和VB.NET中的通用接口相比,具体类更经常与之交互,所以它们在命名方案中是一流的,因此应具有最简单的名称-因此,IParser用于接口和Parser默认实现。

但是在Java和类似语言的情况下,首选接口而不是具体的类,Bob叔叔是对的,因为I应该删除,而接口应该使用最简单的名称-例如Parser对于您的解析器接口。但是,而不是基于角色的名字一样ParserDefault,该类应该有一个描述名称如何解析器实现

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
}

这是,例如,如何Set<T>HashSet<T>以及TreeSet<T>命名。


5
接口在C#中也是首选。谁告诉过您,最好在dotnet中使用具体类型?
RubberDuck

@RubberDuck语言隐含在某些假设中。例如,sealed默认情况下成员是事实,您必须显式地使其成为成员virtual。虚拟化是C#中的特例。当然,随着多年以来推荐的编写代码的方法发生了变化,为了保持兼容性,需要保留许多过去的遗迹:)关键是,如果您使用C#(以开头的接口I),就知道它完全是一个接口。抽象类型 如果您获得了非接口,通常的假设是这就是您想要的类型,LSP会被禁止。
a安

1
默认情况下,成员是密封的,这样我们就可以清楚地了解继承者可以通过@Luaan进行哪些操作。诚然,有时它是PITA,但与具体类型的偏好无关。在C#中,虚拟当然不是特例。听起来您很不幸在业余爱好者编写的C#代码库中工作。抱歉,这是您使用该语言的经验。最好记住,开发人员不是语言,反之亦然。
RubberDuck

@RubberDuck Hah,我从未说过我不喜欢它。我非常喜欢它,因为大多数人不了解间接。即使在程序员中。我认为默认情况下密封更好。当我开始使用C#时,每个人都是业余爱好者,包括.NET团队:)在“真正的OOP”中,所有东西都应该是虚拟的-每个类型都可以用派生类型替换,该派生类型应该能够覆盖任何行为。但是,“真正的OOP”是专为专家设计的,而不是那些因为更换灯泡而花更少的钱而从更换灯泡转向编程的人:)
Luaan

不,不。这根本不是“真正的OOP”的工作原理。在Java中,您应该花时间来密封不应该被覆盖的方法,而不是像狂野的西部那样使任何人都可以驾驭一切。LSP并不意味着您可以覆盖所需的任何内容。LSP意味着您可以安全地将一个类别替换为另一个类别。Smh
RubberDuck

-8

您是对的,因为我的I =接口约定违反了(新)规则

在过去,您会为所有内容添加类型为intCounter boolFlag等的前缀。

如果要命名时不带前缀,也要避免使用“ ConcreteParser”,则可以使用名称空间

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


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}

11
新?旧?等等,我认为编程更多是关于理性而不是炒作。还是在过去?

3
没有关于编写约定和写博客如何“提高可读性”的全部内容
Ewan,2016年

8
我相信您正在考虑匈牙利符号,实际上您确实在名称中添加了类型前缀。但是没有时间你会写像intCounter或那样的东西boolFlag。这些是错误的“类型”。相反,您将编写pszName(指向包含名称的NUL终止字符串的指针),cchName(字符串“名称”中的字符数),xControl(控件的x坐标),fVisible(指示可见的标志),pFile(指向文件),hWindow(处理到窗口)等等。
科迪·格雷

2
这仍然有很多价值。添加xControl到时,编译器不会捕获该错误cchName。它们都是type int,但是它们具有非常不同的语义。前缀使这些语义对人类显而易见。指向以NUL终止的字符串的指针看起来就像是指向编译器的Pascal样式的字符串的指针。同样,I接口的前缀出现在C ++语言中,其中的接口实际上只是类(实际上对于C,在语言级别上没有类或接口之类的东西,这全都是约定)。
科迪·格雷

4
@CodyGray Joel Spolsky写了一篇不错的文章,关于匈牙利符号及其误解的方式,使代码看起来错误。他有类似的观点:前缀应该是更高级别的类型,而不是原始数据存储类型。他用这个词“厚道”,解释,“我用的是一种有目的,有,因为西蒙尼误用这个词类型在他的论文,和程序员的几代人误解了他的意思。”
约书亚·泰勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.