728x90

리팩토링을 진행하는 것도 좋지만, 리팩토링으로 인해 발생할 수 있는 문제점들 혹은 리팩토링이 어려운 상황에 대해 알아두는 것도 중요하다.




리팩토링으로 인해 발생할 수 있는 문제점들...



- 데이터 베이스

리팩토링에서 문제가 되는 부분 중 하나가 바로 데이터 베이스이다. 많은 비즈니스 어플리케이션은 바탕이 되는 데이터 베이스 스키마와 강력히 결합되어 있다. 이 때문에 데이터 베이스 수정이 어려워진다.


아무리 데이터 베이스 스키마와 객체 모델의 상호 의존성을 최소화하려고 시스템을 꼼꼼하게 계층 구조로 제작했더라도, 데이터 베이스 스키마를 수정하면 데이터도 이전해야 하는데, 이것은 시간도 오래 거릴 뿐 아니라 위험성도 높다.


이 문제를 해결하기 위해서 객체 모델과 데이터 베이스 모델 사이에 별도의 소프트웨어 계층을 두는 방법이 있다.

이렇게 하면 두 모델에 생긴 변경 사항을 따로 유지할 수 있어서 한 모델을 수정할 때 다른 모델은 수정할 필요 없이 그저 중개 계층만 수정하면 된다. 



- 인터페이스 변경

객체의 중대한 장점 중 하나는 인터페이스를 건드리지 않고 내부의 구현 코드를 수정할 수 있다는 점이다.

객체를 이용하는 부분을 수정하지 않고도 객체의 내부 구조를 안전하게 수정할 수 있지만, 인터페이스를 수정하면 어떤 문제가 발생할지 알 수 없으므로 인터페이스를 건드리지 않는다는 것은 큰 장점이다.


리팩토링에서 불안한 점은 상당수의 리팩토링이 인터페이스를 수정한다는 것이다.

함수명 변경도 그 자체가 인터페이스를 수정하는 작업이다.


특히, 이미 배포를 한 프로그램이고 내부 함수명을 수정했다면 그 인터페이스를 사용하는 측에서도 수정을 해야하므로 더 복잡하게 수정해야만 할 수도 있다.


위 문제를 해결하기 위해선 기존 인터페이스가 새 인터페이스를 호출하게 수정하면 간단하게 해결이 된다.





리팩토링을 하면 안되는 상황.



- 코드를 처음부터 새로 작성해야 할 때

코드가 돌아가지 않는다면 그건 완전히 새로 작성을 하라는 신호이다. 테스트만 하려다 코드가 버그 투성이라 안정화할 수 없음을 알게 될 경우가 바로 그 때이다. 


코드가 제대로 돌아가는 것이 가장 중요하다. 리팩토링은 그 이후의 일임을 반드시 명심하자.



- 납품일이 임박했을 때

납품일이 가까운 시점에 리팩토링을 해봤자 그로 인한 생산성은 납기가 지난 후에나 가시화될 테니 쓸데 없다.


하지만, 납품일이 임박한 경우가 아니면 시간이 없다는 핑계로 리팩토링을 미루면 안 된다.




728x90

'Basic Programming > Refactoring' 카테고리의 다른 글

Refactoring - 리팩토링을 해야 할 때  (0) 2017.09.14
Refactoring - 리팩토링 이란 ?  (0) 2017.09.14
728x90

리팩토링은 조금씩 조금씩 단계적으로 수정을 하고, 수정을 했으면 항상 신뢰도 높은 각종 테스트를 해야 한다.

그리고 각종 테스트는 반드시 자체 테스트가 되게 만들어서 테스트를 해서 시간을 단축하는게 좋다.




리팩토링이란?

1) (명사) 겉으로 드러나는 기능은 그대로 둔 채, 알아보기 쉽고 수정하기 간편하게 소프트웨어 내부를 수정하는 작업.

2) (동사) 리팩토링 기법을 연달아 적용해서 겉으로 드러나는 기능은 그대로 둔 채 소프트웨어 구조를 변경.



리팩토링과 최적화는 다른 작업이다.

리팩토링은 소프트웨어의 유지보수를 조금 더 쉽게할 수 있게 하고, 코드 가독성을 올리며 기능 추가를 더 쉽게 할 수 있게 하여 오히려 성능이 더 떨어질 수도 있다.

하지만 최적화 작업은 성능은 올라가더라도 코드의 가독성이 더 떨어질 때가 많다.



리팩토링에 대한 명언

리팩토링을 적용할 때에는 버그도 그대로 있어야 한다.



리팩토링을 하는 이유

1) 소프트웨어의 설계 개선. (중복 코드 제거)

2) 소프트웨어를 이해하기가 더 쉬워진다. (유지보수)

3) 버그를 찾기 쉬워진다.

4) 프로그래밍 속도가 빨라진다.



리팩토링을 하는 시기

한달에 하루 이런식으로 날잡고 하는게 아니라 개발과정에서 틈틈이 작업을 해야한다.


1) 같은 작업의 3진 아웃 때 

어떤 작업을 처음 할땐 그냥하고, 두 번째 작업할 땐 조금 찝찝하더라도 그냥 하고, 세 번째 하게 되면 그때 리펙토링을 실시하는 것이다.


2) 기능을 추가할 때

(1) 코드를 이해하기 쉽게 만들기 위해서 기능을 추가할 때 리팩토링을 해야한다.

(2) 설계가 지저분해서 어떤 기능을 추가하기 힘들 때에는 망설이지 말고 리팩토링을 진행해야 한다.


3) 버그를 수정할 때

코드의 기능을 파악하려다 이해하기 힘들다면 이해하기 쉽게 만들기 위해 리팩토링을 진행해야 한다.

리팩토링을 하고 나면 버그를 찾기 쉬워질 것이다.

버그 리포트가 입수되었을 때에도 리팩토링을 진행해야 한다. 왜냐하면 버그 리포트를 받을 때까지 버그가 있는 줄도 모를 정도면 그 코드가 엄청 지저분하다는 반증이기 때문이다.


4) 코드를 검수할 때

자신의 코드는 자기가 볼 땐 쉬울지 몰라도 다른 팀원이 볼 때에는 알아보기 힘들 수도 있다. 

리팩토링을 진행하면 다른 사람이 개발한 코드를 검수하기도 쉬워진다. 







728x90
728x90

특히 신입 개발자분들은... 이글을 꼭 읽어보도록 하자.


영어로 되어 있지만 구글 번역기가 있으므로 부담없이 읽을 수 있다. ㅎㅎ




원문 : https://software.intel.com/en-us/articles/the-ultimate-question-of-programming-refactoring-and-everything?utm_campaign=IHI-HPC-Q4_16&utm_medium=Syndication&utm_source=Taboola&utm_content=&utm_term=&utm_source=taboola&utm_medium=referral




728x90
728x90

어댑터라는 것은 우리 주변에서도 흔히 볼 수 있다. 대표적으로 110V 전용 가전 제품에 220V 어댑터를 끼워 사용하는 것이다.


어댑터를 보면 어댑터 패턴이 무엇인지 대충 감이 올 수도 있겠다.



이 패턴을 사용할만 한 경우를 만들어보면,



"포토뷰어 엔진을 판매하는 회사 A가 있다. 이 A 회사의 엔진이 우리 회사의 포토뷰어 엔진보다 성능이 좋다는 결과를 통보 받았다. 그래서 나는 우리 회사의 포토 뷰어 엔진을 교체 하기 위해 A라는 회사에 엔진을 요청했고 엔진 명세서를 받아봤다."



즉 '나'는 성능의 향상을 위해 포토 뷰어 엔진을 기존의 엔진에서 A 업체가 제공하는 엔진으로 바꿔야만 하는 상황이다.



이제 명세표를 보면 A엔진과 기존 엔진의 구조를 살표보자.


/* 내가 쓰는 엔진 명세 (MyLib) */ class MyLib { public: void printPhoto(String fileName)          { ... } void printPhotoList(String[] listName)    { ... } void deletePhoto(String fileName)        { ... } void addPhoto(String fileName)           { ... } }



/* A 엔진 명세 (ALib) */ class ALib { public:

void printPhoto(String fileName)          { ... } void deletePhoto(String fileName)        { ... } void addPhoto(String fileName)          { ... } }




엔진이 완전히 같다면 좋을텐데 미묘하게 다른 구석이 있다. 차이가 있는 것은 제공 받은 엔진에서 printPhotoList() 가 없다는 것이다. 어쨋든 엔진 교체를 위해 코드 분석에 들어가보자. 아래 코드는 기존 엔진을 사용한 코드이다. 기존 엔진의 이름은 MyLib 이다.


/* 기존 코드 */ int main() { MyLib lib = new MyLib(); String photoList = { "abc.jpg" , "def.jpg" }; lib.printPhotoList(photoList); lib.printPhoto("abc.jpg"); lib.deletePhoto("abc.jpg"); lib.addPhoto("aaa.jpg"); }




자, 이제 함수명이 동일하니 한번 엔진을 바꿔보자.


/* 바뀐 코드 Ver.1 */ int main() { ALib lib = new ALib(); // change String photoList = { "abc.jpg" , "def.jpg" }; for(int i = 0 ; i < photoList.length; i++)    // for문 추가됨

{ lib.printPhoto(photoList[i]); } lib.printPhoto("abc.jpg"); lib.deletePhoto("abc.jpg"); lib.addPhoto("aaa.jpg"); }




이제 프로그램은 잘 돌아갈거라고 생각이 되지만, 기본 로직에 변경이 생겼다. 바로 전에 없었던 for()이 들어가게 된 것이다. 물론 이 코드를 더 이상의 유지 보수를 하지 않을 예정이라면 이대로 놔둬도 상관없겠지만, 프로그램을 만들면 무조건 유지 보수를 해야하기 때문에 이렇게 두는 것은 좋지 않은 방법이다.




따라서 기본 로직을 그대로 이용할 수 있게끔 어댑터 패턴을 사용해보자.


class IPhotoEngine { public:

virtual void printPhoto(String fileName) = 0;     virtual void printPhotoList(String[] listName) = 0;

virtual void deletePhoto(String fileName) = 0; virtual void addPhoto(String fileName) = 0; }




인터페이스 클래스를 만들었다. 이제 어댑터 클래스를 만들어보자.



class ALibAdapter : public IPhotoEngine { private: ALib lib; public: ALibAdapter( ALib lib ) { this.lib = lib; } void printPhoto(String fileName) { lib.printPhoto( fileName ); } void printPhotoList(String[] listName) { for(int i = 0 ; i < listName.length; i++) { lib.printPhoto( listName[i] ); } } void deletePhoto(String fileName) { lib.deletePhoto( fileName ); } void addPhoto(String fileName) { lib.addPhoto( fileName ); } }





이제 이 클래스를 사용해 보자.



/* 바뀐 코드 Ver.2 */
int main()
{
	ALib alib = new ALib();

	PhotoEngine lib = new ALibAdapter( alib );
	String photoList = { "abc.jpg" , "def.jpg" };
	lib.printPhotoList(photoList);
	lib.printPhoto("abc.jpg");
	lib.deletePhoto("abc.jpg");
	lib.addPhoto("aaa.jpg");
}



물론 ALib가 아닌 다른 엔진을 사용할 때에도 마찬가지로 IPhotoEngine 인터페이스를 포함한 어댑터 클래스를 만들기만 하면 언제든 로직을 재사용할 수 있다.


이것이 바로 어댑터 패턴을 사용하는 목적이기도 하다.


만약 기존엔진을 다시 써야하는 상황이 올때를 대비해서 기존 엔진의 어댑터 클래스를 만들어보자.



class MyLibAdapter : public PhotoEngine { private: MyLib lib; public: MyLibAdapter( MyLib lib ) { this.lib = lib; } void printPhoto(String fileName) { lib.printPhoto( fileName ); } void printPhotoList(String[] listName) { lib.printPhotoList( listName ); } void deletePhoto(String fileName) { lib.deletePhoto( fileName ); } void addPhoto(String fileName) { lib.addPhoto( fileName ); } }



그리고 간단하게 기존 엔진을 사용하도록 바꾸면 된다.



/* 바뀐 코드 Ver.3 */
int main()
{
	MyLib myLib = new myLib();
	PhotoEngine lib = new MyLibAdapter( myLib );
	String photoList = { "abc.jpg" , "def.jpg" };
	lib.printPhotoList(photoList);
	lib.printPhoto("abc.jpg");
	lib.deletePhoto("abc.jpg");
	lib.addPhoto("aaa.jpg");
}




출처 : http://jdm.kr/blog/11




728x90
728x90

컴포지트 패턴(Composite Pattern)은 간단하게 말해서 단일 객체(Single Instance)든 객체들의 집합(Group of Instance) 이든 같은 방법으로 취급하는 것이다. 다시 말해, 개별적인 객체들과 집합간의 처리 방법의 차이가 없을 경우 사용하면 된다.


여기서 컴포지트의 의미는 일부 또는 그룹을 표현하는 객체들을 트리 구조(Tree Structures)로 구성한다는 뜻이다.




컴포지트 패턴을 활용은 다음과 같은 곳에서 사용할 수 있다.


파일 시스템을 간단하게 구현한다고 생각했을 때, 먼저 필요한 것은 파일이다. 그래서 파일 클래스를 만들었다.


class File { private: String name; // ... }



그리고 파일들을 가질 수 있는 디렉토리 클래스가 필요할 것이다. 


class Directory { private: String name; List<File> children; // ... public: void add(File file) { // ... } }




디렉토리 클래스는 자신의 이름과 파일들을 가질 수 있다. add() 를 이용해 파일을 추가할 수도 있을 것이다.

근데 이 구조에서 디렉토리 안에 디렉토리가 있는 것을 어떻게 표현해야 할까?




/** INode 클래스는 기본적인 파일 및 디렉토리의 근간이라고 가정합니다. 모든 파일과 디렉토리는 이름을 가지고 있을테니 이름을 반환할 getName() 메소드를 가집니다. */ class INode { public: virtual String getName() = 0; } /** File 클래스는 INode 인터페이스를 구현하면 끝입니다.

자신은 자식 요소를 가질 필요가 없기 때문이죠.

*/ class File : public INode { private: String name; // ... public: virtual String getName()    { return name; } } /** Directory 클래스는 INode 인터페이스를 구현하는 것 외에도

자식 요소를 담아둘 List가 필요합니다. */ class Directory : public INode { private:     String name;     std::list<INode> children; // ... public: virtual String getName()    { return name; } public void add(Node node) { children.add(node); } }



이제 사용을 한다면 다음과 같이 사용하게 될 것이다.



Directory dir = new Directory();
dir.add(new File()); // 디렉토리에 파일 하나를 삽입!
dir.add(new Directory()); // 디렉토리에 디렉토리를 삽입!
Directory secondDir = new Directory();
secondDir.add(dir); // 기존 루트 디렉토리를 새로 만든 디렉토리에 삽입!



간단하지만 트리 구조를 표현할 수 있는 파일 시스템을 만들었다. 이것이 바로 컴포지트 패턴을 이용한 것이다.



실제로 컴포지트 패턴의 UML은 다음과 같다.



Component : 모든 표현할 요소들의 추상적인 인터페이스를 말한다. 위의 예제 코드에서 INode 인터페이스가 이 역할을 

                  담당하고 있었다.


Leaf : Leaf 요소는 Component로 지정된 인터페이스를 구현한 것이다. 위의 예제 코드에서 File 클래스가 이 역할을 담당

        하고 있었다.


Composite : Composite 요소는 Component 요소를 자식으로 가진다. 따라서 Component 요소를 관리하기 위한 메소드들

                을 추가적으로 구현해줘야한다. 위의 예제 코드에서는 Directory 클래스가 이 역할을 담당하고 있었다.




출처 : http://jdm.kr/blog/228

728x90
728x90

stdafx (Standard Application Frameworks) 는 개발자의 생산성 향상을 위해 MS에서 제공하는 소프트웨어 라이브러리 체계를 뜻한다.


C / C++ 에서 헤더 파일은 C 전처리기(PreProcessor) 에 의해 자동적으로 소스 코드를 포함하게 된다. 

그런데 일부 헤더 파일의 경우 방대한 크기의 소스 코드를 포함할 수 있고 (ex : stdio.h, window.h) 이런 코드들을 매번 컴파일하면 빌드의 시간이 점점 길어지게 된다. 

그래서 자주 바뀌지 않는 기본적인 라이브러리들의 경우에 컴파일의 시간을 줄이고자 컴파일러가 사전에 헤더 파일들을 미리 컴파일 해놓고 쓸 수 있게 하고 있다.


이렇게 컴파일 시간을 줄이기 위해 사전에 컴파일한 결과물이 Visual Studio의 경우 .pch(PreCompiled Header) 파일 이다.


쉽게 말하면 stdafx.h 는 미리 컴파일된 헤더 (Precompiled Header) 이다.



미리 컴파일된 헤더의 설정은 프로젝트의 속성에서 확인할 수 있다.




728x90
728x90

프로젝트를 진행하는 중에 Database 를 사용해야하는 일이 생겨서, C++ 에서 SQL을 사용하려면 어떤게 좋을 지 검색하던 도중 SQLite 라는 것을 찾았다.


그래서 이것을 이용해서 작업을 하던 도중... 한글이 지원이 안된다는 것을 깨달았다. 

Header를 보니... 전부 char 형.... ㅠㅠ


그래서 부랴부랴 찾아보니 역시 감사하신 분들이 많이 계신다.

동일한 코드에 Unicode를 지원하게 해주신 것이다.


sqlite_unicode.zip



사용법


매개변수가 char 형인 함수에 16을 붙여주고 wchar_t로 사용하면된다.

ex) sqlite3_exec() -> sqlite3_exec16()



자세한 내용까지 적으면 원글의 작성자 분께 너무 죄송하므로 링크를 추가한다.

출처 : http://greenfishblog.tistory.com/78

728x90
728x90

MFC로 툴을 만드는 프로젝트를 진행하는 도중에 다음과 같은 에러가 발생하였다.


error RC2108: expected numerical dialog constant


검색 결과 이 에러는 Visual Studio 2013에서 Picture Control을 사용할 때에 발생하는 버그라고 한다.


MS 고객 문의 결과 : https://connect.microsoft.com/VisualStudio/feedback/details/806403/bug-in-ressourceneditor



이 에러를 고치는 가장 확실한 방법은 


Visual Studio2013 -> 도구 -> 확장 및 업데이트 -> 업데이트 

에서 최신 버전으로 업데이트를 해주는 방법이다.



임시 방편으로 고치는 방법은 아래와 같이 수동으로 rc 파일을 수정하는 방법이다.

 

변경 전 (오류 발생)

변경 후

 CONTROL IDB_BITMAP1,IDC_STATIC,0,0,0,0,NOT WS_GROUP

CONTROL IDB_BITMAP1,IDC_STATIC,"Static",SS_BITMAP,0,0,0,0,NOT WS_GROUP

 

728x90

+ Recent posts