谈谈static和util/helper类

今天先聊聊static方法的那些事儿,然后再引出static使用的“极致” util/helper类,说说它们的问题。

static方法什么时候用?

简单说在面向对象的场景下,绝大多数情况都不应使用static方法。这里有一些关于static使用场景的讨论: When should I use static methods?

Java: when to use static methods

上面讨论总结一下可以归纳为两点:

  1. 逻辑上不和某个类实例绑定,如Singleton中的GetInstance()
  2. 方法比较轻量,不涉及类变量,最好是一个“纯函数”,调用之后没有副作用

其中第一点是最值得注意之处。

下面两个例子可以使用static方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static double Sigmoid(double x, double upper = 10.0, double lower = -10.0)
{
if (x >= upper)
{
return 0.999999;
}

if (x <= lower)
{
return 0.000001;
}

return 1.0 / (1.0 + Math.Exp(-x));
}

1
2
3
4
public static long UnixTimeStamp(DateTime time)
{
return (long)time.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
}

还有一些情况必须使用static方法的,比如C#中的extention method实现 Extension Methods (C# Programming Guide):

1
2
3
4
5
6
7
public static class ListShuffle
{
public static IList<T> Shuffle<T>(this IList<T> list)
{
return list.OrderBy(x => new Random().Next()).ToList();
}
}

不该用static方法

static方法一定程度上与面向对象的思想是对立的,设想一种极端情况,如果一个程序中全是static方法,那么它将退化成面向过程的程序,且很有可能组织不得当。比如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class QueryValidation
{
private static readonly HttpClient Client = new HttpClient();
private const string Url = "http://xxx/api";

public static async Task<bool> IsValidQueryAsync(string query)
{
var requestMessage = new HttpRequestMessage(HttpMethod.Post, Url)
{
Content = new StringContent(JsonConvert.SerializeObject(query))
};

try
{
using (var cancelTokenSource = new CancellationTokenSource(timeoutInMs))
{
var result = await Client.SendAsync(requestMessage, cancelTokenSource.Token).ConfigureAwait(false);
...
return true;
}
}
catch (Exception e)
{
...
return false;
}
}
}

IsValidQueryAsync()就不应使用static方法,首先逻辑上也看不出该方法应属于类的全体而非类的实例。其次方法不轻量,远程调用,且用到了静态类变量。

因此,不用static方法,将类名改为QueryValidator,其他类聚合了它并用QueryValidator.IsValidQueryAsync()方法来验证Query更为合理。

关于性能

99.999%以上的情况下可以忽略。Speed Test: Static vs Instance Methods,这里有前人做过测试,二者之间的性能差异微乎其微,不应作为是否static的首要考虑因素。

避免使用Util/Helper类

为什么聊static方法会谈到Util/Helper类呢?因为曾在代码库见过一些所谓Util,把各种很复杂的方法都堆在里面,都写为static方法“方便”调用。一个类堆了上千行,里面什么东西都有。

这种Util美其名曰“代码复用”,实际上是偷懒,没有仔细思考类之间的逻辑抽象。面向对象的代码复用有许多方式,继承、聚合都可以,用static方法复用的方式最省脑子,但上千行的Util基本可以判断为anti-pattern。

这么做的思想很朴素:A和B需要复用一段代码,于是就将共用的代码变成static放在公用的Util类中,然后C和D又要利用一段代码,算了,不再新建一个类了,也static后放在公用的Util类中。再看编译器报错了,在static方法中使用了非static的变量,得,把这变量也static了……

这种Util有许多问题,第一,多个类调用Util类中的静态方法,那么这些类都增加了额外依赖,与面向对象希望达到的解耦目的不符。第二,Util类完全不内聚,代码垃圾场,违反了SOLID原则中的单一职责原则。

我个人认为Util Class并非绝对禁用,只是大部分情况下使用者没有想清楚为什么要用,是否有更优雅的替代方案。还有一个问题就是命名,如果整个项目中只有一个Util类,即使里面所有函数都是static纯函数,那它也不符合单一职责原则。解决方案:1. 将这些方法放进使用它的类中 2. 将方法分类,分拆成更内聚的具体类并起一个合适的名称,比如DateTimeConverter之类。

这里也有一些关于Util Class的讨论: Avoid Utility Classes

Anti-Patterns and Worst Practices – Utils Class

结论

在面向对象程序中尽量不用static。当不确定是否要用时,不用。