C#模式可以清晰地处理“自由功能”,避免使用Helper风格的“实用程序包”静态类


9

我最近正在查看一些浮动的Helper风格的“实用程序包”静态类,这些静态类围绕我使用的一些大型C#代码库进行浮动,基本上类似于以下非常简短的代码段:

// Helpers.cs

public static class Helpers
{
    public static void DoSomething() {}
    public static void DoSomethingElse() {}
}

我审查过的具体方法是

  • 彼此之间几乎没有关系
  • 没有明确的状态在调用之间持续存在,
  • 小,和
  • 每一种都由各种不相关的类型消耗。

编辑:上面的内容并非旨在列出所指控的问题。这是我正在审查的特定方法的共同特征的列表。帮助答案提供更多相关解决方案的上下文。

仅针对此问题,我将把这种方法称为GLUM(通用轻量级实用程序方法)。部分地意在“消极”的负面含义。抱歉,这是愚蠢的双关语。

即使撇开我自己对GLUM的默认怀疑态度,我也不喜欢以下内容:

  • 静态类仅用作命名空间。
  • 静态类标识符基本上是没有意义的。
  • 添加新的GLUM时,要么(a)毫无理由地触摸此“ bag”类,要么(b)创建一个新的“ bag”类(这本身通常不成问题;糟糕的是,新的静态类通常只会重复不相关性问题,但方法更少。
  • 元的命名无疑也是可怕的,非标准的,通常内部不一致,无论是HelpersUtilities或什么的。

什么是重构的合理好简单的模式,最好解决上述问题,并且最好轻触一下?

我可能应该强调:我正在处理的所有方法都是成对的彼此无关。似乎没有一种合理的方法可以将它们分解为更细粒度但仍为多成员的静态类方法包。


1
关于GLUMs™:我认为可以删除“轻量级”的缩写,因为通过遵循最佳做法可以使方法轻便;)
Fabio

@Fabio我同意。我把这个词作为一个(可能并不十分有趣,可能令人困惑)的玩笑,作为该问题写作的缩写。我认为“轻量级”在这里似乎是适当的,因为所讨论的代码库是长期存在的,遗留的并且有点混乱,即碰巧还有许多其他“重量级”方法(通常不是这些GUM;-通常如此)。如果目前我有更多(预算,任何预算),我肯定会采取更积极的方法来解决。
威廉

Answers:


17

我认为您有两个彼此密切相关的问题。

  • 静态类包含逻辑上不相关的功能
  • 静态类的名称不提供有关所包含函数的含义/上下文的有用信息。

通过仅将逻辑相关的方法组合到一个静态类中,并将其他方法移动到其自己的静态类中,可以解决这些问题。根据您的问题领域,您和您的团队应该决定将什么方法用于将方法分为相关的静态类的标准。

问题和可能的解决方案

方法大多彼此无关 - 问题。 将一个静态类下的相关方法组合在一起,并将不相关的方法移到它们自己的静态类中。

方法没有在调用之间持久保留的显式状态 -这不是问题。 无状态方法是功能,而不是错误。无论如何,静态状态都很难测试,并且仅在需要在方法调用之间维护状态时才应使用静态状态,但是有更好的方法来做到这一点,例如状态机和yield关键字。

方法很小 - 没问题。-方法应该小。

每个方法都由各种不相关的类型使用 -这不是问题。您已经创建了一种通用方法,可以在许多不相关的地方重复使用该方法。

静态类仅用作命名空间 - 没问题。这就是使用静态方法的方式。如果这种调用方法的样式困扰您,请尝试使用扩展方法或using static声明。

静态类标识符基本上是没有意义的 - 问题。这是没有意义的,因为开发人员正在将不相关的方法放入其中。将不相关的方法放入其自己的静态类中,并为其指定特定且易于理解的名称。

当添加新的GLUM时,要么(a)触及了这个“袋子”类(SRP悲伤),如果您遵循一个逻辑类中应该只包含逻辑相关方法的想法,这不是问题SRP这里不违反,因为原则应适用于类或方法。静态类的单一职责意味着为一个一般的“想法”包含不同的方法。这个想法可以是功能,转换,迭代(LINQ),数据规范化或验证...

或更不频繁地(b)创建一个新的“袋子”类(更多袋子) -这不是问题。 类/静态类/函数-是我们的(开发人员)工具,用于设计软件。您的团队在使用课程方面有一些限制吗?“更多行李”的最大限额是多少?编程全都与上下文有关,如果要在您的特定上下文中获得解决方案,您最终会得到200个静态类,这些类可以理解地命名,并以逻辑层次结构逻辑地放置在名称空间/文件夹中-这不是问题。

元的命名无疑也是可怕的,非标准的,通常内部不一致,无论是助手,公用设施,或任何 - 问题。 提供描述预期行为的更好的名称。仅将相关的方法放在一个类中,这有助于更好地命名。


这不是违反SRP,但很明显是违反开放式/封闭式原则。就是说,我通常认为Ocp麻烦多于其应有的价值
Paul

2
@Paul,打开/关闭原则不能应用于静态类。我的观点是,SRP或OCP原则不能应用于静态类。您可以将其应用于静态方法。
法比奥

好的,这里有些混乱。问题的第一个项目列表不是所谓的问题列表。这是我正在审查的特定方法的共同特征的列表。帮助答案提供更适用解决方案的上下文。我将进行澄清。
威廉

1
关于“静态类作为名称空间”,它是一种常见模式并不意味着它不是反模式。在这种情况下,此特定的低内聚静态类中的方法(基本上是从C / C ++借用术语的“自由函数”)是有意义的实体。在此特定上下文中,在子命名空间中A包含100个单方法静态类(在100个文件中)似乎比A在单个文件中具有100个方法的静态类在结构上更好。
威廉

1
我对您的答案做了一些相当大的清理,主要是整容。我不确定你的最后一段是关于什么的;没有人担心他们如何测试调用Math静态类的代码。
罗伯特·哈维

5

只需遵循这样的约定:在C#中,静态类就像名称空间一样使用。命名空间获得好名,这些类也应获得好名。using staticC#6功能也使生活更加轻松。

诸如此类的臭名昭著的类Helpers信号表明,如果可能,应将这些方法拆分为命名更好的静态类,但是您强调的假设之一是,您要处理的方法是“成对无关的”,这意味着拆分为每个现有方法一个新的静态类。如果可能的话,那可能很好

  • 新的静态类有好名字
  • 您认为它实际上使您的项目的可维护性受益。

作为一个人为的示例,Helpers.LoadImage可能会变成FileSystemInteractions.LoadImage

不过,您最终可能会遇到静态类方法包。发生这种情况的一些方法:

  • 也许只有一些(或没有)原始方法为其新类命名。
  • 也许以后新类会演变为包含其他相关方法。
  • 也许原来的班级名称没有臭味,实际上还可以。

重要的是要记住,这些静态类方法包在真正的C#代码库中并不少见。拥有几个小孩子可能不是病态


如果您真的在每个文件中都拥有类似“免费功能”的方法(如果您诚实地认为它确实有益于项目的可维护性,那么这很好并且值得),则可以考虑使用这样的静态类而不是静态类部分类,采用类似于要使用名称空间的静态类名称,然后通过进行使用using static。例如:

使用此模式的虚拟项目的结构

Program.cs

namespace ConsoleApp1
{
    using static Foo;
    using static Flob;

    class Program
    {
        static void Main(string[] args)
        {
            Bar();
            Baz();
            Wibble();
            Wobble();
        }
    }
}

Foo / Bar.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Bar() { }
    }
}

Foo / Baz.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Baz() { }
    }
}

Flob / Wibble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wibble() { }
    }
}

Flob / Wobble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wobble() { }
    }
}

-6

通常,您不应该拥有或不需要任何“通用实用程序方法”。在您的业务逻辑中。再看一眼,放在合适的地方。

如果您正在编写数学库之类的东西,那么尽管我通常讨厌它们,我还是建议您使用扩展方法。

您可以使用扩展方法将功能添加到标准数据类型上。

例如,假设我有一个通用函数MySort()而不是Helpers.MySort或添加了一个新的ListOfMyThings.MySort我可以将其添加到IEnumerable


关键是不要再有“硬汉”。关键是要有一个可重用的模式,该模式可以轻松清除实用程序的“包”。我知道公用设施有异味。问题说明了这一点。现在的目标是明智地隔离这些独立的(但过于紧密放置)域实体并在项目预算中尽可能地简化。
威廉

1
如果您没有 “通用实用程序方法”,则可能无法将逻辑部分与其余逻辑充分隔离。例如,据我所知,.NET中没有通用的URL路径连接功能,因此我经常写一个。作为标准库的一部分,这种处理方式会更好,但是每个标准库都缺少一些东西。另一种选择是将逻辑直接在您的其他代码中重复进行,从而大大降低了可读性。
jpmc26


1
@Ewan不是函数,它是通用委托签名...
TheCatWhisperer

1
@Ewan这就是TheCatWhisperer的全部要点:实用程序类使人们不得不将方法放入类中。很高兴您同意。
jpmc26
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.