Legacy Code Refactoring
- 마틴 파울러의 Refactoring 책 1장 예제를 워크샾 형태로 구성했다.
- 레거시 코드인 비디오 렌탈 시스템을 리팩토링 하는 방법을 단계적으로 살펴본다.
- 마이클 페더스의 Characterization Test 방법을 이용해서 Legacy Code 의 테스트를 작성한다.
- 새로운 기능을 추가할 때는 Sprouting Pattern(마이클 페더스가 만든 용어)를 이용해서 기능을 추가한다.
- 코드 스멜을 찾고 코드 스멜을 제거하면서 코드의 설계를 개선한다.
마이크로서비스 스타일로 만들어진 시스템을 모노리틱 스타일로 이관한 사례와 함께 스프링을 이용해 모듈형 모노리스(modular monoliths)를 만든 경험을 바탕으로 모노리틱/마이크로서비스 보다 본질적인 문제를 제기하고, 문제 해결을 위한 생각을 공유합니다.
https://github.com/arawn/building-modular-monoliths-using-spring
마이크로서비스 스타일로 만들어진 시스템을 모노리틱 스타일로 이관한 사례와 함께 스프링을 이용해 모듈형 모노리스(modular monoliths)를 만든 경험을 바탕으로 모노리틱/마이크로서비스 보다 본질적인 문제를 제기하고, 문제 해결을 위한 아이디어와 코드를 공유합니다.
https://github.com/arawn/building-modular-monoliths-using-spring
이 자료는 2019년 KSUG 세미나에서 진행한 "잘 키운 모노리스 하나 열 마이크로서비스 안 부럽다"를 기반으로 몇가지 내용을 추가하고, 전개 방식을 다듬어 조금 더 친절하게 만들어졌습니다.
마이크로서비스 스타일로 만들어진 시스템을 모노리틱 스타일로 이관한 사례와 함께 스프링을 이용해 모듈형 모노리스(modular monoliths)를 만든 경험을 바탕으로 모노리틱/마이크로서비스 보다 본질적인 문제를 제기하고, 문제 해결을 위한 생각을 공유합니다.
https://github.com/arawn/building-modular-monoliths-using-spring
마이크로서비스 스타일로 만들어진 시스템을 모노리틱 스타일로 이관한 사례와 함께 스프링을 이용해 모듈형 모노리스(modular monoliths)를 만든 경험을 바탕으로 모노리틱/마이크로서비스 보다 본질적인 문제를 제기하고, 문제 해결을 위한 아이디어와 코드를 공유합니다.
https://github.com/arawn/building-modular-monoliths-using-spring
이 자료는 2019년 KSUG 세미나에서 진행한 "잘 키운 모노리스 하나 열 마이크로서비스 안 부럽다"를 기반으로 몇가지 내용을 추가하고, 전개 방식을 다듬어 조금 더 친절하게 만들어졌습니다.
2018.10.18
OKKYCON: 2018 《The Real TDD - TDD 제대로 알기》
박재성님의 <의식적인 연습으로 TDD, 리팩토링 연습하기> 발표자료입니다.
[연사 소개]
박재성 - SW 교육 전문가 전 NEXT 교수
5년 동안 NEXT에서 학생들을 가르치다 NEXT가 문을 닫으면서 1인 교육 사업을 하고 있다. TDD, 리팩토링 경험이 프로그래머의 삶에 큰 영향을 미칠 것으로 판단해 TDD, 리팩토링을 주제로 교육 과정을 개설했는데 좋은 반응을 얻고 있다.
[발표 소개]
TDD와 리팩토링 역량은 책 몇 권 읽고, 반복적인 연습만 한다고 해서 쌓을 수 있는 역량이 아닙니다. 의식적인 연습을 통해 꾸준히 수련해 나갈 때 점진적으로 향상시킬 수 있습니다. 의식적인 연습을 설계하고, 단계적인 수련을 통해 점진적으로 TDD, 리팩토링 역량을 키워가는 과정에 대해 다룹니다.
http://okkycon.com
NATS is a high performance messaging server and also one of the latest additions to the CNCF. In this talk, we will make a deep dive to the internals of the project covering its design, protocol, clustering implementation, security and authorization features that make it an attractive solution for microservices and low latency applications.
애자일을 제대로 실천하려면 실패와 실수에서 학습하고, 공동의 목적을 이루기 위해 다른 사람들을 도우려는 마음가짐이 필요하다.
고착화된 마인드셋에서 성장 마인드셋으로 바뀌었던 경험을 토대로 고착화된 마인드셋에서 애자일 마인드셋을 얻게 되었는지 살펴보았다.
애자일 마인드셋이 필요한 이유와 어떻게 애자일 마인드셋을 키울 것인지도 알아본다.
2018.10.18
OKKYCON: 2018 《The Real TDD - TDD 제대로 알기》
박재성님의 <의식적인 연습으로 TDD, 리팩토링 연습하기> 발표자료입니다.
[연사 소개]
박재성 - SW 교육 전문가 전 NEXT 교수
5년 동안 NEXT에서 학생들을 가르치다 NEXT가 문을 닫으면서 1인 교육 사업을 하고 있다. TDD, 리팩토링 경험이 프로그래머의 삶에 큰 영향을 미칠 것으로 판단해 TDD, 리팩토링을 주제로 교육 과정을 개설했는데 좋은 반응을 얻고 있다.
[발표 소개]
TDD와 리팩토링 역량은 책 몇 권 읽고, 반복적인 연습만 한다고 해서 쌓을 수 있는 역량이 아닙니다. 의식적인 연습을 통해 꾸준히 수련해 나갈 때 점진적으로 향상시킬 수 있습니다. 의식적인 연습을 설계하고, 단계적인 수련을 통해 점진적으로 TDD, 리팩토링 역량을 키워가는 과정에 대해 다룹니다.
http://okkycon.com
NATS is a high performance messaging server and also one of the latest additions to the CNCF. In this talk, we will make a deep dive to the internals of the project covering its design, protocol, clustering implementation, security and authorization features that make it an attractive solution for microservices and low latency applications.
애자일을 제대로 실천하려면 실패와 실수에서 학습하고, 공동의 목적을 이루기 위해 다른 사람들을 도우려는 마음가짐이 필요하다.
고착화된 마인드셋에서 성장 마인드셋으로 바뀌었던 경험을 토대로 고착화된 마인드셋에서 애자일 마인드셋을 얻게 되었는지 살펴보았다.
애자일 마인드셋이 필요한 이유와 어떻게 애자일 마인드셋을 키울 것인지도 알아본다.
TDD 규칙은 간단하지만, TDD 를 배우는 것은 어렵고, 실천하기는 더 어렵다.
왜 그럴까? TDD 는 설계 방법이기 때문이다. TDD 의 규칙 리듬을 알고 따르려고 해도, 설계 용어들을 모르면 TDD 를 제대로 할 수 없다.
TDD 를 잘 하려면, 설계용어의 의미를 이해하고, 언제 적용하는지도 알아야 한다.
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 )
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 리듬을 잘 지킨다.
- 적극적으로 리팩토링한다.
- 중복을 찾고, 중복을 적극적으로 제거한다.
- 문자열(혹은 문자열 구조)의 중복도 찾아서 제거한다.
- 리팩토링할 때마다 반드시 테스트를 실행한다.
29. 잠깐!! 리팩토링이란?
1.명사형
- 소프트웨어를 더 쉽게 이해할 수 있고,
- 적은 비용으로 수정할 수 있도록
- 겉으로 보이는 동작의 변화 없이 내부 구조를 변경하는
것
2.동사형(Refactor)
- 일련의 리팩토링을 적용하여 겉으로 보이는 동작의 변화
없이
- 소프트웨어의 구조를 바꾸다.
30. 리팩토링
그림 출처 : http://vitalflux.com/top-6-refactoring-patterns-to-help-you-score-80-in-code-quality/
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분
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(기능 욕심) 코드 스멜이 있다.
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();
}
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 () {
}
93. switch statement
영화 종류가 여러개이고, 각 영화종류는 같은 질문에 다른
답을 한다.
- Switch 문을 직접 다형성으로 바꿔보자.
Movie
getCharge
getCharge
RegularMovie Children Movie
getCharge
New Release Movie
getCharge