'Refactoring 이란?' 글에 이어 리팩토링 방법에 대해 쉽게 풀어보고자 합니다.
아래의 도서를 기준으로 학습한 내용을 정리하며, 나름 간단하게 주요 핵심를 전하고자 노력해보겠습니다.
조금이나마 업무에 도움이 되기를 바라며,
좀 더 상세하게 이해하고 싶다~ 하시면 "마틴 파울러의 Refactoring" 도서를 추천드립니다.
(각 방법들에 대한 예제 기술은 책의 내용을 발췌해서 사용합니다.)
* 메소드 정리 (Compsing Methods)
긴 Statement 메소드의 경우, 작은 조각으로 분해하는 것이 첫번째 목표.
- 작은 조각의 코드는 관리하기가 쉽다. 다루기가 쉽고, 옮기기도 쉽다.
-> 메소드 내의 지역변수와 파라미터를 주의 깊게 볼 필요가 존재함.
-> 메소드 분리 시, 해당 Return value 의 형태, 값을 점검할 필요가 존재함.
이름을 바꾸는 것이 노력을 들일만한 가치가 있는 것일까?
좋은 코드는 그것이 무엇을 하고 있는지를 명확하게 나타내야 하고, 적절한 변수 이름은 명확한 코드를 만드는 핵심 중의 하나이다. 코드를 명확하게 만들기 위해 이름을 바꾸는 것에 대해 두려움을 갖지 말라. 좋은 찾기/바꾸기 도구가 있다면 별로 어려운 작업이 아니다. 잘못된 부분은 강력한 타입체(strong typing) 와 테스트로 금방 찾을 수 있을 것이다.
컴퓨터가 이해할 수 있는 코드는 어느 누구나 다 짤 수 있다. 좋은 프로그래머는 사람이 이해할 수 있는 코드를 짠다.
1. Extract Method
그룹으로 함께 묶을 수 있는 코드 조각이 있으면, 코드의 목적이 잘 드러나도록 메소드의 이름을 지어 별도의 메소드로 뽑아낸다.
void printOwing(double amount) {
printBanner(); // print details System.out.println( "name:" + _name); System.out.println( "amount" + amount); } |
▶
|
void printOwing(double amount) {
printBanner(); printDetails( amount ); } void printDetails (double amount){ System.out.println( "name:" + _name); System.out.println( "amount" + amount); } |
- 메소드가 잘게 쪼개져 있을 때 다른 메소드에서 사용될 확률이 높아짐
- 고수준의 메소드를 볼 때 일련의 주석을 읽는 것 같은 느낌이 들 수 있음
- 메소드가 잘게 쪼개져 있을 때 오버라이드 하는 것도 훨씬 쉬움
2. Inline Method
메소드의 몸체가 메소드의 이름 만큼이나 명확할 때는 호출하는 곳에 메소드의 몸체를 넣고, 메소드를 삭제한다.
int getRating() {
return (moreThanFiveLateDeliveries())?2:1; } boolean moreThanFiveLateDeliveries() { return _numberOfLateDeliveries > 5; } |
▶
|
int getRating() {
return (_numberOfLateDeliveries>5)?2:1; } |
- 메소드 객체가 포함하고 있어야 할 동작을 가진 메소드에 의해 호출되는 여러 메소드를 인라인화 함
- 메소드와 그 메소드가 호출하는 다른 여러 메소드를 옮기는 것 보다는 메소드 하나만 옮기는 것이 더 쉬움
- 너무 많은 메소드가 사용되면, 그 속에서 길을 잃을 염려도 존재함
3. Inline Temp
간단한 수식의 결과값을 가지는 임시변수가 있고, 그 임시변수가 다른 리팩토링을 하는데 방해가 된다면, 이 임시변수를 참조하는 부분을 모두 원래의 수식으로 바꾼다.
double basePrice = anOrder.basePrice();
return (basePrice > 1000) |
▶
|
return (anOrder.BasePrice() > 1000)
|
4. Replace Temp with Query
어떤 수식의 결과값을 저장하기 위해서 임시변수를 사용하고 있다면, 수식을 뽑아내서 메소드로 만들고, 임시변수를 참조하는 곳을 찾아 모두 메소드 호출로 바꾼다. 새로 만든 메소드는 다른 메소드에서도 사용될 수 있다.
double basePrice = _quantity * _itemPrice;
if ( basePrice > 1000) return basePrice * 0.95; else return basePrice |
▶
|
if ( basePrice() > 1000)
return basePrice() * 0.95; else return basePrice() * 0.98; ... double basePrice() { return _quantity * _itemPrice; } |
- 임시변수는 임시로 사용되고, 특정 부분에서만 의미를 가지므로 문제가 됨
- 임시변수를 Query method로 바꿈으로서 클래스 내의 어떤 메소드도 임시 변수에 사용될 정보를 얻을 수 있음.
5. Introduce Explaining Variable
복잡한 수식이 있는 경우에는, 수식의 결과나 또는 수식의 일부에 자신의 목적을 잘 설명하는 이름으로 된 임시변수를 사용한다.
if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) && wasInittialized() && resize > 0) { // do something } |
▶
|
final boolean isMacOs = platform.toUpperCase().indexOf("MAX") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1); final boolean wasResized = resize > 0; if ( isMaxOs && isIEBrowser && wasResized ) { // do something } |
6. Split Temporary Variable
루프 안에 있는 변수나 collecting temporary variable도 아닌 임시변수에 값을 여러 번 대입하는 경우에는, 각각의 대입에 대해서 따로따로 임시변수를 만든다.
double temp = 2 * (_height + _width);
System.out.println (temp); temp = _height * _width; System.out.println(temp); |
▶
|
final double perimeter = 2 * (_height + width);
System.out.println(perimeter); final dougle area = _height * _width; System.out.println(area); |
- 어떤 변수든 여러 가시 용도로 사용되는 경우에는 각각의 용도에 대해 따로 변수를 사용하도록 바꾸어야 함
--> 하나의 임시변수를 두 가지 용도로 사용하면 코드를 보는 사람이 매우 혼란스러워 인적 실수가 발생할 수 있음
7. Remove Assignments to Parameters
파라미터에 값을 대입하는 코드가 있으면, 대신 임시변수를 사용하도록 한다.
int descount ( int inputVal, int Quantity, int yearToDate) {
if (inputVal > 50) inputVal -= 2; |
▶
|
int descount ( int inputVal, int Quantity, int yearToDate) {
int result = inputVal; if (inputVal > 50) result -= 2; |
- 파라미터에 값을 대입한다? 만약 파라미터로 객체를 넘긴 다음 파라미터에 다른 값을 대입하는 것은 파라미터가 다른 객체를 참조하게 하는 것을 뜻함
- 다만, 명확하지 않으며, 값에 의한 전달과 참조에 의한 전달을 혼동하기 때문
8. Replace Method with Method Object
긴 메소드가 있는데, 지역변수 때문에 Extract Method를 적용할 수 없는 경우에는, 메소드를 그 자신을 위한 객체로 바꿔서 모든 지역변수가 그 객체의 필드가 되도록 한다. 이렇게 하면 메소드를 같은 객체 안의 여러 메소드로 분해할 수 있다.
class Order ...
double price() { double primaryBasePrice; double secondaryBasePrice; double teriaryBasePrice; // long computation; ... } … |
▶
|
|
9. Substitude Algorithm
알고리즘을 보다 명확한 것으로 바꾸고 싶을 때는, 메소드의 몸체를 새로운 알고리즘으로 바꾼다.
String foundPerson(String[] people){
for (int i = 0; i < people.length; i++) { if (people[i].equals ("Don")){ return "Don"; } if (people[i].equals ("John")){ return "John"; } if (people[i].equals ("Kent")){ return "Kent"; } } return ""; } |
▶
|
String foundPerson(String[] people){
ListCandidates = Arrays.asList(new String[] {"Don", John", "Kent"}); for (int i = 0; i < people.length; i++) if (candidates.contains(people[i])) return people[i]; return ""; } |
think about...
주요로 말하고자 하는 내용은,
거대한 메소드에서 작은 부분을 뽑아 내고, 메소드 이름의 의도가 잘 나타나게 함으로써,
코드를 더 이해하기 쉽게 만들 수 있다는 것.
'IT 이야기 > Software Engineering' 카테고리의 다른 글
전사관점의 프로젝트 통합 관리 조직, PMO의 개요 (1) | 2023.09.20 |
---|---|
i-node의 개요 및 구조 (0) | 2023.09.19 |
크립토재킹의 정의와 공격 방식 (0) | 2023.09.19 |
[CA] HA 개념 정리 (2) | 2023.08.26 |
[CA] RAID 개념 정리 (0) | 2023.08.25 |