Поговорим о рефакторинге и оптимизации кода
newbie
Опубликован: | 2019-07-02T05:21:59.215664Z |
Отредактирован: | 2019-07-02T05:21:59.215664Z |
В этом выпуске блога я буду заниматься рефакторингом и оптимизацией кода. А полем для бурной деятельности мне послужит уже решенная в одном из предыдущих выпусков блога задача на преобразование строк в числа.
Код, который разрабатывает программист, почти всегда вариантен, а задача может иметь несколько решений. Как правило, хороший код выражает самое простое и оптимальное решение задачи. Если пристально посмотреть на предложенное ранее решение задачи преобразования строк в числа, то можно заметить одну неприятную особенность. Код решения выглядит так:
#include <iostream> #include <string> #include <cmath> unsigned long to_integer(const std::string & s); int find_i(const char s); int main() { std::string first = "54709879023"; std::string second = "23450980912"; std::cout << first << std::endl << " + " << std::endl << second << " = " << std::endl << to_integer(first) + to_integer(second) << std::endl; return 0; } int find_i(const char s) { const std::string digits = "0123456789"; int r; for (int i = 0; i < 10; i++) { if (s == digits[i]) { r = i; break; } } return r; } unsigned long to_integer(const std::string & s) { const int L = s.length(); unsigned long sum = 0; for (int i = 0; i < L; i++) sum += find_i(s[i]) * std::pow(10, L - i -1); return sum; }
В этом коде, хоть он и является рабочим и решает поставленную задачу, есть излишества. А именно, совершенно лишней и бессмысленной является функция find_i, потому что она:
- слишком громоздкая для начальной задачи и занимает 8 строк кода;
- создаёт при исполнении программы избыточные, совершенно ненужные сущности, которые ещё и занимают память компьютера.
Сама по себе функция преобразует символ (тип char
) в целое число (тип int
), и в данном случае такое преобразование можно сделать кратно проще. Дело в том, что каждый символ ASCII хранится в памяти как целое восьмибитное число, и в сущности может быть приведён к этому значению. Рассмотрим, как могут быть представлены символы от 0
до 9
, для этого я создам временный файл и напишу в него следующий код.
#include <iostream> #include <string> int main() { using namespace std; string digits = "0123456789"; for (int i = 0; i < 10; i++) { cout << "char: " << digits[i] << " is int: " << int(digits[i]) << endl; } return 0; }
Если откомпилировать файл tempo.cpp и исполнить его, то можно увидеть следующую картину.
Таким образом, если из численного значения любого символа от 0
до 9
вычесть целое число 48, то получится целое число, соответствующее этому символу. Это простое свойство приведения типов можно вполне использовать для решения обозначенной задачи. Возвращаюсь к коду файла toint.cpp, нахожу в нём функцию to_integer и в этой функции цикл for. Тело этого цикла в настоящий момент выглядит так:
sum += find_i(s[i]) * std::pow(10, L - i -1);
Заменяю в этом выражении вызов функции find_i на вычисление целого значения с приведением типов.
sum += (s[i] - 48) * std::pow(10, L - i - 1);
Функцию find_i теперь можно вообще удалить из кода программы.
После компиляции файла и исполнения программы вывод не изменился.
Оптимизация кода вполне удалась, программа получилась короче, а при её исполнении более оптимально будут расходоваться ресурсы процессора и занимаемая память.
Здесь мне хочется сделать краткое лирическое отступление... Когда мы разрабатываем код программы, отлаживаем его, тестируем, смотрим все приходящие на ум возможные варианты, очень часто наш глаз замыливается, и мы перестаём замечать некоторые очевидные вещи. Очень полезно показывать свой код другим людям, обсуждать его с коллегами, иногда всплывают вот такие интересные варианты оптимизации. Например, предыдущий выпуск блога с решением этой задачи я опубликовал на Дзене, и тут же получил очень дельный комментарий с предложенной оптимизацией кода от пользователя crem651, за что выражаю этому человеку искреннюю благодарность и признательность. Я сам вряд ли увидел бы такое простое и очевидное решение.