'2. Design/Refactoring'에 해당되는 글 4건
- 2012.05.04 변수와 가독성
- 2009.10.13 두 개의 모자
- 2009.10.13 Refactoring 의 목적 1
- 2009.10.13 Switch 문
출처: The Art of Readable Code (번역서: 읽기 좋은 코드가 좋은 코드다)
☞ 개발자 강추 서적입니다.
변수와 관련된 3가지 문제점들이 있으며,
1. The more variables there are, the harder it is to keep track of them all.
2. The bigger a variable’s scope, the longer you have to keep track of it.
3. The more often a variable changes, the harder it is to keep track of its current value.
이러한 이슈를 처리하는 방법은 다음과 같습니다.
Eliminate Variables
- Useless Temporary Variables
- Eliminating Intermediate Results
- Eliminating Control Flow Variables
Shrink(Reduce) the Scope of Variables
- Creating "Private" Variables
- Moving Definitions Down
Prefer Write-Once Variables
- const or final
- immutable or permanent
예제입니다. 아래와 같은 텍스트 입력 필드를 가지는 웹페이지가 하나 있습니다.
<input type="text" id="input1" value="Dustin"> <input type="text" id="input2" value="Trevor"> <input type="text" id="input3" value=""> <input type="text" id="input4" value="Melissa"> ...
id 는 input 으로 시작하며 1에서 부터 하나씩 증가합니다. 작성해야할 함수는 value 값이 비어 있는 input 을 찾아서 값을 입력하는 것입니다. 앞에서 소개했던 변수와 관련된 원칙들을 고려하지 않고 작성한 코드는 다음과 같습니다.
var setFirstEmptyInput = function (new_value) { var found = false; var i = 1; var elem = document.getElementById('input' + i); while (elem !== null) { if (elem.value === '') { found = true; break; } i++; elem = document.getElementById('input' + i); } if (found) elem.value = new_value; return elem; };
기능을 제대로 수행하지만 이쁘지 않습니다. found, i, elem 변수가 함수 전체에 걸쳐서 여러번 사용되고 있습니다. 우선 found 와 같은 중간 결과값을 저장하는 변수를 제거해 보겠습니다.
var setFirstEmptyInput = function (new_value) { var i = 1; var elem = document.getElementById('input' + i); while (elem !== null) { if (elem.value === '') { elem.value = new_value; return elem; } i++; elem = document.getElementById('input' + i); } return null; };
다음은 elem 변수를 살펴보겠습니다. 이 변수는 저장하는 값이 무엇인지 기억하기 쉽지 않게끔 매우 반복적으로 사용되었습니다. 이를 좀 더 개선해 보겠습니다.
var setFirstEmptyInput = function (new_value) { for (var i = 1; true; i++) { var elem = document.getElementById('input' + i); if (elem === null) return null; // Search Failed. No empty input found. if (elem.value === '') { elem.value = new_value; return elem; } } };
이 코드에서 elem 의 범위가 루프의 안쪽에 국한되었으며 값이 한 번만 할당되는 변수임에 주목하세요. for 루프의 조건으로 true 를 사용하는 게 흔한 일이 아니지만, 그렇게 함으로써 i 의 정의와 i의 값을 변경하는 구문을 한 줄에 담을수 있습니다.
☞ 전 이 예제에서 for 루프를 이용하여 임시 변수 i 를 사용하면서도 while(true) 효과를 주는 방식이 아주 맘에 듭니다.
소프트웨어를 개발하기 위해 리팩토링을 사용할 때, 두 가지 구별된 작업(기능 추가와 리팩토링)을 위해 시간을 나누어야 한다. 기능을 추가할 때는 기존 코드를 건드려서는 안되고 단지 새로운 기능만 추가해야 한다. 테스트를 추가하고, 테스트가 잘 작동하는지를 확인함으로써 진행상황을 알 수 있다. 리팩토링을 할 때는 기능을 추가해서는 안되고, 단지 코드의 구조에만 신경써야 한다. 그리고 어떤 테스트도 추가하지 않는다 (이전에 빼먹은 테스트가 없는 한). 단지 인터페이스가 변하여 작업을 계속하기 위해서 어쩔 수 없는 경우에는 테스트를 수정한다.
소프트웨어를 개발할 때, 아마 모자를 바꿔 쓰는 자신을 발견할 것이다. 새로운 기능을 추가하기 위해 시작했는데, 코드 구조가 바뀌면 작업이 더 쉬워진다는 것을 깨닫는다. 그래서 모자를 바꿔 쓰고, 한동안 리팩토링을 한다. 코드의 구조가 더 좋게 바뀌었으면, 다시 모자를 바꿔 쓰고 새로운 기능을 추가한다. 새로운 기능이 제대로 작동할 때, 새로운 기능에 대한 코드가 이해하기 힘들게 작성되었다는 것을 깨닫고는, 다시 모자를 바꿔 쓴 다음 리팩토링을 한다. 이 모든 작업을 하는데 단지 10분 정도 걸릴 뿐이지만, 그 시간 동안은 항상 자신이 어떤 모자를 쓰고 있는지 알고 있어야 한다.
출처 : Refactoring - 마틴 파울러
소프트웨어를 개발할 때, 아마 모자를 바꿔 쓰는 자신을 발견할 것이다. 새로운 기능을 추가하기 위해 시작했는데, 코드 구조가 바뀌면 작업이 더 쉬워진다는 것을 깨닫는다. 그래서 모자를 바꿔 쓰고, 한동안 리팩토링을 한다. 코드의 구조가 더 좋게 바뀌었으면, 다시 모자를 바꿔 쓰고 새로운 기능을 추가한다. 새로운 기능이 제대로 작동할 때, 새로운 기능에 대한 코드가 이해하기 힘들게 작성되었다는 것을 깨닫고는, 다시 모자를 바꿔 쓴 다음 리팩토링을 한다. 이 모든 작업을 하는데 단지 10분 정도 걸릴 뿐이지만, 그 시간 동안은 항상 자신이 어떤 모자를 쓰고 있는지 알고 있어야 한다.
출처 : Refactoring - 마틴 파울러
첫째, 리팩토링의 목적은 소프트웨어를 보다 이해하기 쉽고, 수정하기 쉽도록 만드는 것이다.
이것과 대조되는 좋은 예가 퍼포먼스 최적화이다. 리팩토링과 마찬가지로 퍼포먼스 최적화도 보통 동작을 바꾸지는 않는다 (속도는 빼고). 단지 내부 구조를 바꿀 뿐이다. 그러나 그 목적이 다르다. 퍼포먼스 최적화는 종종 코드를 이해하기 더 어렵게 만들지만, 필요한 퍼포먼스를 얻기 위해서는 그렇게 해야 한다.
둘째, 리팩토링은 겉으로 보이는 소프트웨어의 기능을 변경하지 않는다는 것이다. 리팩토링 후에도 소프트웨어는 여전히 동일한 기능만을 가지고 있다.
만일 리팩토링을 하다 while 루프의 실행이 늘어 났다고 하자. 시간이 오래걸리는 while 루프는 퍼포먼스를 나쁘게 한다. 많은 프로그래머는 이와 같은 단순한 이유로 이런 리팩토링을 하지 않으려 할지도 모른다. 그러나 단지 그럴지도 모른다는 말에 주목하라. 프로파일(profile)을 보기 전에는 계산을 위해 루프가 얼마나 많은 시간을 소모할 지, 또는 그 루프가 시스템의 전체 퍼포먼스에 영향을 줄 만큼 자주 호출될 지에 대해 알 수 없다. 이 while 리팩토링에 대해 걱정할 필요 없다. 만약 최적화를 하고 있다면 이 부분에 대해 걱정을 해야겠지만, 그렇지 않으면 이와 같이 리팩토링을 함으로써 최적화를 하기가 더 유리해 질 것이고, 최적화를 효과적으로 하기 위한 더 많은 옵션을 가지게 될 것이다.
출처 : Refactoring - 마틴 파울러
이것과 대조되는 좋은 예가 퍼포먼스 최적화이다. 리팩토링과 마찬가지로 퍼포먼스 최적화도 보통 동작을 바꾸지는 않는다 (속도는 빼고). 단지 내부 구조를 바꿀 뿐이다. 그러나 그 목적이 다르다. 퍼포먼스 최적화는 종종 코드를 이해하기 더 어렵게 만들지만, 필요한 퍼포먼스를 얻기 위해서는 그렇게 해야 한다.
둘째, 리팩토링은 겉으로 보이는 소프트웨어의 기능을 변경하지 않는다는 것이다. 리팩토링 후에도 소프트웨어는 여전히 동일한 기능만을 가지고 있다.
만일 리팩토링을 하다 while 루프의 실행이 늘어 났다고 하자. 시간이 오래걸리는 while 루프는 퍼포먼스를 나쁘게 한다. 많은 프로그래머는 이와 같은 단순한 이유로 이런 리팩토링을 하지 않으려 할지도 모른다. 그러나 단지 그럴지도 모른다는 말에 주목하라. 프로파일(profile)을 보기 전에는 계산을 위해 루프가 얼마나 많은 시간을 소모할 지, 또는 그 루프가 시스템의 전체 퍼포먼스에 영향을 줄 만큼 자주 호출될 지에 대해 알 수 없다. 이 while 리팩토링에 대해 걱정할 필요 없다. 만약 최적화를 하고 있다면 이 부분에 대해 걱정을 해야겠지만, 그렇지 않으면 이와 같이 리팩토링을 함으로써 최적화를 하기가 더 유리해 질 것이고, 최적화를 효과적으로 하기 위한 더 많은 옵션을 가지게 될 것이다.
출처 : Refactoring - 마틴 파울러
다른 객체의 속성에 기반 한 switch문을 사용하는 것은 별로 좋은 생각이 아니다. 만약 switch 문을 사용해야 한다면 자기 자신의 데이터를 사용해야지, 다른 객체의 데이터를 사용하면 안된다.
위 코드를 보면 getCharge 메소드를 Movie 클래스로 옮겨야 함을 의미한다.
그리고 _movie 에 추상 메소드와 상속을 이용한 다형성을 주어 구현하면 깔끔하게 switch 문을 제거할 수 있다.
출처 : Refactoring - 마틴 파울러
class Rental... double getCharge() { double result = 0; switch(getMovie().getPriceCode()) { case Movie.REGULAR: ... case Movie.NEW_RELEASE ... break; case Movie.CHILDREN: ... break; } return result; } |
위 코드를 보면 getCharge 메소드를 Movie 클래스로 옮겨야 함을 의미한다.
class Rental... double getCharge() { return _movie.getCharge(_daysRented); } |
그리고 _movie 에 추상 메소드와 상속을 이용한 다형성을 주어 구현하면 깔끔하게 switch 문을 제거할 수 있다.
출처 : Refactoring - 마틴 파울러