27
Алексей Машанов Поэтапный рефакторинг: success story

Aleksey Mashanov Rit

  • Upload
    rit2010

  • View
    1.429

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Aleksey  Mashanov Rit

Алексей Машанов

Поэтапный рефакторинг: success story

Page 2: Aleksey  Mashanov Rit

Цели рефакторинга● Упрощение добавления новых возможностей за счет

возможности реиспользования кода.

● Упрощение сопровождения кода за счет приведения его в человекочитаемый вид, нормализации кода и структуры базы.

● Избавление от велосипедов и перенос тем самым головной боли по их развитию и поддержке на сообщество.

Page 3: Aleksey  Mashanov Rit

Характеристики системы● Perl + PostgreSQL● ~ 1200 модулей и 400 скриптов● ~ 300000 строк чистого кода● ~ 450 таблиц в БД● ~ 150 хранимых процедур и 140

триггеров

Page 4: Aleksey  Mashanov Rit

Разбиение на этапы

t

Релиз (3­4 недели) Шаг рефакторинга — изменение не сказывающееся на работоспособности системы

Этап рефакторинга — должен укладываться в рамки одного релиза

коммит

Рефакторинг выполняется в основной ветке разработки

Page 5: Aleksey  Mashanov Rit

Test Driven RefactioringДля каждого вносимого в код изменения

Написание автотеста

Проверка автотеста путем поломки тестируемого кода

Внесение модификации (рефакторинг)

Проверка модифицированного кода автотестом

Page 6: Aleksey  Mashanov Rit

Структура автотестов

Class1

Class2

Class3

Class2::Sub1

Class2::Sub2

Class1::Test

Class2::Test

Class3::Test

Class2::Sub1::Test

Class2::Sub2::Test

Test::Class

My::Testlib/ t/lib/ rollback после каждого теста

Page 7: Aleksey  Mashanov Rit

I. Замена самописных ORMна DBIx::Class

Page 8: Aleksey  Mashanov Rit

Структура до рефакторингаEnt Ent::Smth11

Ent::Smth12

Ent::Smth1N

new()

get()

set()

list()

save()

Entity Entity::Smth11

Entity::Smth12

Entity::Smth1N

new()

get()

set()

list()

save()

… …

● Два примерно одинаковых ORM

● Методы модификации и поиска объединены в одном классе

● Доступ к полям объекта как к элементам хэша

Page 9: Aleksey  Mashanov Rit

Что хотим получитьDBIx::Class::Row Schema::Result::Smth11

Schema::Result::Smth1N

Schema::Result::Smth12

Schema::Result::Smth21

Schema::Result::Smth2N

Schema::Result::Smth22

DBIx::Class::ResultSet Schema::ResultSet::Smth11

Schema::ResultSet::Smth1N

Schema::ResultSet::Smth12

Schema::ResultSet::Smth21

Schema::ResultSet::Smth2N

Schema::ResultSet::Smth22

new()get_column()set_column()insert()update()

search()…

● Один ORM

● Методы модификации и поиска в разных классах

● Доступ к полям объекта через акцессоры

Page 10: Aleksey  Mashanov Rit

Зачем?● До рефакторинга

● Два самописных ORM в одной системе это слишком много

● Оба из них не поддерживают отношений между таблицами, тем не менее они нам необходимы, что приводит к обилию в коде plain SQL запросов

● Вновьприбывшие разработчики вынуждены с ними разбираться и вникать в их отличия

● После рефакторинга● Много новых хороших возможностей, которым мы все очень рады

● Мы не одни во вселенной: почти все что нам может понадобиться уже изобрели, реализовали, отладили и устранили почти все баги, а какие не устранили, устраняют довольно-таки быстро

● Опыт работы с DBIx::Class разработчику пригодится не только для работы над нашей системой, поэтому он с большей вероятностью потрудится разобраться в нем поглубже

Page 11: Aleksey  Mashanov Rit

Создание схемыtabletabletable

схемаDBIx::Class::Schema::Loader

code style conventionssimple perl script

Schema::Result::*выстраиваем нужную 

иерархию

DBIx::Class::SchemaИспользуем статическую схему

Page 12: Aleksey  Mashanov Rit

Схема обертки

Ent::SmthN

dbic_class()Ent::Smth2

dbic_class()

Ent EntHash

Ent::Smth1

Schema::Result::SmthX

Schema::ResultSet::SmthX

tiehash

list()

search()

FETCH()

STORE()

EXISTS()

NEXTKEY()

get_coumn()

set_column()

has_column_loaded()

columns()

_DBIC_

_DBICRS_

insert_or_update()

new()

get()set()

save()

dbic_class()

Page 13: Aleksey  Mashanov Rit

Callback методы

Schema::Result::*

DBIC::EntCallback

insert()

update()

delete()

DBIx::Class::Core

caller() eq 'Ent'да

Ent::*

save()

delete()

нет

Page 14: Aleksey  Mashanov Rit

Неспешная миграцияEnt::XXX­>... Schema­>resultset('XXX')­>...

SELECT * FROM xxx Schema­>resultset('XXX')­>select()

Ent::XXX­>save() Schema::Result::XXX­>insert()

Schema::Result::XXX­>update()

Schema::Result::XXX­>delete()Ent::XXX­>delete()

1.

2.

3.

Page 15: Aleksey  Mashanov Rit

Завершение рефакторинга● Удаление иерархии старых ORM

Page 16: Aleksey  Mashanov Rit

Timeline рефакторинга

t

Схема таблиц Schema::*

Обертка Ent вокруг DBIx::Class

Callback методов

Замена Ent::* →Schema­>resultset('*')

Избавление

от plain SQL

Перенос хуков в

Schema::Result::*

Удаление старого ORM

1 релиз

1 релиз

N релизов

Page 17: Aleksey  Mashanov Rit

II. Единый механизмхранения сущностей

Page 18: Aleksey  Mashanov Rit

Исходная структура

User Server VDS

Service

Lbill Client● Service связан с объектом одного из трех

классов, а не с одним.

● User, Server, VDS имеют примерно одинаковый набор финансовых полей, но не используют наследование.

● Лишняя связь от User, Server, VDS к Client.

● Сложные запросы к базе со множественными LEFT JOIN.

● Добавление новой сущности приводит к созданию 1 класса, 3 связей и модификации Service.

Page 19: Aleksey  Mashanov Rit

Желаемая структура

Client

Lbill

Entity

User

Server

VDSService

● Добавление новой сущности приводит к добавлению 1 класса и 1 связи.

● Финансовые операции ограничены работой с Entity, а не с тремя User, Server, VDS.

● При добавлении новой сущности большинство возможностей (кроме технических) - «из коробки».

● Нет лишних связей (нормализация).

Page 20: Aleksey  Mashanov Rit

Структура базыvz_vds

id

entity_id

технические поля

servers

id

entity_id

технические поля

vz_vds

lbill_id

client_idid

финансовые поля

технические поля

servers

lbill_id

client_idid

финансовые поля

технические поля

users

lbills

clients

Было Стало

entities

users

services services

entity_iduser_id

server_id

vz_vds_id

id

entity_id

технические поля

id

lbill_id

финансовые поля

id

client_id

id

lbill_id

client_idid

финансовые поля

технические поля

Page 21: Aleksey  Mashanov Rit

Миграционные триггеры

vz_vdsservers

entities

users

AFTER UPDATEОбновление соответствующих финансовых полей в таблицах users, servers, vz_vds при их изменении

BEFORE INSERT1.Проверка, что все синхронизируемые из entities поля IS NULL — это означает, что не выполняется попытка установить их значение при INSERT2.Автоматическое заполнение синхронизируемых полей данными из соответствующей записи в entities

Проверка, что все значения полей соответствуют значениям всех соответствующих полей в таблице entities

AFTER INSERT OR UPDATE

Page 22: Aleksey  Mashanov Rit

Заполнение даннымиvz_vds

id entity_id24786 138798 278969 3

serversid entity_id

24786 138798 278969 3

entitiesid123

usersid entity_id

24786 138798 278969 3INSERT

UPDATE SET entity_id

servicesid user_id

724786 78969338798978969

server_id

6783

vds_id

2786

entity_id3

26365

1.

2.

UPDATE SET entity_id

Page 23: Aleksey  Mashanov Rit

Обертка в ORM

Ent::User

Ent

EntHash

EntHash::User

Schema::Result::User

Schema::Result::Entity

Schema::Result::Lbill

hash_class()

tiehash

EntHash::ProxyAux

EntHash::Proxy

client_id

is_proxied()

is_proxied()

Schema::ResultSet::User

list() search(){ prefetch => { entity =>'lbill' } }

client_idis_proxied($_)

lbill.client_identity.$_

Page 24: Aleksey  Mashanov Rit

Неспешная миграция

entity.$field

users.$fields

servers.$fieldsvz_vds.$field

services.user_id

services.server_idservices.vds_id

services.entity_id

1.

2. Ent::XXX­>new() Schema­>resultset('XXX')­>new()

3. SELECT * FROM xxx Schema­>resultset('XXX')­>search()

Page 25: Aleksey  Mashanov Rit

Завершение рефакторинга● Удаление переехавших в entities полей из

таблиц users, servers, vz_vds; полей user_id, server_id, vz_vds_id из таблицы services

● Удаление миграционных триггеров● Удаление оберточных классов и прочих

миграционных подпорок

Page 26: Aleksey  Mashanov Rit

Timeline рефакторинга

t

Создание таблиц

Написание триггеров Обертка ORMЗаполнение даннымиЗамена plain SQLмодификаций

Удаление ненужных полей и подпорок

Замена plain SQLзапросов

users.$field →entities.$field

1 релиз

1 релиз

N релизов

Page 27: Aleksey  Mashanov Rit

Вопросы?