C#允许使用#region
/ #endregion
关键字使代码区域在编辑器中可折叠。每当我这样做时,我都会隐藏可能会重构为其他类或方法的大量代码。例如,我见过一些方法,其中包含500行代码以及3或4个区域,只是为了使其易于管理。
那么明智地使用区域是否会带来麻烦呢?对我来说似乎是这样。
C#允许使用#region
/ #endregion
关键字使代码区域在编辑器中可折叠。每当我这样做时,我都会隐藏可能会重构为其他类或方法的大量代码。例如,我见过一些方法,其中包含500行代码以及3或4个区域,只是为了使其易于管理。
那么明智地使用区域是否会带来麻烦呢?对我来说似乎是这样。
Answers:
代码异味是一种症状,表明设计中存在一个问题,这可能会增加错误的数量:对于区域而言,情况并非如此,但是区域可能会导致产生代码异味,就像长方法一样。
以来:
反模式(或反模式)是在社交或企业运营或软件工程中使用的模式,通常可以使用,但实际上无效和/或适得其反
区域是反模式。他们需要做更多的工作,这些工作不会提高代码的质量或可读性,也不会减少错误的数量,并且可能只会使代码更难以重构。
方法必须简短。如果方法中只有十行,则在处理其他五行时,可能不会使用区域来隐藏其中的五行。
同样,每种方法都只能做一件事情。另一方面,区域旨在分离不同的事物。如果您的方法执行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
}
使第一个区域崩溃而将注意力集中在第二个区域上不仅有风险:我们可以轻松地忘记阻止流程停止流动的异常(可能有一个带有a的保护子句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相关的其他方法,等等。在第一种情况下,它变得一团糟,难以维护,阅读并理解。在第二种情况下,更好的设计可能是创建多个类。
否。有一个旧用途:生成的代码。尽管如此,代码生成工具只需要使用部分类即可。如果C#具有区域支持,则主要是因为这种传统用途,并且由于现在有太多人在其代码中使用区域,因此在不破坏现有代码库的情况下就不可能删除它们。
考虑一下goto
。语言或IDE支持功能这一事实并不意味着应每天使用该功能。StyleCop SA1124规则很明确:您不应使用区域。决不。
我目前正在对同事的代码进行代码审查。该代码库包含许多区域,并且实际上是如何不使用区域以及区域为何导致不良代码的完美示例。这里有些例子:
4000 LOC怪物:
我最近在Programmers.SE上的某个地方读到,当文件包含太多using
s(在执行“删除未使用的用法”命令之后)时,这是一个好兆头,表明该文件中的类做得太多。这同样适用于文件本身的大小。
在查看代码时,我遇到了一个4000 LOC文件。看起来,这段代码的作者只是简单地将同一行的15行方法复制粘贴了数百次,只是略微更改了变量的名称和被调用的方法。一个简单的正则表达式允许通过添加一些泛型将文件从4000 LOC减少到500 LOC。我非常确定,通过更巧妙的重构,此类可以减少到几十行。
通过使用区域,作者鼓励自己忽略该代码无法维护且编写不良的事实,并大量复制代码而不是对其进行重构。
地区“ Do A”,地区“ Do B”:
另一个很好的例子是怪物初始化方法,该方法仅执行任务1,然后执行任务2,然后执行任务3,等等。有五个或六个完全独立的任务,每个任务都在容器类中进行初始化。所有这些任务都被分组为一种方法,并分组为区域。
这有一个优点:
另一方面,问题是多方面的:
区域之间是否存在依赖关系并不明显。希望不会重复使用变量。否则,维护工作可能更像一场噩梦。
该方法几乎无法测试。您如何轻松地知道一次执行20件事的方法是否正确?
字段区域,属性区域,构造函数区域:
审阅的代码还包含许多区域,这些区域将所有字段,所有属性组合在一起,等等。这存在一个明显的问题:源代码增长。
当您打开文件并看到巨大的字段列表时,您更倾向于先重构该类,然后再使用代码。在区域中,您习惯于折叠东西而忘却它。
另一个问题是,如果您在所有地方都这样做,则会发现自己创建了一个块区域,这没有任何意义。我所检查的代码实际上就是这种情况,其中#region Constructor
包含一个构造函数。
最后,字段,属性,构造函数等应该已经按顺序排列。如果它们是并且它们符合约定(以大写字母开头的常量等),则已经清楚元素类型在何处停止而其他在何处开始,因此您无需为此显式创建区域。
令我感到不可思议的是,有多少人如此讨厌地区!
我完全同意他们的许多反对意见:将代码#region
隐藏到a中以使其看不见是一件坏事。将一个类划分为何#regions
时应该将其重构为单独的类显然是错误的。使用#region
嵌入冗余语义信息是多余的。
但是,这些都不意味着在代码中使用区域存在任何内在的错误!我只能假设大多数人的反对意见来自在其他人倾向于不正确使用IDE功能的团队中工作。我拥有独自工作的能力,并且我对地区帮助组织工作流程的方式表示赞赏。也许这是我的强迫症,但我不喜欢一次在屏幕上看到一堆代码,无论它写得多么整洁。将事物分离成逻辑区域可以使我折叠不关心的代码来处理我所做的代码关心。我并不是在忽略写得不好的代码,对其进行重构的意义不大,而且附加的“元”组织是描述性的,而不是毫无意义的。
现在,我已经花了更多时间在C ++中工作,更直接地使用Windows API进行编程,我发现自己希望对区域的支持与对C#的支持一样好。您可能会争辩说,使用备用GUI库会使我的代码更简单或更清晰,从而消除了将无关的代码噪声从屏幕上移开的需要,但是我还有其他不想这样做的原因。我对自己的键盘和IDE足够精通,可以使扩展/折叠细分为区域的代码花费的时间不到一秒钟。我节省了精力,试图将自己的注意力集中到我目前正在处理的代码上,这是值得的。它们全都属于一个类/文件,但并非同时都属于我的屏幕。
关键是要使用#regions
代码来分离和逻辑划分代码不是一件坏事,要不惜一切代价避免。正如Ed所指出的,这不是“代码异味”。如果您的代码有味道,则可以确保它不是来自区域,而是来自您尝试将其埋在这些区域中的任何代码。如果某个功能可以帮助您更井井有条,或者编写更好的代码,那么我就说使用它。如果它成为障碍,或者您发现自己使用不正确,请停止使用它。如果情况变得更糟,而您又不得不与使用它的人一起工作,请记住键盘快捷键以关闭代码概述:Ctrl+ M,Ctrl+P。别再抱怨了 有时候,我觉得这是另一种方式,那些希望被视为“真实”,“核心”程序员的人喜欢尝试证明自己。避开区域比避开语法着色更好。它并没有使您成为更强壮的开发人员。
所有这样说的,区域内的方法都只是纯属无稽之谈。每当您发现自己想要这样做时,都应该将其重构为单独的方法。没有理由。
首先,我再也受不了“代码气味”这个词了。它使用得太频繁了,并且大部分时间都被无法识别优质代码的人扔掉。无论如何...
我个人不喜欢使用很多地区。这使获取代码变得更加困难,而代码正是我感兴趣的代码。当我有很多不需要经常接触的代码时,我喜欢使用区域。除此之外,它们似乎妨碍了我,“私有方法”,“公共方法”等区域使我发疯。它们类似于各种评论i++ //increment i
。
我还要补充一点,区域的使用实际上不能成为“反模式”,因为该术语通常用于描述程序逻辑/设计模式,而不是文本编辑器的布局。这是主观的;使用对您有用的东西。由于您对区域的过度使用,您永远都不会以无法维护的程序告终,这就是反模式的全部意义所在。:)
是的,区域有代码气味!
我很高兴看到将区域从编译器中完全删除。每个开发人员都会想出自己的毫无意义的修饰方案,这种方案永远不会对其他程序员有价值。我与想要装饰和美化他们的婴儿的程序员有一切关系,与任何真正的价值无关。
您能想到一个示例,尽管您“哎呀,我希望我的同事在这里使用过某些区域!”?
即使我可以将IDE配置为自动扩展所有区域,但它们仍然令人感到烦恼,并且有损于阅读真实的代码。
如果我所有的公共方法都聚集在一起,我真的会在乎。恭喜,您知道变量声明和初始化之间的区别,无需在代码中显示它!
毫无价值的修饰!
另外,如果您的文件需求和使用区域的“信息体系结构”,您可能想解决核心问题:您的课程太大了!将其分解成更小的部分会更加有益,并且在正确完成后可以增加真正的语义/可读性。
我个人使用区域作为将各种类型的方法或部分代码组合在一起的一种方式。
因此,打开后的代码文件可能如下所示:
我没有将区域放在方法内部。恕我直言,这是代码气味的迹象。我曾经遇到过一种方法,该方法长1200行以上,其中有5个不同的区域。真是恐怖的景象!
如果您将其用作组织代码的方式,从而可以更快地为其他开发人员查找内容,那么我认为这并不意味着麻烦。如果您使用它来隐藏方法内部的代码行,我想是时候重新考虑该方法了。
这里的关键词是“明智的”。很难想象将区域放入方法内部是明智的情况。这很可能是代码隐藏和懒惰。但是,有充分的理由在自己的代码中到处都有一些区域。
如果有很多区域,我确实认为这是一种代码味道。区域通常暗示将来可能进行重构。很多地区意味着实际上没有人接受过提示。
明智地使用它们可以在具有多个方法的单个类的结构与每个仅包含几个方法的多个类的结构之间提供良好的中间立场。当一个类开始将其重构为多个类但还远远不够时,它们最有用。通过将相关方法分组在一起,如果以后数量继续增加,我以后可以轻松地将一组相关方法提取到自己的类中。例如,如果我有一个接近500行代码的类,那么在一个区域中使用总共200行代码收集在一起的一组方法可能是重构的好方法-而在另一个区域中有100行代码方法也可能是一个很好的目标。
我喜欢使用区域的另一种方法是减少重构大型方法的负面影响之一:许多小型,简洁,易于重用的方法,读者必须滚动浏览才能找到另一种几乎不相关的方法。区域可能是一种为读者提供元封装方法及其帮助程序的好方法,因此,使用类的其他方面的人员可以将它们折叠起来并迅速消除该部分代码。当然,这仅在您的区域组织得很好并且实际上被用作记录代码的另一种方式时才有效。
通常,与不使用区域相比,我发现区域可以帮助我保持井井有条,帮助“编写文档”我的代码,并帮助我更快地找到要重构的地方。
如果您在代码中包含区域,那么您肯定会遇到问题(除非生成代码)。将区域放入代码中基本上是在说“重构”。
但是,还有其他情况。我回想了一段时间:一个有几千个预先计算的项目的表。这是对几何的描述,没有表格中的错误,永远不会有机会查看它。当然,我可以从资源或类似资源中获取数据,但是这将排除使用编译器来帮助使其易于阅读的情况。
可以在质量好的代码中使用区域吗?大概。我敢打赌,在许多情况下,它们都是如此。但是,我的个人经历使我非常怀疑-我看到几乎完全被滥用的区域。我会说我很疲倦,但仍然很乐观。
我可以将迄今为止使用的区域代码大致分为三类:
分解系数差的代码:我见过的大多数代码都将区域用作穷人分解系数的工具。例如,一类已经发展到可以针对不同目的对其进行专门化处理的地步,可以改为将其分为不同的区域,每个目的一个区域。
针对问题域使用错误的库(有时甚至是错误的语言)编写的代码通常,当程序员针对问题域使用的库集不正确时,您会发现代码变得非常冗长-带有许多辅助功能确实不属于它们(它们可能属于自己的库)。
学生或应届毕业生编写的代码。某些程序和课程似乎试图通过各种奇怪目的将区域灌输给学生。您会看到区域将源代码乱七八糟,直到区域标签与代码行的比率在1:5或更差的范围内。
我会说这是“代码气味”。
反模式通常是软件中的基本结构性问题,而区域本身只会在编辑器中造成令人讨厌的行为。使用区域实际上并没有本质上的坏处,但是经常使用它们,尤其是隐藏代码块可以表明在其他地方还存在其他独立的较大问题。
我仅将区域用于一件事(至少我无法想到使用它们的其他地方):将单元测试分组为一种方法。
我通常每个类都有一个测试类,然后通过使用具有方法名称的区域将每个方法的单元测试分组。不知道那是代码的味道还是什么,但是由于基本思想是单元测试不需要更改,除非它们因为代码中的某些内容发生更改而中断,这使我更容易找到特定方法的所有测试很快。
我过去可能曾经使用区域来组织代码,但是我不记得上一次这样做了。不过,我坚持在单元测试课程中学习所在的地区。
我相信这是一种反模式,并且坦率地认为应该将其消除。但是,如果您处于在标准位置工作的不幸境地,Visual Studio会提供一个很棒的工具来最小化您每次看到一个区域时想要呕吐的数量。I Hate #Regions
这个插件将使该区域的字体大小很小。它们也会被扩展,因此您不必点击ctr + m + l即可打开所有区域。它不能解决这种形式的代码癌症,但是确实可以忍受。
我使用区域来包含可见性和成员类型的每种组合。因此,所有私有功能都进入一个区域,等等。
我这样做的原因不是,所以我可以折叠代码。这是因为我编写了编辑器脚本,因此可以插入对代理的引用:
#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等的区域。
这给了我粒度,但是我可以依靠的是一致的,明确的粒度。
区域是预处理器表达式-换句话说,它们被视为注释,基本上被编译器忽略。它们纯粹是Visual Studio中使用的可视工具。因此,#region并不是真正的代码味道,因为它不是代码。代码气味更确切地说是800行方法,其中嵌入了许多不同的职责。因此,如果您在一种方法中看到10个区域,则可能是用来隐藏代码气味。话虽如此,我已经看到它们非常有效地用于使一堂课更加悦目,更易于导航-也是在编写得井井有条的结构化课中!
区域是一个很漂亮的组织构想,但是没有考虑到某些开发人员倾向于对所有内容进行过度分类的趋势,按照大多数现代的OOP惯例,区域通常是不必要的...在某种意义上,它们是“气味”,使用它们通常表示您的类/方法太大,应该进行重构,因为您可能违反了SOLID原则的“ S” ...但是,就像任何气味一样,它不一定表示某些情况会变坏。
区域在功能代码中而不是在面向对象的代码IMO中具有更多的用途,在IMO中,有很长的顺序数据功能可以分解,但是有些时候我亲自在c#中使用了它们,并且它们几乎总是专注于您不需要/不想看的代码。对我来说,这些通常是用于NPoco或其变体的代码库中的原始SQL字符串常量。除非您真正关心数据如何通过您的ORM填充POCO对象,否则这些都是毫无意义的……如果您确实关心,请扩大区域和BAM!超过150行的复杂SQL查询为您带来观赏乐趣。