不要在C#中使用“静态”?


109

我提交了我写给其他一些架构师的应用程序,以供代码审查。其中一个几乎立即写信给我,说:“不要使用“静态”。您不能使用静态类和方法编写自动测试。要避免使用“静态”。

我检查了一下,并将全部1/4的班级标记为“静态”。当我不打算创建类的实例时,我会使用static,因为该类是整个代码中使用的单个全局类。

他继续提到了涉及模拟,IOC / DI技术的一些静态代码无法使用的技术。他说,由于第三方库不可测试,当第三方库为静态库时很不幸。

这个建筑师是正确的吗?

更新:这是一个示例:

APIManager-此类保留我正在调用的第三方API的字典以及下一个允许的时间。它强制执行许多第三方在其服务条款中规定的API使用限制。我可以通过调用Thread.Sleep(APIManager.GetWait(“ ProviderXYZ”))在调用第三方服务的任何地方使用它;在拨打电话之前。这里的所有内容都是线程安全的,并且与C#中的TPL一起使用时效果很好。


37
static很好 static 田地需要非常小心地对待
Marc Gravell

2
他为什么要为第三方库编写测试?您不是通常只是假设第3方库的创建者完成了测试之后就对您自己的代码进行测试吗?
2012年

3
那个异议对我来说太笼统了。在大多数情况下,您不会移动静态类,而是嘲笑参数。静态字段,它是需要模拟的聚合类,然后是。

8
显然,您的同事病情很重-急性全身性OOPus erythematosus。他们需要尽快接受治疗!
SK-logic

6
有一个提供答案的答案部分,我不明白为什么人们要试图给某人一个答案/建议发表评论……这违背了StackExchange的目的,肯定会要求提供更多信息。
peteski 2012年

Answers:


121

这取决于静态类是否保持状态。就我个人而言,在静态类中将无状态函数组合在一起我没有问题。


111
正确。没有副作用的静态函数是所有函数中可测试性最高的函数。
罗伯特·哈维

9
+1,但带有安全条款:几乎每个抽象算法都是无状态的,但这并不意味着您应该将其实现为无状态的静态类或方法。以这种方式实现它会使使用它的所有代码几乎都无法链接到它。例如,这意味着如果您将排序算法实现为静态方法并在项目中使用它-您将无法暂时将排序替换为另一种算法,例如用于测试目的和缩小问题范围。在许多情况下,这是一个巨大的障碍。除此之外,如果合理使用static完全可以。

11
简而言之:它们是最可测试的函数,但不一定使它们使其他代码更可测试!这就是建筑师可能的意思。

4
@tereško:如果您不想创建类的实例来调用该方法,则C#语言要求将静态方法作为静态类的一部分。也许您的意思是“实例”,而不是“阶级”。
罗伯特·哈维

8
@quetzalcoatl:将委托(即不同的算法)传递给静态方法是完全合法的;Linq中的扩展方法都是静态方法。例如:msdn.microsoft.com/en-us/library/…–
罗伯特·哈维

93

他对此太笼统了。他是正确的,这阻碍了测试。但是,static类和方法有其位置,它实际上取决于上下文。没有代码示例,您将无法真正分辨。

当我不打算创建类的实例时,我会使用static,因为该类是整个代码中使用的单个全局类。

这可能是严重的代码异味。您将这些课程用于什么?要保存数据?要访问数据库?那他是正确的。在这种情况下,您应该考虑依赖注入,因为这样的静态类实际上是隐式单例。

如果将它们用于扩展方法或辅助程序,而这些方法或辅助程序不更改状态,而仅对提供的​​参数进行操作,则通常可以。


3
+1表示该声明是针对常规的,并提到了扩展方法,据我所知,扩展方法也可以进行测试,并且仍然可以模拟依赖项。
2012年

4
埃静态单类的实例作为一个隐含的单,那将变得混乱....

“要访问数据库?” 为什么不 ?如果表适配器不是静态的,则创建一个静态类@强类型数据集,它没有任何问题。...除非您通过引用或示例证明自己的观点?
数学

27

我检查了一下,并将全部1/4的班级标记为“静态”。当我不打算创建类的实例时,我会使用static,因为该类是整个代码中使用的单个全局类。

最好的办法是尝试对代码进行单元测试。尝试设计可重复,独立,简单的测试,一次只测试一种方法。尝试以不同的随机顺序运行测试。您可以获得稳定的“绿色”建筑吗?

如果是,那是保护您的代码的正确点。但是,如果遇到困难,那么也许应该使用基于实例的方法。


7
这也是让我印象深刻的一点。可以使用一些静态和无状态的“帮助程序”样式类,但是如果您将全部类的25%当作静态类,那么您的工作就不可能仅限于这种情况。
马修·沙利

2
@MatthewScharley-他可能有4个课程,其中1个是Static :)
Alexus

1
请注意,如果您使用这种固定器(例如单例),那么以后创建更多实例可能会很困难。就像制作一个处理文档的应用程序一样,以后要管理多个文档时也会遇到问题。
Michel Keijzers'8

11

已经发布的答案涵盖了很多非常好的要点,但是似乎缺少了一个要点:

静态字段永远不会被垃圾收集。

如果您的应用程序具有很多内存限制,那么这非常重要,当人们尝试实现缓存时,这种模式可能很常见。

静态函数几乎没有那么糟糕,但是其他人已经足够详细地介绍了这一点。


10

从IoC / DI获得的优点之一是,类之间的大多数交互都是在接口之间协商的。这使单元测试变得容易,因为可以自动或半自动模拟接口,因此可以测试每个部分的输入和输出。此外,除了可验证性之外,在所有内容之间放置接口还可以使您拥有尽可能模块化的代码-您可以轻松地替换一个实现,而不必担心担心搞砸了依赖性。

因为C#没有元类,所以类不能使用静态功能实现接口,因此类的静态功能最终会花费很多精力来实现纯IoC / DI对象模型。就是说,您不能创建一个模拟来测试它们,而必须创建真正的依赖关系。

如果您的公司/项目在进行IoC方面投入了大量资金,那么这当然是一个合理的问题,而您正在经历的痛苦就是为了所有人的利益。但是,从体系结构的角度来看,我个人不认为应该遵循任何模型。使用静态方法可以实现一些有意义的事情,例如单例设计策略。我本人不太喜欢静态类,因为我认为它们倾向于开始失去OO的优点,但是有时候,库类可能比引擎更自然地表达某种东西。


Commenter让我想起了扩展方法,这些方法当然必须在C#中的静态类中。这是合法的,并且是一个示例,说明纯IoC如何无法在C#中很好地工作,至少在您尝试利用该语言的广度的情况下。


1
如果出于正确的原因将一个类设为静态并正确实现(例如扩展方法),则它们仍然非常可测试,因为它们没有外部依赖性,并且是自包含的,因此不需要模拟。接口需求不是由设计需求驱动的,而不是为了保持纯IoC / DI项目而最好地满足业务需求吗?
2012年

1
您和我在总体观点上都同意,您应该为正确的工作选择正确的工具,而不受哲学的束缚。但是,如果在您的纯IoC / DI项目中建立了良好的文化,那么与将传统的自上而下的解决方案转换为IoC / DI一样,这将带来很大的伤害。工程需要考虑过渡成本。顺便说一句,我认为扩展方法并不能帮助您实现目标。我认为使用静态类的IoCish行为的更好解决方案是在编译时使用C样式的预处理程序指令在多个类之间进行选择
jwrush 2012年

1
呃。我很笨 您的意思是用于HOLDING扩展方法的静态类,当然,这是您必须使用的方法。是的,当然,您需要一个静态类用于此目的。这让我无所适从:这表明C#不适用于IoC。:)
jwrush 2012年

是的,没错,如果现有项目中存在一种现有文化,那将比帮助改变事情的进行方式更具破坏性。
2012年

5

静态类有时会被滥用,应该是:

  • 一个单例(非静态类具有一个静态成员和一个公共静态Instance变量,仅可检索/创建一次)。
  • 另一个类中的方法(状态很重要)。如果您有一个成员,该成员不使用该类中的数据,则该成员可能不应该属于该类。

它也可能是所谓的“实用程序”功能,并且是实用程序类的一部分。实用程序类是仅包含静态方法的类,可充当没有任何上下文的辅助函数。


@topomorto因为该类不应通过其构造函数实例化而导致多个对象/实例,而应通过特殊方法(通常为instance())<实例化,该方法始终返回相同的实例,该实例在首次调用instance()之后实例化。
Michel Keijzers '16

@topomorto我刚刚检查了一下,您完全正确,只有一些成员是静态的(实例变量和实例函数)。我将相应地更改答案;感谢您的提及。
Michel Keijzers '16

4

静态方法很好用,并且在编程中应有的地位。听起来您的架构师拥有一个不完全支持静态方法的测试框架。如果您的代码是更大项目的一部分,那么满足架构师指南很重要。但是,在适当的时候,不要让该项目阻止您使用静态方法。


1

我一直在使用静态属性来处理类的所有实例所共有的内容。而且我一直在使用静态方法来获取类对象组。我绝不是专家,但是到目前为止,这对我一直有效。

PHP示例:

class vendorData {
  private static $data_table = 'vendor_data'; // static property
  private $id; // instance property
  private $name; // instance property

  public function __construct($vendor_id) {
    if(!self::validId($vendor_id) ) { // static method
      return false; // id doesn't exist
    }
    $this->id = $vendor_id; // id has been validated
    $this->getProperties(); // object method
  }

  private static function validId($vendor_id) {
    // send db query to find if the id exists
    // return true/false;
  }

  private function getProperties() { // object method
    $sql = "SELECT * FROM `{self::$data_table}` // using static property
        WHERE `id` = {$this->id}"; // using object instance property
    // get resultset
    foreach($result as $property => $value) {
      $this->$property = $value; // object instance properties all set
    }
  }

  // and here
  public static function getBy($property,$value) { // static method to return object array
    $sql = "SELECT `id` FROM `{self::$data_table}` // using static property
      WHERE `$property` = '$value'";
    // send query, get $ids array
    $obj_array = array();
    foreach($ids as $id) {
      // create array of current class objects using special static keyword
      // meaning of static here is different than in property/method declarations
      $obj_array[$id] = new static($id);
    }
    return $obj_array;
  }
}

1

我会说他们有原则,但是声明(不要使用静态)可能是错误的。您的代码覆盖率是多少?如果数量很大,并且您对应用程序的单元测试感到满意,那么您就可以了。如果没有,那么我建议复查代码。您可能会发现静态类是否是原因,这取决于很多事情。


-3

具有静态方法的类滥用OOP的原理。它还不是面向对象编程,而是COP-面向类的编程。

他们很少有明确的“个性”(我在这里用我最喜欢的人类比喻)或身份。所以他们不知道自己是谁。仅此一点就足以摆脱OOP中的这一概念,但是还有更多。

下一个是第一点的结果。由于此类人不知道自己是谁,所以他们往往从大到大。

第三,使您的代码绝对不可维护。静态方法的使用引入了隐藏的依赖关系。它们在整个地方弹出,您永远都不知道课堂上正在发生什么。您不能仅仅通过查看构造函数签名来确保这一点。

慢慢地,但不可避免地,由于对隐藏的依赖关系的控制不力,您的代码变得越来越缺乏凝聚力。他们似乎是看不见的。添加另一个很容易!您不必修改任何方法的签名。您要做的就是调用静态方法。然后是一个丑陋的代码...

好吧,您可能已经猜到了。紧密耦合经常导致低内聚。如果有很多客户在使用某个类,那么耦合会越来越紧密。使用已经编写好的只需要很小的修改的类或方法就会引起很大的诱惑。仅一种方法标志参数。

用什么代替静态方法?好吧,我的建议是OOP。为什么不尝试一下呢?

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.