refactoring with ruby (리펙토링 루비)
Post on 13-Apr-2017
51 Views
Preview:
TRANSCRIPT
리펙토링 루비4 번째 발표
컬렉션 캡슐화248p
준비• 컬렉션이란 ?– 1 개 이상의 엘리먼트들을 담는 자료 구조– C++ : 컨테이너• Vector, map, set, hash_map, …
• 컬렉션 캡슐화란 ?–컬렉션을 캡슐화 하여 직접 읽거나 쓰지 않고 컬렉션 접근 메소드를 통해 읽거나 쓰도록 하는 것 .
동기• 의도되지 않은 자료 조작을 방지– Add/Remove 할 때 추가적인 처리가 필요한 경우–상황에 따라 Add/Remove 등의 조작을 제한하고 싶을 경우–…
• 내부 데이터 구조 노출–구조 변경이 어려워짐–클라이언트에게 불필요한 인식을 요구
1. add/remove 메소드 추가• def add_course(course)
@courses << courseend
def remove_course(course)@courses.delete(course)
end
2. 컬렉션 초기화• Def initialize
@courses = []end
3. Setter 호출 부분 수정(before)
• kent = Person.new• courses = []• courses << Course.new("Smalltalk
Programming", false)• courses <<
Course.new("Appreciating Single Malts", true)
• kent.course =courses
3. Setter 호출 부분 수정 (after)• kent = Person.new• Kent.add_course(Course.new("Smallt
alk Programming", false))• Kent.add_course(Course.new("Apprec
iating Single Malts", true))
4. Getter 가 사본을 리턴하도록 수정• Def courses
@courses.dupend
• Def courses@courses.class
end
5. 컬렉션을 사용하는 객체로 들어가야 할 기능 추출 및 이동 (before)• number_of_advanced_courses = ken-
t.courses.select do |course|course.advanced?
end.size
5. 컬렉션을 사용하는 객체로 들어가야 할 기능 추출 및 이동 (after1)• def number_of_advanced_courses
kent.courses.select { |course| course.advanced? }.size
end
5. 컬렉션을 사용하는 객체로 들어가야 할 기능 추출 및 이동 (after2)• def number_of_advanced_courses
@courses.select { |course| course.advanced? }.size
end• Def number_of_courses
@courses.sizeend
레코드를 데이터 클래스로 전환254p
준비• 레코드란 ?–일련의 기록
• 덤 데이터 객체 (dumb data object) 란 ?–데이터만 존재하는 클래스 ( 구조체 )–데이터와 인터페이스가 함께 존재해야 한다는 객체 지향의 원칙과는 맞지 않음
동기• 레코드 구조를 “가독성 있고 편리하게” 인터페이싱 할 수 있도록 해줌
– 배열을 객체로 전환과 비슷• Struct UserData• {
– Name– Age– Email
• }• Open 회원가입 Dialog(UserData data)
방법• 레코드를 나타낼 클래스를 작성• 클래스에 필드 추가 및 필요한 항목에 대한 접근 메서드 작성• 끝–나머진 후에 나옴 .( 다른 분께서… )
타입 코드를 재정의로 전환255p
준비• 타입 코드란 ?–흔히 말하는 enum• BikeType_Rigid
BikeType_FrontSuspensionBikeType_FullSuspension
• 재정의란 ?–타입별 기능에 대한 재정의를 말함 ( 원서 225p)
동기• 조건문 제거–코드의 복잡성을 낮춤
1. 타입에 해당하는 클래스 작성 및 기본 클래스를 모듈로 변환• Class RigidMountainBike
include MoutainBikeend
Class FrontSuspensionMountainBikeinclude MountainBike
end
Class FullSuspensionMountainBike include MountainBike
end
Class module MountainBike….
2. 원본 클래스 생성을 원하는 타입의 클래스 생성으로 대체 및 테스트• Bike = MountainBike.new(…)• -> Bike = FrontSuspensionMountain-
Bike.new(…)
3. 타입 코드에 의존적인 메서드 중 하나를 각 타입 클래스에 맞게 재정의 및 테스트• Class RigidMountainBike …
def priceend
Class FrontSuspensionMountainBike …def price
end
Class FullSuspensionMountainBike …def price
end
module MountainBike …def price
4. 나머지 메서드도 재정의 및 테스트• Class RigidMountainBike …
def pricedef off_road_ability
end
Class FrontSuspensionMountainBike …def pricedef off_road_ability
end
Class FullSuspensionMountainBike …def pricedef off_road_ability
end
module MountainBike …def pricedef off_road_ability
5. 모듈 제거• module MountainBike …
def pricedef off_road_ability
end
타입 코드를 모듈 확장으로 전환263p
준비• 모듈 확장 (Module Extension) 이란 ?– Class 에 모듈을 붙여 기능을 확장시키는 것– C/C++ 에서는 지원하지 않음
동기• 조건문 제거• 객체의 타입 동적 전환–타입 코드를 재정의로 전환에서는 불가능
• 모듈에서 확장될 클래스의 멤버 변수 접근 가능 ( 편리 )• # 객체의 타입 동적 전환이 가능하다고는 하지만 자유롭지는 못하다–확장이 되면 축소하기가 복잡함
1. 타입 코드에 필드 자체 캡슐화 실시• 234p
2. 타입에 맞는 모듈 작성 및 타입 변경에 따른 모듈 확장 , 원본 클래스는 기본 기능을 반환하도록 수정• Module FrontSuspensionMountainBike
def priceend
Module FullSuspensionMountainBikedef price
end
class MountainBike …def type_code=(value)
@type_code = valuecase type_code
when :front_suspension: extend(FrontSuspensionMountainBike)
when :full_suspension: extend(FullSuspensionMountainBike)end
def price# return rigid_price…
endend
3. 나머지 메서드도 재정의 및 테스트• Module FrontSuspensionMountainBike
def pricedef off_road_ability
end
Module FullSuspensionMountainBikedef pricedef off_road_ability
end
class MountainBike …def type_code=(value)@type_code = valuecase type_codewhen :front_suspension: extend(FrontSuspensionMountainBike)when :full_suspension: extend(FullSuspensionMountainBike)end
def price# return rigid_price…end
def off_road_ability# return rigid road ability…end
end
4. 타입 코드 대신 모듈을 전달 (before)
• Def type_code=(value)@type_code = valuecase type_code … # extend
module
• Bike = MountainBike.newBike.type_code = :front_suspension…
4. 타입 코드 대신 모듈을 전달 (after)• Def type_code=(mod)
extend(mod)end
• Bike = MountainBike.newBike.type_code =
FrontSuspensionMountainBike…
타입 코드를 상태 / 전략 패턴으로 전환270p
동기• 조건문 제거• 타입 코드의 자유로운 동적 전환
1. 타입 코드 필드 자체 캡슐화• 모듈 확장과 동일
2. 타입에 해당하는 클래스 작성• Class RigidMountainBike
end
Class FrontSuspensionMountainBikeend
Class FullSuspensionMountainBikeend
3. 타입 코드가 변할 때 해당하는 타입 클래스 생성• Class MountainBike …
def type_code=(value)@type_code = value@bike_type = case type_code
when :rigid: RigidMountainBike.newwhen :front_suspension: FrontSuspensionMoun-
tainBike.newwhen :full_suspension: FullSuspensionMountain-
bike.newend
end
4. 하나의 메서드를 선택해 타입 객체에 위임 및 타입 객체 생성시 필요한 data 전달• Class RigidMountainBike
def off_road_ability@tire_width * TIRE_WIDTH_FACTORend
end
Class FrontSuspensionMountainBikedef off_road_ability …
end
Class FullSuspensionMountainBikedef off_road_ability …
end
Class MountainBike …
extend Forwardabledef_delegators :@bike_type, :off_road_ability
def type_code(value) ...… when :rigid: RigidMountainBike.new( :tire_width => @tire_width)…
end
5. 나머지 메서드도 타입 객체에 위임• Class RigidMountainBike
def off_road_abilitydef price
end
Class FrontSuspensionMountainBikedef off_road_abilitydef price
end
Class FullSuspensionMountainBikedef off_road_abilitydef price
end
Class MountainBike …
extend Forwardabledef_delegators :@bike_type, :off_road_ability, :price
end
6. 타입 코드 대신 타입 객체로 생성• Bike =
MountainBike.new(FrontSuspensionMountainBike.new(
:tire_width => @tire_width,:front_fork_travel => @front_fork_travel,… ))
• Class MountainBike …def initialize(bike_type)
@bike_type = bike_typeend
end
7. 기타 (upgradable parame-ters)
• Class RigidMountainBike…def upgradable_parameters {:tire_width => @tire_width,:base_price => @base_price,…}
end
Class FrontSuspensionMountainBike …def upgradable_parameters { … }
…
• Class MountainBike…def add_front_suspension(params)@bike_type =FrontSuspensionMountainBike.new(@bike_type.upgradable_parameters.merge(params)end
end
하위클래스를 필드로 전환283p
동기• 하위 클래스가 상수 메서드만 정의–상속 구조가 복잡도를 증가시킴
리펙토링 전• Class Person…
end
class Female < Persondef female?trueend
def code‘F’end
end
class Male < Persondef female?falseend
def code?‘M’end
end
1. 생성자를 팩토리 메서드로 전환• Class Person
def self.create_femaleFemale.new
end
def self.create_maleMale.new
endend
2. 호출 코드를 팩토리 메서드로 전환• Scott = Male.new• -> Scott = Person.create_male
3. 상위클래스에 필드 추가 및 하위 클래스 초기화시 필드 초기화• Class Person …
def initialize( female, code )@female = female@code = codeend
Class Female …def initializesuper( true, ‘F’ )end
Class Male …def initializesuper( false, ‘M’ )end
4. 팩토리 메서드에 초기화 메서드 내용 직접 삽입 및 하위클래스 제거• Person …
Def self.create_femalePerson.new(true, ‘F’)end
Def self.create_malePerson.new(false, ‘M’)end
end
• Class male < Person …Class female < Person …
속성 초기화를 사용시로 미루기287p
동기• 가독성–초기화 메서드가 복잡한 경우 초기화 로직을 분리
방법 1. ||= 사용하기 (before)• Class Employee
attr_reader :emails, :voice_mails
def initialize@emails = []@voice_mails = []
endend
방법 1. ||= 사용하기 (after)• Class Employee
def emails@emails ||= []
end
def voice_mails@voice_mails ||= []
endend
방법 2. instance_variable_defined? 사용하기 (before)• 이유–속성에게 nil 이나 false 가 “유효한” 값이라면
1 번 방법을 사용할 수 없다 .• Class Employee
def initialize@assistant =
Employee.find_by_boss_id( self.id )end
end
방법 2. instance_variable_defined? 사용하기 (after)• Class Employee
def assistantunless instance_variable_defined? :@assistant
@assistant = Employee.find_by_boss_id( self.id )
endend
속성 초기화를 생성 시로 당기기290p
동기• 가독성–어떤 사람은 한 곳에 있는게 편하다 !;;
방법• 다시 원래 대로…
• 두 기법 중 하나를 선택하여 일관되게 사용하는 것이 중요
감사합니다
top related