설계 및 선언 🔐

안녕하세요! 두두코딩 입니다 ✋
오늘은 인터페이스 설계에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

들어가기 전에

소프트웨어 설계란 소프트웨어가 사용자가 원하는 동작을 하도록 틀을 짜는 방법을 말한다. 많은 개발자들은 “어떻게 하면 좋은 C++ 인터페이스를 설계하고 선언할 수 있을까?” 를 고민한다. 이번 장에서는 C++에서 어떤 인터페이스를 설계하든지 막론하고 중요한 지침들에 대해 알아본다. 가장 중요한 지침은 제대로 쓰기엔 쉽게 엉터리로 쓰기엔 어렵게 라는 지침이다.

제대로 쓰기엔 쉽게 엉터리로 쓰기엔 어렵게 라는 포문을 열고, 이 지침을 기반으로 정확성, 효율성, 효율, 캡슐화, 유지보수성 및 확장성 그리고 준수규약에 이르는 인터페이스의 설계에 얽힌 많은 문제에 대해 알아보자.

제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게

C++에서는 함수, 클래스, 템플릿 등과 같이 인터페이스가 아주 많은 영역에서 사용된다. 보통 사용자들은 우리가 만든 인터페이스를 통해 우리의 소프트웨어를 사용한다. 의도를 갖지 않는 이상 사용자는 인터페이스에 의존해 작업을 수행할 것이다. 만약 잘못 사용할 경우, 우리는 인터페이스가 최소한 항의의 몸부림이라도 보여주어야 하는 의무를 갖고 있다.

제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵운 인터페이스를 개발하기 위해, 우리는 사용자가 저지를 만한 실수를 생각하고, 선조치를 취해야한다.

일례로, 날짜를 나타내는 어떤 클래스에 넣을 생성자를 설계하고 있다고 가정해보자.

class Date {
public:
	Date(int month, int day, int year);
	...
};

우리가 흔히 사용하는 기법이다. Date라는 클래스를 만들고, 생성자의 인자로 월, 일, 년도를 입력받는다. 해당 클래스 같은 경우 별 문제 없이 보이지만, 인터페이스 관점에서 보게되면 2 가지 문제가 있다.

// 1 번 문제
Data d (30, 3, 1995);

위와 같이, 첫 번째 문제는 일과 월을 바꿔서 입력할 경우이다. 즉, 3월 30일이어야 하는데 30월 3일로 잘못 전달할 경우 문제가 된다.

// 2번 문제
Date d (3, 40, 1995);

두 번째 문제는 월과 일에 해당하는 숫자를 잘못 입력할 경우이다. 즉, 3월 30일을 입력하려고 있는데, “키보상 3 옆에 있는 4를 눌러” 3월 40일을 입력할 경우이다.

위의 문제를 해결하기 위해서는 새로운 타입을 정의하는 것을 활용해 사용자 실수를 막을 수 있다.

인터페이스 문제를 해결 하기 위한 새로운 타입 정의

위와 같은 케이스는 사용자가 잘못 입력헀지만, int라는 타입에는 명확하게 숫자가 전달돼 문제 없이 컴파일 된다. 이를 막기 위해시는 타입 시스템을 활용하면 된다.

일, 월, 년을 구분하는 새로운 타입을 만들고, 이 타입을 Date 생성자 안에 두면 문제를 해결 수 있다.

struct Day {
	explicit Day(int d) : val(d) {}

	int val;
};

struct Month {
	explicit Month(int d) : val(d) {}

	int val;
};

struct Year {
	explicit Year(int d) : val(d) {}

	int val;
};

class Date {
public:
	Date(const Month& m, const Day& d, const Year& y);
	...
};

Date (Month(3), Day(40), Year(2021));  // 타입이 옳지 않아요
Date (Month(30), Day(3), Year(2021));  // 타입 확인해주세요!
Date (Month(3), Day(30), Year(2021));  // 정상입니다.

위와 같이, 타입을 적절히 새로 준비만 해도 인터페이스 사용 에러를 막는 데는 충분하다. 일단 타입이 있으면, 각 타입별로 제약을 가할 수 있는데, 제약을 통해 좀 더 인터페이스 에러를 쉽게 막을 수 있다.

예를 들어, 월이 가질 수 있는 유효값은 12개이다. Month 타입은 12개라는 제약을 활용해 인터페이스 에러를 막을 수 있다. 보통 enum을 사용하려고 하는데, enum 같은 경우 int타입으로 변할 수 있기 때문에 타입 안정성 측면에서는 좋지 못한 선택이다. 따라서, 타입 안정성까지 고려하기 위해서는 Month 집합을 먼저 고려해 만들어 두면 된다.

class Month {
public:
	static Month Jan() { return Month(1); }
	static Month Feb() { return Month(2); }
	...
	static Month Dec() { return Month(12); }
	...

private:
	// Month 값이 일반적인 인자로 받아 생성되지 않도록 함
	explicit Month(int m);
};

Date d(Month::Mar(), Day(30), Year(2021));

위의 코드와 같이, 구축할 경우 안전한 인터페이스를 구축할 수 있다. 우리가 Month 클래스 내 월 별로 함수를 생성하고, 함수에 대한 반환 값을 static으로 선언해주게 된다면, 타입에러는 물론 비지역 정적 객체에서 발생할 수 있는 문제도 막을 수 있다. (비지역 정적 객체에서 발생할 수 있는 문제에 대해 이해가 되지 않는 다면 여기를 클릭해 알아보자.)

예상되는 사용자 실수를 막는 또 다른 방법으로는 const를 활용하는 방법이 존재한다.

const 활용

이전 포스팅에서 다루었지만, 다시 한번 복습해보자. 제약 부여 하는 방법으로 흔히 쓰는 방법 중 하나로 const 붙이기가 있다. 자세한 내용을 보고 싶다면 여기를 참고하자.

예를들어, operator*를 구현한다고 가정해보자. 위 연산자를 구현하기 위해서는 const라는 키워드를 적어줘야한다.

// 헉.. 나는 비교하려고 한건데, 모르고 =를 적어버렸다ㅠ
if (a * b = c)
	...

위와 같이, == 연산자를 사용해야되는 자리에 =를 사용할 경우를 방지하기 위해서는 const를 활용하면 된다. 즉, const 키워드가 있게 될 경우 = 연산자를 사용할 경우 컴파일 에러가 발생하게 된다. const 키워드 같은 경우 상수화 시키는 역할을 하기 때문에, = 연산자는 사용할 수 없게 된다.

인터페이스 만들기 일반적 지침

위의 const방법으로 제약을 논한 이유는 일반적 지침을 이야기 하고 싶어서이다.

❗ 일반적 지침 : 그렇게 하지 않을 번듯한 이유가 없다면 사용자 타입은 기본 제공 타입 처럼 동작하게 만들어라

위의 지침을 잘 기억하자.

int와 같은 기본타입은 사용자들도 기억하고 잘 사용하고 있다. 따라서, 우리가 인터페이스를 위해 타입을 새로 만든다거나 할 때, 기본타입과 동일하게 만든다면 사용자들이 쉽게 사용할 수 있다.

위의 const키워드도 생각해보면 당연하게 접근할 수 있다. ab가 int 라면 a*b 연산자의 값에 =을 한다는 것은 말이 안되는 행위이다. 기본타입에서 생각해보면 너무 당연한 이야기이다. 계산된 결과 값은 상수이고, 해당 상수는 다른 곳에 =연산자를 통해 들어갈지언정 본인의 값에 = 연산자를 사용할 수 없다는 이야기이다.

우리가 제대로 쓰기에 괜찮은 인터페이스를 만드는 가장 중요한 요인은 기본타입과 유사하도록 만드는 것 즉, 인터페이스를 일관적이게 만드는 것이다😙

STL 컨테이너의 인터페이스는 전반적으로 일관성을 갖고 있다. 이 때문에 사용자가 사용하는데 부담이 줄어든다. 예를들어, STL 컨테이너들은 size라는 멤버 함수를 개방해 사용한다. 해당 함수는 컨테이너에 들어 있는 원소를 알려준다. (일관적이게 모두가 size라는 멤버함수를 가지고 있다.)

하지만, Java 같은 경우 원소의 개수를 파악하기 위해 Array에 대해서는 length라는 프로퍼티를 사용하고, List에 대해서는 size 메소드를 사용해야한다.

닷넷 (C#) 같은 경우도 Array에서는 length라는 프로퍼티를 사용하는데, ArrayList 에서는 Count라는 프로퍼티를 사용한다.

위와같이 인터페이스가 구축되어져 있어도, IDE 개발환경에서 자동으로 함수를 찾아 주겠지만.. 개발자라면 인터페이스 구축할 때 일관성을 지키는 것이 좋다. (자바를 쓴다고 개발자가 아니라는 말은 아니다.)

사용자 정의 인터페이스의 구축의 마지막으로 사용자 쪽에서 뭔가를 외우도록 하는 인터페이스는 지양하도록 하자라는 부분을 이야기해보록 하자.

포스팅이 너무 길어 졌으니, 다음 포스팅을 통해 마저 알아보도록 하자!🙃