it-swarm.cn

#region是反样式或代码气味吗?

C#允许使用#region/#endregion关键字,以使代码区域在编辑器中可折叠。每当我这样做时,我都会隐藏可能会被重构为其他类或方法的大量代码。例如,我见过一些方法,其中包含500行代码以及3或4个区域,只是为了使其易于管理。

那么明智地使用区域是否会带来麻烦呢?对我来说似乎是这样。

274
Craig

代码异味是一种症状,表明设计中存在一个问题,这可能会增加错误的数量:对于区域而言,情况并非如此,但是区域可能会导致产生代码异味,就像长方法一样。

以来:

反模式(或反模式)是在社交或企业运营或软件工程中使用的模式,通常可以使用,但实际上无效和/或适得其反

区域are反模式。他们需要做更多的工作,这些工作不会提高代码的质量或可读性,也不会减少错误的数量,并且可能只会使代码更难以重构。

不要在方法内部使用区域;重构

方法必须简短。如果一个方法中只有十行,则在处理其他五行时,可能不会使用区域来隐藏其中的五行。

此外,每个方法都只能做一件事情。另一方面,区域旨在用于分隔不同的事物。如果您的方法先执行A,然后执行B,则创建两个区域是合乎逻辑的,但这是错误的方法。相反,您应该将方法重构为两个单独的方法。

在这种情况下使用区域也会使重构更加困难。假设您有:

private void DoSomething()
{
    var data = LoadData();
    #region Work with database
    var verification = VerifySomething();
    if (!verification)
    {
        throw new DataCorruptedException();
    }

    Do(data);
    DoSomethingElse(data);
    #endregion

    #region Audit
    var auditEngine = InitializeAuditEngine();
    auditEngine.Submit(data);
    #endregion
}

折叠第一个区域以专注于第二个区域不仅有风险:我们可以轻松地忘记停止流的异常(可能有一个带有return的保护子句,这更加难以发现),但是如果应该以这种方式重构代码,也会有一个问题:

private void DoSomething()
{
    var data = LoadData();
    #region Work with database
    var verification = VerifySomething();
    var info = DoSomethingElse(data);

    if (verification)
    {
        Do(data);
    }

    #endregion

    #region Audit
    var auditEngine = InitializeAuditEngine(info);
    auditEngine.Submit(
        verification ? new AcceptedDataAudit(data) : new CorruptedDataAudit(data));
    #endregion
}

现在,区域没有意义,如果不查看第一个区域中的代码,就不可能阅读和理解第二个区域中的代码。

我有时看到的另一种情况是:

public void DoSomething(string a, int b)
{
    #region Validation of arguments
    if (a == null)
    {
        throw new ArgumentNullException("a");
    }

    if (b <= 0)
    {
        throw new ArgumentOutOfScopeException("b", ...);
    }
    #endregion

    #region Do real work
    ...
    #endregion
}

当参数验证开始跨越数十个LOC时,很容易使用区域,但是有一种更好的方法来解决此问题:.NET Framework源代码使用的一种方法:

public void DoSomething(string a, int b)
{
    if (a == null)
    {
        throw new ArgumentNullException("a");
    }

    if (b <= 0)
    {
        throw new ArgumentOutOfScopeException("b", ...);
    }

    InternalDoSomething(a, b);
}

private void InternalDoSomething(string a, int b)
{
    ...
}

不要使用方法以外的区域进行分组

  • 有人用它们将字段,属性等分组在一起。这种方法是错误的:如果您的代码符合StyleCop规范,则字段,属性,私有方法,构造函数等已被分组在一起,很容易找到。如果不是,那么该是时候开始考虑应用确保整个代码库统一的规则了。

  • 其他人使用区域来隐藏大量相似实体。例如,当您有一个包含一百个字段的类(如果计算注释和空格,则至少要编写500行代码),您可能会想将这些字段放在一个区域内,将其折叠并忘记它们。再一次,您做错了:在一个类中有这么多字段,您应该更好地考虑使用继承或将对象切成几个对象。

  • 最后,有些人很想使用区域将相关事物分组在一起:一个事件及其委托人,或者一个与IO相关的方法以及与IO相关的其他方法,等等。在第一种情况下,它变得一团糟,难以维护,阅读和理解。在第二种情况下,更好的设计可能是创建多个类。

区域有很好的用途吗?

No.有一个遗留用途:生成的代码。尽管如此,代码生成工具只需要使用部分类即可。如果C#具有区域支持,则主要是因为这种传统用途,并且由于现在有太多人在其代码中使用区域,因此在不破坏现有代码库的情况下就不可能删除它们。

考虑一下goto。语言或IDE支持某种功能的事实并不意味着应每天使用该功能。StyleCop SA1124规则很明确:您不应使用区域。永不。

例子

我目前正在对同事的代码进行代码审查。该代码库包含许多区域,并且实际上是如何不使用区域以及区域为何导致不良代码的完美示例。这里有些例子:

4 000 LOC怪物:

我最近在Programmers.SE上的某个地方读到,当文件包含过多的usings(执行“删除未使用的用法”命令后)时,这是一个好兆头,表明该文件中的类做得太多。这同样适用于文件本身的大小。

在查看代码时,我遇到了一个4000 LOC文件。看起来,这段代码的作者只是简单地将同一行的15行方法复制粘贴了数百次,只是略微更改了变量名和被调用方法。一个简单的正则表达式允许通过添加一些泛型将文件从4000 LOC减少到500 LOC。我非常确定,通过更巧妙的重构,此类可以减少到几十行。

通过使用区域,作者鼓励自己忽略该代码无法维护且编写不良的事实,并大量复制代码而不是对其进行重构。

区域“ Do A”,区域“ Do B”:

另一个很好的例子是怪物初始化方法,该方法仅执行任务1,然后执行任务2,然后执行任务3,等等。有五个或六个完全独立的任务,每个任务都在容器类中进行初始化。所有这些任务都被分组为一种方法,并分组为区域。

这有一个优点:

  • 通过查看区域名称可以很清楚地理解该方法。话虽如此,重构后的相同方法将与原始方法一样清晰。

另一方面,问题是多方面的:

  • 区域之间是否存在依赖关系并不明显。希望不会重复使用变量;否则,维护工作可能更像一场噩梦。

  • 该方法几乎无法测试。您如何轻松地知道一次执行20件事的方法是否正确?

字段区域,属性区域,构造函数区域:

审阅的代码还包含许多区域,这些区域将所有字段,所有属性组合在一起,等等。这存在一个明显的问题:源代码增长。

当您打开文件并看到巨大的字段列表时,您更倾向于先重构类,然后再使用代码。在区域中,您习惯于折叠东西而忘却它。

另一个问题是,如果您在任何地方都执行此操作,则会发现自己创建了一个块区域,这没有任何意义。我所检查的代码实际上就是这种情况,其中有很多#region Constructor包含一个构造函数。

最后是字段,属性,构造函数等。 应该已经在顺序中 。如果它们是并且它们符合约定(以大写字母开头的常量等),则已经清楚元素类型在何处停止而其他在何处开始,因此您无需为此显式创建区域。

291
Arseni Mourzenko

我真是难以置信,有多少人如此热情hate地区!

我完全同意他们的许多反对意见:将代码放入#region将其隐藏起来是一件坏事。将一个类划分为#regions何时应将其重构为单独的类显然是一个错误。用一个 #region嵌入多余的语义信息是多余的。

但是这些都没有意味着在代码中使用区域有任何错误固有地!我只能假设大多数人的反对意见来自在其他人倾向于不正确使用IDE)这样的功能的团队中工作。区域帮助组织工作流程的方式。也许是我的强迫症,但是我不希望一次在屏幕上看到一堆代码,无论它写得多么整洁和优雅。进入逻辑区域使我可以折叠我关心的代码以处理我do关心的代码。我不会忽略编写不好的代码,对其进行重构就没有多大意义了,而且附加的“元”组织是描述性的,而不是毫无意义的。

现在,我已经花了更多时间在C++中工作,更直接地使用Windows API进行编程,我发现自己希望对区域的支持与对C#的支持一样好。您可能会争辩说,使用备用GUI库会使我的代码更简单或更清晰,从而消除了将无关的代码噪声从屏幕上移开的需要,但是我还有其他不想这样做的原因。我足够熟练地使用键盘和IDE,扩展/折叠细分为多个区域的代码只需不到一秒钟的时间。仅对我当前正在使用的代码而言,这是值得的,它们全部都属于一个类/文件,但并非同时属于我的屏幕。

关键是使用#regions来分隔代码并在逻辑上进行划分并不是一件坏事,无论如何都要避免。正如Ed所指出的,这不是“代码异味”。如果您的代码有味道,则可以确保它不是来自区域,而是来自您尝试将其埋在这些区域中的任何代码。如果某个功能可以帮助您变得更有条理或编写更好的代码,那么我说使用它。如果它成为障碍,或者您发现自己使用不正确,请stop使用它。如果最糟糕的情况变得更糟,并且您被迫与使用它的人一起工作,请记住键盘快捷键以关闭代码概述: Ctrl+M, Ctrl+P。别抱怨了有时候,我觉得这是另一种方式,那些希望被视为“真实”,“核心”程序员的人喜欢尝试证明自己。避开区域比避开语法着色更好。它并没有使您成为更强壮的开发人员。

综上所述,区域内部方法简直是胡说八道。每当您发现自己想要这样做时,都应该将其重构为单独的方法。没有理由。

115
Cody Gray

首先,我再也受不了“代码气味”这个词了。它使用得太多了,并且大部分时间都被无法识别好的代码的人扔掉。无论如何...

我个人不喜欢使用很多地区。这使获取代码变得更加困难,而代码正是我感兴趣的代码。当我有很多不需要经常接触的代码时,我喜欢使用区域。除此之外,它们似乎妨碍了我,“私有方法”,“公共方法”等区域使我发疯。它们类似于i++ //increment i

我还要补充一点,区域的使用实际上不能成为“反模式”,因为该术语通常用于描述程序逻辑/设计模式,而不是文本编辑器的布局。这是主观的;使用对您有用的东西。由于您对区域的过度使用,您永远都不会以无法维护的程序告终,这就是反模式的全部意义所在。 :)

70
Ed S.

是的区域有代码气味!

我很高兴看到将区域从编译器中完全删除。每个开发人员都会想出自己的毫无意义的修饰方案,这种方案永远不会对其他程序员有价值。我与想要装饰和美化他们的婴儿的程序员有一切关系,与任何真正的价值无关。

您能想到一个示例,尽管您“哎呀,我希望我的同事在这里使用过某些区域!”?

即使我可以将我的IDE)配置为自动扩展所有区域,但它们仍然让人大吃一惊,并有损于阅读真实的代码。

如果我所有的公共方法都聚集在一起,我真的会在乎。恭喜,您知道变量声明和初始化之间的区别,无需在代码中显示它!

毫无价值的修饰!

另外,如果您的文件需求和使用区域的“信息体系结构”,您可能想解决核心问题:您的课程太大了!将其分解成较小的部分会更加有益,并且如果正确完成,则会增加真正的语义/可读性。

23
Joppe

我个人使用区域作为将各种类型的方法或部分代码组合在一起的一种方式。

因此,打开后的代码文件可能如下所示:

  • 公共财产
  • 建设者
  • 保存方法
  • 编辑方法
  • 私人帮手方法

我没有将区域放在方法内部。恕我直言,这是代码气味的迹象。我曾经遇到一种方法,该方法长1200行以上,其中有5个不同的区域。真是恐怖的景象!

如果您将其用作组织代码的方式,从而可以更快地为其他开发人员查找内容,那么我认为这并不意味着麻烦。如果您使用它来隐藏方法内部的代码行,我想是时候重新考虑该方法了。

15
Tyanna

使用#region块使非常大的类可读性通常是违反单一职责原则的标志。如果它们被用来对行为进行分组,那么可能表示该类做得太多(再次违反SRP)。

坚持“代码气味”思路,#region块本身并不是代码的味道,但是它们更像“ Febreze for code”,因为它们试图隐藏味道。虽然我过去曾大量使用它们,但是当您开始进行重构时,您会看到的东西更少了,因为它们最终没有隐藏太多。

10
Austin Salonen

这里的关键词是“明智的”。很难想象将区域放入方法内部是明智的情况。这很可能是代码隐藏和懒惰。但是,有充分的理由在自己的代码中到处都有一些区域。

如果有很多区域,我确实认为这是一种代码味道。区域通常暗示将来可能进行重构。很多地区意味着实际上没有人接受过提示。

明智地使用它们可以在具有多个方法的单个类的结构与每个仅具有几个方法的多个类的结构之间建立良好的中间立场。当一个类开始将其重构为多个类但还远远不够时,它们最有用。通过将相关方法分组在一起,如果它们继续增加,我以后可以轻松地将一组相关方法提取到自己的类中。例如,如果我有一个接近500行代码的类,那么在一个区域中使用总共200行代码收集在一起的一组方法可能是重构的好方法-而在另一个区域中有100行代码方法也可能是一个很好的目标。

我喜欢使用区域的另一种方法是减少重构大型方法的负面影响之一:许多小型,简洁,易于重用的方法,读者必须滚动浏览才能找到另一种几乎不相关的方法。区域可能是将方法及其辅助程序元封装给读者的一种不错的方法,因此,使用该类不同方面的人员可以将它们折叠起来并迅速消除该部分代码。当然,这仅在您的区域组织得很好并且实质上被用作记录代码的另一种方式时才有效。

通常,与不使用区域相比,我发现区域可以帮助我保持井井有条,帮助“编写文档”我的代码,并帮助我更快地找到要重构的地方。

5
Ethel Evans

我主要使用CRUD服务器类的区域来组织各种类型的操作。即使那样,我也可以很高兴地没有他们。

如果广泛使用,它将引起危险信号。我将寻找责任过多的课程。

以我的经验,一个有数百行代码的方法绝对是一种味道。

4
TaylorOtwell

我的经验法则是:如果文件中有5个以上的区域,则有代码味道

即,描述字段,方法,属性和构造函数可能很好,但是如果您开始将其他所有方法包装在它自己的区域中,则表示严重错误

..是的,我经常参与许多项目,通常是因为编码标准不佳,代码生成或两者兼而有之。必须快速切换Visual Studio中的所有轮廓才能获得良好的代码概述,这很快就过去了。

4
Homde

区域已使用

在Windows窗体应用程序之前,我曾亲自使用它们进行“手动编码”界面事件。

但是,在我的工作中,我们使用代码生成器来处理SQL,并且它自动使用区域来整理其选择,更新,删除等类型的方法。

因此,尽管我不经常使用它们,但是它们非常适合删除大块代码。

4
Ken

如果您有区域[〜#〜] in [〜#〜]代码,则您肯定会遇到问题(除非生成代码。)将区域放入代码中基本上是在说“重构”。

但是,还有其他情况。我回想了一段时间:一个有几千个预先计算的项目的表。这是对几何的描述,没有表格中的错误,永远不会有机会查看它。当然,我可以从资源或类似资源中获取数据,但是这将排除使用编译器来使其易于阅读的麻烦。

4
Loren Pechtel

我会说这是“代码气味”。

反模式通常是软件中的基本结构问题,而区域本身只会在编辑器中造成令人讨厌的行为。使用区域实际上并没有本质上的坏处,但是经常使用它们,尤其是隐藏代码块可以表明在其他地方还存在其他独立的更大问题。

3
whatsisname

在最近的项目中,有一种1700行方法,其中嵌入了多个区域。有趣的是,这些区域划分了在该方法中正在执行的不同操作。我能够在每个区域上执行重构->提取方法,而不会影响代码的功能。

通常,用于隐藏样板代码的区域很有用。我建议不要使用区域来隐藏属性,字段等,因为如果它们在类中工作时太笨拙而无法查看,则可能表明该类应该进一步分解。但是,作为一个硬规则,如果要将区域放入方法中,则最好提取另一种方法来解释正在发生的事情,而不是将该块包装在区域中。

3
Michael Brown

可以在质量好的代码中使用区域吗?大概。我敢打赌,在很多情况下,它们都是如此。但是,我的个人经历使我感到非常怀疑-我看到几乎完全被滥用的区域。我会说我很沮丧,但仍然很乐观。

我可以将迄今为止使用的区域代码大致分为三类:

  • 分解系数差的代码:我见过的大多数代码都将区域用作穷人分解系数的工具。例如,一类已经发展到可以将其专门用于不同目的的意义的类,可以改为分成不同的区域,每个目的一个。

  • 针对问题域使用错误的库(有时甚至使用错误的语言)编写的代码通常,当程序员没有为问题域使用正确的库集时,您会看到代码变得非常冗长-带有许多辅助功能确实不属于它们(它们可能属于自己的库)。

  • 学生或应届毕业生编写的代码。一些程序和课程似乎试图通过各种奇怪的目的将区域的使用灌输给学生。您会看到区域将源代码乱七八糟,直到区域标签与代码行的比率在1:5或更差的范围内。

3
blueberryfields

我仅将区域用于一件事(至少我无法想到使用它们的其他地方):将单元测试分组为一种方法。

我通常每个类都有一个测试类,然后通过使用具有方法名称的区域将每个方法的单元测试分组。不知道这是代码的味道还是其他东西,但是由于基本思想是单元测试不需要更改,除非它们因代码中的某些内容发生更改而中断,这使我更容易找到特定方法的所有测试很快。

我过去可能曾经使用区域来组织代码,但我不记得上一次这样做了。不过,我坚持在单元测试课程中学习所在的地区。

3
Anne Schuessler

我相信这是一种反模式,并且坦率地认为应该将其消除。但是,如果您处于在标准位置工作的不幸情况,Visual Studio提供了一个很棒的工具,可以最大程度地减少每次看到一个区域时您想要呕吐的数量 I Hate #Regions

这个插件将使该区域的字体大小很小。它们也会被扩展,因此您不必点击ctr + m + l即可打开所有区域。它不能解决这种形式的代码癌症,但是确实可以忍受。

2
DeadlyChambers

区域是一个不错的组织构想,但是没有考虑到某些开发人员想要对所有内容进行过度分类的倾向,并且根据现代大多数情况,通常没有必要OOP做法...它们是“气​​味” ,从某种意义上说,使用它们通常表示您的类/方法太大,应该对其进行重构,因为您可能违反了SOLID闻到,这并不一定表示有问题。

区域在功能代码中而不是在面向对象的代码IMO中有更多用途,在IMO中,有很长的顺序数据功能很有意义,可以拆分,但是有些时候我亲自在c#中使用了它们,并且它们几乎总是专注于不需要/不想看的代码。对我来说,这些通常是用于NPoco或其变体的代码库中的原始SQL字符串常量。除非您真正关心数据如何通过您的ORM填充POCO对象,否则这些都是毫无意义的……如果您确实关心,嘿,只需扩展区域并(BAM!超过150行的复杂SQL查询为您带来观赏乐趣。

0
Jeremy Holovacs

我使用区域来包含可见性和成员类型的每种组合。因此,所有私有功能都进入一个区域等。

我这样做的原因不是,所以我可以折叠代码。这是因为我编写了编辑器的脚本,因此可以插入对代理的引用:

#region "private_static_members"
 /// <summary>
 /// cache for LauncherProxy
 /// </summary>
private static LauncherProxy _launcherProxy;
#endregion

#region "protected_const_properties"
protected LauncherProxy LauncherProxy{
  get{
    if(_launcherProxy==null) {
      if (!God.Iam.HasProxy(LauncherProxy.NAME)) {
        God.Iam.RegisterProxy(new LauncherProxy());
      }
      _launcherProxy=God.Iam.Proxy(LauncherProxy.NAME) as LauncherProxy;
    }
    return _launcherProxy;
  }
}
#endregion

放入代码中,并将每个部分整齐地塞入适当的区域。

在这种情况下,宏将分析我的项目,给我一个代理列表框,并为我想要的代码注入代码。我的光标甚至都没有动。

在学习C#的开始,我曾考虑过使用区域来保持共同性,但这是一个命中注定的命题,因为它并非始终都是一对一的关系。谁想为两个地区使用的成员烦恼,或者甚至开始按照这些条款分手。

分离的唯一另一种类型是方法-我将把方法分解为Commands,Functions和Handlers,因此我将有一个区域,用于公共,私有等Commands等。

这给了我粒度,但是它是一致的,明确我可以依靠的粒度。

0
Mark

区域是预处理器表达式-换句话说,它们被视为注释,基本上被编译器忽略。它们纯粹是Visual Studio中使用的可视工具。因此,#region并不是真正的代码味道,因为它不是代码。代码气味更确切地说是800行方法,其中嵌入了许多不同的职责。因此,如果您在一种方法中看到10个区域,则可能是用来隐藏代码气味。话虽如此,我已经看到他们非常有效地使用了它,使一堂课更加令人赏心悦目,并且易于导航-也是在编写得很好且结构化的课中!

0
BKSpurgeon