开发手记 · 第 5 / 5 篇 合集目录 ‹ 上一篇

翻一个老工程的时候,我撞见一组看着特别正常的代码。一个方法收下一个选项,选项一共两个,写在一张单子上,名字也不含糊。方法里按选中的值分成两条路,各造一个对象去干同一件事。整段读下来挑不出毛病,注释写着,缩进规矩,命名比周围都整齐。

public enum SamplingMode
{
    Interpolate = 0,      // 线性插值
    Nearest     = 1       // 就近取值
}

单看这一段,这是个好习惯。留两条路很常见,写的时候你不知道将来用哪一条,或者明知现在只用得上一条,还是先把另一条写出来,省得以后换的时候回头改。留一条路空着,和留一条路能用但一直在睡觉,是两件不同的事。

我想看看平时大家传的是哪个值,就去搜这个方法的调用点。搜完整棵树,一共两个地方在调用它,两处传的都是同一个值。另一个值,从这段代码存在的那天起就没被人传过。

// 全仓只有这两处调用,两处都传 Nearest
CurveSampler.Calculate(SamplingMode.Nearest, input);
CurveSampler.Calculate(SamplingMode.Nearest, input);

// SamplingMode.Interpolate:调用点 0 处

到这里还算正常。我差点就往下翻了,顺手多看了那个没人走的分支一眼。

留着两条路,但只有一条能走 一个选项 SamplingMode InterpolateStrategy 算好了,最后没有返回它 NearestStrategy 生产上跑的就是这条

调用点 0 处 从存在的第一天起 调用点 2 处 两处传的都是它

虚线那条更麻烦:就算哪天有人把选项拨过去,它也交不出活。

那个分支自己也有毛病。它把该算的东西一步一步算出来了,一个个放进一个临时的容器里,然后最后一步没有用它,返回了原来那个值。

public override IList<float> ResolveValue(long position, ISampleRow currentRow, ISampleRow? nextRow)
{
    if (currentRow == null) return SamplerConsts.Empty;
    if (currentRow.Samples.IsPlaceholder()) return SamplerConsts.Null;
    if (nextRow == null) return currentRow.Samples;

    var x1 = currentRow.Position;
    var x2 = nextRow.Position;
    var values = new List<float> { };
    var channelCount = GetChannelCount();

    for (int i = 0; i < channelCount; i++)
    {
        var y1 = currentRow.Samples[i];
        var y2 = nextRow.Samples[i];
        values.Add(Interpolate(position, x1, y1, x2, y2));   // 逐点算好,装进 values
    }

    return currentRow.Samples;    //                        ← 交出去的却是原来那一份
}

我没法确定它是怎么变成这样的,也不替当年的人编理由。能确定的是,机器不会拦这种事。算出来的东西装进临时容器,方法最后返回了另一个变量,两边还完全对得上,编译一路绿灯。

留后路这个习惯我不打算改,它救过我。麻烦在另一件事上,这条路到底该往哪边走,代码里没有任何一个地方写过。没有注释,没有日志,翻遍整个工程也找不到一句话说现在走的是哪一条。想知道生产上实际跑的是哪条,只有一个办法,去读调用点;反过来,谁哪天把那个值改掉,也不会有人提醒他这条路其实是空的。

后来我在别的地方也见过类似的东西,一份躺在那儿没人读的配置,一个永远返回空结果的入口,一套写在文档里但从没验证的备选方案。留下来的那一刻都理直气壮,之后就再没人认领它。现在我会多问一句,这个我留下来的东西,是谁在用。答不上来的,要么补上让它真能用,要么删掉。