[welc] 22. i need to change a monster method and i can’t write tests for it

23
22. I Need to Change a Monster Method and I Can’t Write Tests for It ohyecloudy(http://ohyecloudy.com) 2008.12.20

Upload: jongbin-oh

Post on 25-Dec-2014

898 views

Category:

Technology


0 download

DESCRIPTION

 

TRANSCRIPT

Page 1: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

22. I Need to Change a Monster Method and I Can’t Write Tests

for It

ohyecloudy(http://ohyecloudy.com)

2008.12.20

Page 2: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Bulleted method

• nearly no indentation

___________________________ ____________________ __________________________ ___________________________ ___________________ ____________________________ ______________________ __________________ ____________ _______________ __________________ _______________

Page 3: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Snarled Method

• dominated by a single large, indented section

____________________________ ____________________ __________________________ ___________________________ ___________________ ____________________________ ______________________ ___________________________ _______________________ __________________________ __________________ ______________________________ _______________

Page 4: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

몬스터 함수 격파에 앞서

• refactoring tool이 있고 없고 차이 크다.

• extract method 같은 경우 사용 빈도가 크기 때문에 대부분의 리팩토링 툴이 지원하는데,

• 툴이 안젂하게 extract method를 수행하면 테스트 안 해도 된다.

Page 5: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Tackling Monsters with Automated Refactoring Support

• key goals

– dependency가 작은 chunk부터 logic과 분리 시킨다.

– 이후 진행될 refactoring의 test를 쉽게 하기 위해 seam을 삽입한다.

• 리팩토링 툴을 사용하면 extract method는 test 없이 진행 가능하다.

Page 6: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

class CommoditySelectionPanel { … public void update() { if (commodities.size() > 0 && commodities.GetSource().equals(“local”)) { listbox.clear(); for (Iterator it = commodities.iterator();

it.hasNext(); ) { Commodity commodity = (Commodity)it.next(); if (commodity.isTwilight()

&& !commodity.match(broker)) { listbox.add(commodity.getView()); } } } } }

Page 7: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

class CommoditySelectionPanel { … public void update() { if (commoditiesAreReadyForUpdate()) { clearDisplay(); updateCommodities(); } } private boolean commoditiesAreReadyForUpdate() { return commodities.size() > 0 && commodities.GetSource().equals(“local”); } private void clearDisplay() { listbox.clear(); } private void updateCommodities() { for (Iterator it = commodities.iterator(); it.hasNext(); ) { Commodity commodity = (Commodity)it.next(); if (singleBrokerCommodity(commodity)) { displayCommodity(commodity.getView()); } } } private boolean singleBrokerCommodity(Commodity commodity) { return commodity.isTwilight() && !commodity.match(broker); } private void displayCommodity(CommodityView view)

{ listbox.add(commodity.getView()); } }

동작이 함수단위로 변경됐을 뿐 구조적으로 바뀐 건 하나도 없다. 이후 Dependency를 제거하고 유닛 테스트를 진행한다.

Page 8: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

The Manual Refactoring Challenge

• 유닛 테스트를 진행하면서 리팩토링.

• Extract method에서 실수하는 부분.

– 변수 젂달을 잊는 경우

– base class의 method와 동일한 이름을 짓는 경우

– 잘못된 type을 패러매터로 선언 혹은 리턴

Page 9: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Introduce Sensing Variable public class DOMBuilder { … void processNode(XDOMNSnippet root, List childNodes) { if (root != null) { if (childNodes != null) root.addNode(new XDOMNSnippet(childNodes)); root.addChild(XDOMNSnippet.NullSnippet); } List paraList = new ArrayList(); XDOMNSnippet snippet = new XDOMNReSnippet(); snippet.setSource(m_state); for (Iterator it = childNodes.iterator(); it.hasNext(); ) { XDOMNNode node = (XDOMNNode)it.next(); if (node.type() == TF_G || node.type() == TF_H || (node.type() == TF_GLOT && node.isChild())) { paraList.addNode(node); } } } }

Page 10: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

public class DOMBuilder { … public boolean nodeAdded = false; void processNode(XDOMNSnippet root, List childNodes) { if (root != null) { if (childNodes != null) root.addNode(new XDOMNSnippet(childNodes)); root.addChild(XDOMNSnippet.NullSnippet); } List paraList = new ArrayList(); XDOMNSnippet snippet = new XDOMNReSnippet(); snippet.setSource(m_state); for (Iterator it = childNodes.iterator(); it.hasNext(); ) { XDOMNNode node = (XDOMNNode)it.next(); if (node.type() == TF_G || node.type() == TF_H || (node.type() == TF_GLOT && node.isChild())) { paraList.addNode(node); nodeAdded = true; } } } }

Page 11: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Sensing Variable을 사용한 Unit Test void testAddNodeOnBasicChild() { DOMBuilder builder = new DomBuilder(); List children = new ArrayList(); children.add(new XDOMNNode(XDOMNNode.TF_G)); builder.processNode(new XDOMNSnippet(), children); assertTrue(builder.nodeAdded); }

void testNoAddNodeOnBasicChild() { DOMBuilder builder = new DomBuilder(); List children = new ArrayList(); children.add(new XDOMNNode(XDOMNNode.TF_A)); builder.processNode(new XDOMNSnippet(), children); assertFalse(builder.nodeAdded); }

Page 12: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

public class DOMBuilder { … public boolean nodeAdded = false; void processNode(XDOMNSnippet root, List childNodes) { if (root != null) { if (childNodes != null) root.addNode(new XDOMNSnippet(childNodes)); root.addChild(XDOMNSnippet.NullSnippet); } List paraList = new ArrayList(); XDOMNSnippet snippet = new XDOMNReSnippet(); snippet.setSource(m_state); for (Iterator it = childNodes.iterator(); it.hasNext(); ) { XDOMNNode node = (XDOMNNode)it.next(); if (isBasicChild(node)) { paraList.addNode(node); nodeAdded = true; } } } private boolean isBasicChild(XDOMNNode node) { return node.type() == TF_G || node.type() == TF_H || node.type() ==

TF_GLOT && node.isChild(); } }

extract method를 한 후에 unit test를 통과하면 sensing variables과 test code를 삭제한다.

Page 13: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Break Out a Method Object

• Sensing variable 대신에 사용할 수 있다.

• 메서드의 parameter를 생성자의 parameter로 받고 run() 또는 execute()와 같은 메서드로 동작한다.

• temporary variable을 instance variable로 변경할 수 있다.

– sensing variable 같은 경우 기존에 있는 변수를 사용하기에 적합하나 temporary 혹은 local 변수라서 test에 사용하기 어려운 경우가 있다.

– Method Object는 좋은 해결책이 된다.

Page 14: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Extract What You Know

• 작은 코드 조각을 테스트 없이 extract한다.

– two or three lines

• 테스트와 다음 작업을 위한 좋은 출발점

• Coupling count가 0일때 사용.

– # of values that pass into and out

• Coupling count가 0보다 클때는 Sensing variable을 사용해라.

Page 15: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Coupling count 예 void process(int a, int b, int c) { int maximum; if (a > b) maximum = a; else maximum = b; }

void process(int a, int b, int c) { int maximum = max(a,b); }

in : a, b out : max return int Coupling count : 3

Page 16: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Gleaning Dependencies

• 보호하고 지켜야 하는 logic에 대해서 test를 작성한다.

• 테스트 범위에 들어가지 않는 chuck를 추출한다.

• 적어도 중요한 동작이 제대로 돌아갂다는 확신을 할 수 있다.

Page 17: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

void addEntry(Entry entry) { if (view != null && DISPLAY == true) { view.show(entry); } … if (entry.category().equals(“single”) ||

entry.category(“dual”)) { entries.add(entry); view.showUpdate(entry, view.GREEN); } else { … } }

• Display code에 관한 실수가 있으면 빨리 알아챌 수 있다.

• 하지만 add login에서 에러가 있으면 찾는데 시갂이 걸릴 것이다.

• add login에 관한 테스트를 작성하자.

• 테스트가 성공하면 display code를 추출한다.

Page 18: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Sensing variables

• 테스트 하는데 사용해서 리팩토링을 돕는다.

• sensing variables로 쓰기에 딱인데, method의 local variable인 경우가 많다.

– instance variable인 경우에는 method가 동작한 후에도 sense할 수 있다.

• 그래서 local variables into instance variable로 고치는데 많이 혺동된다.

Page 19: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Strategy

• Skeletonize Methods

• Find Sequences

• Extract to the Current Class First

• Extract Small Pieces

• Be Prepared to Redo Extractions

Page 20: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Skeletonize Methods if (marginalRate() > 2 && order.hasLimit()) { order.readjust(rateCalculator.rateForToday()); order.recalculate(); }

if (orderNeedsRecalculation(order)) { recalculateOrder(order, rateCalculator); }

메서드만 남아있는 상태

Page 21: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Find Sequences if (marginalRate() > 2 && order.hasLimit()) { order.readjust(rateCalculator.rateForToday()); order.recalculate(); }

… recalculateOrder(order, rateCalculator); … void recalculateOrder(Order order, RateCalculator rateCalculator) { if (marginalRate() > 2 && order.hasLimit()) { order.readjust(rateCalculator.rateForToday()); order.recalculate(); } }

chunk가 하나의 메서드가 되서 연산(operation)의 한 순서(sequence)

Page 22: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

Skeletonize Methods , Find Sequences

• bulleted methods lean me toward finding sequences

• snarled methods lean me toward skeletonizing

Page 23: [WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It

• Extract to the Current Class First

– 현재 클래스에서 먼저 extract해라.

• Extract Small Pieces

– 작은 조각을 extract하는 것은 좋은 출발점.

• Be Prepared to Redo Extractions