Я в свое время возился с небольшими функциями, пытаясь найти способы их рефакторинга (недавно я читал книгу Мартина Фаулера Рефакторинг: улучшение дизайна существующего кода ). Я обнаружил следующую функцию 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()
- это функция, которую я нашел в коде, который я обновлял на работе. Цель этой функции - перевести ThisIsAString в This String . Он используется в полдюжине мест в коде и довольно незначителен для всей схемы вещей.
Я построил вторую функцию исключительно как академическое упражнение, чтобы увидеть, займет ли использование регулярного выражения больше времени или нет.
Ну, вот результаты:
С 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
Вот код после Refactor # 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;
}
Refactor # 2 - Use 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
Я передвинул 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);
}
}
Вопросы
- Что делает эти функции такими разными в течение длительного времени, и
- Как я могу улучшить эту функцию
а) быть более ремонтопригодным или
б) бежать быстрее?
- Как бы я сделал тесты памяти на них, чтобы увидеть, кто использовал меньше памяти?
Спасибо за ваши ответы до сих пор. Я вставил все предложения, сделанные @Jon Skeet, и хотел бы получить отзыв об обновленных вопросах, которые я задал в результате.
NB : Этот вопрос предназначен для изучения способов рефакторинга функций обработки строк в C #. Я скопировал / вставил первый код as is
. Я хорошо знаю, что вы можете удалить System.Convert.ToString()
в первом методе, и я сделал именно это. Если кто-либо знает о каких-либо последствиях удаления System.Convert.ToString()
, это также будет полезно знать.