Приветствую всех, сегодня мы рассмотрим что такое рефакторинг на практике. Рефакторинг- своего рода перепроектирования кода программы, уменьшив ее в объеме но не изменив ее функциональности, порой после рефакторинга код программы может сократиться в десятки раз. И так рассмотрим код метода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static bool ShouldFire(bool enemyInFront, string enemyName, int robotHealth) { bool shouldFire = true; if (enemyInFront == true) { if (enemyName == "boss") { if (robotHealth < 50) shouldFire = false; if (robotHealth > 100) shouldFire = true; } } else { return false; } return shouldFire; } |
Посмотрев на код выше, сразу бросается количество ветвлений условных операторов. И относительно не большая функциональность метода, занимает слишком много строк кода. Попробуйте самостоятельно уменьшить ее размер и если ли не получиться подстмотрите код ниже. Функциональность метода нельзя нарушать, лишь сократить ее в объеме. В первую очередь для меня бросается локальная переменная shouldFire которая тут вообще не нужна. А весь остальной код можно сгруппировать в return. И так смотрите что у меня получилось:
1 2 3 4 |
static bool ShouldFire2(bool enemyInFront, string enemyName, int robotHealth) { return enemyInFront? ((enemyName=="boss")&& robotHealth<50?false:true):false; } |
Метод настолько уменьшился что поместился в одну строку, а функциональность его осталось не изменой. Рефакторинг программы следует проводить только тогда, кода ваш код рабочий, не имеет ошибок. Только после этого надо думать над тем как его уменьшить. Но сильно уменьшать его тоже не стоит, так как сокращение кода может вести к сложности читаемости кода, в особенности если над кодом работать будут и другие разработчики.
При рефакторинге стоит учитывать то, что метод должен вмещаться в 20 строк кода и максимум в один экран, в случаи разрастания кода следует метод разделять на под методы, это уменьшит их, упростит процедуру отладки, а другим программистам сократит время на понимание логики. Так же при работе с операторами выбора, такими как switch не стоит злоупотреблять, в программе, а лучше всего их заменить на словари Directionary, а так же Делегаты.
Используйте StringBuilder над String, чтобы получить лучшую производительность:
Есть целый ряд статей и сообщений , которые говорят , что StringBuilder
является более эффективным , поскольку он содержит изменяемый буфер строки. .NET Строки неизменны, что является причиной , почему новый String
объект создается каждый раз , когда мы изменяем его (вставка, Append, удалить и т.д.).
В следующем разделе я объясню это более подробно, чтобы дать новичкам четкое представление об этом факте.
Я написал следующий код, и , как вы можете видеть, я определил String
переменную и StringBuilder
переменную. Я затем добавить строку в обоих этих переменных:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace StringTest { class Program { static void Main(string[] args) { string s = "Pranay"; s += " rana"; s += " rana1"; s += " rana122"; StringBuilder sb = new StringBuilder(); sb.Append("pranay"); sb.Append(" rana"); } } } |
Если разобрать этот код под рефлетором станет понятно по какой причине лучше использовать StringBuilder.
Если вы зашли в рефлектор можете видеть, Concat
функция принимает два аргумента и возвращает String
. Следующие шаги выполняются , когда мы выполняем Append с типом строки:
- Проверяет, является ли строка пустой или нет
- Создает строку
dest
и выделяет память для строки - Заполняет
dest
строкуstr0
иstr1
- Возвращает
dest
строку, которая представляет собой новую переменную строку
Таким образом, это доказывает, что всякий раз, когда я делаю операцию конкатенации строк, он создает новую строку из-за неизменное поведение строк.
Функция Append принимает аргумент типа String
и возвращает StringBuilder
объект. Следующие шаги выполняются , когда мы выполняем Append
с StringBuilder
типом:
- Получить значение строки из
StringBuilder
объекта - Проверьте, если требуется выделить память для новой строки мы будем добавлять
- Выделение памяти при необходимости и добавить строку
- Если не требуется, чтобы выделить память, а затем добавить строку непосредственно к существующей выделенной памяти
- Возвращает
StringBuilder
объект , который вызывает функцию, используяthis
ключевое слово
Таким образом, она возвращает тот же объект, не создавая новый.
Я надеюсь , что этот пример помог вам понять внутренние детали о том, почему мы должны использовать StringBuilder
чаще чем , String
чтобы получить более высокую производительность , когда мы делаем основную обработку строк в коде.
Структура инициализации в C #
Структуры в C # позволяют нам группировать переменные и методы. Они несколько похожи на классы, но есть ряд отличий между ними. В этой статье я не буду обсуждать это. Я собираюсь объяснить, как инициализировать структуру.
факты
- Структура является типом значения
- Это не позволяет создать параметр меньше конструктора, поскольку он инициализирует переменную со значениями по умолчанию
Теперь рассмотрим ниже случай, когда я создал две структуры:
Структура 1: с открытыми переменными.
1 2 3 4 5 |
public struct StructMember { public int a; public int b; } |
Структура 2: со свойствами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public struct StructProperties { private int a; private int b; public int A { get { return a; } set { a = value; } } public int B { get { return b; } set { b = value; } } } |
Учитывая вышеуказанные два факта, я пытался использовать обе структуры, как показано ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class MainClass { public static void Main() { StructMembers MembersStruct; StructProperties PropertiesStruct; MembersStruct.X = 100; MembersStruct.Y = 200; PropertiesStruct.X = 100; PropertiesStruct.Y = 200; } } |
После этого, когда я пытаюсь скомпилировать код, я получаю сообщение об ошибке.
C # компилятор сообщает нам, что позволяет использовать первую структуру без ошибок, но не позволяет использовать вторую структуру, которая выставляет свойство. Чтобы решить эту проблему, я написал ниже строки кода для инициализации второй структуры:
1 |
StructProperties PropertiesStruct = new StructProperties(); |
Дело в том, что структура может быть реализована без использования new
оператора. Если вы не используете new
, то поля будут оставаться Unassigned и объект не может использоваться , пока все поля не инициализированы.
В .NET, все простые типы структур когда вы пишете код на C # необходимо инициализировать, рассмотрим случай ниже, где я создаю целочисленную переменную:
1 2 |
int a; Console.WriteLine(a); |
Компилятор выдает ошибку, что вы не можете использовать переменную без инициализации. Так что вам нужно написать либо:
1 |
int a =0; |
или же
Используйте новый оператор для вызова конструктора по умолчанию и присвоить значение по умолчанию для переменной
1 |
<span class="code-keyword">int</span> a = <span class="code-keyword">new</span> <span class="code-keyword">int</span>(); |
Это очень важно, чтобы инициализировать структуру правильно.
Оператор Checked
В следующем разделе я буду объяснять о Checked оператора доступна в C # .NET для обработки целочисленных переполнений.
В моей системе управления заказами, я должен вычислить точку продаж для каждого клиента, который размещает заказ. Пункты продажи являются целыми числами, которые получают заработанные клиент на основе продуктов, которые они покупают, и получает вычитаются из общей суммы счета, как они покупают новые продукты, используя эти точки. Но есть некоторые клиенты, которые не знают о балльной системе или не потребляют эти точки так, что точки накапливают более чем предел целочисленных переменных, то есть, 2 ^ 32. Таким образом, всякий раз, когда расчет происходит, я получаю некоторое значение опасное для тех клиентов, которые имеют значение точек больше, чем максимально допустимые целочисленное значение.
Чтобы избежать этой проблемы переполнения целых значений и информировать клиентов о своих точках, я использую Checked оператор C # .NET.
Проверено оператор для проверки на переполнение в математических операциях и переходах для целочисленных типов.
Синтаксис
1 |
Checked( expression ) |
1 |
Checked { statements...... } |
1 2 3 4 5 6 7 8 9 10 11 |
public static void Main() { int a; int b; int c; a = 2000000000; b = 2000000000; c = checked(a+ b); System.Console.WriteLine(Int1PlusInt2); } |
Когда мы запустим код выше, он бросает исключение.
который говорит , что c
больше , чем максимально допустимое целочисленное значение.
Точно так же, в моем приложении, я ловлю исключение переполнения брошенного при расчете точке заказа, с и затем отправить почту клиенту с напоминанием о необходимости использовать очки.
GO TO Switch..Case
Go To Идти к..
Позволяет нам прыгать безоговорочно, когда это требуется и не рекомендуется для использования в большой степени.
Switch..Case
Позволяет выполнить Case
блок на основе значения переменной, то есть, позволяет сделать программирование на основе условий.
В моем приложении, я достиг той стадии , когда я должен был выполнить код Case 1
из Switch..Case
и если какое — то условие было выполнено, я должен был выполнить код Case 3
из Switch..Case.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Switch(myvariable) { case 1: //statements …........ if ( expression ) execute case 3: break; case 2: ….. break; case 3: …...... break; } |
Первое решение этой проблемы , чтобы скопировать код Case 3
и поместить его в if
блоке Case 1
.
1 2 3 4 5 6 |
case 1: //statements …........ if ( expression ) //code of case 3 break; |
Но проблема выше решения заключается в том, что он делает избыточный код.
Второе решение заключается в создании функции и поместить код в том , а затем выполнить код.
1 2 3 4 5 6 7 8 9 10 11 |
case 1: //statements …........ if ( expression ) Case3Code(); break; function Case3Code() { …. } |
Проблема с этим решением является то, что я должен создать дополнительную функцию, которая не нужна.
Третье решение , чтобы сделать использование Go To
в Switch..Case
блоке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
switch(MyVariable) { case 1: //statements …........ if ( expression ) goto case 3: break; case 2: ….. break; case 3: …...... break; } |
Go to
В Switch..Case
позволяет мне сделать код легко и в обслуживаемой образом.
В заключительной части статьи о рефакторинге кода я бы посоветовал вам использовать паттерны при написании кода, ускоряют процесс их написания, так как они основываются на ООП, тем самым вы избавитесь от дублирования кода в своих приложениях, Он становиться более лаконичен и читабельнее.
Вторая версия с тернарными операторами работает быстрее,
у меня получилось приблизительно ~3030ms против ~3400ms на 100000000 итераций.