Понякога клиентите ни дават заявки за функции, които наистина не ни харесват. Не че не харесваме нашите клиенти - ние обичаме нашите клиенти. Не че функцията не ни харесва - повечето искани от клиента функции са перфектно съобразени с техните бизнес цели и приходи.
Понякога не ни харесва заявка за функция, защото най-лесният начин да я решим е да напишем лош код и нямаме елегантно решение в горната част на главите си. Това ще изпрати много от нас Разработчици на релси за безплодни търсения RubyToolbox , GitHub , блогове за разработчици и StackOverflow търси скъпоценен камък или плъгин или примерен код, който ще ни накара да се чувстваме по-добре за себе си.
Понякога не ни харесва заявка за функция, защото най-лесният начин да я разрешим е да напишем лош код. TweetЕ, тук съм, за да ви кажа: добре е да пишете лош код. Понякога лошият код на Rails е по-лесно да се преработи в красив код, отколкото лошо обмислено решение, внедрено при времева криза.
Това е процесът на реконструкция на Rails, който обичам да следвам, когато масажирам проблеми от моите ужасни решения за лепене:
За алтернативна перспектива, тук е Git регистър на регистрацията за функция, която е реконструирана стъпка по стъпка:
И ето още една интересна статия за мащабен рефакторинг от колега в мрежата ApeeScape.
Нека да видим как се прави.
Етап 1. Започнете в Views
Да кажем, че започваме билет за нова функция. Клиентът ни казва: „Посетителите трябва да могат да видят списък с активни проекти на страницата за приветствие.“
Този билет изисква видима промяна, така че разумно място за започване на работа ще бъде в Views. Проблемът е ясен и такъв, който всички сме били обучавани да решаваме многократно. Ще го реша Грешният път и демонстрирам как да рефакторирам решението си в подходящите области. Решаване на проблем Грешният път може да ни помогне да преодолеем гърбицата на непознаването на правилното решение.
За начало приемете, че имаме модел, наречен Project
с булев атрибут, наречен active
. Искаме да получим списък с всички Projects
където active
е равно на true
, така че можем да използваме Project.where(active: true)
и да го преместваме с each
блок.
app/views/pages/welcome.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
Знам какво казвате: „Това никога не би преминало преглед на код“ или „Моят клиент със сигурност би ме уволнил за това“. Да, това решение нарушава разделянето на проблемите на Model-View-Controller, може да доведе до безстопанствени повиквания към база данни, които са трудни за проследяване и може да стане трудно да се поддържат в бъдеще. Но помислете за стойността на това Грешният път :
Стъпка 2. Частици
След като го направи Грешният път , Чувствам се зле за себе си и искам да изолирам лошия си код. Ако тази промяна очевидно се отнасяше само до слоя View, бих могъл да преправя срама си частично.
app/views/pages/welcome.haml: = render :partial => 'shared/projects_list' app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
Това е малко по-добре. Ясно е, че все още правим грешката на заявка за модел в изглед, но поне когато по-късно влезе поддържащ и види ужасното ми частично, те ще имат ясен начин да се справят с проблема с кода на Rails. Поправянето на нещо очевидно тъпо винаги е по-лесно от поправянето на лошо внедрена, бъги абстракция.
Поправянето на нещо очевидно тъпо винаги е по-лесно от поправянето на лошо внедрена, бъги абстракция. TweetСтъпка 3. Помощници
Helpers in Rails са начин за създаване на DSL (специфичен за домейн език) за част от вашите Изгледи. Трябва да пренапишете кода си, като използвате content_tag вместо хамъл или HTML, но вие получавате предимството да имате право да манипулирате структури от данни, без други разработчици да ви ядосват за 15 реда непечатни кодове за преглед.
Ако трябваше да използвам помощници тук, вероятно бих рефакторирал li
етикет.
app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| = project_list_item(project) app/helpers/projects_helper.rb: def project_list_item(project) content_tag(:li, :class => 'project') do link_to project_path(project), project.name end end
Стъпка 4. Преместете го на контролерите
Може би вашият ужасен код не е само загриженост за View. Ако вашият код все още мирише, потърсете заявки, които можете да прехвърлите от изгледите към контролерите.
app/views/shared/projects_list.haml: %ul.projects - @projects_list.each do |project| = project_list_item(project) app/controllers/pages_controller.rb: def welcome @projects = Project.where(active: true) end
Стъпка 5. Филтри за контролери
Най-очевидната причина за преместване на код в контролер before_filter
или after_filter
е за код, който сте дублирали в множество действия на контролера. Можете също да преместите код в Филтър на контролера ако искате да отделите целта на действието на контролера от изискванията на вашите възгледи.
app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end
Стъпка 6. Контролер на приложения
Да предположим, че се нуждаете от кода си, за да се показва на всяка страница, или искате да направите помощните функции на контролера достъпни за всички контролери, можете да преместите функцията си в ApplicationController. Ако промените са глобални, може да искате да промените и оформлението на приложението си.
app/controllers/pages_controller.rb: def welcome end app/views/layouts/application.haml: %ul.projects - projects_list.each do |project| = project_list_item(project) app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.where(active: true) end
Стъпка 7. Рефакторинг към модела
Както гласи девизът на MVC: Дебел модел, кльощави контролери и тъпи гледки . Очаква се да преработим всичко, което можем, в модела и е вярно, че най-сложната функционалност в крайна сметка ще се превърне в асоциации на модели и методи. Винаги трябва да избягваме да правим форматиране / преглед на нещата в Модела, но трансформирането на данни в други видове данни е допустимо. В този случай най-доброто нещо за рефакториране в модела би било where(active: true)
клауза, която можем да превърнем в обхват. Използването на обхват е ценно не само защото прави повикването да изглежда по-хубаво, но ако някога сме решили да добавим атрибут като delete
или outdated
, можем да модифицираме този обхват, вместо да преследваме всички наши where
клаузи.
app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.active end app/models/project.rb: scope :active, where(active: true)
Стъпка 8. Филтри за модели
Нямаме конкретна употреба за модела before_save
или after_save filters
в този случай, но следващата стъпка, която обикновено предприемам, е да преместя функционалността от контролери и методи на модел във филтри за модели.
Да кажем, че имахме друг атрибут, num_views
. Ако num_views > 50
, Проектът става неактивен. Можем да разрешим този проблем в изгледа, но извършването на промени в базата данни в изгледа е неподходящо. Бихме могли да го разрешим в контролера, но нашите контролери трябва да са възможно най-тънки! Можем да го разрешим лесно в Модела.
app/models/project.rb: before_save :deactivate_if_over_num_views def deactivate_if_over_num_views if num_views > 50 self.active = false fi end
Забележка: трябва да избягвате обажданията self.save
във филтър за модел, тъй като това причинява рекурсивно съхраняване на събития, а слоят за манипулиране с база данни на вашето приложение трябва да бъде контролерът така или иначе.
Стъпка 9. Библиотеки
Понякога функцията ви е достатъчно голяма, за да може да гарантира, че е собствена библиотека. Може да искате да го преместите в библиотечен файл, защото той се използва повторно на много места или е достатъчно голям, за да искате да го разработите отделно.
Добре е да съхранявате библиотечни файлове в lib / директория, но докато растат, можете да ги прехвърлите в истински RubyGem ! Основно предимство на преместването на вашия код в библиотека е, че можете да тествате библиотеката отделно от вашия модел.
Така или иначе, в случай на списък с проекти, бихме могли да оправдаем преместването на scope :active
обаждане от Project
модел в библиотечен файл и го върнете обратно в Ruby:
app/models/project.rb: class Project 50 self.active = false end end end
Забележка: self.included
метод се извиква, когато се зарежда клас Rails Model и преминава в обхвата на класа като променливата k
В този урок за рефакторинг на Ruby on Rails взехме по-малко от 15 минути и внедрихме решение и го поставихме на етап за потребителско тестване, готово да бъде прието в набора от функции или премахнато. До края на процеса на рефакторинг имаме част от кода, която определя рамка за внедряване на списъчно-активиращи се елементи в множество модели, които биха преминали дори и най-строгия процес на преглед.
В собствения си процес на рефакторинг на Rails, не се колебайте да пропуснете няколко стъпки надолу по тръбопровода, ако сте уверени в това (напр. Прескочете от View към Controller или Controller to Model). Само имайте предвид, че потокът от код е от изглед към модел.
Не се страхувайте да изглеждате глупаво. Това, което разделя съвременните езици от старите приложения за визуализиране на шаблони CGI, не е, че правим всичко Правилният начин всеки път - това е, че отделяме време за рефакториране, повторно използване и споделяне на нашите усилия.
Свързани: Съкращаване на клеймото за време: Приказка за ActiveRecord на Ruby on Rails