최근에 객체지향프로그램인 JAVA의 초기 입문서인 Head First Java를 모두 읽었습니다. 그런데 읽으면서 아주 흥미로운 사실 하나를 읽게되었고 깨닫게 되었습니다. 바로 객체지향에 있어서 어떤 프로그램을 작성하는데 세포단위(즉 더 이상 쪼갤 수 없는 기능이 상실되지 안는 최소한의 단위)로 쪼개어 설계하고 기능을 만들어야 한다는 점 이었습니다.


물론 이런 식의 글은 누군가는 썼을 것입니다. 사실 고찰이라는 제목을 썼지만, 아주 개똥철학일 수 있습니다. 누군가에게 나의 대단함을 내보이려기 보다는 그냥 내 생각을 정리함으로 조금더 확실한 학습을 유도하려는 스스로에대한 글인 것입니다.


돌아 와서 세포단위로 쪼개어 설계하고 기능을 구현한다는 것은 다음을 예를 들어 설명하고 싶습니다.


세포단위 기능구현


예를들어 자바에서는 외부의 파일을 읽어 드리거나 외부 파일을 만들거나 파일에 내용을 기제할 때, InputStream 또는 OutputStream과 같은 객체를 다른 객체와 Chain하여 쓰고 있습니다. 왜 굳이 Stream과 특정 객체를 체인하여 사용할까요?? 그것은 Stream에서 하는일과 각 객체 File 또는 FileWriter에서 하는 일을 불리함으로서 File 또는 FileWriter가 아닌 다른 객체 또는 다른 일(예를 들어서 Stream형태로 외부의 컴퓨터와 통신을 하게 될 때)을 하게 될 때 쉽게 변경 또는 확장하는 것이 쉽기 때문입니다.


이와 같이 객체지향적으로 프로그램을 하려면 최소단위로 그 기능을 쪼개는 시도와 고민을 많이 해야 할듯 하다는 것이 결론입니다. 물론 이 과정에서 List와 ArrayList와 같은 관계가 생길 수도 있습니다. 즉, 인터페이스와 추상클래스와 같은 다형성의 활용이 필요하게 될 수도 있다는 것입니다. 그렇기 때문에 다형성(Polymorphism)을 알아야 하며, 중요하고 중점적으로 파고들어야 하는 핵심중의 핵심이라고 말하고 싶습니다(핵심이다보니 아주 어렵습니다. 하지만 꼭 잘이해를 해야만 하는 부분임은 확실합니다).


세포단위 기능구현에서 주의 할 점


프로그램을 작성하는데 있어서 세포단위로 생각한다는 것은 아주 중요하지만, 주의 할 점이 많다고 생각합니다.


1.세포로서의 생명력(?)이 있어야 합니다.

2.증식 또는 배양을 통하여 또 다른 종으로서 사용할 수 있게해야합니다.


위의 두 가지 규칙은 반드시 존재해야만 하는 기능이라고 필자 스스로는 생각합니다. 이유는 간단합니다. 하나하나 풀어보자면 필자가 "세포단위"라고 말할 때 "세포"라는 단어를 굳이 선택한 이유는 세포라는 것은 최소한이긴 하지만 생명력이 있다는 것을 말하고 싶어서 였습니다. 프로그램에서 생명력이란 하나의 움직임 즉 기능이라고 할 수 있습니다. 가장 기초가 되는 기능 그 것이 바로 "세포단위" 기능 구현인 것입니다.


예를들자면 자바에서 String은 하나의 클래스입니다. String클래스는 그 하위에 많은 메소드들을 가지고 있는데, 이 것이 String클래스를 정의하는 최소한의 기능들입니다. 이 기능들을 활용하거나 확장하여 다른 많은 작업들을 할 수 있습니다. 이 String 클래스에서 구현되어있는 최소한의 기능들은 분리되거나 한다면 그 생명력은 String클래스 하나만으로는 존재할 수 없는 즉 생명력이 없는(다른 클래스가 있어야 기능이 되거나 하는) 클래스가 될 것입니다. 물론 인터페이스나 추상클래스와 같은 것 들은 생명력이 없는 것 아니냐는 질문을 할 수 있지만 그러한 인터페이스나 추상클래스 조차도 어떤 객체에 사용되면 오버라이드가 되지 않는 한 확장 또는 구현(Implements)선언 만으로 구현 되는 기능들이 존재합니다. 차라리 생명력이 있는 하나의 클래스를 만들기 전에 인터페이스나 추상클래스같은 것들을 만들어서 파생적으로 세포로서 생명력이 있는 클래스를 만든다면 다형성의 좋은 예로서 활용될 것입니다.


마지막으로 "증식 또는 배양을 통하여 또 다른 종으로서 사용할 수 있게해야합니다."는 주의 사항은 내가 만든 클래스를 누군가 확장하거나 재사용이 가능 하도록 만들어야 한다는 것입니다. 바로 위에서 설명했듯이 인터페이스나 추상클래스와 같은 녀석들은 자체적으로 존재할 수 없습니다. Head First Java에서는 추상클래스를 다음과 같이 예를 들었습니다.


동물에는 개과 고양이과와 같은 식의 종의 분류들이 있습니다. 이 종의 분류는 실제하기는 하지만 종의 분류자체가 하나의 동물은 아닙니다. 이와 같이 추상 클래스는 다형성에서 상속과 관련하여 하나의 종의 분류와 같이 사용할 수는 있지만, 그것 자체로서는 하나의 객체가 될 수 없는 것이어야 한다고 말합니다.


인터페이스도 이와 비슷합니다. 다중상속을 허용하고 있지 않은 자바에서 다중상속과 비슷한 역활을 하는 것이 바로 이 인터페이스입니다. 인터페이스는 다음과같이 설명합니다. 상속관계와 무관하게 어떤 객체가 확장성을 위하여 가지고 있어야 하는 특성(또는 하는 일)이 필요할 때 인터페이스를 만들고 이를 구현합니다.


위에 밑줄 친 두 개의 예문은 좋은 예시가 되어 주리라 생각합니다. 어떤 객체를 만들기 앞서 그 객체들의 고통관심사나 공통분모를 찾아 내어서 하나의 추상클래스나 인터페이스화 하는 것 이것은 후에 내 클래스가 누군가에 의해서 확장되는 데에 많은 영향을 줄 것이라고 생각합니다.


마치며...


필자는 최근에 2개의 프로그램을 만들어 보았습니다. 하나는 TelegramSpliter(SVN 주소입니다. 받아서 확장하실 분은 메일 주시거나 댓글로 의사표현을 해주시면 함께 발전해 나갈 수 있었으면 하는 바램입니다)로 전문을 받아서 사용자가 입력해준 전문 길이로 전문을 나누고 사용자가 입력해준 클래스로 자동으로 Wrapping해주는 프로그램을 만들어 보았고, 또 하나는 전문을 가지고 오는 프로그램(각가지 방식-FTP, HTTP, SSH등-으로 통신하는)을 만들어 보았습니다. 이 때 Head First Java에서 배운 내용으 가지고 여러가지 활용을 해보면서 사용해 보았습니다. 앞으로 내공을 더 쌓아서 객체지향 프로그램에서 스프링이나 스트러츠를 만들었던 분들과 같이 되고 싶은 것이 내 바램입니다.

Posted by gofly

제목특집3부_리팩토링을 이용한 자바 성능 최적화 기법
작성일자2006.12.27출처마이크로소프트 [2006년 12월호]

리팩토링을 이용한 자바 성능 최적화 기법

 

허광남 | GS홈쇼핑 EC정보팀 과장

 

리팩토링, 복잡다단해지는 현대의 소프트웨어 개발에서 이 단어는 점점 중요한 위치를 차지해 가고 있다. 이제 리팩토링은 진정한 개발자의 덕목 중에 하나라고 단언할 수 있을 정도다. 리팩토링을 한다는 것은 개선에 대한 의지가 있음을 뜻하고, 좀 더 나은 코드, 구조, 프로세스를 지향한다는 의미가 된다. 리팩토링으로 소프트웨어의 성능을 직접적으로 높이지는 못 한다. 하지만 코드의 가독성을 증대시켜, 생각하는 프로그래머들의 머릿속 성능을 높여준다. 3부에서는 리팩토링 방법들에 대해 알아본다.

 

햄버거나 커피 등을 살 때, 또는 백화점이나 편의점에서 물건을 살 때, 우리는 1회용 물건을 쓰는 것에 대한 세금을 낸다. 1회용 물건을 쓰면 환경이 그만큼 빨리 피폐해지기 때문이란다. 그게 사실인지 아닌지 모르겠지만, 내 돈이 나가는 것은 용납이 안 된다. 1회용품의 편리함. 그 반대급부로 만들어지는 쓰레기 처리에 따른 비용을 지불한다고 하는데, 영 맘에 안 든다.

혹시 프로그램을 짤 때도 1회용 프로그램을 짠다는 생각을 해본 적이 있는가? 그런데 우리는 1회용 프로그램을 짜도 세금을 내지 않는다. 다행일까? 1회용 프로그램이 환경 자원을 소모시키지는 않는다. 다만 1회용 프로그램은 쓰레기를 양산한다. 그때 그때 필요한대로 찍어낸 프로그램은 수많은 중복코드를 양산해낸다. 재활용하지 않는 습관 탓에 시스템이라는 환경이 무거워지고 손이 많이 가도록 바뀌는 것이다.

재활용성은 객체지향 프로그램의 핵심원리 중의 하나이다. 재활용성을 높인다는 것은 찍어낼 때 사용하는 템플릿을 얘기하는 것이 아니다. 오히려 업무나 기능을 제어 가능한 곳에 집약시켜서 관리할 수 있도록 시스템 전체의 청결한 상태를 유지하는 것이다. 이리저리 산재된 중복 코드를 정리하는 것이 핵심이다. 이 때 필요한 기술이 리팩토링이다. 이쯤 얘기하면 리팩토링은 정리 정돈에 비견된다. 군대에서 총기수입을 하는 것과도 같고 집에서 설거지를 하는 것과도 같다.

 

리팩토링이란



Refactoring (Re + Factor + ing) 영어 단어를 요소별로 나눠보면 요소들을 재구성한다는 뉘앙스를 받을 수 있다. 이는 마틴 파울러의 책에서 비롯된 단어인데, 책에 있는 리팩토링의 정의를 보면 다음과 같다.

“리팩토링은 외부 동작을 바꾸지 않으면서 내부 구조를 개선하는 방법으로, 소프트웨어 시스템을 변경하는 프로세스이다.” 마틴 파울러, 리팩토링, P10. 대청출판사 책에 이어서 나오는 내용은 버그가 끼어들지 않도록 주의하면서 코드를 작성한 후에 더 나은 디자인으로 개선하는 방법이라고 한다. 디자인을 먼저 한 후 코드를 만드는 것이 아니라 일단 돌아가는 코드를 작성하고, 그 후에 그 코드가 더 좋은 구성을 갖도록 바꾼다는 것이다. 우리들의 코딩 관행을 돌아보면, 일단 돌아가는 프로그램을 짠다. 그리고? 끝이다. 그 다음으로 넘어간다. 정리? 남은 사람이 알아서 할 것이다. 남은 사람이 자기 밖에 없다면? 날 잡아서 정리하거나, 회사 옮긴다.

 

 

리팩토링을 하는 이유



야심찬 초급 개발자가 자주하는 것 중에 하나가 이전 소스에 대한 비평이다. “도대체 어떻게 이렇게 소스를 짤 수 있지. 발로 짜도 이것보다는 낫겠네. 왜 이렇게 if else가 많은 거야. 이 소스 이해할 시간 있으면 차라리 다시 짜고 만다.” 그래서, 다시 짠다. 그리고 오픈하면 이것 저것 버그 리포트와 요구사항이 들어온다. 이것 저것 예외 처리를 해주다 보면 내가 짠 코드지만 보기 싫어진다. 어느 정도 서비스가 안정적으로 돌아가도록 소스를 수정해 놓으니, 이런, 전에 내가 막 뭐라고 했던 이전 개발자의 소스와 별반 차이가 없다.

“제길, 다음 후임이 누가 될지는 몰라도 내 욕 무진장 하겠군.” 문서라도 잘 주면 모르겠지만, 처음 개발할 때 보고했던 문서 그대로다. 요구사항과 수정을 통해서 변경된 내용을 문서에 업데이트하질 못했다. “할 시간이 있어야지.” SM(System Maint enance)분야에서는 거의 이렇게 사는 것이 보통이다.

이전 사람이 만든 소스에서 버릴 것은 거의 없다. 정리가 안 되서 몇 달간 목욕 못한 모습일 뿐이지, 처리할 수 있는 모든 경우의 수는 그 안에 다 가지고 있다. 이런 코드를 새로 짠다는 것은 그 모든 경우의 수를 처음부터 다시 감수하겠다는 의미가 된다.
이전 소스를 씻기고 다듬는 것이 소스 수정을 위한 필수 과정이다. 정리하지 않고 계속해서 소스를 추가해 가는 일은 운동하지 않고 계속해서 먹어대는 것과 같이 시스템을 비만상태로 만들어간다. 움직임이 점차 둔해질 것이다. 정리 안 된 방처럼 발 디딜 팀이 없는 소스가 될 것이다.

무엇인가 소스의 변경이 필요할 때, 기능 추가나 삭제, 수정 작업이 일어날 때 소스의 리팩토링은 포장이사처럼 편하게 작업하도록 도와준다. 리팩토링은 소스의 중복된 부분을 모듈화 시켜준다. 모듈화는 입출력이 명확하기 때문에 이식성을 높여준다. 중복을 제거한다는 것은 시스템의 칼로리를 빼는 것과 같다. 시스템의 복잡도, 즉 코드를 읽는 사람의 머리가 열받는 정도를 낮춰준다. 물론 그렇다 해도 이사 자체는 귀찮은 일이다.

 

 

 

리팩토링을 위한 도구



리팩토링은 그로 인해 영향 받는 프로그램의 수가 적을 대에만 수작업으로 작업해야 한다. 사실 리팩토링을 수작업으로 한다는 것은 추천하지 않는다. 좋은 개발 환경이 있는데 사서 고생할 필요가 없는 탓이다. 리팩토링을 위한 좋은 툴이 많이 나왔다. 일단 통합개발환경(IDE, Integrated Development Environment)을 준비한다. 요즘의 자바 개발 시 많이 사용되는 IDE는 기본적으로 리팩토링을 지원한다.

리팩토링과 함께 진행되어야 할 JUnit 테스트케이스 자동 생성도 같이 지원되고 있다. 리팩토링 작업을 할 경우 여러 줄의 코드들이 수정된다. 이때 영향을 받는 프로그램들을 모두 불러내서 수작업으로 수정할 경우 리팩토링에 대한 공수가 많이 필요한 탓에 감히 리팩토링에 대한 엄두를 낼 수 없다. 하지만 요즘 통합 개발 환경을 지원하는 개발 도구들은 변경 받는 파일들의 목록과 변경 전 후의 코드 비교, 자동 변경 기능을 지원한다. 덕분에 리팩토링에 드는 수고가 전혀 수고로 생각되지 않을 정도다.

 

 

 

리팩토링 진행 방법



리팩토링하는 이유와 리팩토링 도구까지 알아보았으니 이제 리팩토링 방법에 대해 알아볼 차례다. 주저리 주저리 방법들을 늘어놓을 수 도 있겠지만 개발자는 코드로 얘기한다. 바로 이클립스에서 리팩토링을 사용하는 방법을 설명하도록 하자.

 

<리스트 1> 리팩토링 샘플
1 public void deleteArticle(Connection conn, int seq) throws SQLException {
2 if (conn == null)
3 return;
4
5 // db에서 삭제 - 삭제 테이블로 이동
6 PreparedStatement pstmt = null;
7 pstmt = conn.prepareStatement(QUERY_MOVE);
8 pstmt.setInt(1, seq);
9 pstmt.executeUpdate();
10
11 pstmt.close();
12
13 pstmt = conn.prepareStatement(QUERY_DELETE);
14 pstmt.setInt(1, seq);
15 pstmt.executeUpdate();
16
17 pstmt.close();
18
19 // memo 삭제 생략
20
21 }


리팩토링에 대한 간단한 예를 들기 위해서 <리스트 1>을 보며 설명하겠다. 7~11번 줄의 코드가 13~17번 줄의 코드와 유사한 것을 알 수 있다. 중복이 계속되는 것은 일정한 패턴을 갖고 있는데 중복이 심해지면 패턴 변경에 따른 공수가 많이 필요하므로 소스의 유연성이 떨어지게 된다. 때문에 반복되는 패턴을 메소드화 시켜서 쉽게 코드를 읽을 수 있도록 한다. 



<화면 1> 반복되는 부분, 메소드 추출의 대상

   

<화면 2> 메소드 추출(Extract Method)

이클립스에서 패턴부분을 선택하고, 오른쪽 버튼을 눌러 콘텍스트 메뉴를 열면 중간 위치에 [Refactor…]라는 메뉴가 보인다. 확장 메뉴에서 [Extract Method…]를 선택하면 <화면 2>와 같은 다이얼로그 창이 뜬다. ‘doQuery’라고 메소드명을 입력한 뒤에 파라미터들을 확인한다.

화면 아래쪽의 버튼 중 [Preview]를 클릭하면 <화면 3>과 같이 미리보기 창으로 바뀐다. 이때 화면에 표시되는 정보들이 기가 막힌다. 리팩토링을 통해서 변경되는 소스의 비교와 상단에는 이 리팩토링에 영향을 받는 소스들과 메소드명까지 친절하게 알려준다. 게다가 이클립스가 모두 다 자동으로 바꿔준다.


 


<화면 3> 리팩토링 결과 미리보기


<화면 4>에서는 다이얼로그에서 만든 doQuery() 메소드의 내용을 볼 수 있다. 소스 비교란의 맨 오른쪽에 있는 네모는 소스 전체에서 변경이 일어난 부분을 표시한 것이다.


 


<화면 4> 리팩토링으로만들어진 메소드

비교가 끝났다면 [OK] 버튼을 클릭해서 리팩토링을 실행한다. 소스 리팩토링을 마친 뒤에 doQuery() 메소드를 보면, <화면 5>처럼 파라미터가 Connection conn, int seq 두 개임을 알 수 있는데, 여기에 하나가 더 필요하다. 바로 쿼리 부분인데, 이것을 파라미터로 받아야 비로로 doQuery()가 공용으로 쓰일 수 있게 된다.


<화면 5> 리팩토링으로 만들어진 약간 아쉬운 메소드

QUERY_MOVE라는 상수를 파라미터로 대치한다. 이 상수에 마우스 오른쪽 버튼을 클릭한 뒤에 [Refactor]-[Introduce Para meter] 메뉴를 실행시키면 <화면 6>과 같은 다이얼로그 창을 볼 수 있다. 새로운 파라미터 이름을 ‘query’로 정하고 우측의 [up] 버튼을 클릭해서 파라미터의 위치를 조정한다. 파라미터의 변경은 메소드의 모습인 시그니처(signature)를 변경하는 것이다.



마찬가지로 [Preview] 버튼을 클릭하면 <화면 7>과 같이 리팩토링 전후의 소스를 비교할 수 있다.

<그림 7>에서 [OK] 버튼을 클릭해서 만들어진 doQuery() 메소드는 반복되는 쿼리 실행 부분을 메소드 추출(Extract Met hod)과 파라미터로 빼기(Introduce Parameter) 리팩토링을 이용해서 만든 것이다. <리스트 2>는 그것을 이용해서 바뀐 소스의 모습이다. 



<그림 7> 파라미터로 만들기 적용하기 전 미리보기

 

<리스트 2> QUERY_DELETE 부분 리팩토링 과정
1 public void deleteArticle(Connection conn, int seq) throws SQLException {
2 if (conn == null)
3 return;
4
5 // db에서 삭제 - 삭제 테이블로 이동
6 PreparedStatement pstmt;
7 doQuery(conn, QUERY_MOVE, seq);
8 doQuery(conn, QUERY_DELETE, seq);
9
10 pstmt = conn.prepareStatement(QUERY_DELETE);
11 pstmt.setInt(1, seq);
12 pstmt.executeUpdate();
13
14 pstmt.close();
15
16 // memo 삭제 생략
17
18 }

19 private void doQuery(Connection conn, String query, int seq) throws SQLException {
20 PreparedStatement pstmt = null;
21 pstmt = conn.prepareStatement(query);
22 pstmt.setInt(1, seq);
23 pstmt.executeUpdate();
24
25 pstmt.close();
26 }


<리스트 2>는 아직 변경 중인 샘플코드이다. 앞서 만든 doQuery() 메소드를 이용해서 쿼리만 다른 것을 보내면 된다. 필자가 추가한 8번 줄은 10~14번 줄과 동일한 기능을 수행하게 된다. 코드를 정리하면 다음과 같이 된다.

 

<리스트 3> QUERY_DELETE 부분 리팩토링 후
1 public void deleteArticle(Connection conn, int seq) throws SQLException {
2 if (conn == null)
3 return;
4
5 // db에서 삭제 - 삭제 테이블로 이동
6 doQuery(conn, QUERY_MOVE, seq);
7 doQuery(conn, QUERY_DELETE, seq);
8 // memo 삭제 생략
9
10 }


하단의 구문이 지워지면서 이 deleteArticle() 메소드 내의 PreparedStatement pstmt 선언은 불필요하기 때문에 삭제했다. 처음 보았던 소스에서 많이 정리되었다. 정리를 하고 보니 deleteArticle() 메소드를 호출하는 곳에서 비슷한 기능을 하는 부분을 볼 수 있다.

 

<리스트 4> 리팩토링 적용 범위 확대
1 // password 확인
2 if (confirmPassword.equals(MASTER_PASSWORD)
3 || confirmPassword.equals(article.getPassword())) {
4 deleteArticle(conn, seq);
5 deleteFiles(conn, seq);
6 } else {
7 resourceName = "/jsp/error.jsp";
8 throw new Exception(CommonUtil.k2a("잘못된 비밀번호"))
9 }

10 public void deleteFiles(Connection conn, int seq) throws SQLException {
11 if (seq == 0){
12 return;
13 }
14
15 // file db에서 삭제 - sts 값 0 로 변경
16 PreparedStatement pstmt = conn.prepareStat ement(QUERY_DEL_SEQ_FILE);
17 pstmt.setInt(1, seq)
18 pstmt.executeUpdate();
19 
20 pstmt.close();
21 
22 // file 삭제 생략
23 }


<리스트 4>의 16~20번 줄을 보면 앞서 추출한 메소드 doQuery()로 변경할 수 있을 것 같다. 그럼 코드는 <리스트 5>와 같이 수정될 것이다.

 

<리스트 5> QUERY_DEL_SEQ_FILE 부분 리팩토링 과정

10 public void deleteFiles(Connection conn, int seq) throws SQLException {
11 if (seq == 0){
12 return;
13 }
14
15 // file db에서 삭제 - sts 값 0 로 변경
16 doQuery(conn, QUERY_DEL_SEQ_FILE, seq);
17 
18 // file 삭제 생략
19 }

 

이렇게 정리하고 난 후에 다시 전체적인 코드를 생각해보면 두 개의 메소드가 불필요하다 생각이 든다. 즉 <리스트 6>과 같이 deleteArticles()와 deleteFiles() 메소드를 지우고 바로 doQuery() 를 호출하도록 바꿀 수 있을 것이다. <리스트 6>은 리팩토링을 통해 최종적으로 정리된 소스이다.

 

<리스트 6> 리팩토링 적용으로 개선된 코드
1 // password 확인
2 if (confirmPassword.equals(MASTER_PASSWORD)
3 || confirmPassword.equals(article.getPassword())) {
4 // db에서 삭제 - 삭제 테이블로 이동
5 doQuery(conn, QUERY_MOVE, seq);
6 doQuery(conn, QUERY_DELETE, seq);
7 // memo 삭제 생략
8 // file db에서 삭제 - sts 값 0 로 변경
9 doQuery(conn, QUERY_DEL_SEQ_FILE, seq);
10 // file 삭제 생략
11 } else {
12 resourceName = "/jsp/error.jsp";
13 throw new Exception(CommonUtil.k2a("잘못된 비밀번호"));
14 }


앞에서 보았던 소스의 if else 구문과 비교해보면 doQuery() 라는 공통으로 사용할 수 있는 메소드와 5줄이 늘어났지만 deleteArticle(), deleteFiles() 두 개의 메소드가 사라졌다. 이전 소스와 비교해보면 메소드 구성은 <화면 8>과 같이 변경되는 것을 알 수 있다.

추가된 메소드는 +화살표, 제거된 메소드는 화살표로 표시되고 변경된 메소드는 그냥 검은 화살표로 표시된다. 화면 아래쪽에 표시되는 소스 비교하는 곳을 보면 더욱 명확하게 알 수 있다. 

지금까지 샘플 소스의 구조를 개선하면서 두 가지 리팩토링 기법에 대해 알아보았다. 이 외에도 많은 기법들이 리팩토링 책에 소개되어있고, 이클립스에도 더 많은 리팩토링 기능이 지원된다.


 

<그림 8> 리팩토링 전 후 메소드 비교

 

 

리팩토링 경험담



필자는 이 글을 쓰고 있는 지금 큰 프로젝트를 진행하고 있다. 6년간 하나도 버려지지 않고 운영되면서 그때그때 패치된 페이지를 스프링 프레임워크에 맞춰서 바꾸는 작업이다. 그런데, 작업을 하는 동안 필자가 간과한 것이 있었다. 그렇게 복잡하게 얽히고설킨 페이지를 스프링 프레임워크의 새로운 바닥부터 하나씩 쌓아 올린 것이다. 기존에 운영하고 있는 소스에서 하나씩 뜯어서 새로운 토양으로 옮겨심기를 한 것이다. 이것은 재개발에 가까운 것이었고, 굉장히 많은 시간이 필요했다. 만약 옮겨야 할 소스를 기존의 토양 위에서 조금씩 리팩토링한 후에 옮겼다면 오히려 많은 시간을 절약할 수 있었을 것이다.

실수했다고 생각하는 부분은 다음과 같다. 우선적으로 모든 기능을 다 옮겨올 때까지 신규 페이지는 아직 미완성이다. 하지만 기존의 페이지 내에서 리팩토링을 한다고 하면 이미 모든 기능과 데이터를 다 갖고 있는 상태이다. 다른 파트에서 데이터가 필요하다고 할 때에도 현재 갖고 있는 데이터에서 데이터를 뽑아내서 보다 빨리 전달할 수 있을 것이다.

두 번째로 시간의 압박이다. 기존의 페이지는 언제든지 답이 나온다. 하지만 신규페이지는 모든 테스트를 마칠 때까지 계속 기다리라고 얘기해야만 한다. 바닥부터 모든 것들에 대해서 테스트를 만들어야 하는 탓에 더 많은 테스트 코드들이 필요하다. 여기에도 만만치 않은 시간이 투입된다.

세 번째는 애플리케이션에 대한 자신감이 떨어진다는데 있다. 맥가이버도 아닌데 시간에 쫓기면서 개발할 경우 만들어진 소스는 분명히 수많은 버그를 품고 있을 가능성이 높다. 그 값이 절대 정확하다고 이야기하기 힘들다. 하지만 리팩토링을 통해서 내부로부터 개혁해 나갈 경우 빠진 것 없이 소스를 재구성할 수 있기 때문에 안정된 기반에서 작업할 수 있을 것이다.

전산의 불문율 가운데 유명한 것이 하나 있다. ‘잘 돌아가는 것은 손대지 마라.’ 칼퇴근을 위해서 절대 절명으로 필요한 말이다. 이렇게 관리되는 소프트웨어의 품질은 논하기 힘들다. 그냥 먹고 살기 위한 프로그램과 그것을 관리하는 직장인이 되어버리게 된다. 반면에 리팩토링의 기본 사상은 개선을 위한 노력이다. 막무가내 개선이 아니라 현명한 개선을 위한 방법을 제시하고 있고, 친구격인 테스트 케이스가 그 안전장치가 되어 준다. 한 순간의 품질이 아닌 지속적인 소프트웨어의 건강을 생각한다면 꾸준히 리팩토링으로 손질할 필요가 있다. 그것이 끝없이 변하는 웹 애플리케이션과 같은 소프트웨어일 경우는 더욱 그렇다.
개선을 위한 작은 몸짓에 진정한 프로그래머가 되고 싶은 독자들을 초대한다.

 

참고 자료
1. 리팩토링, 마틴파울러, 윤성준,조재박 역, 대청, 2002년3월
2. 패턴을 활용한 리팩터링, 죠슈아 케리에브스키, 윤성준,조상민 역, 인사이트, 
2006년7월

 

리팩토링 관련 사이트 

1. http://www.refactoring.com/
Refactoring Home Page 

2. http://xper.org/wiki/xp/ReFactoring
김창준 님의 Refactoring에 관한 정보 

3. http://c2.com/cgi/wiki?CodeSmell
Code Smell 

4. http://xper.org/wiki/xp/CodeSmell
Code Smell 번역 

5. http://www.okjsp.pe.kr/lecture/ide/eclipse/refactor/eclipse_refactoring.html 
Eclipse의 refactoring기능 

6. http://www.okjsp.pe.kr/lecture/ide/eclipse/eclipse_install.html 
Eclipse 시작하기

Posted by gofly

얼마전 저는 Ubuntu 12.04를 설치 후에 개발환경 구축을 위해서 JAVA JDK 7을 설치했습니다.

ppa에 Oracle-java7-installer를 추가하고, 설치를 하려고 했는데, 인스톨 스크립트에서 에러가 나는지 계속 설치가 않되는 것이다.

뭐, 반대로 삭제는 두말할 것도 없이 삭제가 않되었습니다. 

패키지 설치/삭제시 에러메세지프로그램을 설치하거나 삭제시에 나타나는 메세지 화면


그래서 이것 저것 찾다가 강제로 삭제하는 방법을 알아 내서 이렇게 Posting을 합니다.

첫째는 dpkg 폴더에 있는 status파일을 수정합니다.


$ cd /var/lib/dpkg

$ sudo vi status


그리고 삭제하려고 하는 패키지의 이름을 찾습니다.(vi 에디터에서 글자 찾기는 "/"를 누르고 /다음에 prompt가 뜨면 찾고자 하는 글귀를 치고 엔터를 누릅니다.)


삭제할 패키지를 status찾습니다status 파일에서 삭제하고자하는 Package를 찾습니다.


그리고 찾아지는 패키지에대한 내용들이 있으면 삭제를 합니다. 삭제방법은 다음과 같이 하시면 됩니다. 


패키지를 삭제하는 예시패키지를 삭제하는 예시


위의 내용은 우분투 한국커뮤니티에 올라와있는 그림을 참고해온 것입니다.

삭제시 주의점은 간단합니다. 삭제를 할 때 저의 경우에는 vi 에디터에서 줄을 하나씩 지울 때 "d"키를 두 번씩 눌르면 됩니다. 그런데 지우실 때 어디까지 지워야 할지 모를 수가 있습니다. 그래서 다음 설명을 잘 보셔야 합니다.

지우는 부분은 Package에서 부터 다음 Package라는 구문이 나오기 전까지 입니다.

 Package: oracle-java7-installer <==여기서 부터
-내용-
-내용-
-내용-
-내용-
-내용-
-내용-<==여기까지

Package: qt4-qtconfig

위와 같이 Package정보가 여러가지가 존재 합니다. 그 중에 강제로 삭제를 하려고하는 Package만 삭제하시면 됩니다.

위의 설명 처럼 Package만 삭제하면 됩니다. 저의 경우에는 oracle-java7-installer를 지워야 하기 때문에 oracle-java7-installer package에 대한 내용을 모두 삭제하고 저장하고 vi에디터를 빠져 나옵니다. 다음으론 available파일도 수정을 합니다.


$ sudo vi available


이 파일에서도 위에서 status파일에서 했던 것과 동일하게 지우시고자 하는 Package를 찾아서 삭제하고 저장하고 파일에서 나옵니다.

다음은 설치되어있는 것들을 찾아내서 삭제해야 합니다. 저는 패키지 이름이 oracle-java7-installer이기 때문에 아래와 같이 {패키지 이름}.list를 찾아서 내용을 확인합니다. 여기서 어디에 oracle-java7-installer에 관련된 파일들이 깔려 있는지를 볼 수가 있습니다.

$ sudo vi /var/lib/dpkg/info/oralce-java7-installer.list


이렇게하면 아래와 같이 해당 패키지의 리스트 정보를 보여 줍니다.<아래는 단지 참고 화면입니다.>


dpkg info에 깔려 있는 패키지의 Deploy정보dpkg info에 깔려 있는 패키지의 Deploy정보


주의 하셔야 할 것은 폴더는 삭제하시면 안된다는 것입니다. /usr/share/doc/ 아래에 있는 것만 빼고요.....^^

*.desktop 파일이나 *.png, *.xpm등은 물론 tar파일 zip, gzip 파일들을 모두 삭제합니다. 찾아서 삭제하셔야 합니다.

자 이제는 *.list와 같은 것들을 찾아 삭제합니다.


$ sudo rm oracle-java7-installer.list oracle-java7-installer.postinst oracle-java7-installer.prerm oracle-java7-installer.md5sums oracle-java7-installer.postrm 


자 이것만 하고 나면 일단 모든 것을 수동으로 삭제했습니다. 자 이제는 apt-get 명령어를 이용해서 패키지에 대해서 clean을 해줍니다.


$ sudo apt-get clean

$ sudo apt-get update

$ sudo apt-get upgrade


여기까지 했으면 수동으로 잘못된 패키지를 모두 삭제한 것입니다.

뭐 저에 경우에는 이 이후로 에러메세지는 않뜨네요^^ ㅋㅋㅋㅋㅋ

그런데 조금 문제가 있네요. 리눅스가 발전하기 위해서는 이런 부분들이 조금더 편하게 할 수 있도록 바꾸는 것이 필요 한 것 같네요.



출처 : http://ubuntu.or.kr/viewtopic.php?p=36847


Posted by gofly
먼저 이 글을 Head First Java에서 배운 내용을 잊지않기 위해서 정리차원에서 제가 적어두는 것입니다.
오해없으시길 바랍니다.


자바는 어떤식으로 돌아갈까요?


처음 고려 사항은 애플리케이션 하나를 만들어서 여러 친구들이 가지고 있는 다양한 장치에 보낼 파티 초대장을  만드는 방법입니다.

1.소스->2.컴파일러->3.결과물->4.각 기계(Device)의 가상머신

1.소스: 소스를 만들되 문법을 지켜야 함
2.컴파일러: 소스 코드를 컴파일 처리한다. 이 때 오류가 없는지 체크하고, 통과한 경우에만 최종 결과를 만들어 줍니다.
3.결과물: 소스를 컴파일 하면 바이트코드(Bytecode)라는 코딩된 문서를 만들어 줌. 이것이 기계에 실행명령을 내림.
4.가상머신: 각 기계마다 소프트웨어 자바가상머신(J.V.M)이 있어 3번 결과물을 가상머신에서 실행 시키게 됩니다.

 
 

1.소스: *.java로 된 파일
2.컴파일러: 커맨드 창에 "%javac *.java "와 같이 javac를 실행시켜 *.java파일을 컴파일 합니다.
3.결과물: *.java파일이 컴파일 되면 바이트코드가 담긴 *.class파일이 생성됩니다.
4.가상머신: 커맨드 창에서 "%java *.class"를 실행시키면 *.class파일의 바이트코드를 JVM에서 실행한다. 

뭐 대충 여기까지가 자바의 구동원리 입니다.

아래는 자바의 버전별 클래스 포함의 변천사를 간단히 도표화 한 것입니다.

간단히 정리해 본 자바의 역사

간단히 정리해 본 자바의 역사




자바는 예전 C와 같이 프로그래머가 필요한 부분을 일일이 구현할 필요가 없이, 제공되는 클래스(또는 API)를 통하여 간단하게 호출하여 사용하기만 하면 됩니다. 이 것이 프로그래머들에게 아주 매력적인 요소중에 하나죠.

*Head First Java의 참 재미있는 부분은 가끔 나오는 QnA입니다.

Q: 위에 있는 표에 보니까 자바 2랑 자바 5.0은 있는데, 자바 3하고 자바 4는 어디 있어요? 그리고 자바 5.0이라고 나와 있는데, 자바 2는 왜 자바 2.0이라고 안쓰죠?

A: 마케팅의 세계란 정말 심오하죠... 자바 버전이 1.1에서 1.2로 올라갔을 때, 바뀐 것이 정말 많았어요. 그러다 보니 마케팅 팀에서 아예 이름을 새로 정해야 되겠다는 생각에 "자바 2"라는 이름을 붙였죠. 실제 자바 버전은 1.2였는데도 말예요. 버전 1.3하고 1.4는 계속 자바2로 간주되었습니다. 자바 3나 4 같은 건 없었죠. 자바 1.5가 나올 때도 정말 많은 변화가 있었습니다. 그래서 마케팅 팀에서는 (개발자들도 대부분 동의했죠) 새로운 이름이 필요하다는 생각에 어떤 이름을 쓸까 고민을 했습니다. 사실 숫자 상으로 볼 때 "3"이 와야 했는데, 자바 1.5를 "자바 3"이라고 부르려니 오히려 더 혼란 스러울 것 같다는 생각이 들어서 그냥 버전 "1.5"의 "5"에 맞춰서 "자바 5.0"이라고 부르기로 했습니다.

그래서 오리지널 자바는 버전 1.02(처음으로 공식 릴리스된 버전)부터 1.1까지는 "자바"였습니다. 버전 1.2, 1.3, 1.4는 "자바 2"였죠. 그리고 버전 1.5부터는 "자바5.0"이라고 부릅니다. 하지만 종종 (.0을 빼고) "자바 5"라고 부르기도 하고, 또는 (코드명을 따라서) "타이거"라고 부르기도 합니다. 다음 릴리즈에 어떤 이름이 붙을지는 저희도 잘 모르겠네요.


*개인적으로 저는 이런 비하인드 스토리를 알게 되면 뭔가 재미가 느껴질 까?? ㅎㅎㅎㅎ


자바 코드의 구조


자바코드의 구조

자바코드의 구조



1.소스파일에는 무엇이 들어 있을까?

소스 코드파일(java라는 확장자자 붙은 파일)에서는 클래스(class) 각각 한 개씩을 정의합니다. 클래스는 보통 프로그램의 한 부분이라고 할 수 있지만 아주 작은 애플리케이션 중에는 클래스 단 하나만으로 이뤄진 것도 있습니다. 클래스는 한 쌍의 중괄호({ })안에 들어가야 합니다.

public class Dog {

//클래스



2.클래스 안에는 무엇이 들어있을까요?

클래스에는 메소드(method)가 한 개 이상 들어갑니다. 예를 들어, (개를 나타내는) Dog 클래스에는 (짖는 것을 의미하는) bark라는 메소드가 들어갈 수 있으며, 이 메소드에는 개가 짖는 방법을 지시하는 내용이 들어가면 될 것입니다. 메소드는 클래스 안에서(즉, 클래스 전체를 감싸는 중괄호 안에서) 선언해야 합니다.

public class Dog {

    void bark() {

    } // 메소드

//클래스 



3.메소드 안에는 무엇이 들어있을까요?

메소드를 감싸는 중괄호 안에는 메소드에서 처리할 일을 지시하는 내용이 들어갑니다. 메소드 코드는 기본적으로 일련의 선언문을 모아놓은 것이므로 지금은 메소드를 일종의 함수나 프로시저와 비슷한 것으로 생각해도 됩니다.

public class Dog {

    void bark() {

        statement1; //선언문
        statement2;

    }// 메소드

}
//클래스



클래스를 해부합시다.

JVM이 실행되면 명령행에서 지정했던 클래스를 살피게 됩니다.
그리고 나서 main() 메소드를 찾아 봅니다.

public static void main(String[] args) {
    //코드가 들어갈 자리
}

 
이런 메소드를 찾으면 JVM에서는 main 메소드의 중괄호({ }) 안에 있는 것을 모두 실행시킵니다.
모든 자바 애플리케이션에는 
최소한 클래스한개가 있어야 하며 적어도 main 메소드 하나가 있어야 합니다.(클래스마다나씩이 아니라 애플리케이션마다 하나씩 있어야 합니다. *참고로 WAS와 같은 웹서버는 main메소드를 따로 구성하실 필요가 없겠죠^^ main메소드로 WAS가 이미 구동중이니까요^^)

main이 들어있는 클래스 만들기

자바에서는 모든 것이 클래스 안에 들어갑니다.
우선 소스 코드(.java 확장자가 붙어있는) 파일을 입력한 다음 컴파일해서(.class 확장자가 붙어있는) 새로운 클래스 파일을 만들면 됩니다. 프로그램을 실행시킨다는 것은 사실 클래스를 실행시키는 것이라고 할 수 있습니다.

프로그램을 실행시킨다는 것은 자바 가상 머신(JVM Java Virtual Machine)에 "Hello 클래스를 불러 오고 그 main() 메소드를 시작하라. 그리고 main() 메소드에 있는 모든 코드가 실행될 때까지 계속 실행시켜라"라는 뜻의 명령을 내리는 것입니다.

클래스에 대한 자세한 내용은 다음 포스팅에서 알아보기로 하고, 지금은 실행 가능한 자바 코드를 만드는 방법에 대해 살펴보기로 하겠습니다. 그러면 우선 main()부터 시작해보죠.
 
프로그램 실행 절차가 시작되는 부분은 바로 main()메소드입니다.

프로그램이 아무리 커도(바꿔 말하자면 프로그램에서 얼마나 많은 개수의 클래스를 사용하든 상관없이)프로그램을 실행시키려면 반드시 main() 메소드가 필요합니다. 

1.저장 -> 2.컴파일 -> 3.실행 

main 메소드란?

일단 main 안으로 들어가면 (또는 어느 메소드든) 본격적으로 뭔가가 돌아갑니다. 즉
대부분의 프로그래밍 언어에서 컴퓨터로 하여금 어떤 일을 하게 만드는 모든 일반적인 지시사항은 메소드 안에 들어있습니다.

코드에서는 JVM에 다음과 같은 것을 지시할 수 있습니다.

1.뭔가를 하는 것

선언문: 선언, 대입, 메소드 호출 등

int x = 3;
String name = "Dirk";
x = x* 17;
System.out.print("x 는 " + x + "입니다.");
//주석은 이렇게 씁니다.


2.뭔가를 여러 번 반복하는 것

순환문: for와 while

while (x > 12) {
    x = x -1;
}

for(int x -0 ; x < 10; x = x+1) {
    System.out.print("x의 값은 "+x+ "입니다.");
}



3.조건에 따라 뭔가를 하는 것

분기문: if/else 테스트

if(x== 10) {
    System.out.print("x가 10이군요.");
} else {
    System.out.print("x가 10이 아닙니다.");
if((x< 3) & (name.equals("Dirk"))) {
    System.out.print("Gently");
}
System.out.print("이 선언문은 무조건 실행됩니다.");


돌리고 돌리고 돌리고...

자바에는 while, do-while, for의 세 가지 표준 순환 구조가 있습니다. 순환문에 대한 자세한 내용은 나중에 알아보기로 하고 일단 여기서는 while만 생각해 보겠습니다.

문법이 워낙 간단하기 때문에 설명이 지루하게 느껴질지도 모르겠네요. 어떤 조건이 만족되기만 하면 순환문 블록(block) 안에 들어있는 작업을 모두 처리합니다. 중괄호 한 쌍 안에 들어가는 내용ㅇ이 바로 순환문 블록이며 반복하고자 하는 내용은 그 블록 안에 집어넣으면 됩니다.

순환문에서 가장 중요한 것은 바로 조건 테스트 부분입니다. 자바에서 조건 테스트의 결과는 부울 값(boolean)입니다. 즉, 참(true) 또는 거짓(false) 값을 가지게 됩니다.

"iceCreamInTheTub 가 참인 동안 계속 아이스크림을 퍼라"와 같은 것이 바로 부울 테스트라고 할 수 있습니다. 통 안에 아이스크림이 있거나 없거나 둘 중 하나기 때문이지요. 하지만 이 때 참과 거짓이 분명하지 않으면 안 됩니다. 반드시 참과 거짓이 명확하게 구분되는 것만 조건 테스트로 사용할 수 있습니다.


'Develop Story > Head First Java' 카테고리의 다른 글

2.객체 마을로의 여행  (0) 2011.03.26
1 껍질을 깨고  (0) 2011.03.25
Posted by gofly