devon 2011-b-5 효과적인 레거시 코드 다루기

42
초록 좋은 설계와 개발 과정에 대한 많은 노력이 기울여져 왔다. 불행히도 이러한 노력은 프로젝트가 아무 것도 단계에서 시작하는 것을 간주했다. 하지만 대부분의 프로젝트는 레거시 코드를 수반한다. 레거시 코드는 신규 코드보다 100:1비율로 많다. 신규 코드로 작업하는 보다 레거시 코드로 작업하는 것은 많은 노력을 요한다. 발표에서는 레거시 코드에 기능을 추가, 수정하는 략과 몇가지 리팩토링, TDD 기법을 사례 중심으로 알아 보도록 한다. 아무리 좋은 설계로 시작한 프로젝트라도 테스트가 없으면 변경이 두려워 리팩토링, 개선을 못하게 되고 점진적으로는 코드가 부폐하게 된다. 아무리 나쁜 설계로 시작한 프로젝트라도 포괄적인 테스트가 있다면 변경에 대한 두려움이 없어서 점진적으로 리팩토링, 개선을 하여 좋은 구조를 갖게된다.

Upload: daum-dna

Post on 29-Jun-2015

364 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Devon 2011-b-5 효과적인 레거시 코드 다루기

초록좋은 설계와 개발 과정에 대한 많은 노력이 기울여져 왔다. 불행히도 이러한 노력은 프로젝트가 아무 것도 없는 단계에서 시작하는 것을 간주했다.

하지만 대부분의 프로젝트는 레거시 코드를 수반한다. 레거시 코드는 신규 코드보다 100:1의 비율로 더 많다. 신규 코드로 작업하는 것 보다 레거시 코드로 작업하는 것은 더 많은 노력을 요한다.

이 발표에서는 레거시 코드에 기능을 추가, 수정하는 전략과 몇가지 리팩토링, TDD 기법을 사례 중심으로 알아보도록 한다.

아무리 좋은 설계로 시작한 프로젝트라도 테스트가 없으면 변경이 두려워 리팩토링, 개선을 못하게 되고 점진적으로는 코드가 부폐하게 된다.아무리 나쁜 설계로 시작한 프로젝트라도 포괄적인 테스트가 있다면 변경에 대한 두려움이 없어서 점진적으로 리팩토링, 개선을 하여 좋은 구조를 갖게된다.

Page 2: Devon 2011-b-5 효과적인 레거시 코드 다루기

효과적으로 레거시 코드 다루기

서비스플랫폼개발팀백명석

Page 3: Devon 2011-b-5 효과적인 레거시 코드 다루기

Contents

The Way We Work

The Way We Have To Do

Working Effectively with Legacy Code

Case Studies

우리가 어떻게 일해 왔는지,우리가 일해야 하는지,그리고 테스트가 없는 레거시 코드를 효과적으로 다루는 전략과 사례에 대해서 말씀드리겠습니다.

Page 4: Devon 2011-b-5 효과적인 레거시 코드 다루기

The Way We Work

“Quick & Dirty” ➜ “Slow & Dirty”

그린필드 프로젝트라고 불리는 신규 프로젝트는 3개월 정도의 짧은 시간에 개발됩니다. 시간이 없이니 테스트, 리팩토링 없이 개발합니다. 사전 작업에 대한 제약이 없으므로 이때는 아무런 문제가 없습니다.서비스가 런칭되면 대개 3년 이상 운영됩니다. 신규 요구사항은 넘치고, 버그 수정에 대부분의 시간을 보내게됩니다. 테스트가 없는 dirty 코드는 테스트, 리팩토링이 어려워 많은 비용을 유발하고, 기존 기능에 버그를 유발할 수도 있기에 Quick & dirty는 결국 slow & dirty됨

Page 5: Devon 2011-b-5 효과적인 레거시 코드 다루기

The Way We Have To Do

We Already Have Dirty Working Code

Work Effectively with Legacy Code

그럼 어떻게 해야 할까요 ? 이미 동작하고 있는 지저분한 코드가 있는데.우리는 테스트가 없는 레거시 코드를 효과적으로 다룰 수 있어야 합니다.

Page 6: Devon 2011-b-5 효과적인 레거시 코드 다루기

Working Effectively with Legacy Code

Wait Until A New Feature is Needed

Change a little bit of design

Add few tests in the affected area

Eventually spread the test throughout the legacy code

레거시에 코드를 다루는 기본적인 전략은 레거시에 테스트 추가하기 위해 특공대를 투입해서 거대한 새로운 프로젝트를 하는 것이 아니라 “새로운 기능이 필요할때까지 기다리는 것입니다”그리고 설계를 조금씩 개선하고, 영향받는 코드들에 대한 테스트를 조금씩 추가해서 최종적으로는 레거시 코드 전체에 테스트를 확산시키는 것입니다.

Page 7: Devon 2011-b-5 효과적인 레거시 코드 다루기

One More:Automation Tools

또 하나의 전략으로 자동화 툴이 있습니다.MoreUnit, EclEmma와 같은 eclipse plugin, eclipse의 hot fix, content assist, refactoring와 같은 자동화 기능mockito와 같은 유닛 테스트 작성을 위한 라이브러리등을 활용하는 것이 도움이 됩니다.

Page 8: Devon 2011-b-5 효과적인 레거시 코드 다루기

Case Studies

Sprout Method

Characterization Test

Subclass and Override Method

Extract Method Object & Extract Method

이제부터는 레거시 코드 활용의 사례에 대해서 말씀드리겠습니다.

Page 9: Devon 2011-b-5 효과적인 레거시 코드 다루기

Have to Change But Not Enough Time

Not Enough Time to Break Dependency and Add Tests

기존 코드에 새로운 기능을 추가해야 하는데 의존성을 제거하고 테스트를 추가하기에는 시간이 너무 부족한 경우를 생각해 보겠습니다.필요한 새로운 메소드를 기존 클래스에 10분이면 추가할 수 있는데, 이 클래스에 대한 테스트를 만드려면 2시간은 걸릴 것 같은 상황이 있을 수 있습니다.

Page 10: Devon 2011-b-5 효과적인 레거시 코드 다루기

Sprout Method

You Need to Add a Feature to a System

Can be formulated completely as new code

Write the code in a new method

Call it where the new functionality is supposed to be

시스템에 새로운 기능을 추가해야 하는데 새로운 기능이 완전히 분리된 새로운 메소드로 구현할 수 있다면새로운 기능을 새로운 메소드로 작성하고 새로운 메소드를 해당 기능이 필요한 기존 코드에서 호출하는 방법이 sprout method의 개념입니다.

Page 11: Devon 2011-b-5 효과적인 레거시 코드 다루기

Steps

Identify where you need to make your code change.

Write down a call for a new method then comment it out.

Develop the sprout method using TDD

Remove the comment to enable the call.

1. 변경이 필요한 곳을 식별2. 새로운 메소드 호출을 추가하고, 커멘트 처리3. 새로운 메소드를 TDD로 구현4. 커멘트를 제거하여 메소드 호출이 일어나도록 한다.

Page 12: Devon 2011-b-5 효과적인 레거시 코드 다루기

Sprout Method Example

Add Synchronized Token Pattern to EditArticleController

for prohibiting abusing

compare session token and request token

게시글 수정 컨트롤러에 어뷰징 방지를 위해 synchronized token pattern을 적용하는 예제

Page 13: Devon 2011-b-5 효과적인 레거시 코드 다루기

Need to Make Change But Don’t Know What Tests to Write

이와 같이 복잡한 상속구조를 가지고 있는 SearchEngineCreateArticleRequestFactory라는 클래스를 변경해야 하는데 어떻게 테스트를 작성할 지 모르는 경우를 생각보겠습니다.

Page 14: Devon 2011-b-5 효과적인 레거시 코드 다루기

Need to Make Change But Don’t Know What Tests to Write

Need to Know What the S/W Supposed To Do

Write Tests Based on Those Ideas

Dig up Old Requirements Documents and Project Memos ???

- 변경을 가하기 전에 시스템이 어떻게 동작하도록 되어 있는지 알아내야 할 필요가 있다.- 이러한 요구사항에 기반하여 테스트를 작성할 수 있다.- 어떻게 해야 하나 ? 오래된 요구 사항 문서를 조사해야 하나 ? 프로젝트 메모들을 뒤져야 하나 ?

Page 15: Devon 2011-b-5 효과적인 레거시 코드 다루기

Characterization Test

What the systems does is more important than what it is supposed to do.

We want preserve behavior

Test that characterizes the actual behavior of a piece of code.

- 시스템이 어떻게 동작하기로 약속되었는지 보다 실제로 어떻게 동작하는지가 훨씬 중요하다.- 비록 코드에 잘못이 있더라도 그 행위를 보존하기를 원합니다.- 코드가 실제로 어떤 행동을 하는지를 나타낼 수 있는 테스트를 작성하는 것.이러한 기법을 Characterization Test라고 한다.

Page 16: Devon 2011-b-5 효과적인 레거시 코드 다루기

Steps

Write an assertion that you know will fail

Let the failure tell you what the behavior is

Change the test so that it expects the behavior that the code produces

절차1. 실패할 것이라고 알고 있는 assert 문장을 작성한다.2. 이 실패를 통해 실제 행위가 무엇인지 발견한다.3. 코드가 생산하는 행위를 기대하도록 테스트를 변경한다.

Page 17: Devon 2011-b-5 효과적인 레거시 코드 다루기

Characterization Test Example

XML 요청을 만드는 팩토리에 대한 테스트이 테스트는 다소 복잡해서 설명의 편의를 위해 미리 슈퍼 클래스에 복잡한 부분을 구현해 놓았습니다.

Page 18: Devon 2011-b-5 효과적인 레거시 코드 다루기

Hard to Add Test Because of Unwanted Dependency

static funciton등과 같은 원치 않는 의존성으로 인해 테스트를 작성하기 어려운 경우가 있습니다.

Page 19: Devon 2011-b-5 효과적인 레거시 코드 다루기

Subclass and Override Method

A core technique for breaking dependencies in OOP

Use inheritance in the context of a test

to nullify behavior that you don’t care about

to get access to behavior that you do care about

subclass and override method는 OOP에서 의존성 제거를 위한 핵심 기능이다.테스트에서 subclassing을 아래와 같은 목적으로 사용한다.1. 원치 않는 행위를 무효화하기 위해2. 관심있는 행위에 접근하기 위해

Page 20: Devon 2011-b-5 효과적인 레거시 코드 다루기

Steps

Identify the dependencies that you want to separate

Make each method overridable(adjust the visibility of the methods)

Create a subclass that overrides the methods.

분리하고 하는 의존성을 식별한다.메소드의 가시성을 protected 등으로 변경하여 메소드가 override 가능하도록 한다.해당 메소드들을 override할 서브클래스를 생성한다.

Page 21: Devon 2011-b-5 효과적인 레거시 코드 다루기

Subclass and Override Method Example

static 함수는 powermock을 사용해서 mocking이 가능하다. 하지만 static 함수 자체가 싱글톤과 마찬가지로 전역변수를 사용하는 것과 같은 잘못된 습관이다.그러므로 Static 함수로 인한 테스트 어려움을 제거하는 방법을 알아보자.

Page 22: Devon 2011-b-5 효과적인 레거시 코드 다루기

Big Function

쉽게 이해하기 어려운 큰 함수를 개선하는 경우를 생각해 보자.

Page 23: Devon 2011-b-5 효과적인 레거시 코드 다루기

Big function hides classes

큰 함수는 (인자, 로컬 변수로 표현되는 일련의 변수들)과 (이 변수들에 동작하는 들여쓰기로 구분되는 기능들)을 가지고 있다. 클래스 = (일련의 변수(필드)들_ + (이 변수들에 동작하는 함수들)의 집합이다.그리므로 큰 함수는 클래스로 추출되어야 한다.

Page 24: Devon 2011-b-5 효과적인 레거시 코드 다루기

Extract Method Object

Steps

Extract Method(invoke)

Create New Inner Class

Convert Arg. to Field

Convert Local Var. to Field

Move Initialization Logic to constructor

1. 큰 메소드를 invoke라는 이름의 메소드로 추출한다.2. invoke 메소드를 갖는 inner 클래스를 생성한다. - 함수의 인자는 클래스의 생성자에 인자로 전달하고, invoke 메소드에서는 인자를 제거합니다.3. 생성자에 전달된 인자를 필드로 변경한다.4. 함수의 로컬 변수를 필드로 변경한다. 이러한 변경은 클래스 내부에서 메소드 호출시 불필요한 인자를 없애준다.5. 변수들의 initialization 로직은 constructor로 이동한다.

Page 25: Devon 2011-b-5 효과적인 레거시 코드 다루기

Extract Method Object

Steps

Extract Methods Till You Drop

Move Type to New File

Rename class / method

Method Ordering(Top-Down, To-paragraph)

Parameterize Differences

6. 이제 클래스에 더 이상 extract할 메소드가 없을 때 까지 extract method를 수행한다. 이때 반복적으로 나타나는 중복 문장들에서 약간씩 다른 점들이 존재한다면 이를 파라미터로 처리합니다.7. inner 클래스를 일반 클래스로 변경8. 클래스 이름과 메소드 이름을 의미있게 변경한다.9. 메소드 순서를 변경해서 top-down(to paragraph)으로 읽기 쉽게 변경한다.

Page 26: Devon 2011-b-5 효과적인 레거시 코드 다루기

Extract Method ObjectExample

Fitness

이 예제에서는 큰 함수를 extract method object 기법을 통해 새로운 클래스로 추출하고,extract method를 수행하는 예를 보이도록 하겠다.

Page 27: Devon 2011-b-5 효과적인 레거시 코드 다루기

Usage of Extract Method Object

Extract Method Object는 TDD에서도 유용하게 사용될 수 있다. 이 예제는 켄트벡이 TDD 스크린 캐스트에서 사용한 예제인데, key-value 스토어의 일종이 tyrant의 클라이언트를 만드는 예제이다.1. 이와 같이 클라이언트를 생성하고, put하고 get하여 동일한 값인지 확인하는 테스트로 시작한다. 이 기능을 구현하려면 많은 시간이 소요된다. 켄트벡 자신이 2시간 가량 걸릴 것이라고 했으니.

Page 28: Devon 2011-b-5 효과적인 레거시 코드 다루기

Usage of Extract Method Object

이 보다는 본래의 의도는 커멘트 처리하고 소켓을 생성해서 서버에 연결이 되는지 부터 확인하는 것이 쉽다.디버깅 필요 없이 바로 실행이 되니

Page 29: Devon 2011-b-5 효과적인 레거시 코드 다루기

Usage of Extract Method Object

이렇게 테스트 코드에서 실제 구현을 다 해보는 것이다. 바로 수행해 볼 수 있으니 매우 편리하고 스피디하게 진행된다.

Page 30: Devon 2011-b-5 효과적인 레거시 코드 다루기

Usage of Extract Method Object

기능이 다 구현되면 extract method object 기법을 이용해서 production class로 추출한다.

Page 31: Devon 2011-b-5 효과적인 레거시 코드 다루기

Usage of Extract Method Object

약간의 리팩토리을 통해 상수를 제거하고, open, close, get 메소드를 추출하면 이와 같이 된다.테스트에서 시작해서 production code을 추출했기 때문에 대한 테스트가 자연적으로 생기는 것을 알 수 있다.항상 테스트부터 시작함으로써 테스트가 없는 레거시를 만들지 않고 항상 설계 개선을 위해 리팩토링을 할 수 있는 구조가 된다.

Page 32: Devon 2011-b-5 효과적인 레거시 코드 다루기

Quick & Dirty is OK

If you had tests

It’ll remove Fear to clean it

TDD debugging time / low level documentation / decoupling

Page 33: Devon 2011-b-5 효과적인 레거시 코드 다루기

Reference

Working Effectively with Legacy Code

Clean Coders Code Cast by Uncle Bob

TDD Code Cast by Kent beck

토비의 스프링 3

Page 34: Devon 2011-b-5 효과적인 레거시 코드 다루기

Q & A

[email protected]

http://twitter.com/ctemplate

. Grady Booch: Clean code should read like well written prose.

. Martin Fowler: 어떤 바보도 컴퓨터가 이해할 수 있는 코드를 작성할 수 있다. 하지만 사람이 이해할 수 있는 코드를 작성하는 것은 좋은 프로그래머를 요한다.

Page 35: Devon 2011-b-5 효과적인 레거시 코드 다루기

End

Page 36: Devon 2011-b-5 효과적인 레거시 코드 다루기

Etc.

Adding New Feature

TDD / Programming by Difference

Reuse What

Study Test

Programming by Difference OO에서 새로운 기능을 추가하는 방법 중 하나 기존 클래스를 직접 수정하지 않고 새로운 기능을 추가하기 위해 상속을 사용 새로운 기능을 추가한 후에 어떻게 기능이 통합되어지길 원하는지 정확히 파악할 수 있다. 상속을 후에 위임으로 변경 가능상속의 문제점 다중 상속 불가(2개 이상의 클래스에 구현된 기능을 상속을 통해 사용할 수 없다). 상위 클래스의 기능을 하위 클래스에서 재사용(???) 한다면 상위 클래스 변경시 많은 하위 클래스의 변경이 유발됨상위 클래스에 로직을 구성하고 변경 가능한 부분은 인터페이스로 정의하고 필요에 따라 구현 클래스를 추가해야함.객체지향은 새로운 타입 추가에 열려있음. 새로운 함수 추가가 아니라.우리가 재사용하고자 하는 것은 상위 레벨의 로직이지 하위 레벨의 API가 아님 Independent Deployable(Development) Layered Architecture

Page 37: Devon 2011-b-5 효과적인 레거시 코드 다루기

Don’t Start Big Project

New Project for Refactoring or Testing

Everything is fine at start

Doing New design

Cause Double-duty

Impossible to replace the old system

- 처음엔 좋다. 모든 것을 원하는 대로 할 수 있다.- 좋은 디자인을 위해서도 시간을 좀 보낸다.- 기존 시스템에 버그 수정, 기능 추가 요청이 들어온다. PM이 신 시스템이 나올때까지 요구사항 반영을 미뤄보려 하지만 고객은 기다리지 않는다.- 신규 프로젝트 팀은 기존 기능을 개선해서 구현하는 것 외에 새로운 요구사항도 구현해야 하는 이중고에 시달린다.- 더 이상 신규 프로젝트를 완료해서 신 시스템이 기존 시스템을 교체할 수 있다고 믿지 못한다.- 이 접근법은 항상 실패한다.

Page 38: Devon 2011-b-5 효과적인 레거시 코드 다루기

Wait Until New Feature is Needed

Change little bit of design

Add a few tests in the affected area

Eventually spread the test through out the legacy code

Not all but important/vital bits are get tested

Page 39: Devon 2011-b-5 효과적인 레거시 코드 다루기

For all new code you write

write test first

Option either modify or add a new module

write a new module with TFD

Always Prefer Test First

Page 40: Devon 2011-b-5 효과적인 레거시 코드 다루기

Have to Understand but Function is Too Big

Function Size(Screenful)

Function Should Do One Thing

Extract Major Sections into Function

Extract Different levels of abstract

함수 내에서 주요 섹션을 새로운 함수로 추출하는 것은 쉽다.하지만 추상화 수준이 다른 것을 모두 추출하는 것은 모호하다.

Page 41: Devon 2011-b-5 효과적인 레거시 코드 다루기

Extract Different Levels of Abstraction

Extract Till You Drop

Until simply restates the code without changing abstraction level

Most basic refactoring technique

Safe with tool support

- 이 코드가 한가지 일을 하는 것인가 ? 세가지 일을 하는 것인가 ? 이 코드를 한가지로 줄인다면 includeSetupsAndTeardownsIfTestPage로 extract하면 추상화 수준의 변경 없이 단순히 코드를 재인용하는 것 밖에는 안된다. 이런 경우는 더 이상 extract할 수 없는 것이다.- 리팩토링의 가장 기본 기법- 이클립스와 같은 툴의 지원으로 테스트 없이도 안전하게 수행할 수 있음.

Page 42: Devon 2011-b-5 효과적인 레거시 코드 다루기

As a Result

Composed Method Pattern

To Paragraphs

All class’s methods are less than 4 lines

{} in if, while stmt removed

Comment removed

Easy to read in top-down way