“연결성을 이해하면 품질이 보인다” 정적 코드 결합의 5가지 종류
컨텐츠 정보
- 조회 435
본문
느슨하게 결합된 코드가 좋다는 데는 이론의 여지가 없지만, 느슨한 결합이 무엇을 의미하는지가 항상 명확하지는 않다. 연결성은 코드가 어떻게 결합되는지 추론하는 데 도움이 된다. 코드의 결합을 이해하지 못하면 코드를 분리할 수도 없다.
물론 뭔가가 작동하기 위해서는 코드 모듈이 어떤 방식으로든 결합돼야 한다. ‘느슨하게 결합된다’라는 말은 코드가 강하게 결합되기보다는 약하게 결합되는 것을 의미하는데, 그 결합도를 어떻게 측정할까? 코드가 결합되는 정도라는 것을 애초에 어떻게 판단할까? 여기서 다룰 구체적인 사항은 대부분 당연해 보일 것이다. 실제로 대부분이 개발자의 상식에 속한다. 그럼에도 연결성의 분류 체계는 코드의 구성에 대한 놀라운 통찰력을 제공한다는 측면에서 여전히 유익하다.
연결성에는 정적 연결성과 동적 연결성, 두 가지 유형이 있다. 정적 연결성은 코드를 시각적으로 살펴보는 것으로 발견할 수 있지만 동적 연결성은 코드를 실행해야만 발견할 수 있다. 이번 주에는 정적 연결성에 대해서만 살펴보고, 동적 연결성은 다음 주에 다룬다.
정적 연결성에는 5가지 유형이 있다. 이 5가지 유형을 인지하고 이해하면 코드와 코드의 작동 원리를 더 깊이 이해하는 데 도움이 된다. 이 중에서 다수, 경우에 따라는 5가지 모두 익숙하겠지만, 이것들을 ‘결합’이라고 생각해 본 적은 아마 없을 것이다.
이름 연결성
이름 연결성은 두 요소가 무언가의 이름에 대해 합의해야 할 때 발생한다. 가장 약한 형태의 연결성, 또는 가장 느슨한 형태의 결합이며 우리가 적극적으로 지향해야 할 형태의 결합이기도 하다. 다음과 같이 프로시저를 선언한다고 가정해 보자.
function DoSomething(): void { // Code that does something}이 경우 해당 프로시저의 이름, 즉 DoSomething을 사용해 호출해야 한다. 일단 이름을 정하면 이후에는 그 이름을 계속 사용해야 하며, 이름을 변경할 경우 다른 곳에서도 변경해야 한다. 프로시저의 이름을 바꾸려면 해당 프로시저를 호출한 모든 곳에서 이름을 바꿔야 한다. 이는 자명한 사실처럼 보인다. 물론 이러한 수준의 연결성은 불가피하며 바람직하기도 하다. 이름 연결성은 가능한 가장 낮은 수준의 결합이다. 만약 결합을 이름 연결성으로 제한할 수 있다면 아주 잘 하고 있는 것이다.
타입 연결성
타입 연결성은 두 개체가 무언가의 타입에 합의해야 할 때 발생한다. 대표적인 예는 메서드의 매개변수다. 다음과 같이 함수를 선언한다고 가정해 보자.
class SomeClass { processWidget(aWidget: Widget, aAction: WidgetActionType): boolean { // Function logic goes here return false; // Example return value }}이 경우 모든 호출 코드는 Widget과 WidgetActionType을 processWidget 함수의 매개변수로 전달해야 하며, 결과 타입으로 boolean을 받아야 한다.
타입 연결성은 이름 연결성만큼 약하지는 않지만 여전히 약한 수준이며, 허용 가능한 정도의 연결성으로 간주된다. 사실 타입 연결성이 없으면 제대로 뭔가를 해낼 수 없다. 무슨 작업이든 하려면 클래스의 메서드를 호출해야 하는데, 타입을 일치시키는 작업이 큰 부담은 되지 않는다. 대부분의 코드는 타입 연결성을 통해 결합하지 않으면 제대로 작동하지 않는다. 타입 언어 컴파일러의 경우 타입 연결성으로 결합되지 않은 코드는 아예 컴파일조차 하지 않는다.
의미 연결성
의미 연결성은 여러 구성요소가 특정 값의 의미에 대해 합의해야 할 때 발생한다. 이 연결성은 매직 넘버를 사용할 때, 즉 여러 위치에서 의미를 갖는 특정 값을 사용할 때 가장 일반적으로 발생한다.
다음 코드를 보자.
function getWidgetType(aWidget: Widget): number { if (aWidget.status === 'Working') { return 1; } else if (aWidget.status === 'Broken') { return 2; } else if (aWidget.state === 'Missing') { return 3; } else { return 0; }}위 코드를 사용하려면 GetWidgetType 함수의 결과 코드의 의미를 알아야 한다. 결과 코드(1, 2, 3, 0) 중 하나를 변경하거나 새 코드를 추가한다면 이 함수를 사용하는 모든 코드에서도 그에 맞춰 변경이 필요하다. 또한 이 변경 작업을 위해서는 각 결과 코드의 의미를 알아야 한다. 물론 이 함수를 보고 'Working'이 1이라는 것을 알 수 있지만, 함수를 호출하는 입장에서는 그 의미가 전혀 명확하지 않다.
해결책은 코드를 리팩터링해서 결과 코드에 상수 이름을 사용하는 것이지만 더 나은 방법은 상태의 의미를 정의하는 열거형을 사용하는 것이다. 이렇게 하면 결합 수준이 의미 연결성에서 이름 연결성으로 낮아진다. 즉, 바람직한 결과다. 높은 수준에서 낮은 수준의 연결성으로 리팩터링할 때마다 결합도가 낮아지고 코드는 개선된다.
위치 연결성
위치 연결성은 서로 다른 두 곳에 위치하는 코드가 어떤 요소의 위치에 대해 합의해야 할 때 발생한다. 가장 흔한 예는 메서드에서 매개변수의 순서가 필요한 매개변수 목록이다. 기존 매개변수 목록의 중간에 매개변수를 추가하는 경우 이 메서드를 사용하는 모든 곳에서 올바른 위치에 새 매개변수를 추가해야 한다.
위치 연결성의 정도는 루틴에서 매개변수의 수를 제한하는 방법으로 낮출 수 있다. 예를 들어 매개변수 목록을 단일 타입으로 줄여 위치 연결성에서 타입 연결성으로 전환할 수 있다. 타입 연결성이 상대적으로 더 약한 결합이므로 바람직하다.
예를 들어 다음 루틴을 살펴보자.
class UserManager { addUser(aFirstName: string, aLastName: string, aAge: number, aBirthdate: Date, aAddress: Address, aPrivileges: Privileges): void { // add the user here }}위치 연결성을 리팩터링해서 타입 연결성을 사용하도록 함으로써 연결성을 낮추거나 결합을 더 느슨하게 할 수 있다. 예를 들어 다음을 보자.
type UserRecord = { firstName: string; lastName: string; age: number; birthday: Date; address: Address; privileges: Privileges;};class UserManager { addUser(aUser: UserRecord): void { // add user here }}여기서는 하나의 타입을 만들고, addUser 프로시저가 긴 매개변수 목록의 위치가 아니라 하나의 매개변수 타입에 의존하도록 함으로써 결합도를 낮췄다. 이렇게 해서 UserRecord의 데이터를 원하는 순서대로 입력할 수 있으므로 addUser 함수를 호출할 때의 인지적 부담도 줄어든다.
연결성을 약화함으로써, 즉 결합의 강도를 줄임으로써 코드를 개선했다.
알고리즘 연결성
알고리즘 연결성은 두 모듈이 함께 작동하기 위해 특정 알고리즘에 합의해야 할 때 발생한다.
C# API로 서버를 구축하고 이를 타입스크립트 클라이언트에서 소비하는 상황을 가정해보자. 두 모듈 간에 주고받는 정보에는 민감한 데이터가 포함돼 있어 반드시 암호화해야 한다. 이때 이 두 모듈은 알고리즘 연결성에 의해 결합된다. 사용되는 암호화 알고리즘에 대해 둘이 합의해야 하기 때문이다. 서버에 대한 암호화 알고리즘이 변경되는 경우 클라이언트에 대해서도 동일한 변경이 이뤄져야 한다.
알고리즘 연결성은 줄이기가 어려울 수 있다. 대부분의 경우 높은 지역성을 갖기 때문이다. 즉, 논리적으로나 물리적으로 서로 멀리 떨어진 모듈 간에 결합이 발생한다. 한 가지 방법은 알고리즘이 있는 유일한 장소가 되는 제3의 모듈을 만들고, 서버와 클라이언트 모듈이 이 세 번째 모듈을 사용하도록 하는 것이다. 이 경우 한 위치에서 모든 암호화를 처리하는 세 번째 서비스를 생성하게 된다.
아마 개발자는 이런 모든 상황을 이미 겪은 적이 있고, 그 때마다 ‘중복을 지양할 것’ 또는 ‘매직 넘버를 사용하지 말 것’과 같은 잘 알려진 원칙에 따라 직관적으로 올바른 방법을 파악했을 것이다. 그러나 코드의 결합은 난해하고, 위와 같은 격언이 적용되는 문제와 달리 눈에 잘 띄지 않는 경우가 많다. 정적 연결성을 이해하고 있으면 이런 상황에서 더 깔끔하고 결합 강도가 낮은 코드를 작성하는 데 도움이 될 수 있다.
dl-itworldkorea@foundryco.com
관련자료
-
링크
-
이전
-
다음





