Начало Код с душком
Техника: Составление методов
В продолжении, техника рефакторинга по книге Рефакторинг. Улучшение существующего кода Мартин Фаулер.
Синтаксис будет на C#, но главное понимать идею, а её можно использовать в любом другом языке программирования.
Перемещение функций между объектами
- Перемещение метода (создайте новый метод с аналогичным телом в классе, который чаще им пользуется).
Метод должен быть в классе, который лучше отражает его предметную область
Fromclass View { private Shop shop; private List<User> FilterUsers(List<User> users, decimal koef) { List<User> activeUsers = new List<User>(); foreach(User user in users) { if(shop.IsActiveUser(user, koef)) { activeUsers.Add(user); } } return activeUsers; } } class Shop { public bool IsActiveUser(User user, decimal koef) { decimal rate = 1; if(koef > 1000) { rate = koef * 1.75; } return user.Boss || user.HasDiscount() && rate > 10; } } class User { private bool boss; public bool Boss { get { return boss; } } public bool HasDiscount() { // some condition } }
To
class View { private Shop shop; private List<User> FilterUsers(List<User> users, decimal koef) { List<User> activeUsers = new List<User>(); foreach(User user in users) { if(user.IsActiveUser(koef)) { activeUsers.Add(user); } } return activeUsers; } } class Shop { } class User { private bool boss; public bool Boss { get { return boss; } } public bool HasDiscount() { // some condition } public bool IsActiveUser(decimal koef) { decimal rate = 1; if(koef > 1000) { rate = koef * 1,75; } return Boss || HasDiscount() && rate > 10; } }
- Перемещение поля (создайте в целевом классе новое поле).
Поле должно быть в классе, который лучше отражает его предметную область
Fromclass Shop { private decimal rate; private DiscountType discountType; public Shop(DiscountType discountType, decimal rate) { this.rate = rate; this.discountType = discountType; } public decimal Rate { get{ return rate; } set{ rate = value; } } public bool HasDiscount(DateTime from, DateTime to) { // some calculation of flag return discountType.IsValid(rate) && flag; } public decimal GetDiscount(decimal amount, bool fullSum) { // some calculation of koef return discountType.GetAmount(rate, fullSum) * koef; } } class DiscountType { public bool IsValid(decimal rate) { // some calculation using rate } public decimal GetAmount(decimal rate, bool fullSum) { // some calculation using rate } }
To
class Shop { private DiscountType discountType; public Shop(DiscountType discountType) { this.discountType = discountType; } public bool HasDiscount(DateTime from, DateTime to) { // some calculation of flag return discountType.IsValid() && flag; } public decimal GetDiscount(decimal amount, bool fullSum) { // some calculation of koef return discountType.GetAmount() * koef; } } class DiscountType { private decimal rate; public DiscountType(decimal rate) { this.rate = rate; } public decimal Rate { get{ return rate; } set{ rate = value; } } public bool IsValid() { // some calculation using rate } public decimal GetAmount(bool fullSum) { // some calculation using rate } }
- Выделение класса (создайте новый класс и переместите поля и методы из старого класса в новый).
Класс должен содержать свою модель данных и методы для работы с ней, иначе он превращается в God object.
Fromclass User { private string name; private string street; private string city; public string Name { get{ return name; } set{ name = value; } } public string Street { get{ return street; } set{ city = value; } } public string City { get{ return city; } set{ city = value; } } public string GetAddressInfo() { return string.Format("{0} {1}", city, street); } }
To
class User { private string name; private Address address; public string Name { get{ return name; } set{ name = value; } } public string GetAddressInfo() { return address.GetFullAddress(); } } class Address { private string city; private string street; public string Street { get{ return street; } set{ city = value; } } public string City { get{ return city; } set{ city = value; } } public string GetFullAddress() { return string.Format("Adress: {0} {1}", city, street); } }
- Встраивание класса (переместите все функции в другой класс и удалите исходный).
Класс выполняет слишком мало функций.
Fromclass User { private string name; private Address address; public string Name { get{ return name; } set{ name = value; } } public Address GetAddress() { return address } } class Address { private string areaCode; private string country; public string AreaCode { get{ return areaCode; } set{ areaCode = value; } } public string Country { get{ return country; } set{ country = value; } } }
To
class User { private string name; private string areaCode; private string country; public string AreaCode { get{ return areaCode; } set{ areaCode = value; } } public string Country { get{ return country; } set{ country = value; } } public string Name { get{ return name; } set{ name = value; } } }
- Сокрытие делегирования (создайте методы, скрывающие делегирование).
Инкапсуляция, некоторых частей системы.
Fromclass View { private void Init() { // some code User manager = currentUser.Office.Manager; } } class User { private Department department; public Department Office { get{ return department; } set{ department = value; } } } class Department { private User manager; public Department(User manager) { this.manager = manager; } public User Manager { get{ return manager; } } }
To
class View { private void Init() { // some code User manager = currentUser.Manager; } } class User { private Department department; public User Manager { get{ return department.Manager; } } } class Department { private User manager; public Department(User manager) { this.manager = manager; } public User Manager { get{ return manager; } } }
- Удаление посредника (заставьте клиента обращаться к делегату непосредственно).
Класс слишком занят простым делегированием.
Fromclass View { private void Init() { // some code User manager = currentUser.Manager; decimal rate = currentUser.Rate; } } class User { private Department department; private UserType userType; public User Manager { get{ return department.Manager; } } public decimal Rate { get{ return userType.Rate; } } } class Department { private User manager; public User Manager { get{ return manager; } } } class UserType { private decimal rate; public decimal Rate { get{ return rate; } } }
To
class View { private void Init() { // some code User manager = currentUser.Office.Manager; decimal rate = currentUser.Type.Rate; } } class User { private Department department; private UserType userType; public Department Office { get{ return department; } } public UserType Type { get{ return userType; } } } class Department { private User manager; public User Manager { get{ return manager; } } } class UserType { private decimal rate; public decimal Rate { get{ return rate; } } }
- Введение внешнего метода (у клиента добавьте метод, которому в качестве 1-го параметра передается класс сервера).
Необходимо внести метод, но отсутсвует возможность модификации класса.
Fromclass View { private string GetUserName() { // some code return """ + userName + """; } private string GetCompanyName() { // some code return """ + companyName + """; } }
To
class View { private string GetUserName() { // some code return userName.InDoubleQuote(); } private string GetCompanyName() { // some code return companyName.InDoubleQuote(); } } static class StringExtension { public static string InDoubleQuote(this string str) { return """ + str + """; } }
- Введение локального расширения (создайте обёртку для класса (или с помощью наследования) и дополните необходимыми методами).
Необходимо внести методы, но отсутствует возможность модификации класса.
Fromsealed class Account { public decimal CalculateSum() { // calculation summ return summ; } }
To
class ImportantAccount { private Account account; public decimal CalculateMaxSum() { // calculation rate return account.CalculateSum() * rate; } }
Конфликты интересов (в рамках Review).
Как правило, всё споры/разногласия возникающие между review-ером и comitt-ерром можно разделить на следующие:
- Коллеги обсуждают выбор подходящего решения, т.к. возникло разногласие по реализации. Всё в рамках корректного общения, они понимают, что делают общее дело и нужно просто выбрать решение (10%).
- Коллегам нужно поболтать: пятница, тяжелая рабочая неделя или просто не хватает общения. Есть и такая категория людей, которым нужно просто потрещать. В принципе это не лишено здравого смысла, т.к. общение и обсуждения это здорово (10%).
- Споры, которые приводят к новым правилам. Например, когда в .NET появилось ключевое слово var кто-то из коллег стал его использовать везде, кто-то наоборот. Поэтому при ревью возникали споры, что лучше. В итоге после некоторых дискуссий, было принято решение (10%).
// если операнд с правой стороны, однозначно трактует возвращаемый тип, то можно использовать var var users = new List<User>(); var departments = user.Departmens.Cast<Department>(); // плохо var users = departments.Select(d => d.User).Where(u => !u.Deleted); var departments = service.GetNotDeletedDepartments();
- Жаркие споры по поводу того, чьё решение лучше. И если программисты одного уровня, страсти могут разгораться. Причём все эти споры, лишь трата времени и желание показать себя крутыми (70%). Тут хочется дать совет, если вы ввязались в такой спор, никогда не переходите не личности. И фразу «Твое решение не подходит», лучше заменить на «Решение не совсем верно и объяснить свою точку зрения». Т.е. акцент надо делать на обсуждении решения, а не навыков коллеги.
Забавные случаи:
1) Review-ер — «необходимо поправить код»
Commiter — «тебе не нравится ты и правь»/«ты нашёл ты и исправляй»2) Или как правильно
void Method() { ... if(flag) koef = GetMaxRate(); ... }
или
void Method() { ... if(flag) { koef = GetMaxRate(); } ... }
Надеюсь продолжить и дальше «Техника: Организация данных».
Автор: den_labs