refactoring with ruby (리펙토링 루비)

Post on 13-Apr-2017

51 Views

Category:

Software

3 Downloads

Preview:

Click to see full reader

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