본문 바로가기
개발

더 나은 코드를 고민해보기. (Method Overloading 활용)

by 최승환 2024. 2. 16.

서비스 로직을 작업하다보면, 다음과 같은 코드 패턴을 만날 때가 있다.

채팅 서비스가 있다고 하였을 때, 아래는 간략하게 Controller LayerService Layer 를 정의해 본 코드이다.

 

(아래 코드들은 내용을 전달하기 위해, 간략하게 생략한 코드 입니다.)

class RoomController {
    ...
    @Post()
    createRoom(...)    {
        this.roomService.createRoom(...);
    }

    @Post()
    joinRoom(...) {
        this.roomService.joinRoom(...);
    }
}
class RoomService {
    constructor(
        private roomRepository: Repository<Room>,
        private roomActivityRepository: Repository<RoomActivity>
    ) {}

    ...

    getRoom(roomId: String, params: getRoomParams) {
        ...    
    }

    createRoom(params: createRoomParams) {
        ...
        const createdRoom = this.roomRepository.create({...});

        this.joinRoom(createdRoom.roomId, joinRoomParams);
        ...    
    }

    joinRoom(roomId: String, params: joinRoomParams) {
        ...
        const room = this.getRoom(roomId);

        this.roomActivityRepository.create({room, user ...});
        ...
    }


}

 

로직 상, 호스트 유저가 Room 을 생성한 후, 바로 생성한 Roomjoin이 된다고 하였을 때,

createRoom() 함수에서 Room 을 생성한 후, joinRoom() 을 호출하여 Room 에 참여하게 된다.

 

이번엔, 게스트 유저가 Room 에 참여한다고 하였을 때,

joinRoom() 을 호출하여 Room 에 참여하는 동작이 될 것이다.

 

내가 자주 맞이하던 문제는, 이러한 경우에서 발생하게 된다.

joinRoom() 의 동작은 코드 흐름에서, 호스트의 참여와 같이, 이미 Room 인스턴스를 가지고 있는 경우에도 호출이 되고,

게스트의 참여와 같이, Room 인스턴스를 가지지 않은 경우에도 호출이 된다.

이 때, 단순히 위 코드대로라면, 이미 Room 인스턴스가 있는 경우에도 불필요하게 joinRoom(roomId) 을 호출하게 되어 성능 낭비가 발생하게 된다.

 

따라서, 이러한 불필요한 동작을 없애기 위해, 메소드 오버로딩을 적용해 볼 수 있다.

 

메소드 오버로딩

메소드 오버로딩은 같은 이름을 가진 메소드를 인자를 다르게 하여 여러 번 정의하는 프로그래밍 기법으로,
이 때 각 정의된 각 메소드는 인자의 수나, 타입이 달라야 함.
메소드 오버로딩을 사용하면 같은 로직을 수행하나 다른 매개변수를 받는 케이스도 핸들링 할 수 있어, 코드의 가독성과 재사용성이 증가함.

 

아래는 메소드 오버로딩을 적용해 본 코드이다.

joinRoom(room: Room, params: joinRoomParams) {
    doJoinRoom(...) 
}

joinRoom(roomId: String, params: joinRoomParams) {
    ...
    const room = this.getRoom(roomId);

    doJoinRoom(...) 
    ...
}

doJoinRoom(room: Room, params: joinRoomParams) {
    ...
    this.roomActivityRepository.create({room, user ...});
    ...
}

 

 

joinRoom() 의 인자로 roomId 가 전달 되었을 경우엔, getRoom() 으로 Room 을 얻어온 후, doJoinRoom() 동작을 수행하며,
joinRoom() 의 인자로 Room 이 전달 되었을 경우엔, getRoom() 없이 doJoinRoom() 을 수행한다.

 

실제로 실무에서 로직의 규모가 커져, 여러 함수들을 타고 타고 들어가며 수행하는 경우에, 위와 같은 방법이 코드 품질 향상에 많은 도움이 되었던 적이 있다.

 

추가로, 아래 코드는 내가 사이드 프로젝트를 진행하던 중, 위의 경우와 유사하게 만났던 문제를 메소드 오버로딩으로 풀어간 코드이다.

async joinCommitment(commitment: Commitment, user: User): Promise<CommitmentInfo>;
async joinCommitment(commitmentId: string, user: User): Promise<CommitmentInfo>;
async joinCommitment(commitmentOrId: Commitment | string, user: User): Promise<CommitmentInfo> {
  try {
    let commitment: Commitment;

    if (typeof commitmentOrId === 'string') commitment = await this.getCommitment(commitmentOrId);
    else commitment = commitmentOrId;

    if (!commitment) throw new BadRequestException('commitment not founded');

      ...

  } catch (e) {
    ...
  }
}

 

 

Typescript 에서는 메소드 오버로딩 시에 구현체가 단 하나만 존재해야 하며, 이 구현체는 선언한 모든 메소드를 수행할 수 있어야 한다.

joinCommitment() 함수 역시, joinRoom() 함수와 마찬가지로 로직 도중에 인스턴스가 얻어지거나, 얻어지지 않거나 하는 두 가지 경우가 있다.

조건문으로 commitmentOrId 를 체크하여, 해당 인자가 Commitment 인스턴스 인지, 아니면 string 인지 타입 체크를 한다.

 

물론, 이 문서에서 제시한 방법 외에 더 나은 방법은 있겠지만, 혹시나 이 방법으로 아이디어를 얻을 수도 있는 분을 위해 글을 공유해 본다.