给定
有一个IUnit接口。
单元
public interface IUnit
{
string Description { get; }
}
而其实现片和集群。
片
public class Piece : IUnit
{
public string Description { get; }
public Piece(string description) =>
Description = description;
public override bool Equals(object obj) =>
Equals(obj as Piece);
public bool Equals(Piece piece) =>
piece != null &&
piece.Description.Equals(Description);
public override int GetHashCode()
{
unchecked
{
var hash = 17;
foreach (var c in Description)
hash = 23 * hash + c.GetHashCode();
return hash;
}
}
}
簇
public class Cluster : IUnit
{
private readonly IReadOnlyList<Piece> pieces;
public IEnumerable<Piece> Pieces => pieces;
public string Description { get; }
public Cluster(IEnumerable<Piece> pieces)
{
if (!pieces.Any())
throw new ArgumentException();
if (pieces.Select(unit => unit.Description).Distinct().Count() > 1)
throw new ArgumentException();
this.pieces = pieces.ToArray();
Description = this.pieces[0].Description;
}
public Cluster(IEnumerable<Cluster> clusters)
: this(clusters.SelectMany(cluster => cluster.Pieces))
{
}
public override bool Equals(object obj) =>
Equals(obj as Cluster);
public bool Equals(Cluster cluster) =>
cluster != null &&
cluster.Description.Equals(Description) &&
cluster.pieces.Count == pieces.Count;
public override int GetHashCode()
{
unchecked
{
var hash = 17;
foreach (var c in Description)
hash = 23 * hash + c.GetHashCode();
hash = 23 * hash + pieces.Count.GetHashCode();
return hash;
}
}
}
还有一个MergeClusters类,用于处理IUnit集合并将兼容的Cluster序列合并为一个元素。该类的行为已通过测试验证。
合并集群
public class MergeClusters
{
private readonly List<Cluster> buffer = new List<Cluster>();
private List<IUnit> merged;
private readonly IReadOnlyList<IUnit> units;
public IEnumerable<IUnit> Result
{
get
{
if (merged != null)
return merged;
merged = new List<IUnit>();
Merge();
return merged;
}
}
public MergeClusters(IEnumerable<IUnit> units)
{
this.units = units.ToArray();
}
private void Merge()
{
Seed();
for (var i = 1; i < units.Count; i++)
MergeNeighbors(units[i - 1], units[i]);
Flush();
}
private void Seed()
{
if (units[0] is Cluster)
buffer.Add((Cluster)units[0]);
else
merged.Add(units[0]);
}
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (prev is Cluster)
{
if (next is Cluster)
{
if (!prev.Description.Equals(next.Description))
{
Flush();
}
buffer.Add((Cluster)next);
}
else
{
Flush();
merged.Add(next);
}
}
else
{
if (next is Cluster)
{
buffer.Add((Cluster)next);
}
else
{
merged.Add(next);
}
}
}
private void Flush()
{
if (!buffer.Any())
return;
merged.Add(new Cluster(buffer));
buffer.Clear();
}
}
合并集群测试
[Fact]
public void Result_WhenUnitsStartWithNonclusterAndEndWithCluster_IsCorrect()
{
// Arrange
IUnit[] units = new IUnit[]
{
new Piece("some description"),
new Piece("some description"),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
};
MergeClusters sut = new MergeClusters(units);
// Act
IEnumerable<IUnit> actual = sut.Result;
// Assert
IUnit[] expected = new IUnit[]
{
new Piece("some description"),
new Piece("some description"),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
};
actual.Should().BeEquivalentTo(expected);
}
[Fact]
public void Result_WhenUnitsStartWithClusterAndEndWithCluster_IsCorrect()
{
// Arrange
IUnit[] units = new IUnit[]
{
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
};
MergeClusters sut = new MergeClusters(units);
// Act
IEnumerable<IUnit> actual = sut.Result;
// Assert
IUnit[] expected = new IUnit[]
{
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
};
actual.Should().BeEquivalentTo(expected);
}
[Fact]
public void Result_WhenUnitsStartWithClusterAndEndWithNoncluster_IsCorrect()
{
// Arrange
IUnit[] units = new IUnit[]
{
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
};
MergeClusters sut = new MergeClusters(units);
// Act
IEnumerable<IUnit> actual = sut.Result;
// Assert
IUnit[] expected = new IUnit[]
{
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
};
actual.Should().BeEquivalentTo(expected);
}
[Fact]
public void Result_WhenUnitsStartWithNonclusterAndEndWithNoncluster_IsCorrect()
{
// Arrange
IUnit[] units = new IUnit[]
{
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
};
MergeClusters sut = new MergeClusters(units);
// Act
IEnumerable<IUnit> actual = sut.Result;
// Assert
IUnit[] expected = new IUnit[]
{
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("some description"),
new Piece("some description"),
new Piece("some description"),
new Piece("some description"),
}),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
new Cluster(
new Piece[]
{
new Piece("another description"),
new Piece("another description"),
new Piece("another description"),
new Piece("another description"),
}),
new Piece("another description"),
};
actual.Should().BeEquivalentTo(expected);
}
我们对方法void MergeNeighbors(IUnit,IUnit)类MergeClusters感兴趣。
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (prev is Cluster)
{
if (next is Cluster)
{
if (!prev.Description.Equals(next.Description))
{
Flush();
}
buffer.Add((Cluster)next);
}
else
{
Flush();
merged.Add(next);
}
}
else
{
if (next is Cluster)
{
buffer.Add((Cluster)next);
}
else
{
merged.Add(next);
}
}
}
一方面,它可以正常工作,但另一方面,我想使其更具表现力,并在可能的情况下改善代码指标。我们将使用Analyze>计算代码度量工具(该工具是Visual Studio社区的一部分)来计算度量。最初,它们的意思是:
Configuration: Debug
Member: MergeNeighbors(IUnit, IUnit) : void
Maintainability Index: 64
Cyclomatic Complexity: 5
Class Coupling: 4
Lines of Source code: 32
Lines of Executable code: 10
通常,以下描述的方法不能保证获得漂亮的结果。
胡闹的场合
#392487
. , . , .
© bash.org
. , . , .
© bash.org
重构
第1步
我们检查同一嵌套级别的每个条件链是否以else块结尾,否则我们添加一个空的else块。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (prev is Cluster)
{
if (next is Cluster)
{
if (!prev.Description.Equals(next.Description))
{
Flush();
}
else
{
}
buffer.Add((Cluster)next);
}
else
{
Flush();
merged.Add(next);
}
}
else
{
if (next is Cluster)
{
buffer.Add((Cluster)next);
}
else
{
merged.Add(next);
}
}
}
第2步
如果表达式与条件块位于同一嵌套级别,则将每个表达式包装到每个条件块。如果表达式在块之前,则将其添加到块的开头,否则添加到末尾。我们重复直到在每个嵌套级别,条件块仅与其他条件块相邻。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (prev is Cluster)
{
if (next is Cluster)
{
if (!prev.Description.Equals(next.Description))
{
Flush();
buffer.Add((Cluster)next);
}
else
{
buffer.Add((Cluster)next);
}
}
else
{
Flush();
merged.Add(next);
}
}
else
{
if (next is Cluster)
{
buffer.Add((Cluster)next);
}
else
{
merged.Add(next);
}
}
}
第三步
在每个嵌套级别,对于每个if块,我们都切断条件链的其余部分,创建一个新的if块,其表达式与第一个if的表达式相反,将cut链放入新块中,并删除第一个else词。重复直到没有别的了。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (prev is Cluster)
{
if (next is Cluster)
{
if (!prev.Description.Equals(next.Description))
{
Flush();
buffer.Add((Cluster)next);
}
if (prev.Description.Equals(next.Description))
{
{
buffer.Add((Cluster)next);
}
}
}
if (!(next is Cluster))
{
{
Flush();
merged.Add(next);
}
}
}
if (!(prev is Cluster))
{
{
if (next is Cluster)
{
buffer.Add((Cluster)next);
}
if (!(next is Cluster))
{
{
merged.Add(next);
}
}
}
}
}
第4步
我们“折叠”这些块。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (prev is Cluster)
{
if (next is Cluster)
{
if (!prev.Description.Equals(next.Description))
{
Flush();
buffer.Add((Cluster)next);
}
if (prev.Description.Equals(next.Description))
{
buffer.Add((Cluster)next);
}
}
if (!(next is Cluster))
{
Flush();
merged.Add(next);
}
}
if (!(prev is Cluster))
{
if (next is Cluster)
{
buffer.Add((Cluster)next);
}
if (!(next is Cluster))
{
merged.Add(next);
}
}
}
第5步
对于每个没有嵌套块的if块的条件,请使用&&运算符添加所有父if块的条件。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (prev is Cluster)
{
if (next is Cluster)
{
if (!prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster)
{
Flush();
buffer.Add((Cluster)next);
}
if (prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster)
{
buffer.Add((Cluster)next);
}
}
if (!(next is Cluster) && prev is Cluster)
{
Flush();
merged.Add(next);
}
}
if (!(prev is Cluster))
{
if (next is Cluster && !(prev is Cluster))
{
buffer.Add((Cluster)next);
}
if (!(next is Cluster) && !(prev is Cluster))
{
merged.Add(next);
}
}
}
第6步
仅当没有嵌套块的块保留时,才保留它们在代码中的出现顺序。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (!prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster)
{
Flush();
buffer.Add((Cluster)next);
}
if (prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster)
{
buffer.Add((Cluster)next);
}
if (!(next is Cluster) && prev is Cluster)
{
Flush();
merged.Add(next);
}
if (next is Cluster && !(prev is Cluster))
{
buffer.Add((Cluster)next);
}
if (!(next is Cluster) && !(prev is Cluster))
{
merged.Add(next);
}
}
步骤7
对于每个唯一表达式,按照它们在代码中出现的顺序,我们写出包含它们的块。同时,我们忽略了块内的其他表达式。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (!prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster)
{
Flush();
}
if (!(next is Cluster) && prev is Cluster)
{
Flush();
}
if (!prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster)
{
buffer.Add((Cluster)next);
}
if (prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster)
{
buffer.Add((Cluster)next);
}
if (next is Cluster && !(prev is Cluster))
{
buffer.Add((Cluster)next);
}
if (!(next is Cluster) && prev is Cluster)
{
merged.Add(next);
}
if (!(next is Cluster) && !(prev is Cluster))
{
merged.Add(next);
}
}
步骤8
通过将||运算符应用于其条件,可以将具有相同表达式的块组合在一起。...
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (!prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster ||
!(next is Cluster) && prev is Cluster)
{
Flush();
}
if (!prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster ||
prev.Description.Equals(next.Description) && next is Cluster && prev is Cluster ||
next is Cluster && !(prev is Cluster))
{
buffer.Add((Cluster)next);
}
if (!(next is Cluster) && prev is Cluster ||
!(next is Cluster) && !(prev is Cluster))
{
merged.Add(next);
}
}
步骤9
使用布尔代数规则简化条件表达式。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (prev is Cluster && !(next is Cluster && prev.Description.Equals(next.Description)))
{
Flush();
}
if (next is Cluster)
{
buffer.Add((Cluster)next);
}
if (!(next is Cluster))
{
merged.Add(next);
}
}
第10步
我们整理一个文件。
结果
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (IsEndOfCompatibleClusterSequence(prev, next))
Flush();
if (next is Cluster)
buffer.Add((Cluster)next);
else
merged.Add(next);
}
private static bool IsEndOfCompatibleClusterSequence(IUnit prev, IUnit next) =>
prev is Cluster && !(next is Cluster && prev.Description.Equals(next.Description));
总
重构后,该方法如下所示:
private void MergeNeighbors(IUnit prev, IUnit next)
{
if (IsEndOfCompatibleClusterSequence(prev, next))
Flush();
if (next is Cluster)
buffer.Add((Cluster)next);
else
merged.Add(next);
}
指标是这样的:
Configuration: Debug
Member: MergeNeighbors(IUnit, IUnit) : void
Maintainability Index: 82
Cyclomatic Complexity: 3
Class Coupling: 3
Lines of Source code: 10
Lines of Executable code: 2
度量标准已得到显着改善,并且代码变得更短,更具表达力。但是,就我个人而言,这种方法最值得注意的是:有人能够立即看到该方法在最终版本中应该看起来像,而有人只能编写初始实现,但至少要有一些表述借助纯机械动作(也许是最后一步的例外)的帮助,可以将行为正确化为最简洁,最直观的形式。
PS 15年前,作者已经在学校获得了已成为出版物中描述的算法的所有知识。为此,他对热心的老师表示感谢,他们为孩子们提供了接受正常教育的基础。Tatyana Alekseevna,Natalya Pavlovna,如果您突然读到这篇文章,非常感谢!