SlideShare a Scribd company logo
1 of 60
Фитнес для вашего
кода: как держать его
в форме
Тренер – Илья Шишков
Вместо введения
Мои советы – это
› мой личный опыт работы
› над realtime backend’ом, который работает 24/7
› существует, развивается и поддерживается в течение
многих лет
4
Что такое код нашего
проекта?
За что нам платят
деньги?
За что нам платят деньги?
7
Код нашего проекта
Код, который работает в
production
За что нам платят деньги?
8
Время
Production надо поддерживать
› Мониторинги
› Тесты
› Специальные отладочные и исследовательские
инструменты
9
Что такое код нашего проекта?
10
Код инструментов
Код мониторингов
Код тестов
Код, который
работает в
production
│Production – лишь
один из сценариев
использования кода
Большая картина
При проектировании и
написании кода следует думать
о большой картине
› Как я буду это
тестировать?
› Какие параметры мне надо
будет мониторить?
› Какие инструменты мне
могут понадобиться?
12
Production Мониторинг
Тесты Инструменты
│− Как?
│− Minimize coupling,
maximize cohesion
Minimize coupling, maximize cohesion
Coupling:
▌ Сопряжение
▌ Сцепка
▌ Связанность
▌ Взаимодействие
14
Cohesion:
▌ Сплочённость
▌ Единение
▌ Связанность
▌ Сцепление
Связанность и сплочённость в жизни
15
Связанность Сплочённость
Связанность и сплочённость в коде
16
void UpdateVector(vector<int>& v) {
int x;
cin >> x;
v.push_back(x);
}
int ReadInt(istream& is) {
int x; is >> x; return x;
}
void AddInt(vector<int>& v, int x) {
v.push_back(x);
}
void UpdateVector(vector<int>& v) {
AddInt(v, ReadInt(cin));
}
Связанность Сплочённость
Биллинг для мобильного оператора
Снятие денег со счёта клиента за звонок
› Вычислить стоимость звонка по его длительности
› Списать деньги со счёта
› Отправить SMS, если баланс стал отрицательным
Пополнение счёта клиента
› Зачислить деньги на счёт абонента
› Отправить SMS о поступлении денег на счёт
Сначала будем думать только о production
17
Код для production
Самый популярный принцип проектирования классов
▌ Напишем класс, в котором будет всё, что относится к
биллингу
18
class Billing {
public:
Billing(string usersDatabase, string tariffsDatabase);
void ChargePhoneCall(int userId, int minutes);
void AddMoney(int userId, int rubles);
19
private: // class Billing
void LoadUsers(string db);
void LoadTariffs(string db);
struct Tariff {
int costPerMinute_;
};
class UserInfo {
public:
void ChargePhoneCall(int minutes, const Tariff& tariff);
void AddMoney(int rubles);
int GetTariff() const;
private:
class Event;
void SendSms(string message);
int money_;
int tariff_;
vector<Event> history_;
}; // class Billing::UserInfo
vector<UserInfo> users_;
vector<Tariff> tariffs_;
}; // class Billing
20
Billing::Billing(string usersDatabase, string tariffsDatabase) {
LoadUsers(usersDatabase);
LoadTariffs(tariffsDatabase);
}
void Billing::ChargePhoneCall(int userId, int minutes) {
UserInfo& user = users_[userId];
const Tariff& t = tariffs_[user.GetTariff()];
user.ChargePhoneCall(minutes, t);
}
void Billing::AddMoney(int userId, int rubles) {
users_[userId].AddMoney(rubles);
}
21
void Billing::UserInfo::ChargePhoneCall(int minutes, const Billing::Tariff& tariff) {
money_ -= minutes * tariff.costPerMinute_;
history_.push_back(Event::PhoneCall(minutes));
if (money_ <= 0) {
SendSms("Your balance is " + to_string(money_));
}
}
void Billing::UserInfo::AddMoney(int rubles) {
money_ += rubles;
history_.push_back(Event::Payment(rubles));
SendSms("Got payment " + to_string(rubles));
}
Текущая схема кода
22
Billing
Production
Очень высокая связанность
› Загрузка пользователей из
БД
› Загрузка тарифов из БД
› Расчёт стоимости звонка
› Снятие и добавление денег
на счёт клиента
› Отправка SMS
23
class Billing {
public:
Billing(string usersDatabase,
string tariffsDatabase);
void ChargePhoneCall(int userId,
int minutes);
void AddMoney(int userId, int rubles);
};
Протестируем перед релизом
24
Список тестов
› чем дольше звонок, тем он
дороже
› звонок нулевой длины не
уменьшает количество
денег на счету
› пополнение счёта
происходит корректно
Автотесты
Billing
Production
25
void Billing::UserInfo::ChargePhoneCall(int minutes, const Billing::Tariff& tariff) {
money_ -= minutes * tariff.costPerMinute_;
history_.push_back(Event::PhoneCall(minutes));
if (money_ <= 0) {
SendSms("Your balance is " + to_string(money_));
}
}
void Billing::UserInfo::AddMoney(int rubles) {
money_ += rubles;
history_.push_back(Event::Payment(rubles));
SendSms("Got payment " + to_string(rubles));
}
26
class Billing {
…
private:
class UserInfo {
…
private:
int money_;
int tariff_;
vector<Event> history_;
};
};
struct Tariff {
int costPerMinute_;
int GetCost(int minutes) const {
return minutes * costPerMinute_;
}
};
struct UserAccount {
int money_ = 0;
void ApplyCharge(int rubles) {
money_ -= rubles;
}
void ApplyPayment(int rubles) {
money_ += rubles;
}
};
UserAccount account_;
struct Tariff {
int costPerMinute_;
};
27
void Billing::UserInfo::ChargePhoneCall(int minutes, const Billing::Tariff& tariff) {
money_ -= minutes * tariff.costPerMinute_;
history_.push_back(Event::PhoneCall(minutes));
if (money_ <= 0) {
SendSms("Your balance is " + to_string(money_));
}
}
void Billing::UserInfo::AddMoney(int rubles) {
money_ += rubles;
history_.push_back(Event::Payment(rubles));
SendSms("Got payment " + to_string(rubles));
}
account_.ApplyCharge(tariff.GetCost(minutes));
account_.ApplyPayment(rubles);
28
void TestTalkMorePayMore() {
const Tariff tariff{1};
assert(tariff.GetCost(15) > tariff.GetCost(5));
}
void TestNoChargeForZeroMinutes() {
UserAccount ua{15};
const Tariff tariff{10};
ua.ApplyCharge(tariff.GetCost(0));
assert(ua.money_ == 15);
}
void TestPayment() {
UserAccount ua{15};
ua.ApplyPayment(10);
assert(ua.money_ == 25);
}
Структура кода
29
Production Тесты
Billing
Tariff
UserAccoun
t
Tariff
UserAccount
Мы частично заменили связанность…
› Расчёт стоимости звонка
› Уменьшение баланса клиента
› Формирование истории действий пользователя
› Отправка SMS
30
void Billing::UserInfo::ChargePhoneCall(int minutes, const Billing::Tariff& tariff) {
money_ -= minutes * tariff.costPerMinute_;
history_.push_back(Event::PhoneCall(minutes));
if (money_ <= 0) {
SendSms("Your balance is " + to_string(money_));
}
}
… на сплочённость
31
struct Tariff {
int costPerMinute_;
int GetCost(int minutes) const;
};
struct UserAccount {
int money_ = 0;
void ApplyCharge(int rubles);
void ApplyPayment(int rubles);
};
account_.ApplyCharge(tariff.GetCost(minutes));
› Мы смогли написать юнит-
тесты
› GetCost может быть
сделан виртуальным
› ApplyCharge может быть
использован для других
услуг
Выкатили в production, хотим
мониторинг
› Считать количество
отправленных SMS
› Для каждого клиента считать
сумму потраченных денег
› Вывод статистики в формате
XML
32
Billing
Production
Автотесты
Tariff
UserAccount
Tariff UserAccount
Мониторинг
Считаем количество отправленных SMS
33
class Billing {
public:
void AddMoney(int userId, int rubles);
void ChargePhoneCall(int userId, int minutes);
void PrintStatsAsXml() {
cout << "<sms>" << smsSent_ << "</sms>";
}
int smsSent_ = 0;
private:
void LoadUsers(string db);
void LoadTariffs(string db);
class UserInfo;
vector<UserInfo> users_;
vector<Tariff> tariffs_;
};
34
void Billing::ChargePhoneCall(int userId, int minutes) {
UserInfo& user = users_[userId];
const Tariff& t = tariffs_[user.GetTariff()];
user.ChargePhoneCall(minutes, t);
}
void Billing::UserInfo::ChargePhoneCall(int minutes, const Tariff& tariff) {
account_.ApplyCharge(tariff.GetCost(minutes));
history_.push_back(Event::PhoneCall(minutes));
if (account_.money_ <= 0) {
SendSms("Your balance is " + to_string(account_.money_));
// Здесь надо увеличить Biiling::smsSent_
}
}
Считаем количество отправленных SMS
Обработка счёта клиента и отправка SMS связаны друг с
другом
Разорвём эту связанность – вынесем отправку SMS в Billing
35
void Billing::UserInfo::ChargePhoneCall(int minutes, const Tariff& tariff) {
account_.ApplyCharge(tariff.GetCost(minutes));
history_.push_back(Event::PhoneCall(minutes));
if (account_.money_ <= 0) {
SendSms("Your balance is " + to_string(account_.money_));
// Здесь надо увеличить Biiling::smsSent_
}
}
36
class Billing {
private: // class Billing
class UserInfo {
public:
void ChargePhoneCall(int minutes, const Tariff& tariff);
void AddMoney(int rubles);
int GetTariff() const;
UserAccount account_;
}; // class Billing::UserInfo
vector<UserInfo> users_;
vector<Tariff> tariffs_;
}; // class Billing
void SendSms(int userId, string message);
void SendSms(string message);
private:const UserAccount& GetAccount() const { return account_; }
void Billing::ChargePhoneCall(int userId, int minutes) {
UserInfo& user = users_[userId];
const Tariff& t = tariffs_[user.GetTariff()];
user.ChargePhoneCall(minutes, t);
const int money = user.GetAccount().money_;
if (money <= 0) {
SendSms(userId, "Your balance is " + to_string(money));
}
}
void Billing::AddMoney(int userId, int rubles) {
users_[userId].AddMoney(rubles);
SendSms(userId, "Got payment " + to_string(rubles));
}
37
void Billing::ChargePhoneCall(int userId, int minutes) {
UserInfo& user = users_[userId];
const Tariff& t = tariffs_[user.GetTariff()];
user.ChargePhoneCall(minutes, t);
}
void Billing::AddMoney(int userId, int rubles) {
users_[userId].AddMoney(rubles);
}
38
void Billing::SendSms(int userId, string message) {
++smsSent_;
// ...
}
Считаем, сколько денег потратил
клиент
39
struct UserStats {
int moneySpent_ = 0;
void AddExpense(int rubles) {
moneySpent_ += rubles;
}
};
class Billing::UserInfo {
public:
void ChargePhoneCall(int minutes, const Tariff& tariff);
void AddMoney(int rubles);
int GetTariff() const;
const UserAccount& GetAccount() const;
private:
class Event;
int tariff_;
UserAccount account_;
vector<Event> history_;
}; // class Billing::UserInfo
UserStats stats_;
const UserStats& GetStats() const { return stats_; }
40
void Billing::UserInfo::ChargePhoneCall(int minutes, const Tariff& tariff) {
int cost = tariff.GetCost(minutes);
account_.ApplyCharge(cost);
history_.push_back(Event::PhoneCall(minutes));
void Billing::PrintStatsAsXml() {
cout << "<sms>" << smsSent_ << "</sms>";
} for (const UserInfo& u : users_) {
cout << "<spent>" << u.GetStats().moneySpent_ << "</spent>";
}
stats_.AddExpense(cost);}
Структура кода
41
Production Тесты
Billing
Tariff
UserAccoun
t
Tariff
UserAccount
UserStats
Billing
Мониторинг
Добавили мониторинг
Мы заменили связанность
▌ Отправка SMS была связана с обработкой счёта клиента
на сплочённость
▌ Отправка SMS выполняется классом Billing
и смогли добавить мониторинги, не вмешиваясь в логику
других компонентов системы
42
Теперь хотим проводить исследования
43
Billing
Production
Автотесты
Tariff
UserAccount
Tariff UserAccount
Инструменты
BillingUserInfo
Мониторинг
Теперь хотим проводить исследования
Хотим провести исследование нового тарифа
▌ Дать каждому клиенту начальный баланс в 500 рублей
▌ Повторить все действия из его истории
▌ Посчитать
› количество потраченных денег
› сколько раз баланс оказывался отрицательным
44
Класс Billing нам не поможет
› Загрузка клиентов и тарифов связаны друг с другом
› Шлёт SMS клиентам
45
class Billing {
public:
Billing(string usersDatabase, string tariffsDatabase) {
LoadUsers(usersDatabase);
LoadTariffs(tariffsDatabase);
}
void ChargePhoneCall(int userId, int minutes);
void AddMoney(int userId, int rubles);
private:
void LoadUsers(string db);
void LoadTariffs(string db);
vector<UserInfo> users_;
vector<Tariff> tariffs_;
};
Заменим связанность на сплочённость
46
class Billing {
public:
Billing(string usersDatabase, string tariffsDatabase)
void LoadUsers(string db);
void LoadTariffs(string db);
vector<UserInfo> LoadUsers(string db);
vector<Tariff> LoadTariffs(string db);
private:
: users_(LoadUsers(usersDatabase))
, tariffs_(LoadTariffs(tariffsDatabase))
{}
{
LoadUsers(usersDatabase);
LoadTariffs(tariffsDatabase);
}
// Не компилируется, UserInfo – приватный класс
class UserInfo;
vector<UserInfo> users_;
vector<Tariff> tariffs_;
};
47
vector<UserInfo> users_;
vector<Tariff> tariffs_;
}; // class Billing
vector<UserInfo> LoadUsers(string db);
vector<Tariff> LoadTariffs(string db);
class Billing {
private:
class UserInfo {
public:
void ChargePhoneCall(int minutes, const Tariff& tariff);
void AddMoney(int rubles);
int GetTariff() const;
const UserAccount& GetAccount() const { return account_; }
const UserStats& GetStats() const { return stats_; }
private:
class Event;
int tariff_;
UserStats stats_;
UserAccount account_;
vector<Event> history_;
}; // class Billing::UserInfo
Сплотим компоненты вместе
48
Tariff
UserAccount
Event
Инструмент
UserStats
LoadUsers
UserInfo
49
void InvestigateNewTariff() {
const Tariff newTariff{2};
vector<UserInfo> users = LoadUsers("users.sql");
int negativeBalanceCount = 0;
for (const UserInfo& realUser : users) {
UserAccount ua{500};
UserStats stats;
for (const Event& e : realUser.GetHistory()) {
if (e.IsPayment()) {
ua.ApplyPayment(e.GetMoney());
} else {
int cost = newTariff.GetCost(e.GetDuration());
ua.ApplyCharge(cost);
stats.AddExpense(cost);
}
if (ua.money_ < 0) {
++negativeBalanceCount;
}
}
cout << stats.moneySpent_ << endl;
}
cout << negativeBalanceCount << endl;
}
Мы прошли путь от связанности…
50
Billing
Production
… к сплочённости
51
Production
Тесты
Tariff UserAccount
UserStats Billing
Мониторинг
Tariff
UserAccount EventUserStats
LoadUsersUserInfo
Инструмент
Tariff
UserAccount EventBilling
LoadUsersUserInfo
Особенности «большой картины»
Условия неопределённости
› Неизвестно, какие
инструменты будут нужны
› Неизвестно, какие
возможности надо будет
добавить в production
› Неизвестно, что надо
будет мониторить
52
Minimize coupling, maximize cohesion
сразу!
53
Production
Тесты
Tariff UserAccount
UserStats Billing
Мониторинг
Tariff
UserAccount EventUserStats
LoadUsersUserInfo
Инструмент
Tariff
UserAccount EventBilling
LoadUsersUserInfo
Billing
Слушатель в пятом ряду
│Зачем?
Отрефакторим,
когда будет надо
54
Докладчик
│Наличие
возможности не
означает, что ею
воспользуются
Докладчик
│Цена внесения
изменений может
быть слишком
высока
Minimize coupling, maximize cohesion
сразу!
57
Недостатки применения принципа
MCMC
› Приходится писать больше кода
› Много сущностей в публичном доступе
› Проблемы с discoverability
› Повышение порога входа в систему
› Более высокая ответственность при разработке
интерфейсов
58
Достоинства применения принципа
MCMC
› Упрощение повторного использования кода,
тестирования, создания служебных инструментов
› Низкая стоимость внесения изменений в систему
› Меньше глобальных рефакторингов
› Уменьшение порога входа в каждый отдельный
компонент
› Ваш код всегда в форме!
59
ishfb@yandex-team.ru
Спасибо
Илья Шишков
Старший разработчик компании Яндекс
telegram: ishfb
ishfb
Bonus track – сплочённость в детстве

More Related Content

What's hot

Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода Pavel Tsukanov
 
Антон Полухин, Немного о Boost
Антон Полухин, Немного о BoostАнтон Полухин, Немного о Boost
Антон Полухин, Немного о BoostSergey Platonov
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
 
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...Alexey Paznikov
 
Аскетичная разработка браузера
Аскетичная разработка браузераАскетичная разработка браузера
Аскетичная разработка браузераPlatonov Sergey
 
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...Alexey Paznikov
 
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...Alexey Paznikov
 
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Platonov Sergey
 
Об особенностях использования значимых типов в .NET
Об особенностях использования значимых типов в .NETОб особенностях использования значимых типов в .NET
Об особенностях использования значимых типов в .NETAndrey Akinshin
 
Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Sergey Schetinin
 
Максим Хижинский Lock-free maps
Максим Хижинский Lock-free mapsМаксим Хижинский Lock-free maps
Максим Хижинский Lock-free mapsPlatonov Sergey
 
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2Dima Dzuba
 
Объектно-ориентированное программирование. Лекции 9 и 10
Объектно-ориентированное программирование. Лекции 9 и 10Объектно-ориентированное программирование. Лекции 9 и 10
Объектно-ориентированное программирование. Лекции 9 и 10Dima Dzuba
 
Объектно-Ориентированное Программирование на C++, Лекции 3 и 4
Объектно-Ориентированное Программирование на C++, Лекции  3 и 4 Объектно-Ориентированное Программирование на C++, Лекции  3 и 4
Объектно-Ориентированное Программирование на C++, Лекции 3 и 4 Dima Dzuba
 
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
 
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Yandex
 
Объектно-ориентированное программирование. Лекция 5 и 6
Объектно-ориентированное программирование. Лекция 5 и 6Объектно-ориентированное программирование. Лекция 5 и 6
Объектно-ориентированное программирование. Лекция 5 и 6Dima Dzuba
 

What's hot (20)

Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 
Антон Полухин, Немного о Boost
Антон Полухин, Немного о BoostАнтон Полухин, Немного о Boost
Антон Полухин, Немного о Boost
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
 
C++ exceptions
C++ exceptionsC++ exceptions
C++ exceptions
 
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
 
Аскетичная разработка браузера
Аскетичная разработка браузераАскетичная разработка браузера
Аскетичная разработка браузера
 
Concepts lite
Concepts liteConcepts lite
Concepts lite
 
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. М...
 
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...
 
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
 
Об особенностях использования значимых типов в .NET
Об особенностях использования значимых типов в .NETОб особенностях использования значимых типов в .NET
Об особенностях использования значимых типов в .NET
 
Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование
 
Максим Хижинский Lock-free maps
Максим Хижинский Lock-free mapsМаксим Хижинский Lock-free maps
Максим Хижинский Lock-free maps
 
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
 
Объектно-ориентированное программирование. Лекции 9 и 10
Объектно-ориентированное программирование. Лекции 9 и 10Объектно-ориентированное программирование. Лекции 9 и 10
Объектно-ориентированное программирование. Лекции 9 и 10
 
Объектно-Ориентированное Программирование на C++, Лекции 3 и 4
Объектно-Ориентированное Программирование на C++, Лекции  3 и 4 Объектно-Ориентированное Программирование на C++, Лекции  3 и 4
Объектно-Ориентированное Программирование на C++, Лекции 3 и 4
 
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
 
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
 
Объектно-ориентированное программирование. Лекция 5 и 6
Объектно-ориентированное программирование. Лекция 5 и 6Объектно-ориентированное программирование. Лекция 5 и 6
Объектно-ориентированное программирование. Лекция 5 и 6
 

Similar to Фитнес для вашего кода: как держать его в форме

Jboss drools expert (ru)
Jboss drools expert (ru)Jboss drools expert (ru)
Jboss drools expert (ru)Victor_Cr
 
Stack energo
Stack energoStack energo
Stack energostackit
 
Парсим и кодогенерируем для С++ с использованием clang
Парсим и кодогенерируем для С++ с использованием clangПарсим и кодогенерируем для С++ с использованием clang
Парсим и кодогенерируем для С++ с использованием clangcorehard_by
 
Применение онлайн B2 b решений для повышения качества обслуживания бизнес кли...
Применение онлайн B2 b решений для повышения качества обслуживания бизнес кли...Применение онлайн B2 b решений для повышения качества обслуживания бизнес кли...
Применение онлайн B2 b решений для повышения качества обслуживания бизнес кли...WEB100Platform
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановСтатический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановYandex
 
Аспектно-Ориентированный Подход
Аспектно-Ориентированный ПодходАспектно-Ориентированный Подход
Аспектно-Ориентированный Подходakopium
 
управленческая отчетность. зоя стрелкова
управленческая отчетность. зоя стрелковауправленческая отчетность. зоя стрелкова
управленческая отчетность. зоя стрелковаTraining Institute - ARB Pro Group
 
Lviv iCamp 2014. Анна Боднарчук “Як правильно порахувати прибуток і ефективні...
Lviv iCamp 2014. Анна Боднарчук “Як правильно порахувати прибуток і ефективні...Lviv iCamp 2014. Анна Боднарчук “Як правильно порахувати прибуток і ефективні...
Lviv iCamp 2014. Анна Боднарчук “Як правильно порахувати прибуток і ефективні...Lviv Startup Club
 
ВЫРУЧКА (МСФО №18)(В рамках программы “Электронная коммерция”)
ВЫРУЧКА (МСФО №18)(В рамках программы “Электронная коммерция”) ВЫРУЧКА (МСФО №18)(В рамках программы “Электронная коммерция”)
ВЫРУЧКА (МСФО №18)(В рамках программы “Электронная коммерция”) Юрий Ж
 
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Ivan Tsyganov
 
Точка безубыточности. Затраты. EBITDA-показатель эффективности. Презентация п...
Точка безубыточности. Затраты. EBITDA-показатель эффективности. Презентация п...Точка безубыточности. Затраты. EBITDA-показатель эффективности. Презентация п...
Точка безубыточности. Затраты. EBITDA-показатель эффективности. Презентация п...Kharkov IT Cluster
 

Similar to Фитнес для вашего кода: как держать его в форме (14)

Jboss drools expert (ru)
Jboss drools expert (ru)Jboss drools expert (ru)
Jboss drools expert (ru)
 
Stack energo
Stack energoStack energo
Stack energo
 
Парсим и кодогенерируем для С++ с использованием clang
Парсим и кодогенерируем для С++ с использованием clangПарсим и кодогенерируем для С++ с использованием clang
Парсим и кодогенерируем для С++ с использованием clang
 
Применение онлайн B2 b решений для повышения качества обслуживания бизнес кли...
Применение онлайн B2 b решений для повышения качества обслуживания бизнес кли...Применение онлайн B2 b решений для повышения качества обслуживания бизнес кли...
Применение онлайн B2 b решений для повышения качества обслуживания бизнес кли...
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановСтатический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий Леванов
 
Всеведа
ВсеведаВсеведа
Всеведа
 
Аспектно-Ориентированный Подход
Аспектно-Ориентированный ПодходАспектно-Ориентированный Подход
Аспектно-Ориентированный Подход
 
управленческая отчетность. зоя стрелкова
управленческая отчетность. зоя стрелковауправленческая отчетность. зоя стрелкова
управленческая отчетность. зоя стрелкова
 
Lviv iCamp 2014. Анна Боднарчук “Як правильно порахувати прибуток і ефективні...
Lviv iCamp 2014. Анна Боднарчук “Як правильно порахувати прибуток і ефективні...Lviv iCamp 2014. Анна Боднарчук “Як правильно порахувати прибуток і ефективні...
Lviv iCamp 2014. Анна Боднарчук “Як правильно порахувати прибуток і ефективні...
 
ВЫРУЧКА (МСФО №18)(В рамках программы “Электронная коммерция”)
ВЫРУЧКА (МСФО №18)(В рамках программы “Электронная коммерция”) ВЫРУЧКА (МСФО №18)(В рамках программы “Электронная коммерция”)
ВЫРУЧКА (МСФО №18)(В рамках программы “Электронная коммерция”)
 
Mobilefest2012
Mobilefest2012Mobilefest2012
Mobilefest2012
 
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?Moscow Python Conf 2016. Почему 100% покрытие это плохо?
Moscow Python Conf 2016. Почему 100% покрытие это плохо?
 
Vuejs composition API
Vuejs composition APIVuejs composition API
Vuejs composition API
 
Точка безубыточности. Затраты. EBITDA-показатель эффективности. Презентация п...
Точка безубыточности. Затраты. EBITDA-показатель эффективности. Презентация п...Точка безубыточности. Затраты. EBITDA-показатель эффективности. Презентация п...
Точка безубыточности. Затраты. EBITDA-показатель эффективности. Презентация п...
 

Фитнес для вашего кода: как держать его в форме

  • 1.
  • 2. Фитнес для вашего кода: как держать его в форме Тренер – Илья Шишков
  • 3. Вместо введения Мои советы – это › мой личный опыт работы › над realtime backend’ом, который работает 24/7 › существует, развивается и поддерживается в течение многих лет 4
  • 4. Что такое код нашего проекта?
  • 5. За что нам платят деньги?
  • 6. За что нам платят деньги? 7 Код нашего проекта Код, который работает в production
  • 7. За что нам платят деньги? 8 Время
  • 8. Production надо поддерживать › Мониторинги › Тесты › Специальные отладочные и исследовательские инструменты 9
  • 9. Что такое код нашего проекта? 10 Код инструментов Код мониторингов Код тестов Код, который работает в production
  • 10. │Production – лишь один из сценариев использования кода
  • 11. Большая картина При проектировании и написании кода следует думать о большой картине › Как я буду это тестировать? › Какие параметры мне надо будет мониторить? › Какие инструменты мне могут понадобиться? 12 Production Мониторинг Тесты Инструменты
  • 12. │− Как? │− Minimize coupling, maximize cohesion
  • 13. Minimize coupling, maximize cohesion Coupling: ▌ Сопряжение ▌ Сцепка ▌ Связанность ▌ Взаимодействие 14 Cohesion: ▌ Сплочённость ▌ Единение ▌ Связанность ▌ Сцепление
  • 14. Связанность и сплочённость в жизни 15 Связанность Сплочённость
  • 15. Связанность и сплочённость в коде 16 void UpdateVector(vector<int>& v) { int x; cin >> x; v.push_back(x); } int ReadInt(istream& is) { int x; is >> x; return x; } void AddInt(vector<int>& v, int x) { v.push_back(x); } void UpdateVector(vector<int>& v) { AddInt(v, ReadInt(cin)); } Связанность Сплочённость
  • 16. Биллинг для мобильного оператора Снятие денег со счёта клиента за звонок › Вычислить стоимость звонка по его длительности › Списать деньги со счёта › Отправить SMS, если баланс стал отрицательным Пополнение счёта клиента › Зачислить деньги на счёт абонента › Отправить SMS о поступлении денег на счёт Сначала будем думать только о production 17
  • 17. Код для production Самый популярный принцип проектирования классов ▌ Напишем класс, в котором будет всё, что относится к биллингу 18 class Billing { public: Billing(string usersDatabase, string tariffsDatabase); void ChargePhoneCall(int userId, int minutes); void AddMoney(int userId, int rubles);
  • 18. 19 private: // class Billing void LoadUsers(string db); void LoadTariffs(string db); struct Tariff { int costPerMinute_; }; class UserInfo { public: void ChargePhoneCall(int minutes, const Tariff& tariff); void AddMoney(int rubles); int GetTariff() const; private: class Event; void SendSms(string message); int money_; int tariff_; vector<Event> history_; }; // class Billing::UserInfo vector<UserInfo> users_; vector<Tariff> tariffs_; }; // class Billing
  • 19. 20 Billing::Billing(string usersDatabase, string tariffsDatabase) { LoadUsers(usersDatabase); LoadTariffs(tariffsDatabase); } void Billing::ChargePhoneCall(int userId, int minutes) { UserInfo& user = users_[userId]; const Tariff& t = tariffs_[user.GetTariff()]; user.ChargePhoneCall(minutes, t); } void Billing::AddMoney(int userId, int rubles) { users_[userId].AddMoney(rubles); }
  • 20. 21 void Billing::UserInfo::ChargePhoneCall(int minutes, const Billing::Tariff& tariff) { money_ -= minutes * tariff.costPerMinute_; history_.push_back(Event::PhoneCall(minutes)); if (money_ <= 0) { SendSms("Your balance is " + to_string(money_)); } } void Billing::UserInfo::AddMoney(int rubles) { money_ += rubles; history_.push_back(Event::Payment(rubles)); SendSms("Got payment " + to_string(rubles)); }
  • 22. Очень высокая связанность › Загрузка пользователей из БД › Загрузка тарифов из БД › Расчёт стоимости звонка › Снятие и добавление денег на счёт клиента › Отправка SMS 23 class Billing { public: Billing(string usersDatabase, string tariffsDatabase); void ChargePhoneCall(int userId, int minutes); void AddMoney(int userId, int rubles); };
  • 23. Протестируем перед релизом 24 Список тестов › чем дольше звонок, тем он дороже › звонок нулевой длины не уменьшает количество денег на счету › пополнение счёта происходит корректно Автотесты Billing Production
  • 24. 25 void Billing::UserInfo::ChargePhoneCall(int minutes, const Billing::Tariff& tariff) { money_ -= minutes * tariff.costPerMinute_; history_.push_back(Event::PhoneCall(minutes)); if (money_ <= 0) { SendSms("Your balance is " + to_string(money_)); } } void Billing::UserInfo::AddMoney(int rubles) { money_ += rubles; history_.push_back(Event::Payment(rubles)); SendSms("Got payment " + to_string(rubles)); }
  • 25. 26 class Billing { … private: class UserInfo { … private: int money_; int tariff_; vector<Event> history_; }; }; struct Tariff { int costPerMinute_; int GetCost(int minutes) const { return minutes * costPerMinute_; } }; struct UserAccount { int money_ = 0; void ApplyCharge(int rubles) { money_ -= rubles; } void ApplyPayment(int rubles) { money_ += rubles; } }; UserAccount account_; struct Tariff { int costPerMinute_; };
  • 26. 27 void Billing::UserInfo::ChargePhoneCall(int minutes, const Billing::Tariff& tariff) { money_ -= minutes * tariff.costPerMinute_; history_.push_back(Event::PhoneCall(minutes)); if (money_ <= 0) { SendSms("Your balance is " + to_string(money_)); } } void Billing::UserInfo::AddMoney(int rubles) { money_ += rubles; history_.push_back(Event::Payment(rubles)); SendSms("Got payment " + to_string(rubles)); } account_.ApplyCharge(tariff.GetCost(minutes)); account_.ApplyPayment(rubles);
  • 27. 28 void TestTalkMorePayMore() { const Tariff tariff{1}; assert(tariff.GetCost(15) > tariff.GetCost(5)); } void TestNoChargeForZeroMinutes() { UserAccount ua{15}; const Tariff tariff{10}; ua.ApplyCharge(tariff.GetCost(0)); assert(ua.money_ == 15); } void TestPayment() { UserAccount ua{15}; ua.ApplyPayment(10); assert(ua.money_ == 25); }
  • 29. Мы частично заменили связанность… › Расчёт стоимости звонка › Уменьшение баланса клиента › Формирование истории действий пользователя › Отправка SMS 30 void Billing::UserInfo::ChargePhoneCall(int minutes, const Billing::Tariff& tariff) { money_ -= minutes * tariff.costPerMinute_; history_.push_back(Event::PhoneCall(minutes)); if (money_ <= 0) { SendSms("Your balance is " + to_string(money_)); } }
  • 30. … на сплочённость 31 struct Tariff { int costPerMinute_; int GetCost(int minutes) const; }; struct UserAccount { int money_ = 0; void ApplyCharge(int rubles); void ApplyPayment(int rubles); }; account_.ApplyCharge(tariff.GetCost(minutes)); › Мы смогли написать юнит- тесты › GetCost может быть сделан виртуальным › ApplyCharge может быть использован для других услуг
  • 31. Выкатили в production, хотим мониторинг › Считать количество отправленных SMS › Для каждого клиента считать сумму потраченных денег › Вывод статистики в формате XML 32 Billing Production Автотесты Tariff UserAccount Tariff UserAccount Мониторинг
  • 32. Считаем количество отправленных SMS 33 class Billing { public: void AddMoney(int userId, int rubles); void ChargePhoneCall(int userId, int minutes); void PrintStatsAsXml() { cout << "<sms>" << smsSent_ << "</sms>"; } int smsSent_ = 0; private: void LoadUsers(string db); void LoadTariffs(string db); class UserInfo; vector<UserInfo> users_; vector<Tariff> tariffs_; };
  • 33. 34 void Billing::ChargePhoneCall(int userId, int minutes) { UserInfo& user = users_[userId]; const Tariff& t = tariffs_[user.GetTariff()]; user.ChargePhoneCall(minutes, t); } void Billing::UserInfo::ChargePhoneCall(int minutes, const Tariff& tariff) { account_.ApplyCharge(tariff.GetCost(minutes)); history_.push_back(Event::PhoneCall(minutes)); if (account_.money_ <= 0) { SendSms("Your balance is " + to_string(account_.money_)); // Здесь надо увеличить Biiling::smsSent_ } }
  • 34. Считаем количество отправленных SMS Обработка счёта клиента и отправка SMS связаны друг с другом Разорвём эту связанность – вынесем отправку SMS в Billing 35 void Billing::UserInfo::ChargePhoneCall(int minutes, const Tariff& tariff) { account_.ApplyCharge(tariff.GetCost(minutes)); history_.push_back(Event::PhoneCall(minutes)); if (account_.money_ <= 0) { SendSms("Your balance is " + to_string(account_.money_)); // Здесь надо увеличить Biiling::smsSent_ } }
  • 35. 36 class Billing { private: // class Billing class UserInfo { public: void ChargePhoneCall(int minutes, const Tariff& tariff); void AddMoney(int rubles); int GetTariff() const; UserAccount account_; }; // class Billing::UserInfo vector<UserInfo> users_; vector<Tariff> tariffs_; }; // class Billing void SendSms(int userId, string message); void SendSms(string message); private:const UserAccount& GetAccount() const { return account_; }
  • 36. void Billing::ChargePhoneCall(int userId, int minutes) { UserInfo& user = users_[userId]; const Tariff& t = tariffs_[user.GetTariff()]; user.ChargePhoneCall(minutes, t); const int money = user.GetAccount().money_; if (money <= 0) { SendSms(userId, "Your balance is " + to_string(money)); } } void Billing::AddMoney(int userId, int rubles) { users_[userId].AddMoney(rubles); SendSms(userId, "Got payment " + to_string(rubles)); } 37 void Billing::ChargePhoneCall(int userId, int minutes) { UserInfo& user = users_[userId]; const Tariff& t = tariffs_[user.GetTariff()]; user.ChargePhoneCall(minutes, t); } void Billing::AddMoney(int userId, int rubles) { users_[userId].AddMoney(rubles); }
  • 37. 38 void Billing::SendSms(int userId, string message) { ++smsSent_; // ... }
  • 38. Считаем, сколько денег потратил клиент 39 struct UserStats { int moneySpent_ = 0; void AddExpense(int rubles) { moneySpent_ += rubles; } }; class Billing::UserInfo { public: void ChargePhoneCall(int minutes, const Tariff& tariff); void AddMoney(int rubles); int GetTariff() const; const UserAccount& GetAccount() const; private: class Event; int tariff_; UserAccount account_; vector<Event> history_; }; // class Billing::UserInfo UserStats stats_; const UserStats& GetStats() const { return stats_; }
  • 39. 40 void Billing::UserInfo::ChargePhoneCall(int minutes, const Tariff& tariff) { int cost = tariff.GetCost(minutes); account_.ApplyCharge(cost); history_.push_back(Event::PhoneCall(minutes)); void Billing::PrintStatsAsXml() { cout << "<sms>" << smsSent_ << "</sms>"; } for (const UserInfo& u : users_) { cout << "<spent>" << u.GetStats().moneySpent_ << "</spent>"; } stats_.AddExpense(cost);}
  • 41. Добавили мониторинг Мы заменили связанность ▌ Отправка SMS была связана с обработкой счёта клиента на сплочённость ▌ Отправка SMS выполняется классом Billing и смогли добавить мониторинги, не вмешиваясь в логику других компонентов системы 42
  • 42. Теперь хотим проводить исследования 43 Billing Production Автотесты Tariff UserAccount Tariff UserAccount Инструменты BillingUserInfo Мониторинг
  • 43. Теперь хотим проводить исследования Хотим провести исследование нового тарифа ▌ Дать каждому клиенту начальный баланс в 500 рублей ▌ Повторить все действия из его истории ▌ Посчитать › количество потраченных денег › сколько раз баланс оказывался отрицательным 44
  • 44. Класс Billing нам не поможет › Загрузка клиентов и тарифов связаны друг с другом › Шлёт SMS клиентам 45 class Billing { public: Billing(string usersDatabase, string tariffsDatabase) { LoadUsers(usersDatabase); LoadTariffs(tariffsDatabase); } void ChargePhoneCall(int userId, int minutes); void AddMoney(int userId, int rubles); private: void LoadUsers(string db); void LoadTariffs(string db); vector<UserInfo> users_; vector<Tariff> tariffs_; };
  • 45. Заменим связанность на сплочённость 46 class Billing { public: Billing(string usersDatabase, string tariffsDatabase) void LoadUsers(string db); void LoadTariffs(string db); vector<UserInfo> LoadUsers(string db); vector<Tariff> LoadTariffs(string db); private: : users_(LoadUsers(usersDatabase)) , tariffs_(LoadTariffs(tariffsDatabase)) {} { LoadUsers(usersDatabase); LoadTariffs(tariffsDatabase); } // Не компилируется, UserInfo – приватный класс class UserInfo; vector<UserInfo> users_; vector<Tariff> tariffs_; };
  • 46. 47 vector<UserInfo> users_; vector<Tariff> tariffs_; }; // class Billing vector<UserInfo> LoadUsers(string db); vector<Tariff> LoadTariffs(string db); class Billing { private: class UserInfo { public: void ChargePhoneCall(int minutes, const Tariff& tariff); void AddMoney(int rubles); int GetTariff() const; const UserAccount& GetAccount() const { return account_; } const UserStats& GetStats() const { return stats_; } private: class Event; int tariff_; UserStats stats_; UserAccount account_; vector<Event> history_; }; // class Billing::UserInfo
  • 48. 49 void InvestigateNewTariff() { const Tariff newTariff{2}; vector<UserInfo> users = LoadUsers("users.sql"); int negativeBalanceCount = 0; for (const UserInfo& realUser : users) { UserAccount ua{500}; UserStats stats; for (const Event& e : realUser.GetHistory()) { if (e.IsPayment()) { ua.ApplyPayment(e.GetMoney()); } else { int cost = newTariff.GetCost(e.GetDuration()); ua.ApplyCharge(cost); stats.AddExpense(cost); } if (ua.money_ < 0) { ++negativeBalanceCount; } } cout << stats.moneySpent_ << endl; } cout << negativeBalanceCount << endl; }
  • 49. Мы прошли путь от связанности… 50 Billing Production
  • 50. … к сплочённости 51 Production Тесты Tariff UserAccount UserStats Billing Мониторинг Tariff UserAccount EventUserStats LoadUsersUserInfo Инструмент Tariff UserAccount EventBilling LoadUsersUserInfo
  • 51. Особенности «большой картины» Условия неопределённости › Неизвестно, какие инструменты будут нужны › Неизвестно, какие возможности надо будет добавить в production › Неизвестно, что надо будет мониторить 52
  • 52. Minimize coupling, maximize cohesion сразу! 53 Production Тесты Tariff UserAccount UserStats Billing Мониторинг Tariff UserAccount EventUserStats LoadUsersUserInfo Инструмент Tariff UserAccount EventBilling LoadUsersUserInfo Billing
  • 53. Слушатель в пятом ряду │Зачем? Отрефакторим, когда будет надо 54
  • 56. Minimize coupling, maximize cohesion сразу! 57
  • 57. Недостатки применения принципа MCMC › Приходится писать больше кода › Много сущностей в публичном доступе › Проблемы с discoverability › Повышение порога входа в систему › Более высокая ответственность при разработке интерфейсов 58
  • 58. Достоинства применения принципа MCMC › Упрощение повторного использования кода, тестирования, создания служебных инструментов › Низкая стоимость внесения изменений в систему › Меньше глобальных рефакторингов › Уменьшение порога входа в каждый отдельный компонент › Ваш код всегда в форме! 59
  • 60. Bonus track – сплочённость в детстве