본문 바로가기
책/크로스플랫폼 핵심 모듈 설계의 기술

2장. 개발기법 (#1)

by 정선한 2023. 6. 16.
728x90
반응형

코드는 개발의 산물이다
양질의 코드를 생산하기 위해 여러 가지 개발 기법을 아는 것이 중요한 이유이다. 개발기법은 컴파일러나 링커 같은 외부 툴을 이용하는 경우도 있고, 모듈 설계나 표준 헤더 및 대표 헤더를 이용한 코드 작성 요령에 따른 것도 있다. 또한 이 코드의 동작 여부를 판단하기 위한 테스트 기법도 이에 포함된다.

설계를 위한 코딩을 하라.
명확하지 않은 주요 지점을 미리 프로토타이핑 하면서 구조를 좀 더 명확하게 가다듬어 가면서 코딩을 해야 한다. 처음 부터 완벽한 설계 후에 코딩을 시작할 수 없기 때문이다. 대부분의 개발은 그 상황에 맞는 특화된 방법을 찾아나가는 과정이기에 코딩과 설계를 반복하는 경험들을 많이 쌓아나가야 한다.

코드는 작성하는 것도 어렵지만 제대로 동작하는지 확인하는 것은 더 어렵다.
바로바로 결과를 확인하면서 코드를 작성하면 안정적인 로직을 더 완성할 수 있기 때문에 요즘 중요해진 테스트 주도 방법인 TDD는 사용이 예상되는 호출 코드를 작성하고 그에 맞춰 목표 모듈을 작성하는 방법론이다.

개발 기법의 필요성
한때는 복잡도가 높은 코드가 그 개발자의 실력을 판단하는 기준이 되기도 했다. 이런 코드는 덩어리가 크고 기능이 많아 새로운 코드를 덧붙이고 기존 코드를 디버깅하면서 리팩토링하기 어렵기 때문이었다.
하지만 이런 코드는 난잡하기 그지 없다. 이것은 천재의 코드라고 하더라도 좋은 코드라고 말할 수 없으며 간결하고 구조적인 코딩을 할 수 있도록 하여 유지보수가 쉽도록 복잡도를 낮추어야 한다.

"헝가리안 표기법" 단순하게는 이렇게 이름만 잘 지어도 코드의 복잡도를 낮추는데 도움이 된다.

"분할 정복"
개발자가 한 번에 생각해야 할 양을 줄이는 일은 코딩할 때 아주 중요하며, 코딩을 하는 행위는 마치 퀴즈를 푸는 것과 같다. 문제의 난이도를 최대한 낮추고 문제를 단순화하여 구조적으로 복잡해질 부분을 미리 분산하여 구조화시키는 방법을 통해 개발자의 리소스를 정확한 계산에 더 빨리 투자할 수 있다.


컴파일러의 활용
컴파일러의 동작을 이해하는 것이 이번 내용의 목적이다. 컴파일러가 어떻게 동작하는지 아는 것은 매우 도움이 된다.

"상호 참조 오류"
컴파일러란, 코드를 오브젝트 파일로 바꿔주는 도구이다.
컴파일 과정에서 많은 에러와 경고를 마주하기 때문에 누군가는 귀찮은 잔소리꾼 정도로 여기기도 한다. 하지만 컴파일러가 내뿜는 에러나 경고는 중요한 의미들을 내포한다. 그렇기에 컴파일러가 말하는 오류를 해결하다보면 컴파일러의 동작 패턴을 이해할 수 있게 된다.

컴파일러는 구문을 해석하는 시점에서 변수나 구조체가 미리 선언되어 있지 않으면 그게 어떤 것인지 알 수 없다.
C++언어의 include, C++의 컴파일러는 구문을 해석하기 전에 먼저 각 코드들 정확하게는 파일들을 줄 세우는데 그 기준은 include 구문을 통해 정해진다.

선언부와 구현부
선언부는 대략적으로 해당 코드의 내부에서 언젠가는 정의될 내용을 파악 할 수 있도록 한다. 선언한다는 의미는 타입만 알려주는 것이므로 그 안에 어떤 것이 들어있는지 알려주는 것은 아니다.
구현부에 다다르기 전에는 사용하려는 노든 클래스나 변수들이 먼저 정의가 되어있어야 한다.

"선언과 구현의 분리"
변수 뿐 아니라 함수, 클래스, 구조체 또한 선언과 구현을 분리할 수 있다.

C#, JAVA 처럼 애초에 선언이라는 개념을 없앤 언어도 있다. 하지만 선언이라는 개념이 존재하기에 얻는 이점도 있다. 먼저, 헤더 파일을 통해 소스코드의 전체 모습을 훑어보기 편하다. '헤더 파일은 코드 구현의 일부가 아니라 설명의 일부이다'라는 생각을 가지면 헤더 파일을 하나의 큰 지도처럼 사용할 수 있다.
다음으로는 함수에서 '기본 인자 값'을 설정할 수 있는 것이다. 별 것 아닌 이 기능으로 인해 함수의 가독성이 상승되며, 코드를 열어보거나 함수 매뉴얼을 보지 않아도 넣을 수 있는 인자의 값을 파악할 수 있다.

"선언에 직접 구현하는 경우"
때로는 선언부에 직접 구현을 하는 경우도 있다.

이러한 방법은 크게 두 가지 이다. 첫 번째는 구현될 코드가 매우 간단하고 상호 참조 등의 오류에 노출되지 않는 경우이며, 두 번째는 템플릿으로 코드를 작성하는 경우이다.

템플릿으로 작성된 코드는 컴파일러가 재생성한다.
하지만 템플릿으로 구현한 클래스나 함수를 선언만 하는 경우에 컴파일러는 선언만을 재생성한다. 때문에 템플릿의 도움으로 코드를 생성하였으나 함수의 본체가 존재하지 않기에 컴파일은 통과할 수 있어도 다음 링크 단계에서 오류가 발생할 수 있다. 따라서 템플릿으로 코드를 구현할 때에는 헤더 파일에 직접 구현까지 같이 해야 한다. 이렇게 템플릿을 통해 컴파일러가 개발자보다 더 많이 코딩하도록 유도하는 기법을 메타 프로그래밍이라고 한다.

링커

"링커의 역할"
링커란, 컴파일러된 오브젝트 파일들을 하나의 바이너리로 묶어주는 역할을 한다. 링커는 일반 유저들이 많이 사용하는 실행 파일뿐만 아니라 정적 라이브러리 파일이나 동적 라이브러리 파일을 만들 수 있다.

라이브러리는 그 자체로 어떤 기능을 하는 것이 아닌 제 3의 모듈이 자신의 함수를 호출할 수 있도록 기능을 제공하는 것으로 라이브러리 사용 주체는 실행 바이너리일 수 도 있고 다른 라이브러리 일 수도 있다.
하지만 반대로 라이브러리가 실행 파일을 참조하는 경우는 없다.

오브젝트 파일의 집합 = 정적 라이브러리
- 링크 시점에 어떤 함수가 호출될지 알 수 없기 때문에 '모든' 오브젝트 파일을 최종 결과물로 묶는다.
- 자체적으로는 아무것도 될 수 없는 불완전한 오브젝트 파일의 집합체
정적 라이브러리 + 오브젝트 파일의 집합 = 동적 라이브러리
- 동적 라이브러리는 실행 시점에 사용할 수있는 완전한 바이너리라는 점에서 실행파일과 비슷하다.
- '실제로 쓰이는 오브젝트 파일들만' 최종 바이너리로 묶는다.
정적 라이브러리 + 오브젝트 파일의 집합 = 실행 파일
- '실제로 쓰이는 오브젝트 파일들만' 최종 결과물로 묶는다.

"정적 라이브러리 생성"
소스파일 = 헤더파일 + cpp파일
.cpp 파일들은 컴파일러에 의해 오브젝트 파일로 변환되는데, 정적 라이브러리는 헤더는 그냥 두고 오브젝트 파일들만 주워 담는다. 그러면 함수의 구현부만 가지고 있는 형태가 된다.

따라서 제3 개발자가 이렇게 묶인 정적 라이브러리 안의 함수를 사용하려고 하면 여기에 어떤 함수가 존재하는지 알 수 있는 방법이 없다.
대신 컴파일 전에 보유하고 있던 헤더 파일이 그 정보를 알 수 있는데, 헤더파일도 완전히 다 믿을 수 없다.
컴파일러가 헤더 파일의 모든 함수들이 구현되어있는지를 검사하지 않기 때문이다. 대신 함수가 선언되지 않았다는 오류에 대하여는 하이브러리의 사용 쪽에서 오류를 통해 확인이 가능하다.

정적 라이브러리의 헤더 파일은 사용서와 같고, 이러한 함수들이 있다고 서술된 것이라고 생각하면 된다. 단, 앞서 기술했든이 해당 함수가 실제로 존재하는지는 호출해보지 않는 이상 확인할 수 없다.

"동적 라이브러리 생성"
동적 라이브러리는 정적 라이브러리와 다르다.
외부에 노출할 함수들을 명시적으로 지정할 수 있고 지정된 함수는 반드시 오브젝트 파일에 구현되어 있음을 보장해야 한다. 그렇지 않으면 링커는 동적 라이브러리를 빌드하지 않으며 사용하지 않는 불필요한 오브젝트 파일은 제외한다.

동적 라이브러리를 쓸 때에는 호출하는 쪽에서 동적 라이브러리에, 사용하려는 함수의 주소를 요청한다. 
함수가 존재한다면 함수의 주소를 반환하고 없다면 NULL을 반환한다. 이는 동적 라이브러리가 스스로 함수 테이블을 들여다 보고 있으며 명시적으로 함수의 존재여부를 판단하는 것이다.
이렇게 함수 주소를 정상적으로 받아왔다고 하여도 실제로 사용하기 위해서는 그 함수의 반환형이나 임자 값에 대한 정보를 호출하는 쪽에서 정확하게 알고 있어야 한다. 따라서 동적 라이브러리도 정적 라이브러리처럼 헤더 파일이 같이 제공되어야 한다.

"템플릿의 사용과 링크 에러"
코드 자동화를 외해 템플릿을 사용하는 경우가 있는데 이때 링크 에러가 발생할 수 있다.
이 문제는 동적 라이브러리에서 내보내기로 지정하는 함수에서 템플릿의 인자를 사용하는 경우 발생하는 것이다.
그 이유는 동적 라이브러리에서는 외부에서 사용할 수 있는 함수를 명시적으로 지정해야 하는데, 이때 템플릿을 통해 코드를 자동화하는 경우 명시적으로 지정되지 않고 외부로 노출되지 않았기에 함수들이 제외되었기 때문이다.
하지만 정적 라이브러리에서는 반대로 해당 함수들이 전부 포함되기에 링크 에러는 발생하지 않는다.

이와 같이 템플릿을 이용한 인터페이스는 동적 라이브러리에 부적합하다.
이런 특이사항이 발생하는 경우에는 동적 라이브러리와 함께 부가적으로 호출되는 cpp파일들을 정리하여 같이 배포해야 한다.

 


모듈 트리

공통 헤더 파일(stdafx.h)과 대표 헤더 파일의 활용

테스트 기법

728x90
반응형