为什么不在C#中使用'using'指令?


71

大型C#项目上的现有编码标准包括以下规则:所有类型名称均应完全限定,从而禁止使用“ using”指令。因此,而不是熟悉的:

using System.Collections.Generic;

.... other stuff ....

List<string> myList = new List<string>();

(这var也就不足为奇了。)

我最终得到:

System.Collections.Generic.List<string> myList = new System.Collections.Generic.List<string>();

打字量增加了134%,但没有提供任何有用的信息。在我看来,增加的100%是实际上阻碍理解的噪音(杂波)。

在30多年的编程中,我看到过一次或两次提出这样的标准,但从未实施过。它背后的原理使我无所适从。实施标准的人并不愚蠢,我认为他不是恶意的。除非我遗漏了一些东西,否则这会导致误导是唯一的其他选择。

您听说过这样的标准吗?如果是这样,其背后的原因是什么?除了“这很愚蠢”或“其他人都雇用using”之外,您还能想到其他可能说服此人取消该禁令的论点吗?

推理

禁止的原因有:

  1. 将鼠标悬停在名称上以获得完全限定的类型很麻烦。最好始终始终显示完全合格的类型。
  2. 电子邮件代码段没有完全限定的名称,因此可能很难理解。
  3. 在Visual Studio(例如Notepad ++)之外查看或编辑代码时,不可能获得完全限定的类型名称。

我的论点是,这三种情况都是很少见的,而让每个人都为解决一些罕见情况而付出混乱且难以理解的代码的代价是错误的。

我什至没有提到潜在的名称空间冲突问题,而我希望这是最主要的问题。这特别令人惊讶,因为我们有一个名称空间MyCompany.MyProject.Core,这是一个特别糟糕的主意。我很早就学会这东西命名SystemCore在C#是一个快速路径的疯狂。

正如其他人指出的那样,通过重构,名称空间别名或部分限定条件可以轻松解决名称空间冲突。


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

1
一个为什么可能是个好主意的真实示例(但在C ++世界中):答案为何“使用命名空间std”被认为是不好的做法?,靠近“禁止同时使用“使用”指令和声明”。
彼得·莫滕森

在阅读“推理”部分之前,我有点猜出了不切实际的原因,因为我的公司有类似的规则。
Gqqnbig

Answers:


69

更广泛的问题:

您听说过这样的标准吗?如果是这样,其背后的原因是什么?

是的,我听说过,使用完全限定的对象名称可以防止名称冲突。尽管很罕见,但是当它们发生时,要弄清楚它们可能会非常棘手。


一个例子: 用一个例子可以更好地解释这种情况。

假设我们有两个Lists<T>属于两个不同的项目。

System.Collections.Generic.List<T>
MyCorp.CustomCollections.Optimized.List<T>

当我们使用完全限定的对象名称时,很清楚List<T>要使用哪个名称。这种清晰性显然是以冗长为代价的。

您可能会争论:“等等!没有人会使用两个这样的列表!” 我将在其中指出维护方案。

您是Foo为公司编写的模块,它使用经过批准和优化的公司List<T>

using MyCorp.CustomCollections.Optimized;

public class Foo {
    List<object> myList = ...;
}

后来,新的开发人员决定扩展您一直在做的工作。在不了解公司标准的情况下,他们更新了该功能using块:

using MyCorp.CustomCollections.Optimized;
using System.Collections.Generic;

您会很快发现情况变糟了。

应当指出,您可以在同一个项目中拥有两个具有相同名称但在不同名称空间中的专有类。因此,不仅仅是与.NET Framework提供的类冲突的关注。

MyCorp.WeightsAndLengths.Measurement();
MyCorp.TimeAndSpace.Measurement();

现实:
现在,这可能会在大多数项目中发生吗?不,不是。但是,当您在一个具有大量输入的大型项目中工作时,您将尽最大努力将发生爆炸的可能性降到最低。

具有多个贡献团队的大型项目是应用程序世界中的一种特殊野兽。由于项目的输入流以及做出贡献的人可能没有阅读项目指南的可能性,对于其他项目而言似乎不合理的规则变得更加相关。

当两个大型项目合并在一起时,也会发生这种情况。如果两个项目都具有类似命名的类,那么当您开始从一个项目引用到另一个项目时,就会看到冲突。这些项目可能太大而无法重构,否则管理层不会批准用于资助重构时间的费用。


替代方案:
尽管您没有要求,但值得指出的是,这不是一个很好的解决方案问题。创建将在没有名称空间声明的情况下发生冲突的类不是一个好主意。

List<T>,尤其应被视为保留字,而不应用作类的名称。

同样,项目中的各个名称空间应努力具有唯一的类名。不得不尝试回想Foo()您正在使用哪个名称空间是最好的避免方法。换句话说:拥有MyCorp.Bar.Foo()MyCorp.Baz.Foo()会让您的开发人员绊倒,最好避免。

如果没有其他问题,则可以使用部分名称空间来解决歧义。例如,如果您绝对不能重命名任何一个Foo()类,则可以使用其部分名称空间:

Bar.Foo() 
Baz.Foo()

您当前项目的特定原因:

您使用符合该标准的当前项目的具体原因更新了问题。让我们看一下它们,然后沿着兔子小路走开。

将鼠标悬停在名称上以获得完全限定的类型很麻烦。最好始终始终显示完全合格的类型。

“笨拙?” 不。也许烦人。移动几盎司的塑料以移动屏幕上的指针并不麻烦。但是我离题了。

这种推理方式似乎更像是掩盖。随便说一句,我猜应用程序中的类都是弱命名的,您必须依赖命名空间才能收集围绕类名的适当数量的语义信息。

这不是完全合格的类名的有效证明,也许这是使用部分合格的类名的有效证明。

电子邮件代码段没有完全限定的名称,因此可能很难理解。

这种(续?)推理路线使我更加怀疑当前类的命名不正确。同样,使用不良的类名并不是要求所有内容都使用完全限定的类名的充分理由。如果类名难以理解,那么比完全限定的类名可以解决的错误要多得多。

在Visual Studio(例如Notepad ++)之外查看或编辑代码时,不可能获得完全限定的类型名称。

出于所有原因,这几乎使我吐出了酒。但是我又离题了。

我不知道为什么团队经常在Visual Studio之外编辑或查看代码?现在,我们正在寻找一种与名称空间要提供的东西完全正交的理由。这是工具支持的参数,而名称空间则可以为代码提供组织结构。

听起来您自己的项目遭受了不良的命名约定,而开发人员却没有充分利用工具为他们提供的功能。他们没有解决这些实际问题,而是试图对症状之一打个创可贴,并要求使用完全合格的班级名称。我认为将其归类为误导方法是安全的。

鉴于存在命名不佳的类,并且假设您无法进行重构,正确的答案是使用Visual Studio IDE来发挥其全部优势。可能考虑添加VS PowerTools软件包之类的插件。然后,当我查看时AtrociouslyNamedClass(),可以单击类名,按F12键,然后直接转到类的定义,以便更好地了解它的作用。同样,我可以单击Shift-F12来找到当前不得不使用的代码中的所有斑点AtrociouslyNamedClass()

关于外部工具问题-最好的办法就是停止它。如果片段没有立即清除所指内容,则不要来回发送电子邮件。不要在Visual Studio之外使用其他工具,因为这些工具没有团队所需的智能。Notepad ++是一个了不起的工具,但并非可用于此任务。

因此,我同意您对提出的三个具体理由的评估。就是说,我想你被告知的是“该项目存在一些无法/无法解决的根本问题,而这就是我们'解决'这些问题的方式。” 这显然说明了团队内部更深层次的问题,可能会给您带来危险。


1
+1。实际上,我在内部名称空间中也遇到了一些令人讨厌的冲突。既可以将遗留代码移植到“ 2.0”项目中,又可以选择一些在不同名称空间上下文中在语义上有效的类名。真正棘手的是,具有相同名称的两件事通常具有相似的接口,因此编译器不会抱怨……您的运行时会!
svidgen

23
这个问题是通过使用名称空间别名来解决的,而不是通过强加一些笨拙的规则来要求代码,而这些规则会因使用明确的名称空间用法而显得杂乱无章。
David Arno

4
@DavidArno-我不同意你的看法。但是,大型项目可能没有这种选择。我只是指出为什么大型项目可能会施加该规则。不过,我不主张这样做。只是有时需要这样做是因为团队没有其他选择。

4
@ GlenH7您可能要在答案中指定其他可用的解决方案。我不希望看到人们偶然发现这个问题,并想:“哦,好主意!” +1是为了保持客观和务实的态度。
RubberDuck

3
@JimMischel-听起来好像该规则被用作某物的拐杖。虽然可能无法确定那是什么东西。从较高的层次上讲,是的,有时有理由不允许这样做,usings但这听起来不像您的项目符合其中一种情况。

16

我曾在一家公司工作过,那里有许多同名的类,但是在不同的类库中。

例如,

  • 域客户
  • 传统客户
  • ServiceX.Customer
  • ViewModel.Customer
  • 数据库客户

您可能会说,设计不好,但是您知道这些事情是如何有机发展的,还有什么选择-重命名所有类以包括名称空间?重大重构的一切?

无论如何,在许多地方引用所有这些不同库的项目都需要使用该类的多个版本。

由于using直接位于顶部,这是一个很大的难题,ReSharper会做一些奇怪的尝试来使其生效using,即时重写指令,您会得到客户无法铸造的印象,客户风格的错误,不知道哪种类型是正确的。

因此,至少具有部分名称空间,

var cust = new Domain.Customer

要么

public void Purchase(ViewModel.Customer customer)

大大提高了代码的可读性,并明确了您想要的对象。

它不是编码标准,不是完整的名称空间,也没有作为标准应用于所有类。


13
“还有什么选择,重命名所有类以包括名称空间?对所有内容进行重大重构?” 是的,这是(正确的)替代方案。
David Arno

2
仅在短期内。从长远来看,这比在代码中使用显式命名空间便宜得多。
David Arno

3
只要您以现金而非股份支付我,就很乐意接受该论点。
伊万2015年

8
@David:不,这不是正确的选择。正确限定所有标识符或至少完全匹配的标识符是正确的方法,而为什么首先使用名称空间的原因是在不同模块中使用相同名称时提供冲突。关键是某些模块可能来自第三方,您无法修改该代码库。那么,当来自两个不同的第三方库的标识符冲突并且您需要使用两个库,但又不能重构其中的一个库时,您该怎么办?存在名称空间,因为重构并非总是可能的。
Kaiserludi

4
@CarlSixsmith,如果有人赞成Martin Fowler的重构观点,那么如果更改影响到用户,那么您是正确的,那就不是重构。这是重组的另一种形式。但这有两点:1.所有公共方法是否自动成为API的一部分?2. 福勒本人承认,他正在为这个定义而战败,越来越多的人使用“重构”一词来表示广义的重组,包括公共变更。
David Arno

7

太冗长或太短是没有好处的:为了避免细微的错误,同时又避免编写过多的代码,应该在细节上找到一个黄金中间。

在C#中,参数的名称就是一个例子。我遇到了几次问题,我花了数小时寻找解决方案,而更冗长的写作风格可能首先避免了该错误。问题包括参数顺序错误。例如,它看起来对您合适吗?

if (...) throw new ArgumentNullException("name", "The name should be specified.");
if (...) throw new ArgumentException("name", "The name contains forbidden characters.");

乍看起来,它看起来还不错,但是有一个错误。异常的构造函数的签名为:

public ArgumentException(string message, string paramName)
public ArgumentNullException(string paramName, string message)

注意到参数的顺序了吗?

通过采用一种更加冗长的样式,样式每次在该方法接受两个或多个相同类型的参数时都指定一个参数名称,我们可以防止错误再次发生,因此:

if (...) throw new ArgumentNullException(paramName: "name", message: "The name should be specified.");
if (...) throw new ArgumentException(paramName: "name", message: "The name contains forbidden characters.");

显然更长的时间来写,但是却导致更少的时间浪费在调试上。

推到了极点,人们可能会强迫在任何地方使用命名参数,包括在doSomething(int, string)无法混合参数顺序的方法中使用。从长远来看,这可能会损害项目,导致产生更多的代码,而没有防止上述错误的好处。

在您的情况下,“否using”规则背后的动机是,它会使使用错误的类型(例如MyCompany.Something.List<T>vs.)更加困难System.Collections.Generic.List<T>

就个人而言,我会避免使用此规则,因为恕我直言,难于阅读代码的成本远远高于稍微降低了错误使用错误类型的风险所带来的好处,但至少,这条规则是有正当理由的。


此类情况可以通过工具检测。ReSharper paramNameInvokerParameterName属性标记,如果不存在这样的参数,则发出警告。
Johnbot

2
@Johnbot:他们当然可以,但是在少数情况下。如果我有一个SomeBusiness.Something.DoStuff(string name, string description)怎么办?没有工具足够聪明地理解什么是名称和什么是描述。
Arseni Mourzenko 2015年

在这种情况下,@ ArseniMourzenko甚至人类都需要花点时间弄清楚调用是否正确。
Gqqnbig

6

命名空间为代码提供了层次结构,允许使用较短的名称。

   MyCompany.Headquarters.Team.Leader
   MyCompany.Warehouse.Team.Leader

如果您允许使用“ using”关键字,那么会发生一系列事件……

  • 每个人实际上都在使用一个全局名称空间
    (因为它们包含了他们可能想要的每个名称空间)
  • 人们开始遇到名字冲突,或者
  • 担心会发生名称冲突。

因此,为避免风险,每个人都开始使用非常长的名称:

   HeadquartersTeamLeader
   WarehouseTeamLeader

局势升级了……

  • 可能已经有人选择了一个名称,所以您使用更长的名称。
  • 名字变得越来越长。
  • 长度可以超过60个字符,没有最大长度-这太荒谬了。

我已经看到这种情况的发生,因此可以同情那些禁止“使用”的公司。


11
禁止using是核选择。最好使用名称空间别名和部分限定。
Jim Mischel

1

问题using在于它神奇地添加到命名空间而无需显式声明。对于维护程序员来说,这是一个更大的问题,他们遇到类似的东西List<>并且不熟悉该域,却不知道哪个using子句引入了它。

如果一个人写import Java.util.*;而不是写一个import Java.util.List;例子,那么在Java中也会发生类似的问题。后面的声明记录了已引入的名称,以便List<>在源中的后面出现时,可以从import声明中得知其来源。


6
确实,不熟悉该域的人会遇到一些困难,但他要做的就是将鼠标悬停在类型名称上,他将获得完全限定的名称。或者,他可以右键单击并直接转到类型定义。对于.NET Framework类型,他可能已经知道事情在哪里。对于特定领域的类型,可能要花一个星期才能使他适应。那时,完全限定的名称只是妨碍理解的噪音。
Jim Mischel

吉姆..我只是试图将鼠标悬停在定义上,但是它没有用。您是否考虑过世界上某些程序员不使用Microsoft IDE的可能性?无论如何,您的计数器不会使我的观点无效,即使用
use

2
是的,有些在C#中工作的人由于没有使用最好的可用工具而无法自拔。并且,尽管完全限定资格是“自我记录”的论点有一些优点,但这种代码在可读性上却付出了巨大的代价。所有这些无关的代码都会产生使代码中真正重要的部分变得模糊的杂音。
Jim Mischel
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.