1. Методы рефакторинга кода
программного обеспечения
• Встраивание класса (Inline Class)
• Введение локального расширения
(Introduce Local Extension)
• Замена вложенных условных операторов
граничным оператором
(Replace Nested Conditional with Guard Clauses)
2. Встраивание класса
Также известен как: Inline Class
Проблема Решение
Класс почти ничего не делает, ни за что не
отвечает, и никакой ответственности для
этого класса не планируется.
Переместите все члены и
полезные функции из описанного
класса в другой.
3. Встраивание класса
Также известен как: Inline Class
«Встраивание класса» (InlineClass) противоположно
«Выделению класса» (ExtractClass). Прибегать к этой
операции нужно, если от класса становится мало
пользы и его надо убрать. Часто это происходит в
результате рефакторинга, оставившего в классе
мало функций. В этом случае следует вставить
данный класс в другой, выбрав для этого такой
класс, который чаще всего его использует.
Причины рефакторинга Достоинства
Меньше бесполезных классов — больше
свободной оперативной памяти, в том числе, и у
вас в голове.
4. Встраивание класса
Пример кода
public class ClinicInfo
{
public ClinicInfo()
{
Doctors = new HashSet<Doctor>();
}
[Required]
public string Name { get; set; }
[Required]
public string Adress { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string BankCard { get; set; }
public byte[] Image { get; set; }
public virtual ICollection<Doctor> Doctors { get; set; }
public Doctor GetDoctors(Specialization specification)
=> DoctorInforms.Where(x => x.Specialization ==
specification);
}
public class Clinic
{
[Key]
public int Id { get; set; }
public bool ClinicConfirmed { get; set; }
public virtual ClinicInfo ClinicInfo { get; set; }
}
5. Встраивание класса
Порядок рефакторинга
1) Создайте в классе-приёмнике публичные
поля и методы, которые есть в классе-доноре.
Методы должны обращаться к аналогичным
методам класса-донора.
public class Clinic
{
[Key]
public int Id { get; set; }
public bool ClinicConfirmed { get; set; }
public virtual ClinicInfo ClinicInfo { get; set; }
}
public string Name => ClinicInfo.Name;
public string Adress => ClinicInfo.Adress;
public string Description => ClinicInfo.Description;
public string BankCard => ClinicInfo.BankCard;
public byte[] Image => ClinicInfo.Image;
public virtual ICollection<Doctor> Doctors
=> ClinicInfo.Doctors
public Doctor GetDoctors(Specialization specification)
=> ClinicInfo.GetDoctors(specialization);
6. Встраивание класса
Порядок рефакторинга
2) Замените все обращения к классу-донору
обращениями к полям и методам класса-
приёмника.
public List<Doctor> GetClinicDoctors(int clinicId,
string specializations)
{
var clinic = db.Clinics.Find(clinicId);
return clinic.ClinicInfo.GetDoctors(specialization);
}
public List<Doctor> GetClinicDoctors(int clinicId,
string specializations)
{
var clinic = db.Clinics.Find(clinicId);
return clinic.GetDoctors(specialization);
}
7. Встраивание класса
Порядок рефакторинга
3) Самое время протестировать программу и
убедиться, что во время работы не было
допущено никаких ошибок. Если тесты
показали, что все работает так, как должно,
начинаем использовать перемещение
метода и перемещение поля для того, чтобы
полностью переместить все функциональности
в класс-приёмник из исходного класса.
Продолжаем делать это, пока исходный класс
не окажется совсем пустым.
public class Clinic
{
[Key]
public int Id { get; set; }
public bool ClinicConfirmed { get; set; }
public virtual ClinicInfo ClinicInfo { get; set; }
}
public string Name { get; set; }
public string Adress { get; set; }
public string Description { get; set; }
public string BankCard { get; set; }
public byte[] Image { get; set; }
public virtual ICollection<Doctor> Doctors{ get; set; }
public Doctor GetDoctors(Specialization specification)
=> DoctorInforms
.Where(x => x.Specialization == specification);
8. Встраивание класса
Порядок рефакторинга
4) Удалите исходный класс.
public class Clinic
{
[Key]
public int Id { get; set; }
public bool ClinicConfirmed { get; set; }
X public virtual ClinicInfo ClinicInfo { get; set; }
}
public string Name { get; set; }
public string Adress { get; set; }
public string Description { get; set; }
public string BankCard { get; set; }
public byte[] Image { get; set; }
public virtual ICollection<Doctor> Doctors{ get; set; }
public Doctor GetDoctors(Specialization specification)
=> DoctorInforms
.Where(x => x.Specialization == specification);
9. Введение локального расширения
Также известен как: Introduce Local Extension
Проблема Решение
В служебном классе отсутствуют некоторые
методы, которые вам нужны. При этом
добавить их в этот класс вы не можете.
Создайте новый класс, который
бы содержал эти методы, и
сделайте его наследником
служебного класса, либо его
обёрткой.
10. В классе, который вы используете, нет нужных вам
методов. Или ещё хуже — вы не можете их туда
добавить (например, потому что классы находятся
в сторонней библиотеке).
Причины рефакторинга Пути решения
Есть три пути:
• Создать подкласс из интересующего класса,
который будет содержать новые методы, и
наследовать все остальное из родительского
класса. Этот путь проще, но иногда бывает
заблокирован в самом служебном классе с
помощью директивы final (Java) / sealed (C#)
• Создать класс-обёртку, который будет
содержать все новые методы, а остальное
делегировать связанному объекту служебного
класса.
• Создать статические методы расширения (С#).
Введение локального расширения
Также известен как: Introduce Local Extension
Помещая дополнительные методы в отдельный
класс-расширение (обёртку или подкласс), вы не
засоряете клиентские классы кодом, который им не
принадлежит по смыслу. Этим повышается
связность компонентов программы и возможность
их повторного использования.
Достоинства
11. Пример кода
Введение локального расширения
public List<Payment> GePayments(Clinic clinic, DateTime day)
{
var allPayments = clinic.Doctors.SelectMany(doctor => doctor.Appointmnets)
.Where(appointment => appointment.Date == day)
.SelectMany(doctor => doctor.Payments);
var resultPayment = new List<Payment>();
foreach(var payment in allPayments)
{
if (!resultPayment.Any(x => x.AppoinmentId == payment.AppoinmentId))
resultPayment.Add(payment);
}
return allPayments;
}
12. Порядок рефакторинга
1) Создайте новый класс-расширение:
для C# - необходимо создать статический
класс, в котором будут находиться методы
расширения.
public static class IEnumerableExtension
{
}
Введение локального расширения
13. Порядок рефакторинга
public static class IEnumerableExtension
{
}
2) Создайте в классе новые
расширенные методы. Переместите в
него внешние методы из других классов,
либо удалите их, если расширение уже
имеет такой функционал.
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T>
source, Func<T, TKey> keySelector)
{
if (source == null)
yield return default(TSource);
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
Введение локального расширения
14. Порядок рефакторинга
3) Замените использование служебного класса новым классом-расширением в тех местах, где
нужна расширенная функциональность.
public List<Payment> GePayments(Clinic clinic, DateTime day)
{
var allPayments = clinic.Doctors
.SelectMany(doctor => doctor.Appointmnets)
.Where(appointment => appointment.Date == day)
.SelectMany(doctor => doctor.Payments);
return allPayments.DistinctBy(x => x.AppoinmentId);
}
Введение локального расширения
15. Замена вложенных условных операторов граничным
оператором
Также известен как: Replace Nested Conditional with Guard Clauses
Проблема Решение
У вас есть группа вложенных условных операторов,
среди которых сложно выделить нормальный ход
выполнения кода.
Выделите все проверки специальных или граничных
случаев выполнения в отдельные условия и
поместите их перед основными проверками. В
идеале, вы должны получить «плоский» список
условных операторов, идущих один за другим.
16. Разобраться в том, что и как делает оператор с
большим уровнем вложенности довольно сложно,
так как «нормальный» ход выполнения в нем не
очевиден. Такие операторы появляются
эволюционным путём, когда каждое из условий
добавляется в разные промежутки времени без
мыслей об оптимизации остальных условий.
Чтобы упростить такой оператор, нужно
выделить все особые случаи в отдельные условные
операторы, которые бы при наступлении
граничных условий, сразу заканчивали выполнение
и возвращали нужное значение. По сути, ваша
цель — сделать такой оператор плоским.
Причины рефакторинга
Замена вложенных условных операторов граничным
оператором
Также известен как: Replace Nested Conditional with Guard Clauses
17. Пример кода
Замена вложенных условных операторов граничным
оператором
public Doctor EditClinicDoctor(Clinic clinic, int[] doctorIds)
{
if(clinic != null)
{
clinic.Doctors.Clear();
foreach(int doctorId in doctorIds)
{
var doctor = db.Doctors.Find(doctorId);
if(doctor != null)
{
clinic.Doctors.Add(doctor);
}
else
{
throw new Exception($"There are no doctor with id {doctorId}");
}
}
}
else
{
throw new Exception("clinic cann't be null!");
}
}
1
2
3
18. Порядок рефакторинга
Замена вложенных условных операторов граничным
оператором
Постарайтесь избавиться от «побочных
эффектов» в условиях операторов. Разделение
запроса и модификатора может в этом помочь. Такое
решение понадобится для дальнейших перестановок
условий.
1.Выделите граничные условия, которые приводят к
вызову исключения или немедленному возвращению
значения из метода. Переместите эти условия в
начало метода.
2.После того как с переносами покончено, и все тесты
стали проходить, проверьте, можно ли
использовать объединение условных
операторов для граничных условных операторов,
ведущих к одинаковым исключениям или
возвращаемым значениям.
public Clinic EditClinicDoctor(Clinic clinic,
int[] doctorIds)
{
if(clinic == null)
throw new Exception("clinic cann't be null!");
clinic.Doctors.Clear();
foreach(int doctorId in doctorIds)
{
var doctor = db.Doctors.Find(doctorId);
if(doctor == null)
throw new Exception(“Exception message…”);
clinic.Doctors.Add(doctor);
}
return clinic;
}
19. Источники
М.Фаулер - Рефакторинг. Улучшение проекта существующего кода
• Встраивание класса – 104 с.
• Введение локального расширения - 109 с.
• Замена вложенных условных операторов граничным оператором – 164 с.