Build Dumb, Refactor Smart: Ruby on Rails 코드에서 문제를 해결하는 방법
게시 됨: 2022-03-11때때로 클라이언트는 우리가 정말 좋아하지 않는 기능을 요청합니다. 우리가 고객을 좋아하지 않는 것이 아니라 고객을 사랑합니다. 기능이 마음에 들지 않는다는 것은 아닙니다. 대부분의 클라이언트 요청 기능은 비즈니스 목표 및 수입과 완벽하게 일치합니다.
때때로 우리는 기능 요청을 좋아하지 않습니다. 왜냐하면 그것을 해결하는 가장 쉬운 방법은 잘못된 코드를 작성하는 것이고 우리 머리 위에는 우아한 솔루션이 없기 때문입니다. 이것은 RubyToolbox, GitHub, 개발자 블로그, StackOverflow를 통한 무익한 검색으로 많은 Rails 개발자에게 우리 자신에 대해 더 나은 느낌을 줄 보석이나 플러그인 또는 예제 코드를 찾게 만들 것입니다.
Ruby on Rails 코드 리팩터링
글쎄, 나는 당신에게 말하려고 여기 있습니다. 나쁜 코드를 작성하는 것은 괜찮습니다. 때로는 잘못된 Rails 코드가 시간이 촉박한 상황에서 구현된 제대로 생각하지 못한 솔루션보다 아름다운 코드로 리팩토링하는 것이 더 쉽습니다.
이것은 내 끔찍한 반창고 솔루션에서 문제를 해결할 때 따르고 싶은 Rails 리팩토링 프로세스입니다.
다른 관점에서 다음은 단계별로 리팩토링된 기능에 대한 Git 커밋 로그입니다.
그리고 여기 Toptal 네트워크 동료의 대규모 리팩토링에 대한 또 다른 흥미로운 기사가 있습니다.
어떻게 되었는지 봅시다.
관점들
1단계. 보기에서 시작
새로운 기능에 대한 티켓을 시작한다고 가정해 보겠습니다. 클라이언트는 "방문자는 환영 페이지에서 활성 프로젝트 목록을 볼 수 있어야 합니다."라고 말합니다.
이 티켓은 눈에 보이는 변경이 필요하므로 작업을 시작하기에 적절한 위치는 보기입니다. 문제는 간단하고 우리 모두가 여러 번 해결하도록 훈련받은 문제입니다. 나는 그것을 잘못된 방식 으로 해결하고 내 솔루션을 적절한 영역으로 리팩토링하는 방법을 보여줄 것입니다. 문제 해결 잘못된 방법 은 올바른 솔루션을 모른다는 고비를 극복하는 데 도움이 됩니다.
시작하려면 active
라는 부울 속성이 있는 Project
라는 모델이 있다고 가정합니다. active
가 true
와 같은 모든 Projects
의 목록을 얻으려면 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
나는 당신이 말하는 것을 압니다: "그것은 절대 코드 리뷰를 통과하지 못할 것입니다." 또는 "내 고객이 이것 때문에 나를 해고할 것입니다." 예, 이 솔루션은 모델-뷰-컨트롤러의 문제 분리를 깨고 추적하기 어려운 표류 데이터베이스 호출을 초래할 수 있으며 향후 유지 관리가 어려워질 수 있습니다. 그러나 잘못된 방법 으로 수행하는 것의 가치를 고려하십시오.
- 스테이징에서 15분 이내에 이 변경 사항을 얻을 수 있습니다.
- 그대로 두면 이 블록을 쉽게 캐시할 수 있습니다.
- 이 Rails 문제를 수정하는 것은 간단합니다(하급 개발자에게 제공될 수 있음).
2단계. 부분
Wrong Way 를 수행한 후에는 나 자신에 대해 기분이 좋지 않고 내 잘못된 코드를 격리하고 싶습니다. 이 변경 사항이 분명히 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
조금 나아졌네요. 분명히 우리는 여전히 View에서 Model 쿼리의 실수를 저지르고 있지만 적어도 유지 관리자가 나중에 와서 내 끔찍한 부분을 볼 때 특히 Rails 코드 문제를 해결하는 간단한 방법을 갖게 될 것입니다. 분명히 멍청한 것을 수정하는 것이 제대로 구현되지 않고 버그가 있는 추상화를 수정하는 것보다 항상 쉽습니다.
3단계 . 도우미
Rails의 도우미는 보기 섹션에 대한 DSL(Domain Specific Language)을 만드는 방법입니다. haml이나 HTML 대신 content_tag를 사용하여 코드를 다시 작성해야 하지만 인쇄되지 않는 15줄의 View 코드에 대해 다른 개발자의 눈에 띄지 않고 데이터 구조를 조작할 수 있다는 이점을 얻을 수 있습니다.
여기에서 도우미를 사용한다면 아마도 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의 모토는 Fat Model, Skinny Controllers, and Dumb Views 입니다. 우리는 우리가 할 수 있는 모든 것을 모델로 리팩토링할 것으로 예상되며, 가장 복잡한 기능이 결국 모델 연결 및 모델 메서드가 되는 것이 사실입니다. 모델에서 형식 지정/보기 작업을 항상 피해야 하지만 데이터를 다른 유형의 데이터로 변환하는 것은 허용됩니다. 이 경우 모델로 리팩토링하는 가장 좋은 방법은 범위로 전환할 수 있는 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
이면 프로젝트가 비활성화됩니다. View에서 이 문제를 해결할 수 있지만 View에서 데이터베이스를 변경하는 것은 부적절합니다. 컨트롤러에서 해결할 수 있지만 컨트롤러는 가능한 한 얇아야 합니다! Model에서 쉽게 해결할 수 있습니다.
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으로 전송할 수 있습니다! 코드를 라이브러리로 이동하는 주요 이점은 모델과 별도로 라이브러리를 테스트할 수 있다는 것입니다.
어쨌든 프로젝트 목록의 경우 Project
모델에서 scope :active
호출을 라이브러리 파일로 옮기고 이를 다시 Ruby로 가져오는 것을 정당화할 수 있습니다.
app/models/project.rb: class Project < ActiveRecord::Base include Activeable before_filter :deactivate_if_over_num_views end lib/activeable.rb: module Activeable def self.included(k) k.scope :active, k.where(active: true) end def deactivate_if_over_num_views if num_views > 50 self.active = false end end end
참고: self.included
메소드는 Rails 모델 클래스가 로드되고 클래스 범위에 변수 k
로 전달될 때 호출됩니다.
결론
이 Ruby on Rails 리팩토링 튜토리얼에서 우리는 15분 미만의 시간을 들여 솔루션을 구현하고 사용자 테스트를 위한 준비 단계에 올렸으며 기능 세트에 수용되거나 제거될 준비가 되었습니다. 리팩토링 프로세스가 끝나면 가장 엄격한 검토 프로세스를 통과하는 여러 모델에 걸쳐 나열 가능하고 활성화할 수 있는 항목을 구현하기 위한 프레임워크를 배치하는 코드 조각이 있습니다.
자신의 Rails 리팩토링 프로세스에서 자신이 있다면 파이프라인의 몇 단계를 건너뛰어도 됩니다(예: 보기에서 컨트롤러로 또는 컨트롤러에서 모델로 이동). 코드의 흐름은 보기에서 모델로 진행된다는 점을 염두에 두십시오.
바보처럼 보이는 것을 두려워하지 마십시오. 오래된 CGI 템플릿 렌더링 응용 프로그램과 현대 언어를 구분하는 것은 매번 올바른 방식 으로 모든 작업을 수행한다는 것이 아니라 시간을 들여 리팩토링하고, 재사용하고, 노력을 공유한다는 것입니다.