SlideShare a Scribd company logo
Legacy Code
Refactoring Workshop
- Video Rental
오재훈 (ojh420@gmail.com)
목표
Refactoring(Martin Fowler) 1장의 비디오 대여 시스템을 리
팩토링하면서
- 코드 스멜을 찾는 방법
- 단위 테스트를 작성하는 방법
- 레거시 코드를 리팩토링을 하는 방법
- Eclipse 의 리팩토링 자동화를 사용하는 방법
- 클린 코드를 작성하는 방법
을 배운다.
Refactoring
Martin Fowler
프로젝트 준비
1. Eclipse 를 실행하고 Java Project 를 생성한다. ( 프로젝트 이름을 VideoRental )
2. VideoRental 프로젝트에서 src 디렉토리를 선택한다.
3. 압축 파일을 풀고, chapter1 디렉토리를 VideoRental 의 src 디렉토리로 복사한다.
4. src/chapter1/Movie.java, Customer.java, Rental.java 클래스가 복사되었는지 확인한다.
Coverage 측정 도구 준비
1. Eclipse 를 실행한다.
2. Help 메뉴를 선택하고, Eclipse Marketplace 서브메뉴를 선택한다.
3. Search 탭에서 Find 에 eclEmma 를 입력한 후 enter 키를 입력한다.
4. 검색 결과에 "eclEmma Java Code Coverage 2.3.2" 플러그인이 뜨면, 아래쪽에 있는 "Insall" 버튼
을 클릭해서 설치한다.
설치확인하기
1. Package Explorer 에서 Java Project 를 선택한다.
2. 오른쪽 버튼을 눌러서 "Run As" 메뉴 아래에 "Coverage As" 메뉴가 보이는지 확인한다.
Legacy Code 정의하기
Legacy Code 란?
과거에 Legacy Code 에 기능을 추가했던 기억
들을 떠 올려 보고, 영향이 가장 컸던 버그를 포
스트잇에 적는다. ( 2분 )
레거시 코드 기능추가시 발생했던 버그에 대한
경험을 공유한다. ( 10분 )
- 레거시 코드에 기능을 추가하면서 했던 실수 중 가장 큰
실수는?
- 그 실수는 누가 발견했나요?
- 그 실수는 언제 발견되었나요?
- 그 실수를 수정하고 배포하는 데 비용이 얼마나 들었나
요?
- 그 실수가 발견되었을 때, 여러분의 감정은 어떤 상태였
나요?
Legacy Code 정의하기
“Code Without Test”
( Michael Feathers )
People are writing legacy code right now, maybe on your
project.
( http://www.objectmentor.com/resources/articles/WorkingEffectivelyWithLegacyCode.pdf )
Legacy Code: Characteristics
● Poor Architecture
● Non-Uniform Coding Styles
● Poor or Non-Existing Documentation
● Mythical Oral Documentation
레거시 코드에 기능 추가하기
추가 요구사항
- 대여정보를 출력할 때, 고객이 대여한 영화 수
를 영화 종류별로 출력한다.
- New Release : 1
- Children : 0
- Regular : 2
- 진행방식 : Pair Programming
- 시간 : 20분
코드 리뷰
어떤 방식으로 기능을 추가했는가?
기능 추가 방법
Making change inline
- 기존 코드에 새로운 코드 추가하는 방법
- 단점
- 새로운 기능에 대한 자동화된 테스트가 없다.
- 기존의 메소드가 더 길어진다.
- 기존의 메소드가 하는 일이 더 많아진다.
기능 추가 방법
Delegate Method
- 새로운 기능을 새로운 메소드로 추가한다.
- 장점
- 현재 메소드가 더 길어지지 않는다.
- 새로운 메소드를 테스트할 수 있다.
- 한계
- 현재 메소드는 여전히 테스트 할 수 없다.
- 이렇게 하기 어려울 때가 있다.
Sprouting Pattern : Sprout Method
추가 기능을 새로운 메소드로 정의하고, TDD 로 개발한다.
원래 메소드에서 새로운 메소드를 부른다.(delegate 한다.)
Sprouting Pattern : Sprout Class
새로운 메소드가 있는 클래스를 test harness 안에서 실행하
기 어렵다면?
- 새로운 클래스를 추가한다.
- TDD 를 이용해서, 추가 기능을 새로운 클래스의 메소드
로 구현한다.
- 원래 메소드에서 새로운 클래스를 생성한다.
- 새로 생성한 객체의 새로운 메소드를 부른다.(delegate
한다. )
Sprouting Pattern
이미 존재하는 코드에 더 많은 코드를 추가하는 것을 피하라.
대신에, 새로운 클래스와 메소드에 tested code 를 만들어라.
기존의 코드에서 새로운 메소드로 위임하라.
Sprout Method
조금 전의 요구사항을 Sprout Method 로 작성해
본다. ( 기간 : 20분 )
- 진행방식 : Pair Programming
Guide
- Red-Green-Refactoring 리듬을 잘 지킨다.
- 적극적으로 리팩토링한다.
- 중복을 찾고, 중복을 적극적으로 제거한다.
- 문자열(혹은 문자열 구조)의 중복도 찾아서 제거한다.
- 리팩토링할 때마다 반드시 테스트를 실행한다.
Working Effectively with Legacy Code
Michael Feathers
코드 스멜 찾기
코드스멜 찾기
코드 스멜(Code Smell)
- Kent Beck 이 만든 용어
- 리팩토링이 필요한 코드를 식별하
게 도와준다.
- 언제 리팩토링을 해야 하는지를 알
려준다.
Code Smells
1 Alternative Classes with Different Interfaces
2 Combinatorial Explosion
3 Comments
4 Conditional Complexity
5 Data Class
6 Data Clumps
7 Divergent Change
8 Duplicated Code
9 Feature Envy
10 Freeloader ( Lazy Class )
Code Smells
11 Inappropriate Intimacy
12 Incomplete Library Class
13 Incedent Exposure
14 Large Class
15 Long Method
16 Long Parameter List
17 Message Chain
18 Middle Man
19 Oddball Solution
20 Parallel Inheritance Hiearchy
Code Smells
21 Primitive Obsessiion
22 Refused Bequest
23 Shortgun Surgery
24 Solution Sprawl
25 Speculative Generality
26 Switch Statement
27 Temporary Field
Code Smell to Refactoring Cheat Sheet ( Industrial Logic )
( http://www.industriallogic.com/wp-content/uploads/2005/09/smellstorefactorings.pdf )
코드스멜 찾기
Customer 의 statement() 메소드를 보자.
- 이 코드는 좋은 코드인가? 나쁜 코드인가?
- 코드에 개선할 부분이 있는가?
- 코드 스멜을 찾아보자.
- 시간 ( 5분 )
Session 2 - 코드스멜 찾기
발견된 코드 스멜들
리팩토링을 시작해 볼까요?
잠깐!! 리팩토링이란?
1.명사형
- 소프트웨어를 더 쉽게 이해할 수 있고,
- 적은 비용으로 수정할 수 있도록
- 겉으로 보이는 동작의 변화 없이 내부 구조를 변경하는
것
2.동사형(Refactor)
- 일련의 리팩토링을 적용하여 겉으로 보이는 동작의 변화
없이
- 소프트웨어의 구조를 바꾸다.
리팩토링
그림 출처 : http://vitalflux.com/top-6-refactoring-patterns-to-help-you-score-80-in-code-quality/
리팩토링의 전제조건
리팩토링=외부 동작을 바꾸지 않으면서 내부 구
조를 개선하는 것
코드를 변경했을 때, 외부 동작이 바뀌지 않았다
는 것을 증명해야 한다.
외부 동작이 바뀌지 않았다는 것을 어떻
게 증명할 것인가?
증명방법
바뀌지 않았다고 믿는다.
영향을 받을 수 있는 기능을 수동으로 테스트한다.
테스트 코드를 이용하여 자동으로 테스트한다.
리팩토링 vs 리프로그래밍
외부 동작이 바뀌지 않았다는 것을 증명할 수 있
는 단위테스트가 없다면?
- 리팩토링을 하고 있는가?
- 리프로그래밍을 하고 있는가?
레거시 코드 테스트 작성하기
테스트 작성하기
비디오 대여시스템 코드는 현재 테스트 코드가 없다.
리팩토링을 하고 싶다면, 먼저 테스트를 작성해야 한다.
레거시 코드 단위 테스트 작성하기
1. 생성자 테스트 작성하기
2. 테스트 커버리지를 측정한다.
3. 테스트 되지 않은 코드를 커버할 수 있는 새로운 테스트
를 작성한다. ( Characterization Test )
4. 테스트 커버리지가 100%에 가까워질 때까지 테스트 코
드를 작성한다.
생성자 테스트 작성하기
목적 : 객체 생성 비용을 파악한다.
1. JUnit Test Class 를 생성한다. ( CustomerTest )
2. 테스트 메소드 이름을 testCreate 라고 한다.
3. Customer 클래스의 객체를 생성해 본다.
4. 단위 테스트를 실행한다.
생성자 테스트 작성하기
public class CustomerTest {
@Test
public void testCreate() {
Customer customer = new Customer(null);
}
}
Customer 의 statement() 테스트하기
실패하는 테스트 코드 만들기
1. 생성자 테스트 메소드의 이름을 testX 로 바꾼다.
2. assert 문을 추가한다.
assertEquals(“”,customer.statement())
3. 테스트를 실행한다. ( 빨간불 )
Customer 의 statement() 테스트하기
실패하는 테스트 코드 만들기
public class CustomerTest {
@Test
public void testX() {
Customer customer = new Customer(null);
assertEquals(“”,customer.statement())
}
}
Customer 의 statement() 테스트하기
실패하는 테스트 코드 만들기
1. JUnit 창에서 statement() 의 반환값을 복사해서, expected value 에 복사한
다.
2. 테스트를 실행한다. ( 초록불 )
3. 테스트 메소드의 이름을 변경한다.
Customer 의 statement() 테스트하기
테스트 코드 이름 변경하기 (rename)
@Test
public void amountShouldBeZeroWhenCustomerRentNoMovie() {
assertEquals("Rental Record for Johnn"
+ "Amount owed is 0.0n"
+ "You earned 0frequent renter points"
,customer.statement());
}
테스트 커버리지 실행하기
테스트 커버리지 도구를 이용하여, 테스트가 되지 않은 코드
를 파악한다.
- Coverage As 메뉴에서 JUnit Test 를 선택해서 실행
statement() 함수를 확인해서, 테스트 되지 않은 코드를 파악
한다.
테스트 되지 않은 코드를 커버하기 위해 새로운 테스트를 작
성한다.
테스트 커버리지 확인하기
레거시 코드 테스팅 알고리즘
1. 커버리지를 확인하고, 새로운 테스트가 커버할 코드 영
역을 결정한다.
2. 테스트 메소드를 생성하고, 해당 코드 영역을 커버하기
위한 조건을 만든다.
3. 실패하는 테스트 코드를 작성한다.
4. JUnit 결과를 이용하여 테스트를 통과하게 만든다.
5. 커버리지가 100%에 가까워질 때까지 이 과정을 반복한
다.
레거시 코드 테스팅
미션 : 테스트 커버리지가 100%에 가까워질 때
까지 테스트 코드를 작성한다.
시간 : 30분
Characterization Test
레거시 코드의 특성을 이해하기 위해서 작성하
는 테스트
Refactoring
Refactoring 시작하기
테스트 커버리지가 100%에 가까워지면 이제 리
팩토링을 시작할 수 있다.
Code Smell 찾기
Customer.statement() 에 어떤 코드 스멜이 있는
지 다시 한번 찾아보자.
Long Method
발견하기 가장 쉬운 코드 스멜
가장 많이 생기는 코드 스멜
긴 메소드가 왜 코드 스멜인가?
왜 긴 메소드가 생기는가?
Long Method
긴 메소드를 없애기 위한 리팩토링 :
- Extract Method ( Alt+Shift+M )
메소드로 추출할 코드를 선정하는 방법
- 주석이 있는 코드
- 조건문과 루프
- 메소드로 뽑았을 때, 의도를 더 잘 전달할 수 있는 코드
Comments
주석은 왜 코드 스멜인가?
Comments
주석이 도움이 되는 경우
Refactoring - Extract Method
statement() 에서 주석이 달
린 코드를 찾아서 리팩토링
해 보자.
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental)rentals.nextElement();
//각 영화에 대한 요금 결정
switch ( each.getMovie().getPriceCode() ) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;
break;
}
영화 요금 - Extract Method
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental)rentals.nextElement();
//각 영화에 대한 요금 결정
switch ( each.getMovie().getPriceCode() ) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;
break;
}
// 포인트(frequent renter points) 추가
frequentRenterPoints ++;
// 최신을 이틀이상 대여하는 경우 추가 포인트 제공
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1) frequentRenterPoints ++;
// 이 대여에 대한 요금 계산 결과 표시
result += "t" + each.getMovie().getTitle() + "t" +
String.valueOf(thisAmount) + "n";
totalAmount += thisAmount;
}
1. 코드 블록 선택후 Extract Method ( Alt+Shift+M)
2. 메소드 이름 : amountFor
영화 요금 - Extract Method
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental)rentals.nextElement();
thisAmount = amountFor(thisAmount,each)
// 포인트(frequent renter points) 추가
frequentRenterPoints ++;
// 최신을 이틀이상 대여하는 경우 추가 포인트 제공
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1) frequentRenterPoints ++;
// 이 대여에 대한 요금 계산 결과 표시
result += "t" + each.getMovie().getTitle() + "t" +
String.valueOf(thisAmount) + "n";
totalAmount += thisAmount;
}
private double amountFor(double thisAmount,Rental each) {
//각 영화에 대한 요금 결정
switch ( each.getMovie().getPriceCode() ) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;
break;
}
return thisAmount;
}
테스트 실행
리팩토링을 한 후에는 반드시 테스트를 실행해
서
- 기존의 동작이 변경되지 않았는지 확인한다.
amountFor 리팩토링 하기
amountFor 에는 어떤 코드 스멜이 있는가?
amountFor 리팩토링
double amountFor(double thisAmount, Rental each)
에서 thisAmount 는 패러미터로 받을 필요가 없음
메소드에서 패러미터를 삭제하는 리팩토링 : Remove Parameter
이클립스에서는 Change Method Signature 에서 패러미터를 삭제할 수 있음 (
Alt+Shift+C )
1. thisAmount Parameter 선택
2. Remove 클릭
amountFor 리팩토링
thisAmount 패러미터가 삭제되면, thisAmount 가 정의되지 않았기 때문에 컴파
일 오류 발생
- 컴파일 오류가 thisAmount 를 선택 후, Quick Fix 로 로컬 변수 생성
- 테스트 코드 실행 : 테스트가 실패함
- 왜 테스트 코드가 실패할까?
private double amountFor(double thisAmount,Rental each) {
int thisAmount = 0;
switch ( each.getMovie().getPriceCode() ) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;
...
amountFor 리팩토링
amountFor(Rental each) 에서 변수 이름 each 이름 변경하기
- 패러미터 선택후 Rename Method ( Alt+Shift+R)
- 변경이유 :
- each 는 루프에서 사용되던 변수 이름
- 패러미터는 루프와 관계 없기 때문에 이름을 맞게 수정
로컬 변수인 thisAmount 의 이름 변경하기
- 변수 선택후 Rename Local Variable ( Alt+Shift+R )
- 변경 이유
- 변수 이름이 너무 길다.
amountFor 리팩토링
amountFor 는 Customer 객체의 멤버를 전혀 사용하지 않는
다.
Rental 과 Movie 객체의 메소드를 사용한다.
amountFor 에는 Feature Envy(기능 욕심) 코드 스멜이 있다.
Feature Envy
자신이 속한 클래스보다 다른 클래스에 관심을 가지고 있는
경우
적용할 리팩토링 : Move Method
Feature Envy
메소드가 여러 클래스의 메소드를 사용하고 있
다면 어디로 옮겨야 할까?
amountFor 리팩토링
amountFor 를 Rental 클래스로 옮긴다.
- 커서를 amountFor signature 로 이동한다.
- Move Method ( Alt+Shift+V ) 를 실행한다.
amountFor 리팩토링
amountFor 를 Rental 클래스로 옮기고 나면, statement() 에
서 each.amountFor() 형식으로 불린다.
a. Rental 클래스로 옮기기 전에는 … amountFor(each)
형식이어서 영어 문장의 어순과 일치한다.
b. Rental 클래스로 옮긴 후에는 each.amountFor() 라서
영어의 어순과 일치하지 않는다.
c. amountFor 메소드의 이름을 바꾸는 것이 좋다.
로컬 변수 삭제 하기
statement() 의 thisAmount 로컬 변수를 삭제한다.
로컬 변수를 삭제하는 이유는?
// 리팩토링 후 코드
while (rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement();
frequentRenterPoints += each.getFrequentRenterPoints()
// 이 대여에 대한 요금 계산 결과 표시
result += "t" + each.getMovie().getTitle() + "t" +
String.valueOf(each.getCharge()) + "n";
totalAmount += each.getCharge();
}
로컬 변수 삭제 하기
적용할 리팩토링 : Replace Temp with Query
// 리팩토링 후 코드
while (rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement();
frequentRenterPoints += each.getFrequentRenterPoints()
// 이 대여에 대한 요금 계산 결과 표시
result += "t" + each.getMovie().getTitle() + "t" +
String.valueOf(each.getCharge()) + "n";
totalAmount += each.getCharge();
}
Refactoring - 포인트 추가
// 포인트(frequent renter points) 추가
“// 포인트(frequent renter points) 추가” 에 대해서도
amountFor 와 동일한 리팩토링을 수행한다.
// 리팩토링 후 코드
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental)rentals.nextElement();
thisAmount = amountFor(thisAmount,each)
frequentRenterPoints += each.getFrequentRenterPoints()
// 이 대여에 대한 요금 계산 결과 표시
result += "t" + each.getMovie().getTitle() + "t" +
String.valueOf(each.getCharge()) + "n";
totalAmount += each.getCharge();
}
임시 변수 제거하기
statement() 에 어떤 임시 변수가 있는가?
totalAmount 임시변수 제거하기
1. statement() 함수를 복사해서 getTotalCharge() 를 만든다.
2. statement() 함수에서 totalAmount 를 사용하는 곳을 getTotalCharge() 로 바
꾼다.
3. statement() 함수에서 totalAmount 변수를 삭제한다.
totalAmount 임시변수 제거하기
public String statement() {
Enumeration rentals = _rentals.elements();
String result ="Rental Record for " + getName() + "n";
while (rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement();
frequentRenterPoints += each.getFrequentRenterPoints()
result += "t" + each.getMovie().getTitle() + "t" +
String.valueOf(each.getCharge()) + "n";
}
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "n";
result += "You earned " + String.valueOf(frequentRenterPoints) +
"frequent renter points";
return result;
}
frequentRenterPoints 제거하기
public String statement() {
Enumeration rentals = _rentals.elements();
String result ="Rental Record for " + getName() + "n";
while (rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement();
result += "t" + each.getMovie().getTitle() + "t" +
String.valueOf(each.getCharge()) + "n";
}
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "n";
result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) +
"frequent renter points";
return result;
}
멈추고 생각하기
코드량
- 일반적으로 리팩토링하면 코드량이 줄어든다.
- 이번 리팩토링은?
성능
- 이번 리팩토링은 성능에는 어떤 영향을 미치는가?
이번 리팩토링은
- 왜 해야 하는가?
- 어떤 이익을 볼 수 있는가?
변경된 구조
while ( ) {
}
영화 요금 계산
영화 요금 출력
포인트 계산
영화 요금 계산
while () {
}
영화 요금 출력
while () {
}
포인트 계산
while () {
}
기능추가 - HTML 로 구현해 보자.
html 로 출력하는 기능 구현
statement() 과 동일한 내용을
html 로 출력하는 기능을 구현해 보자.
Composed Method 패턴
Composed Method
켄트 벡이 발견한 패턴
1. 코드를 메소드로 나눈다. 메소드는 한가지 일만 해야 한다.
2. 메소드에 있는 코드의 추상화 레벨은 같아야 한다.
3. Composed Method 패턴을 따르면, 메소드 크기가 작아진다.
Composed Method 로 만들기
package refactoring;
public class List {
private Object[] _elements = new Object[10];
private boolean _readOnly;
private int _size = 0;
public void Add(Object child) {
if (!_readOnly) {
int newSize = _size + 1;
if (newSize > _elements.length) {
Object[] newElements = new Object[_elements.length + 10];
for (int i = 0; i < _size; i++) {
newElements[i] = _elements[i];
}
_elements = newElements;
}
_elements[_size] = child;
_size++;
}
}
}
출처: Refactoring to Patterns (Joshua Kerievsky )
statement() 리팩토링 하기
Composed Method 로 만들어 보자.
영화 분류 방법 변경
새로운 분류 방법 도입
대여점에서 영화를 분류하는 방법을 변경할 예정이다.
- 어떻게 변경할지는 결정하지 않았다.
- 새로운 분류방법이 도입될 것이다.
- 새로운 분류방법에 따라 요금과 포인트를 할당할 예정이
다.
현재 시점에서 이런 변경하는 것이 쉬울까?
이런 변화를 쉽게 반영하려면 어떻게 해야 할
까?
switch statement
switch statement 는 왜 코드 스멜인가?
switch statement 는 언제 리팩토링해야 하는가?
switch statement
리팩토링 방법
- Replace Type Code with Subclasses
- Replace Type Code with State/Strategy
- Replace Conditional with Polymorphism
switch statement
Rental 의 getCharge 에서 개선할 것은?
switch statement
Rental 의 getCharge 를 Movie 로 옮기자
설계면에서 어느 것이 더 좋은 선택인가?
- 영화 종류를 Rental 에서 사용하는 것과
- 대여기간을 Movie 로 넘겨주는 것
Refactoring - Replace Type Code
with Subclasses
switch statement
영화 종류가 여러개이고, 각 영화종류는 같은 질문에 다른
답을 한다.
- Switch 문을 직접 다형성으로 바꿔보자.
Movie
getCharge
getCharge
RegularMovie Children Movie
getCharge
New Release Movie
getCharge
switch statement
State Pattern 을 이용하기
Refactoring - Replace Type Code
with State/Strategy
Replace type code with State/Strategy
Step 1. type code 에 Self Encapsulate Field 를 적용하자.
Step 2. Movie 클래스에서 Price 클래스를 Extract 한다.
Move Method
Movie 의 getCharge 를 price 로 옮기기
Replace Conditional with Polymorphism
Price getCharge 코드를 subclass 로 옮기기
모든 작업이 끝나면, price getCharge 를 추상 메소드로 변경
getFrequentRenterPoints
Rental 에서 Movie 로 옮기자
동일하게 적용하자
멈추고 생각하기
스테이트 패턴을 적용하는데 많은 노력이 들었
다.
그럴만한 가치가 있었을까?
이전보다 어떤 점이 개선되었는가?
회고하기
이 과정을 통해서 배운 것중 기억하고 싶은 것을 한가지만
고른다면?
이 과정을 통해서 새로 알게 된 것들이 있다면?
이 과정에서 아쉬운 점이 있었다면?

More Related Content

What's hot

이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
devCAT Studio, NEXON
 
[5분 따라하기] 비주얼 스튜디오 C++에서 JSON 파서 설치하기
[5분 따라하기] 비주얼 스튜디오 C++에서 JSON 파서 설치하기[5분 따라하기] 비주얼 스튜디오 C++에서 JSON 파서 설치하기
[5분 따라하기] 비주얼 스튜디오 C++에서 JSON 파서 설치하기
Jay Park
 
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
SangIn Choung
 
響應式程式開發之 .NET Core 應用 
響應式程式開發之 .NET Core 應用 響應式程式開發之 .NET Core 應用 
響應式程式開發之 .NET Core 應用 
Chen-Tien Tsai
 
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
OKKY
 
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
Ji-Woong Choi
 
Pipes & Filters Architectural Pattern
Pipes & Filters Architectural PatternPipes & Filters Architectural Pattern
Pipes & Filters Architectural Pattern
Fredrik Kivi
 
非同期処理の基礎
非同期処理の基礎非同期処理の基礎
非同期処理の基礎
信之 岩永
 
SpotBugs(FindBugs)による 大規模ERPのコード品質改善
SpotBugs(FindBugs)による 大規模ERPのコード品質改善SpotBugs(FindBugs)による 大規模ERPのコード品質改善
SpotBugs(FindBugs)による 大規模ERPのコード品質改善
Works Applications
 
Event driven architecture : comment Deezer passe en mode réactif
Event driven architecture : comment Deezer passe en mode réactifEvent driven architecture : comment Deezer passe en mode réactif
Event driven architecture : comment Deezer passe en mode réactif
Vincent Lepot
 
2015/11/15 Javaでwebアプリケーション入門
2015/11/15 Javaでwebアプリケーション入門2015/11/15 Javaでwebアプリケーション入門
2015/11/15 Javaでwebアプリケーション入門
Asami Abe
 
認試軟體測試的世界 & TDD/BDD 入門
認試軟體測試的世界 & TDD/BDD 入門認試軟體測試的世界 & TDD/BDD 入門
認試軟體測試的世界 & TDD/BDD 入門
wantingj
 
KubeConEU - NATS Deep Dive
KubeConEU - NATS Deep DiveKubeConEU - NATS Deep Dive
KubeConEU - NATS Deep Dive
wallyqs
 
ドメイン駆動設計 実践ガイド
ドメイン駆動設計 実践ガイドドメイン駆動設計 実践ガイド
ドメイン駆動設計 実践ガイド
増田 亨
 
Crafted Design - Sandro Mancuso
Crafted Design - Sandro MancusoCrafted Design - Sandro Mancuso
Crafted Design - Sandro Mancuso
JAXLondon2014
 
[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기
Sang Heon Lee
 
Dapr: the glue to your microservices
Dapr: the glue to your microservicesDapr: the glue to your microservices
Dapr: the glue to your microservices
Moaid Hathot
 
Windows 성능모니터를 이용한 SQL Server 성능 분석
Windows 성능모니터를 이용한 SQL Server 성능 분석Windows 성능모니터를 이용한 SQL Server 성능 분석
Windows 성능모니터를 이용한 SQL Server 성능 분석
Sung wook Kang
 
bashでWebブラウザ(Selenium WebDriver)を動かした話
bashでWebブラウザ(Selenium WebDriver)を動かした話bashでWebブラウザ(Selenium WebDriver)を動かした話
bashでWebブラウザ(Selenium WebDriver)を動かした話
洋史 東平
 
Java EE パフォーマンスTips #glassfish_jp
Java EE パフォーマンスTips #glassfish_jpJava EE パフォーマンスTips #glassfish_jp
Java EE パフォーマンスTips #glassfish_jp
Norito Agetsuma
 

What's hot (20)

이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
이승재, 실버바인 서버엔진 2 설계 리뷰, NDC2018
 
[5분 따라하기] 비주얼 스튜디오 C++에서 JSON 파서 설치하기
[5분 따라하기] 비주얼 스튜디오 C++에서 JSON 파서 설치하기[5분 따라하기] 비주얼 스튜디오 C++에서 JSON 파서 설치하기
[5분 따라하기] 비주얼 스튜디오 C++에서 JSON 파서 설치하기
 
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
 
響應式程式開發之 .NET Core 應用 
響應式程式開發之 .NET Core 應用 響應式程式開發之 .NET Core 應用 
響應式程式開發之 .NET Core 應用 
 
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
 
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020[오픈소스컨설팅] 스카우터 사용자 가이드 2020
[오픈소스컨설팅] 스카우터 사용자 가이드 2020
 
Pipes & Filters Architectural Pattern
Pipes & Filters Architectural PatternPipes & Filters Architectural Pattern
Pipes & Filters Architectural Pattern
 
非同期処理の基礎
非同期処理の基礎非同期処理の基礎
非同期処理の基礎
 
SpotBugs(FindBugs)による 大規模ERPのコード品質改善
SpotBugs(FindBugs)による 大規模ERPのコード品質改善SpotBugs(FindBugs)による 大規模ERPのコード品質改善
SpotBugs(FindBugs)による 大規模ERPのコード品質改善
 
Event driven architecture : comment Deezer passe en mode réactif
Event driven architecture : comment Deezer passe en mode réactifEvent driven architecture : comment Deezer passe en mode réactif
Event driven architecture : comment Deezer passe en mode réactif
 
2015/11/15 Javaでwebアプリケーション入門
2015/11/15 Javaでwebアプリケーション入門2015/11/15 Javaでwebアプリケーション入門
2015/11/15 Javaでwebアプリケーション入門
 
認試軟體測試的世界 & TDD/BDD 入門
認試軟體測試的世界 & TDD/BDD 入門認試軟體測試的世界 & TDD/BDD 入門
認試軟體測試的世界 & TDD/BDD 入門
 
KubeConEU - NATS Deep Dive
KubeConEU - NATS Deep DiveKubeConEU - NATS Deep Dive
KubeConEU - NATS Deep Dive
 
ドメイン駆動設計 実践ガイド
ドメイン駆動設計 実践ガイドドメイン駆動設計 実践ガイド
ドメイン駆動設計 実践ガイド
 
Crafted Design - Sandro Mancuso
Crafted Design - Sandro MancusoCrafted Design - Sandro Mancuso
Crafted Design - Sandro Mancuso
 
[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기[NDC2016] TERA 서버의 Modern C++ 활용기
[NDC2016] TERA 서버의 Modern C++ 활용기
 
Dapr: the glue to your microservices
Dapr: the glue to your microservicesDapr: the glue to your microservices
Dapr: the glue to your microservices
 
Windows 성능모니터를 이용한 SQL Server 성능 분석
Windows 성능모니터를 이용한 SQL Server 성능 분석Windows 성능모니터를 이용한 SQL Server 성능 분석
Windows 성능모니터를 이용한 SQL Server 성능 분석
 
bashでWebブラウザ(Selenium WebDriver)を動かした話
bashでWebブラウザ(Selenium WebDriver)を動かした話bashでWebブラウザ(Selenium WebDriver)を動かした話
bashでWebブラウザ(Selenium WebDriver)を動かした話
 
Java EE パフォーマンスTips #glassfish_jp
Java EE パフォーマンスTips #glassfish_jpJava EE パフォーマンスTips #glassfish_jp
Java EE パフォーマンスTips #glassfish_jp
 

Viewers also liked

Bnf seeg ws
Bnf seeg wsBnf seeg ws
Bnf seeg wsbbongcsu
 
애자일 마인드셋
애자일 마인드셋애자일 마인드셋
애자일 마인드셋
Jaehoon Oh
 
Clean code
Clean codeClean code
Clean codebbongcsu
 
애자일 개발 프로세스를 이용한 고품질 소프트웨어 개발
애자일 개발 프로세스를 이용한 고품질 소프트웨어 개발애자일 개발 프로세스를 이용한 고품질 소프트웨어 개발
애자일 개발 프로세스를 이용한 고품질 소프트웨어 개발
Jaehoon Oh
 
The roadtocodecraft
The roadtocodecraftThe roadtocodecraft
The roadtocodecraftbbongcsu
 
Refactoring tutorial
Refactoring tutorialRefactoring tutorial
Refactoring tutorial
Bingu Shim
 
Refactoring tutorial 1주차[refactoring 개요]
Refactoring tutorial 1주차[refactoring 개요]Refactoring tutorial 1주차[refactoring 개요]
Refactoring tutorial 1주차[refactoring 개요]
bbongcsu
 
인수테스트 주도 개발
인수테스트 주도 개발인수테스트 주도 개발
인수테스트 주도 개발
Jaehoon Oh
 
Tdd 왜 배우기 어려운가
Tdd 왜 배우기 어려운가Tdd 왜 배우기 어려운가
Tdd 왜 배우기 어려운가
Jaehoon Oh
 
Robot framework 을 이용한 기능 테스트 자동화
Robot framework 을 이용한 기능 테스트 자동화Robot framework 을 이용한 기능 테스트 자동화
Robot framework 을 이용한 기능 테스트 자동화
Jaehoon Oh
 

Viewers also liked (11)

Bnf seeg ws
Bnf seeg wsBnf seeg ws
Bnf seeg ws
 
애자일 마인드셋
애자일 마인드셋애자일 마인드셋
애자일 마인드셋
 
Clean code
Clean codeClean code
Clean code
 
애자일 개발 프로세스를 이용한 고품질 소프트웨어 개발
애자일 개발 프로세스를 이용한 고품질 소프트웨어 개발애자일 개발 프로세스를 이용한 고품질 소프트웨어 개발
애자일 개발 프로세스를 이용한 고품질 소프트웨어 개발
 
The roadtocodecraft
The roadtocodecraftThe roadtocodecraft
The roadtocodecraft
 
Refactoring tutorial
Refactoring tutorialRefactoring tutorial
Refactoring tutorial
 
Bnf seeg
Bnf seegBnf seeg
Bnf seeg
 
Refactoring tutorial 1주차[refactoring 개요]
Refactoring tutorial 1주차[refactoring 개요]Refactoring tutorial 1주차[refactoring 개요]
Refactoring tutorial 1주차[refactoring 개요]
 
인수테스트 주도 개발
인수테스트 주도 개발인수테스트 주도 개발
인수테스트 주도 개발
 
Tdd 왜 배우기 어려운가
Tdd 왜 배우기 어려운가Tdd 왜 배우기 어려운가
Tdd 왜 배우기 어려운가
 
Robot framework 을 이용한 기능 테스트 자동화
Robot framework 을 이용한 기능 테스트 자동화Robot framework 을 이용한 기능 테스트 자동화
Robot framework 을 이용한 기능 테스트 자동화
 

Similar to Legacy code refactoring video rental system

카사 공개세미나1회 W.E.L.C.
카사 공개세미나1회  W.E.L.C.카사 공개세미나1회  W.E.L.C.
카사 공개세미나1회 W.E.L.C.Ryan Park
 
Java 유지보수 가능한 개발 원칙
Java 유지보수 가능한 개발 원칙Java 유지보수 가능한 개발 원칙
Java 유지보수 가능한 개발 원칙
Hyosang Hong
 
유지보수 가능한 개발 원칙
유지보수 가능한 개발 원칙유지보수 가능한 개발 원칙
유지보수 가능한 개발 원칙
Hyosang Hong
 
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
Kenneth Ceyer
 
소프트웨어 개선 그룹(Sig) 개발 원칙
소프트웨어 개선 그룹(Sig) 개발 원칙소프트웨어 개선 그룹(Sig) 개발 원칙
소프트웨어 개선 그룹(Sig) 개발 원칙
Hong Hyo Sang
 
TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기
Wonchang Song
 
Refactoring Tutorial 1주차[ Refactoring 개요]
Refactoring  Tutorial 1주차[ Refactoring 개요]Refactoring  Tutorial 1주차[ Refactoring 개요]
Refactoring Tutorial 1주차[ Refactoring 개요]
Bingu Shim
 
Refactoring Tutorial 1주차[ Refactoring 개요]
Refactoring  Tutorial 1주차[ Refactoring 개요]Refactoring  Tutorial 1주차[ Refactoring 개요]
Refactoring Tutorial 1주차[ Refactoring 개요]
Bingu Shim
 
Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005
Ryan Park
 
Test driven development short lesson
Test driven development   short lessonTest driven development   short lesson
Test driven development short lesson
중선 곽
 
HolubOnPatterns/chapter2_2
HolubOnPatterns/chapter2_2HolubOnPatterns/chapter2_2
HolubOnPatterns/chapter2_2SeungHyun Hwang
 
Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기Daum DNA
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDSuwon Chae
 
DevRookie 리펙터링.pptx
DevRookie 리펙터링.pptxDevRookie 리펙터링.pptx
DevRookie 리펙터링.pptx
MUUMUMUMU
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509
영석 조
 
KGC2010 - 낡은 코드에 단위테스트 넣기
KGC2010 - 낡은 코드에 단위테스트 넣기KGC2010 - 낡은 코드에 단위테스트 넣기
KGC2010 - 낡은 코드에 단위테스트 넣기
Ryan Park
 
Postman과 Newman을 이용한 RestAPI 테스트 자동화 가이드
Postman과 Newman을 이용한 RestAPI 테스트 자동화 가이드 Postman과 Newman을 이용한 RestAPI 테스트 자동화 가이드
Postman과 Newman을 이용한 RestAPI 테스트 자동화 가이드
SangIn Choung
 
[Toolcon2014] WebStorm에서 자바스크립트 리팩토링하기
[Toolcon2014] WebStorm에서 자바스크립트 리팩토링하기[Toolcon2014] WebStorm에서 자바스크립트 리팩토링하기
[Toolcon2014] WebStorm에서 자바스크립트 리팩토링하기
우영 주
 
Working with code
Working with codeWorking with code
Working with code
JaeYeoul Ahn
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스
Sungik Kim
 

Similar to Legacy code refactoring video rental system (20)

카사 공개세미나1회 W.E.L.C.
카사 공개세미나1회  W.E.L.C.카사 공개세미나1회  W.E.L.C.
카사 공개세미나1회 W.E.L.C.
 
Java 유지보수 가능한 개발 원칙
Java 유지보수 가능한 개발 원칙Java 유지보수 가능한 개발 원칙
Java 유지보수 가능한 개발 원칙
 
유지보수 가능한 개발 원칙
유지보수 가능한 개발 원칙유지보수 가능한 개발 원칙
유지보수 가능한 개발 원칙
 
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
 
소프트웨어 개선 그룹(Sig) 개발 원칙
소프트웨어 개선 그룹(Sig) 개발 원칙소프트웨어 개선 그룹(Sig) 개발 원칙
소프트웨어 개선 그룹(Sig) 개발 원칙
 
TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기
 
Refactoring Tutorial 1주차[ Refactoring 개요]
Refactoring  Tutorial 1주차[ Refactoring 개요]Refactoring  Tutorial 1주차[ Refactoring 개요]
Refactoring Tutorial 1주차[ Refactoring 개요]
 
Refactoring Tutorial 1주차[ Refactoring 개요]
Refactoring  Tutorial 1주차[ Refactoring 개요]Refactoring  Tutorial 1주차[ Refactoring 개요]
Refactoring Tutorial 1주차[ Refactoring 개요]
 
Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005
 
Test driven development short lesson
Test driven development   short lessonTest driven development   short lesson
Test driven development short lesson
 
HolubOnPatterns/chapter2_2
HolubOnPatterns/chapter2_2HolubOnPatterns/chapter2_2
HolubOnPatterns/chapter2_2
 
Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDD
 
DevRookie 리펙터링.pptx
DevRookie 리펙터링.pptxDevRookie 리펙터링.pptx
DevRookie 리펙터링.pptx
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509
 
KGC2010 - 낡은 코드에 단위테스트 넣기
KGC2010 - 낡은 코드에 단위테스트 넣기KGC2010 - 낡은 코드에 단위테스트 넣기
KGC2010 - 낡은 코드에 단위테스트 넣기
 
Postman과 Newman을 이용한 RestAPI 테스트 자동화 가이드
Postman과 Newman을 이용한 RestAPI 테스트 자동화 가이드 Postman과 Newman을 이용한 RestAPI 테스트 자동화 가이드
Postman과 Newman을 이용한 RestAPI 테스트 자동화 가이드
 
[Toolcon2014] WebStorm에서 자바스크립트 리팩토링하기
[Toolcon2014] WebStorm에서 자바스크립트 리팩토링하기[Toolcon2014] WebStorm에서 자바스크립트 리팩토링하기
[Toolcon2014] WebStorm에서 자바스크립트 리팩토링하기
 
Working with code
Working with codeWorking with code
Working with code
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스
 

Legacy code refactoring video rental system

  • 1. Legacy Code Refactoring Workshop - Video Rental 오재훈 (ojh420@gmail.com)
  • 2. 목표 Refactoring(Martin Fowler) 1장의 비디오 대여 시스템을 리 팩토링하면서 - 코드 스멜을 찾는 방법 - 단위 테스트를 작성하는 방법 - 레거시 코드를 리팩토링을 하는 방법 - Eclipse 의 리팩토링 자동화를 사용하는 방법 - 클린 코드를 작성하는 방법 을 배운다.
  • 4. 프로젝트 준비 1. Eclipse 를 실행하고 Java Project 를 생성한다. ( 프로젝트 이름을 VideoRental ) 2. VideoRental 프로젝트에서 src 디렉토리를 선택한다. 3. 압축 파일을 풀고, chapter1 디렉토리를 VideoRental 의 src 디렉토리로 복사한다. 4. src/chapter1/Movie.java, Customer.java, Rental.java 클래스가 복사되었는지 확인한다.
  • 5. Coverage 측정 도구 준비 1. Eclipse 를 실행한다. 2. Help 메뉴를 선택하고, Eclipse Marketplace 서브메뉴를 선택한다. 3. Search 탭에서 Find 에 eclEmma 를 입력한 후 enter 키를 입력한다. 4. 검색 결과에 "eclEmma Java Code Coverage 2.3.2" 플러그인이 뜨면, 아래쪽에 있는 "Insall" 버튼 을 클릭해서 설치한다. 설치확인하기 1. Package Explorer 에서 Java Project 를 선택한다. 2. 오른쪽 버튼을 눌러서 "Run As" 메뉴 아래에 "Coverage As" 메뉴가 보이는지 확인한다.
  • 7. 과거에 Legacy Code 에 기능을 추가했던 기억 들을 떠 올려 보고, 영향이 가장 컸던 버그를 포 스트잇에 적는다. ( 2분 )
  • 8. 레거시 코드 기능추가시 발생했던 버그에 대한 경험을 공유한다. ( 10분 ) - 레거시 코드에 기능을 추가하면서 했던 실수 중 가장 큰 실수는? - 그 실수는 누가 발견했나요? - 그 실수는 언제 발견되었나요? - 그 실수를 수정하고 배포하는 데 비용이 얼마나 들었나 요? - 그 실수가 발견되었을 때, 여러분의 감정은 어떤 상태였 나요?
  • 9. Legacy Code 정의하기 “Code Without Test” ( Michael Feathers ) People are writing legacy code right now, maybe on your project. ( http://www.objectmentor.com/resources/articles/WorkingEffectivelyWithLegacyCode.pdf )
  • 10. Legacy Code: Characteristics ● Poor Architecture ● Non-Uniform Coding Styles ● Poor or Non-Existing Documentation ● Mythical Oral Documentation
  • 12. 추가 요구사항 - 대여정보를 출력할 때, 고객이 대여한 영화 수 를 영화 종류별로 출력한다. - New Release : 1 - Children : 0 - Regular : 2 - 진행방식 : Pair Programming - 시간 : 20분
  • 13. 코드 리뷰 어떤 방식으로 기능을 추가했는가?
  • 14. 기능 추가 방법 Making change inline - 기존 코드에 새로운 코드 추가하는 방법 - 단점 - 새로운 기능에 대한 자동화된 테스트가 없다. - 기존의 메소드가 더 길어진다. - 기존의 메소드가 하는 일이 더 많아진다.
  • 15. 기능 추가 방법 Delegate Method - 새로운 기능을 새로운 메소드로 추가한다. - 장점 - 현재 메소드가 더 길어지지 않는다. - 새로운 메소드를 테스트할 수 있다. - 한계 - 현재 메소드는 여전히 테스트 할 수 없다. - 이렇게 하기 어려울 때가 있다.
  • 16. Sprouting Pattern : Sprout Method 추가 기능을 새로운 메소드로 정의하고, TDD 로 개발한다. 원래 메소드에서 새로운 메소드를 부른다.(delegate 한다.)
  • 17. Sprouting Pattern : Sprout Class 새로운 메소드가 있는 클래스를 test harness 안에서 실행하 기 어렵다면? - 새로운 클래스를 추가한다. - TDD 를 이용해서, 추가 기능을 새로운 클래스의 메소드 로 구현한다. - 원래 메소드에서 새로운 클래스를 생성한다. - 새로 생성한 객체의 새로운 메소드를 부른다.(delegate 한다. )
  • 18. Sprouting Pattern 이미 존재하는 코드에 더 많은 코드를 추가하는 것을 피하라. 대신에, 새로운 클래스와 메소드에 tested code 를 만들어라. 기존의 코드에서 새로운 메소드로 위임하라.
  • 19. Sprout Method 조금 전의 요구사항을 Sprout Method 로 작성해 본다. ( 기간 : 20분 ) - 진행방식 : Pair Programming Guide - Red-Green-Refactoring 리듬을 잘 지킨다. - 적극적으로 리팩토링한다. - 중복을 찾고, 중복을 적극적으로 제거한다. - 문자열(혹은 문자열 구조)의 중복도 찾아서 제거한다. - 리팩토링할 때마다 반드시 테스트를 실행한다.
  • 20. Working Effectively with Legacy Code Michael Feathers
  • 22. 코드스멜 찾기 코드 스멜(Code Smell) - Kent Beck 이 만든 용어 - 리팩토링이 필요한 코드를 식별하 게 도와준다. - 언제 리팩토링을 해야 하는지를 알 려준다.
  • 23. Code Smells 1 Alternative Classes with Different Interfaces 2 Combinatorial Explosion 3 Comments 4 Conditional Complexity 5 Data Class 6 Data Clumps 7 Divergent Change 8 Duplicated Code 9 Feature Envy 10 Freeloader ( Lazy Class )
  • 24. Code Smells 11 Inappropriate Intimacy 12 Incomplete Library Class 13 Incedent Exposure 14 Large Class 15 Long Method 16 Long Parameter List 17 Message Chain 18 Middle Man 19 Oddball Solution 20 Parallel Inheritance Hiearchy
  • 25. Code Smells 21 Primitive Obsessiion 22 Refused Bequest 23 Shortgun Surgery 24 Solution Sprawl 25 Speculative Generality 26 Switch Statement 27 Temporary Field Code Smell to Refactoring Cheat Sheet ( Industrial Logic ) ( http://www.industriallogic.com/wp-content/uploads/2005/09/smellstorefactorings.pdf )
  • 26. 코드스멜 찾기 Customer 의 statement() 메소드를 보자. - 이 코드는 좋은 코드인가? 나쁜 코드인가? - 코드에 개선할 부분이 있는가? - 코드 스멜을 찾아보자. - 시간 ( 5분 )
  • 27. Session 2 - 코드스멜 찾기 발견된 코드 스멜들
  • 29. 잠깐!! 리팩토링이란? 1.명사형 - 소프트웨어를 더 쉽게 이해할 수 있고, - 적은 비용으로 수정할 수 있도록 - 겉으로 보이는 동작의 변화 없이 내부 구조를 변경하는 것 2.동사형(Refactor) - 일련의 리팩토링을 적용하여 겉으로 보이는 동작의 변화 없이 - 소프트웨어의 구조를 바꾸다.
  • 30. 리팩토링 그림 출처 : http://vitalflux.com/top-6-refactoring-patterns-to-help-you-score-80-in-code-quality/
  • 31. 리팩토링의 전제조건 리팩토링=외부 동작을 바꾸지 않으면서 내부 구 조를 개선하는 것 코드를 변경했을 때, 외부 동작이 바뀌지 않았다 는 것을 증명해야 한다.
  • 32. 외부 동작이 바뀌지 않았다는 것을 어떻 게 증명할 것인가?
  • 33. 증명방법 바뀌지 않았다고 믿는다. 영향을 받을 수 있는 기능을 수동으로 테스트한다. 테스트 코드를 이용하여 자동으로 테스트한다.
  • 34. 리팩토링 vs 리프로그래밍 외부 동작이 바뀌지 않았다는 것을 증명할 수 있 는 단위테스트가 없다면? - 리팩토링을 하고 있는가? - 리프로그래밍을 하고 있는가?
  • 36. 테스트 작성하기 비디오 대여시스템 코드는 현재 테스트 코드가 없다. 리팩토링을 하고 싶다면, 먼저 테스트를 작성해야 한다.
  • 37. 레거시 코드 단위 테스트 작성하기 1. 생성자 테스트 작성하기 2. 테스트 커버리지를 측정한다. 3. 테스트 되지 않은 코드를 커버할 수 있는 새로운 테스트 를 작성한다. ( Characterization Test ) 4. 테스트 커버리지가 100%에 가까워질 때까지 테스트 코 드를 작성한다.
  • 38. 생성자 테스트 작성하기 목적 : 객체 생성 비용을 파악한다. 1. JUnit Test Class 를 생성한다. ( CustomerTest ) 2. 테스트 메소드 이름을 testCreate 라고 한다. 3. Customer 클래스의 객체를 생성해 본다. 4. 단위 테스트를 실행한다.
  • 39. 생성자 테스트 작성하기 public class CustomerTest { @Test public void testCreate() { Customer customer = new Customer(null); } }
  • 40. Customer 의 statement() 테스트하기 실패하는 테스트 코드 만들기 1. 생성자 테스트 메소드의 이름을 testX 로 바꾼다. 2. assert 문을 추가한다. assertEquals(“”,customer.statement()) 3. 테스트를 실행한다. ( 빨간불 )
  • 41. Customer 의 statement() 테스트하기 실패하는 테스트 코드 만들기 public class CustomerTest { @Test public void testX() { Customer customer = new Customer(null); assertEquals(“”,customer.statement()) } }
  • 42. Customer 의 statement() 테스트하기 실패하는 테스트 코드 만들기 1. JUnit 창에서 statement() 의 반환값을 복사해서, expected value 에 복사한 다. 2. 테스트를 실행한다. ( 초록불 ) 3. 테스트 메소드의 이름을 변경한다.
  • 43. Customer 의 statement() 테스트하기 테스트 코드 이름 변경하기 (rename) @Test public void amountShouldBeZeroWhenCustomerRentNoMovie() { assertEquals("Rental Record for Johnn" + "Amount owed is 0.0n" + "You earned 0frequent renter points" ,customer.statement()); }
  • 44. 테스트 커버리지 실행하기 테스트 커버리지 도구를 이용하여, 테스트가 되지 않은 코드 를 파악한다. - Coverage As 메뉴에서 JUnit Test 를 선택해서 실행 statement() 함수를 확인해서, 테스트 되지 않은 코드를 파악 한다. 테스트 되지 않은 코드를 커버하기 위해 새로운 테스트를 작 성한다.
  • 46. 레거시 코드 테스팅 알고리즘 1. 커버리지를 확인하고, 새로운 테스트가 커버할 코드 영 역을 결정한다. 2. 테스트 메소드를 생성하고, 해당 코드 영역을 커버하기 위한 조건을 만든다. 3. 실패하는 테스트 코드를 작성한다. 4. JUnit 결과를 이용하여 테스트를 통과하게 만든다. 5. 커버리지가 100%에 가까워질 때까지 이 과정을 반복한 다.
  • 47. 레거시 코드 테스팅 미션 : 테스트 커버리지가 100%에 가까워질 때 까지 테스트 코드를 작성한다. 시간 : 30분
  • 48. Characterization Test 레거시 코드의 특성을 이해하기 위해서 작성하 는 테스트
  • 50. Refactoring 시작하기 테스트 커버리지가 100%에 가까워지면 이제 리 팩토링을 시작할 수 있다.
  • 51. Code Smell 찾기 Customer.statement() 에 어떤 코드 스멜이 있는 지 다시 한번 찾아보자.
  • 52. Long Method 발견하기 가장 쉬운 코드 스멜 가장 많이 생기는 코드 스멜 긴 메소드가 왜 코드 스멜인가? 왜 긴 메소드가 생기는가?
  • 53. Long Method 긴 메소드를 없애기 위한 리팩토링 : - Extract Method ( Alt+Shift+M ) 메소드로 추출할 코드를 선정하는 방법 - 주석이 있는 코드 - 조건문과 루프 - 메소드로 뽑았을 때, 의도를 더 잘 전달할 수 있는 코드
  • 56. Refactoring - Extract Method statement() 에서 주석이 달 린 코드를 찾아서 리팩토링 해 보자. while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental)rentals.nextElement(); //각 영화에 대한 요금 결정 switch ( each.getMovie().getPriceCode() ) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDREN: thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; }
  • 57. 영화 요금 - Extract Method while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental)rentals.nextElement(); //각 영화에 대한 요금 결정 switch ( each.getMovie().getPriceCode() ) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDREN: thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } // 포인트(frequent renter points) 추가 frequentRenterPoints ++; // 최신을 이틀이상 대여하는 경우 추가 포인트 제공 if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; // 이 대여에 대한 요금 계산 결과 표시 result += "t" + each.getMovie().getTitle() + "t" + String.valueOf(thisAmount) + "n"; totalAmount += thisAmount; } 1. 코드 블록 선택후 Extract Method ( Alt+Shift+M) 2. 메소드 이름 : amountFor
  • 58. 영화 요금 - Extract Method while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental)rentals.nextElement(); thisAmount = amountFor(thisAmount,each) // 포인트(frequent renter points) 추가 frequentRenterPoints ++; // 최신을 이틀이상 대여하는 경우 추가 포인트 제공 if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; // 이 대여에 대한 요금 계산 결과 표시 result += "t" + each.getMovie().getTitle() + "t" + String.valueOf(thisAmount) + "n"; totalAmount += thisAmount; } private double amountFor(double thisAmount,Rental each) { //각 영화에 대한 요금 결정 switch ( each.getMovie().getPriceCode() ) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case Movie.NEW_RELEASE: thisAmount += each.getDaysRented() * 3; break; case Movie.CHILDREN: thisAmount += 1.5; if (each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } return thisAmount; }
  • 59. 테스트 실행 리팩토링을 한 후에는 반드시 테스트를 실행해 서 - 기존의 동작이 변경되지 않았는지 확인한다.
  • 60. amountFor 리팩토링 하기 amountFor 에는 어떤 코드 스멜이 있는가?
  • 61. amountFor 리팩토링 double amountFor(double thisAmount, Rental each) 에서 thisAmount 는 패러미터로 받을 필요가 없음 메소드에서 패러미터를 삭제하는 리팩토링 : Remove Parameter 이클립스에서는 Change Method Signature 에서 패러미터를 삭제할 수 있음 ( Alt+Shift+C ) 1. thisAmount Parameter 선택 2. Remove 클릭
  • 62. amountFor 리팩토링 thisAmount 패러미터가 삭제되면, thisAmount 가 정의되지 않았기 때문에 컴파 일 오류 발생 - 컴파일 오류가 thisAmount 를 선택 후, Quick Fix 로 로컬 변수 생성 - 테스트 코드 실행 : 테스트가 실패함 - 왜 테스트 코드가 실패할까? private double amountFor(double thisAmount,Rental each) { int thisAmount = 0; switch ( each.getMovie().getPriceCode() ) { case Movie.REGULAR: thisAmount += 2; if (each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; ...
  • 63. amountFor 리팩토링 amountFor(Rental each) 에서 변수 이름 each 이름 변경하기 - 패러미터 선택후 Rename Method ( Alt+Shift+R) - 변경이유 : - each 는 루프에서 사용되던 변수 이름 - 패러미터는 루프와 관계 없기 때문에 이름을 맞게 수정 로컬 변수인 thisAmount 의 이름 변경하기 - 변수 선택후 Rename Local Variable ( Alt+Shift+R ) - 변경 이유 - 변수 이름이 너무 길다.
  • 64. amountFor 리팩토링 amountFor 는 Customer 객체의 멤버를 전혀 사용하지 않는 다. Rental 과 Movie 객체의 메소드를 사용한다. amountFor 에는 Feature Envy(기능 욕심) 코드 스멜이 있다.
  • 65. Feature Envy 자신이 속한 클래스보다 다른 클래스에 관심을 가지고 있는 경우 적용할 리팩토링 : Move Method
  • 66. Feature Envy 메소드가 여러 클래스의 메소드를 사용하고 있 다면 어디로 옮겨야 할까?
  • 67. amountFor 리팩토링 amountFor 를 Rental 클래스로 옮긴다. - 커서를 amountFor signature 로 이동한다. - Move Method ( Alt+Shift+V ) 를 실행한다.
  • 68. amountFor 리팩토링 amountFor 를 Rental 클래스로 옮기고 나면, statement() 에 서 each.amountFor() 형식으로 불린다. a. Rental 클래스로 옮기기 전에는 … amountFor(each) 형식이어서 영어 문장의 어순과 일치한다. b. Rental 클래스로 옮긴 후에는 each.amountFor() 라서 영어의 어순과 일치하지 않는다. c. amountFor 메소드의 이름을 바꾸는 것이 좋다.
  • 69. 로컬 변수 삭제 하기 statement() 의 thisAmount 로컬 변수를 삭제한다. 로컬 변수를 삭제하는 이유는? // 리팩토링 후 코드 while (rentals.hasMoreElements()) { Rental each = (Rental)rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints() // 이 대여에 대한 요금 계산 결과 표시 result += "t" + each.getMovie().getTitle() + "t" + String.valueOf(each.getCharge()) + "n"; totalAmount += each.getCharge(); }
  • 70. 로컬 변수 삭제 하기 적용할 리팩토링 : Replace Temp with Query // 리팩토링 후 코드 while (rentals.hasMoreElements()) { Rental each = (Rental)rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints() // 이 대여에 대한 요금 계산 결과 표시 result += "t" + each.getMovie().getTitle() + "t" + String.valueOf(each.getCharge()) + "n"; totalAmount += each.getCharge(); }
  • 72. // 포인트(frequent renter points) 추가 “// 포인트(frequent renter points) 추가” 에 대해서도 amountFor 와 동일한 리팩토링을 수행한다. // 리팩토링 후 코드 while (rentals.hasMoreElements()) { double thisAmount = 0; Rental each = (Rental)rentals.nextElement(); thisAmount = amountFor(thisAmount,each) frequentRenterPoints += each.getFrequentRenterPoints() // 이 대여에 대한 요금 계산 결과 표시 result += "t" + each.getMovie().getTitle() + "t" + String.valueOf(each.getCharge()) + "n"; totalAmount += each.getCharge(); }
  • 73. 임시 변수 제거하기 statement() 에 어떤 임시 변수가 있는가?
  • 74. totalAmount 임시변수 제거하기 1. statement() 함수를 복사해서 getTotalCharge() 를 만든다. 2. statement() 함수에서 totalAmount 를 사용하는 곳을 getTotalCharge() 로 바 꾼다. 3. statement() 함수에서 totalAmount 변수를 삭제한다.
  • 75. totalAmount 임시변수 제거하기 public String statement() { Enumeration rentals = _rentals.elements(); String result ="Rental Record for " + getName() + "n"; while (rentals.hasMoreElements()) { Rental each = (Rental)rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints() result += "t" + each.getMovie().getTitle() + "t" + String.valueOf(each.getCharge()) + "n"; } result += "Amount owed is " + String.valueOf(getTotalCharge()) + "n"; result += "You earned " + String.valueOf(frequentRenterPoints) + "frequent renter points"; return result; }
  • 76. frequentRenterPoints 제거하기 public String statement() { Enumeration rentals = _rentals.elements(); String result ="Rental Record for " + getName() + "n"; while (rentals.hasMoreElements()) { Rental each = (Rental)rentals.nextElement(); result += "t" + each.getMovie().getTitle() + "t" + String.valueOf(each.getCharge()) + "n"; } result += "Amount owed is " + String.valueOf(getTotalCharge()) + "n"; result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + "frequent renter points"; return result; }
  • 77. 멈추고 생각하기 코드량 - 일반적으로 리팩토링하면 코드량이 줄어든다. - 이번 리팩토링은? 성능 - 이번 리팩토링은 성능에는 어떤 영향을 미치는가? 이번 리팩토링은 - 왜 해야 하는가? - 어떤 이익을 볼 수 있는가?
  • 78. 변경된 구조 while ( ) { } 영화 요금 계산 영화 요금 출력 포인트 계산 영화 요금 계산 while () { } 영화 요금 출력 while () { } 포인트 계산 while () { }
  • 79. 기능추가 - HTML 로 구현해 보자.
  • 80. html 로 출력하는 기능 구현 statement() 과 동일한 내용을 html 로 출력하는 기능을 구현해 보자.
  • 82. Composed Method 켄트 벡이 발견한 패턴 1. 코드를 메소드로 나눈다. 메소드는 한가지 일만 해야 한다. 2. 메소드에 있는 코드의 추상화 레벨은 같아야 한다. 3. Composed Method 패턴을 따르면, 메소드 크기가 작아진다.
  • 83. Composed Method 로 만들기 package refactoring; public class List { private Object[] _elements = new Object[10]; private boolean _readOnly; private int _size = 0; public void Add(Object child) { if (!_readOnly) { int newSize = _size + 1; if (newSize > _elements.length) { Object[] newElements = new Object[_elements.length + 10]; for (int i = 0; i < _size; i++) { newElements[i] = _elements[i]; } _elements = newElements; } _elements[_size] = child; _size++; } } } 출처: Refactoring to Patterns (Joshua Kerievsky )
  • 84. statement() 리팩토링 하기 Composed Method 로 만들어 보자.
  • 86. 새로운 분류 방법 도입 대여점에서 영화를 분류하는 방법을 변경할 예정이다. - 어떻게 변경할지는 결정하지 않았다. - 새로운 분류방법이 도입될 것이다. - 새로운 분류방법에 따라 요금과 포인트를 할당할 예정이 다. 현재 시점에서 이런 변경하는 것이 쉬울까?
  • 87. 이런 변화를 쉽게 반영하려면 어떻게 해야 할 까?
  • 88. switch statement switch statement 는 왜 코드 스멜인가? switch statement 는 언제 리팩토링해야 하는가?
  • 89. switch statement 리팩토링 방법 - Replace Type Code with Subclasses - Replace Type Code with State/Strategy - Replace Conditional with Polymorphism
  • 90. switch statement Rental 의 getCharge 에서 개선할 것은?
  • 91. switch statement Rental 의 getCharge 를 Movie 로 옮기자 설계면에서 어느 것이 더 좋은 선택인가? - 영화 종류를 Rental 에서 사용하는 것과 - 대여기간을 Movie 로 넘겨주는 것
  • 92. Refactoring - Replace Type Code with Subclasses
  • 93. switch statement 영화 종류가 여러개이고, 각 영화종류는 같은 질문에 다른 답을 한다. - Switch 문을 직접 다형성으로 바꿔보자. Movie getCharge getCharge RegularMovie Children Movie getCharge New Release Movie getCharge
  • 94. switch statement State Pattern 을 이용하기
  • 95. Refactoring - Replace Type Code with State/Strategy
  • 96. Replace type code with State/Strategy Step 1. type code 에 Self Encapsulate Field 를 적용하자. Step 2. Movie 클래스에서 Price 클래스를 Extract 한다.
  • 97. Move Method Movie 의 getCharge 를 price 로 옮기기
  • 98. Replace Conditional with Polymorphism Price getCharge 코드를 subclass 로 옮기기 모든 작업이 끝나면, price getCharge 를 추상 메소드로 변경
  • 99. getFrequentRenterPoints Rental 에서 Movie 로 옮기자 동일하게 적용하자
  • 100. 멈추고 생각하기 스테이트 패턴을 적용하는데 많은 노력이 들었 다. 그럴만한 가치가 있었을까? 이전보다 어떤 점이 개선되었는가?
  • 101. 회고하기 이 과정을 통해서 배운 것중 기억하고 싶은 것을 한가지만 고른다면? 이 과정을 통해서 새로 알게 된 것들이 있다면? 이 과정에서 아쉬운 점이 있었다면?