본문 바로가기
우아한테크코스 4기/레벨1

[레벨 1 돌아보기] OOP에서의 정적 메서드 사용

by 나는후니 2022. 4. 11.

레벨 1 레이싱 카 미션을 수행할 때 저는 아래와 같은 질문을 리뷰어에게 남겼습니다.

static 메서드가 메모리를 일반 메서드보다 과하게 사용할 수 있을 것 같은데 static을 최소화하는 것이 좋을까요?

나름대로 좋은 질문이라고 생각했습니다.

정적 메서드는 객체를 생성하지 않고 클래스를 참조하여 메서드를 호출합니다. 그래서 정적 메서드는 사용하는 시점에 메서드 메모리 영역에 올라가는 것이 아니라 컴파일 시점에 정적 할당되어 GC의 대상이 되지 않아 프로그램에 끝날 때 까지 메모리에 유지됩니다.

저는 위와 같은 개념을 갖고 질문을 던졌던 것이죠. 하지만 일반 메서드 또한 메서드 메모리 영역에 할당 되기 때문에 정말 많은 정적 메서드가 존재하지 않는 한 메모리에 큰 차이는 없다고 생각합니다.

하지만 정적 메서드의 사용은 최소화 하는 것이 좋습니다. 그 이유는 뭘까요?

1. 객체지향적인가?

Java는 객체지향 언어입니다. 객체 지향적인 사고는 아래와 같이 표현할 수 있습니다.

5와 9 중 더 큰 수는 x이다. (객체 지향)
5와 9 중 더 큰수를 계산하라. (절차 지향)

즉 객체지향적인가? 라는 물음의 답변을 어떤 값을 객체에 정의하고 객체들이 필요한 순간에 상호작용하는 것이다라고 정리할 수 있습니다.

하지만 정적 메서드는 그렇지 않습니다. 객체에 어떤 값을 정의하는 것이 아니라 위의 예시처럼 더 큰 수를 계산하라는 명령을 남깁니다.

따라서 정적 메서드 는 객체 지향적 사고에 어울리지 않는 방식입니다.

2. 객체지향스럽지 않아도 더 빠르잖아?

글의 초입에 말했듯이 정적 메서드는 객체를 생성하지 않습니다. 클래스를 참조하여 바로 호출해서 사용함으로써 인스턴스를 생성하는 비용을 줄일 수 있어 더 빠르다고 합니다.

하지만 정말 빠른걸까요?

아래 코드를 보겠습니다.

public void do() {
    int res = MyCalculator.mul(10, 100);
    if(isNeed(res)) {
        use(res);
    }
}

만약 res가 필요하지 않다면, res를 계산하지 않을까요? 아닙니다. 필요, 불필요 여부를 떠나서 무조건 결과값을 계산하겠죠.

하지만 선언형 방식은 어떨까요?

public void do() {
    MyCalculator cal = new MyCalculator(10, 100);
    if(isNeed(cal)) {
        use(cal.mul());
    }
}

선언형 방식은 필요한 순간에 계산을 실행합니다. 즉, 컴파일러나 가상머신에서의 최적화 대신 개발자가 직접 코드레벨에서 최적활 할 수 있다는 의미이죠.

이런 측면에서 오히려 정적 메서드를 사용하지 않고 인스턴스 메서드를 사용하는 방식이 더 빠르다고 표현할 수 있습니다.

3. 그럼 객체지향 원칙은 잘지키는가?

객체지향적이지 않은 개념이라도 객체지향 원칙을 잘 지킨다면 충분히 사용할 여지가 있습니다. 하지만 정적 메서드는 객체지향 핵심 원칙 중 하나인 다형성을 지킬 수 없습니다.

다형성은 객체 간 의존성을 느슨하게 만들고 확장에 열려있는 코드를 작성하는 데 큰 힘이 됩니다. 덕분에 유지보수에도 좋은 효과를 주는 아주 고마운 존재이죠.

하지만 정적 메서드는 메서드 재정의와 동적 바인딩이 불가능 합니다. 즉 인터페이스를 사용할 수 없다는 의미입니다.

다형성을 사용하지 못하는 것이 가독성 차원에서 어떤 문제가 생기는지 본다면 조금 더 쉽게 정적 메서드 사용을 포기할 수도 있습니다.

public static void calculate(int a, int b) {
    Scanner scanner = new Scanner();
    String x = scanner.nextLine();
    if ("더하기".equals(x)) {
        System.out.println(a + b);
    } else if ("빼기".equals(x)) {
        System.out.println(a - b);
    } else if ("곱하기".equals(x)) {
        System.out.println(a * b);
    } else {
        System.out.println(a / b);
    }
}

굉장히 간단한 코드지만 이 코드가 하는 일을 해석하는 것은 굉장히 귀찮고, 만약 또 다른 조건이 추가된다면 그것을 또다른 분기문으로 처리하는 것이 많이 귀찮아집니다.

하지만 다형성을 사용하고 오버라이딩을 통해 해당 객체에 calculate 메서드를 호출하기만 한다면 원하는 결과가 잘 나오겠죠. (자세한 코드는 생략)

그래서?

저는 객체지향 원칙을 지키기 위해 정적 메서드 사용 자체를 줄이려고 합니다. 하지만 정적 팩터리 메서드처럼 정적 메서드의 장점을 잘 이용할 수 있는 순간에는 어김없이 static 키워드를 사용하려 합니다 !