Erlang的万圣节👻( Dialyzer 无法找到的dead code)

像我这样谈论 Dialyzer 这样出色的工具的缺点或局限性不太寻常。 尽管如此,今天我还是发现了一些非常死板的代码块,它们通过了Dialyzer的类型分析。

所以,既然万圣节快到了,让我们从它的坟墓里拿出一些死代码吧!

Dialyzer 产生警告的时候

在以前我见过这样的代码……

warn(X) ->
    {just, X};
warn(6) ->
    {six, 6}.

可以触发如下所示的编译器和 Dialyzer 的警告……

===> Compiling my_app
src/el.erl:8: Warning: this clause for warn/1 cannot match because a previous clause at line 6 always matches
===> Dialyzer starting, this may take a while...
src/el.erl
Line 8: The pattern 6 can never match since previous clauses completely covered the type any()

非常好! 该函数中的第二个子句显然是死代码(即,它永远不会被运行),因为 6 匹配 X,因此当作为输入提供时会通过第一个子句进行匹配。 这是我最喜欢的Dialyzer警告之一。 它清晰且可操作。 它提供了包含行号和其他内容的适当上下文。 这非常棒。

Dialyzer 不产生警告时的时候

但是今天我发现了一段非常非常相似的死代码(dead code),然而Dialyzer 和 Compiler 都对它完全保持沉默。 让我演示给你看…

warn(X, X) ->
    {just, X};
warn(6, 6) ->
    {six, 6}.

你看出区别了吗? 让我再给你看一个例子……

warn(X, Y) ->
    {just, X and Y};
warn(6, 6) ->
    {six, 6}.

这确实会产生预期的警告……

===> Compiling my_app
src/el.erl:8: Warning: this clause for warn/2 cannot match because a previous clause at line 6 always matches
===> Dialyzer starting, this may take a while...
src/el.erl
Line 8: The pattern <6, 6> can never match since previous clauses completely covered the type <_,_>

这到底是怎么回事呢?

嗯……似乎不是每个无法匹配的子句都可以被编译器或 Dialyzer 检测到。 可悲的是,那些包含有界变量(如 warn/2 中的第二个 X)或守卫或任何其他语义结构的变量超出了这些检测工具的范围。

所以,如果你有这样的功能,编译器和 Dialyzer 就不会检测到你的死代码,它最终可能会像幽灵一样生活很长很长一段时间。

好吧,在那种情况下,你会呼叫谁呢?

当然是测试覆盖率(Test Coverage)!

如果您制定了良好的测试覆盖率政策(也许像Inakos有我们要求 100% 覆盖所有地方的地方,也许是稍差的一个),你应该立即看到没有测试可以覆盖这些条目。 然后,您可以删除它们。

奖励

在撰写本文时,我检查了其他几个场景,看看哪些是可检测的,哪些是不可检测的。 您可以在下面看到我编写的模块…

-module(el).

-export([warn/2, warn_case/1]).
-export([clean/2, clean_case/1]).

warn(X, Y) ->
{just, X and Y};
warn(6, 6) ->
{six, 6}.

warn_case(X) ->
{_, X} =
if X == 6 ->
{six, 6};
is_integer(X) andalso X == 6 ->
{also_six, 6};
true ->
{other, X};
true ->
{yet_another, X}
end,
case X of
_ ->
{just, X};
6 ->
{six, 6};
X ->
{other, X}
end.

clean_case(X) ->
case X of
X ->
{just, X};
X when X == 6 ->
{six, 6};
6 ->
{still_six, 6};
X when X == 6 ->
{six_again, 6};
X ->
{other, X}
end.

clean(V, V) ->
{same, V};
clean(X, Y) when X == Y ->
{also_same, X};
clean(6, 6) ->
{six, 6};
clean(X, Y) ->
case {X, Y} of
{V, V} ->
{same, V};
{X, Y} when X == Y ->
{also_same, X};
{6, 6} ->
{six, 6};
{X, Y} ->
{other, X, Y}
end.

这些是我得到的唯一警告……

===> Analyzing applications...
===> Compiling lapp
src/el.erl:8: Warning: this clause for warn/2 cannot match because a previous clause at line 6 always matches
src/el.erl:19: Warning: this clause cannot match because a previous clause at line 17 always matches
src/el.erl:25: Warning: this clause cannot match because a previous clause at line 23 always matches
src/el.erl:27: Warning: this clause cannot match because a previous clause at line 23 always matches
===> Dialyzer starting, this may take a while...
src/el.erl
Line 8: The pattern <6, 6> can never match since previous clauses completely covered the type <_,_>
Line 19: The pattern <> can never match since previous clauses completely covered the type <>
Line 25: The pattern 6 can never match since previous clauses completely covered the type any()
Line 27: The variable _ can never match since previous clauses completely covered the type any()

如果您发现更多值得一提的场景,请在下面写下关于它们的评论。

Related Posts

2021 年你需要知道的关于 Erlang 的一切

今天,我们将看一个相当古老且有些古怪的东西。 你们大多数人可能没有注意到的语言。 虽然 Erlang 不像某些现代编程语言那样流行,但它安静地运行着 WhatsApp 和微信等每天为大量用户提供服务的应用程序。 在这篇文章中,我将告诉你关于这门语言的更多事情、它的历史,以及你是否应该考虑自己学习它。 ## 什么是 Erlang,它在哪里使用? Erl

Read More

Erlang JIT中基于类型的优化

这篇文章探讨了 Erlang/OTP 25 中基于类型的新优化,其中编译器将类型信息嵌入到 BEAM 文件中,以帮助JIT(即时编译器)生成更好的代码。 ## 两全其美 OTP 22 中引入的基于SSA的编译器处理步骤进行了复杂的类型分析,允许进行更多优化和更好的生成代码。然而,Erlang 编译器可以做什么样的优化是有限制的,因为 BEAM 文件必须

Read More

Erlang JIT之路

自从Erlang 存在,就一直有让它更快的需求和野心。这篇博文是一堂历史课,概述了主要的 Erlang 实现以及如何尝试提高 Erlang 的性能。 ## Prolog 解释器 Erlang 的第一个版本是在 1986 年在 Prolog 中实现的。那个版本的 Erlang 对于创建真正的应用程序来说太慢了,但它对于找出Erlang语言的哪些功能有用,哪

Read More