我一直在用自己的时间摆弄小函数,试图找到重构它们的方法(我最近读了 Martin Fowler 的书重构:改进现有代码的设计 https://rads.stackoverflow.com/amzn/click/com/0201485672)。我发现了以下功能MakeNiceString()
在更新它附近的代码库的另一部分时,它看起来是一个很好的候选者。事实上,没有真正的理由要更换它,但它足够小并且做了一些小事情,因此很容易遵循,但仍然可以获得“良好”的体验。
private static string MakeNiceString(string str)
{
char[] ca = str.ToCharArray();
string result = null;
int i = 0;
result += System.Convert.ToString(ca[0]);
for (i = 1; i <= ca.Length - 1; i++)
{
if (!(char.IsLower(ca[i])))
{
result += " ";
}
result += System.Convert.ToString(ca[i]);
}
return result;
}
static string SplitCamelCase(string str)
{
string[] temp = Regex.Split(str, @"(?<!^)(?=[A-Z])");
string result = String.Join(" ", temp);
return result;
}
第一个功能MakeNiceString()
是我在工作中更新的一些代码中发现的函数。该函数的目的是翻译这是一个字符串 to 这是一个字符串。它在代码中的六处地方使用,并且在整个方案中非常微不足道。
我构建第二个函数纯粹是作为学术练习,看看使用正则表达式是否会花费更长的时间。
嗯,结果如下:
10 次迭代:
MakeNiceString took 2649 ticks
SplitCamelCase took 2502 ticks
然而,从长远来看,它会发生巨大的变化:
10,000 次迭代后:
MakeNiceString took 121625 ticks
SplitCamelCase took 443001 ticks
重构MakeNiceString()
重构的过程MakeNiceString()
首先简单地删除正在发生的转换。这样做产生了以下结果:
MakeNiceString took 124716 ticks
ImprovedMakeNiceString took 118486
这是重构#1之后的代码:
private static string ImprovedMakeNiceString(string str)
{ //Removed Convert.ToString()
char[] ca = str.ToCharArray();
string result = null;
int i = 0;
result += ca[0];
for (i = 1; i <= ca.Length - 1; i++)
{
if (!(char.IsLower(ca[i])))
{
result += " ";
}
result += ca[i];
}
return result;
}
重构#2 - 使用StringBuilder
我的第二个任务是使用StringBuilder
代替String
。自从String
是不可变的,在整个循环中创建了不必要的副本。使用它的基准如下,代码如下:
static string RefactoredMakeNiceString(string str)
{
char[] ca = str.ToCharArray();
StringBuilder sb = new StringBuilder((str.Length * 5 / 4));
int i = 0;
sb.Append(ca[0]);
for (i = 1; i <= ca.Length - 1; i++)
{
if (!(char.IsLower(ca[i])))
{
sb.Append(" ");
}
sb.Append(ca[i]);
}
return sb.ToString();
}
这导致以下基准:
MakeNiceString Took: 124497 Ticks //Original
SplitCamelCase Took: 464459 Ticks //Regex
ImprovedMakeNiceString Took: 117369 Ticks //Remove Conversion
RefactoredMakeNiceString Took: 38542 Ticks //Using StringBuilder
改变for
循环到一个foreach
循环产生以下基准结果:
static string RefactoredForEachMakeNiceString(string str)
{
char[] ca = str.ToCharArray();
StringBuilder sb1 = new StringBuilder((str.Length * 5 / 4));
sb1.Append(ca[0]);
foreach (char c in ca)
{
if (!(char.IsLower(c)))
{
sb1.Append(" ");
}
sb1.Append(c);
}
return sb1.ToString();
}
RefactoredForEachMakeNiceString Took: 45163 Ticks
正如您所看到的,在维护方面,foreach
循环将是最容易维护的并且具有“最干净”的外观。它比for
循环,但更容易遵循。
替代重构:使用编译的Regex
我将正则表达式移到循环开始之前,希望因为它只编译一次,所以它会执行得更快。我发现(我确信我在某个地方有一个错误)是,这并没有像应该发生的那样发生:
static void runTest5()
{
Regex rg = new Regex(@"(?<!^)(?=[A-Z])", RegexOptions.Compiled);
for (int i = 0; i < 10000; i++)
{
CompiledRegex(rg, myString);
}
}
static string CompiledRegex(Regex regex, string str)
{
string result = null;
Regex rg1 = regex;
string[] temp = rg1.Split(str);
result = String.Join(" ", temp);
return result;
}
最终基准结果:
MakeNiceString Took 139363 Ticks
SplitCamelCase Took 489174 Ticks
ImprovedMakeNiceString Took 115478 Ticks
RefactoredMakeNiceString Took 38819 Ticks
RefactoredForEachMakeNiceString Took 44700 Ticks
CompiledRegex Took 227021 Ticks
或者,如果您更喜欢毫秒:
MakeNiceString Took 38 ms
SplitCamelCase Took 123 ms
ImprovedMakeNiceString Took 33 ms
RefactoredMakeNiceString Took 11 ms
RefactoredForEachMakeNiceString Took 12 ms
CompiledRegex Took 63 ms
所以百分比收益是:
MakeNiceString 38 ms Baseline
SplitCamelCase 123 ms 223% slower
ImprovedMakeNiceString 33 ms 13.15% faster
RefactoredMakeNiceString 11 ms 71.05% faster
RefactoredForEachMakeNiceString 12 ms 68.42% faster
CompiledRegex 63 ms 65.79% slower
(请检查我的数学)
最后,我将用RefactoredForEachMakeNiceString()
当我这样做时,我会将其重命名为有用的名称,例如SplitStringOnUpperCase
.
基准测试:
为了进行基准测试,我只需调用一个新的Stopwatch
对于每个方法调用:
string myString = "ThisIsAUpperCaseString";
Stopwatch sw = new Stopwatch();
sw.Start();
runTest();
sw.Stop();
static void runTest()
{
for (int i = 0; i < 10000; i++)
{
MakeNiceString(myString);
}
}
问题
- 是什么导致这些功能“从长远来看”如此不同,以及
- 我该如何改进这个功能
a) 更易于维护或
b) 跑得更快?
- 我将如何对这些进行内存基准测试,以查看哪些使用较少的内存?
感谢您到目前为止的回复。我已插入 @Jon Skeet 提出的所有建议,并希望获得有关我因此提出的更新问题的反馈。
NB:这个问题旨在探索在 C# 中重构字符串处理函数的方法。我复制/粘贴了第一个代码as is
。我很清楚您可以删除System.Convert.ToString()
在第一种方法中,我就是这样做的。如果有人知道删除该命令的任何影响System.Convert.ToString()
,这也会有帮助。