언제 static 함수 모음 Class를 만들어야 할까? 프로그래밍

먼저 내가 말하는 static 함수 모음 class란  Apache Commons Lang StringUtils 처럼 순전히 static 함수들만을 가지고 있고, 객체를 생성하지 않고 사용하는 클래스를 의미한다(Java 이야기이다).

이에 대한 정확한 명칭이 있는지는 잘 모르겠다.

아무튼 나는 지금 수많은 static 함수 모음 class들 때문에 갑갑함이 이루 말할 수 없다.

이 때문에 단위 테스트를 만드는 것의 복잡도가 크게 증가해서 개발이 어렵다. 또한 코드 실행 환경(profile - development, production 등)에 따른 서로 다른 기능 정의도 힘들다.

그래서 static 함수 모음 클래스를 POJO Bean으로 전환하는 것이 나의 숙원 사업일 정도이다.

이 작업을 하려면 먼저 어떤 것을 POJO Bean으로 전환하고, 어떤 것을 static 함수 모음 class로 만들어야 하는지 기준이 명확해야 다른 프로그래머들과도 공감대를 이루고 작업을 해 나아갈 수 있을 것 같다.

그렇다면 어떠한 상황에서 static 함수 모음 클래스를 만들고 어떠한 상황에서는 객체를 생성하는 POJO Bean 클래스를 만들어야 할까?

내가 생각하는 기준은 딱 하나로 명확하다.

static 함수 모음 클래스의 모든 함수는 인자가 동일할 경우 항상 동일한 결과를 리턴해야 한다. 이 규칙을 지킬 수 없으면 POJO Bean으로 만들라.

이것이 이뤄지려면 함수 안에서는 외부 자원(Resource)에 대해 하나도 의존하면 안된다는 선결 조건을 충족해야 한다.
외부 자원은 그 실행 결과의 일관성을 보장할 수 없기 때문이다.

이에 가장 잘 들어맞는 예는 StringUtils, CollectionUtils 같은 것들이다.

예를 들면, StringUtils.contains("hello world", "hello")는 어떠한 상황에서도 동일한 결과를 리턴한다.

애매한 예를 두 가지 정도 들어보자.

EncryptionUtils 라는 클래스가 있다. 이 클래스는 encrypt(String value)와 decrypt(String encrypted) 라는 두개의 함수가 있다. 이름처럼 암호화와 복호화를 한다고 하자. encrypt 함수와 decrypt 함수는 언뜻 보면 항상 같은 입력에 대해 동일한 결과를 리턴하는 듯 보인다.

하지만 그렇지 않다. 암호화에는 보통 Key가 필요하며 이 키는 실행 환경에 따라 설정 파일에서 다른 값을 지정하게 된다.
설정 값이라는 외부 자원에 의존하며, 그 자원에 따라 결과가 달라지게 된다. 따라서 static 으로 만들면 안되고 POJO Bean으로 만들어야 한다.
굳이 이를 static으로 만들고자 한다면 인자로 Key를 받으면 된다. 하지만 그렇게 하면 결국 설정값을 읽어서 공통 키로 저장하는 또 다른 프로젝트 전용 암호화 유틸리티가 필요해질 것이다. 비록 EncryptionUtils 자체는 키와 값을 모두 인자로 받는 static 함수 클래스로 만들더라도 공통 설정을 읽어서 매번 자동으로 인자를 EncryptionUtils의 함수 호출시에 지정해주는 클래스는 POJO Bean으로 만들어야 한다.

EmailUtils 라는게 있다고 하자. send(각종 인자) 라는 함수는 SMTP 서버 주소를 문자열로 받고 제목, 받는이, 보내는이, 내용 정보를 모두 인자로 받기 때문에 항상 메일 발송 결과가 동일하다. 그렇다면 static으로 만들어도 되겠지?
물론 아니니까 예로 든 것이다. 언뜻 보면 입력에 따른 결과가 항상 동일한 것 같지만 사실은 그렇지 않다.
SMTP 서버는 함수 호출시 그 상황을 명확하게 알 수 없다. 서버가 다운되면 비록 인자들이 동일하더라도 어떨 때는 메일 발송이 성공하고 어떨 때는 오류를 내게 된다. 즉, 외부 자원으로 인해 실행 결과의 일관성이 보장되지 않는다.

Spring Framework에 보면 DataSourceUtils 라는 것이 있다. 이 static 함수 모음 클래스는 DataSource라는 외부 리소스를 인자로 직접 받는다. 이는 외부 자원에 의존적인 것일까? 아니라고 본다. 외부 자원을 나타내는 객체 자체가 인자로 전달되면 외부 자원의 상태도 인자로 전달되는 것이라고 볼 수 있다. 따라서 DataSource가 불안정한 상태로 오면 불안정한 상태에 대해서는 일관성있게 반응하게 된다.
잠깐? 그렇다면 EmailUtils도 비슷한거 아닌가? SMTP 서버라는 외부 자원을 인자로 받지 않는가?

이것은 매우 다르다고 본다. SMTP 서버 정보를 문자열 인자로 받는다면 실제로 SMTP 서버라는 외부 자원에 대한 접속이 함수 안에서 이뤄지게 된다. 즉, 외부 의존이 static 함수 안에서 발생한다. 이 경우 SMTP 서버 자체에 대한 접속과 행위를 Smtp 라는 추상화한 클래스의 객체로 만들어서 EmailUtils에 인자로 넘긴다면 이 문제가 해결된다. 즉, 변화하는 외부 자원이 있고 그에 대한 모든 상태를 객체화해서 인자로 넘겨줄 경우에는 static 모음 클래스로 만들어도 좋다. 더 쉽게 예를 들면..
EmailUtils.send(String smtpServer, ....) 이런 형태는 안된다. 하지만 EmailUtils.send(Smtp smtp, ...) 이런 형태는 허용된다. EmailUtils의 함수 안에서는 절대로 SMTP에 접속하면 안된다. 모든 외부 자원에 대한 접속과 행위는 Smtp 클래스의 객체를 통해 이뤄져야 한다.

이정도를 기준으로 잡으면 대략적으로 무엇을 static 함수 모음 Class로 만들지 답은 나올 것 같다.

마지막으로 static 함수 모음 Class를 만들 때 내가 사용하는 방법 두 가지 정도를 소개한다.

  1. 프로젝트 전용 StringUtils 같은게 필요한 경우가 있다. 이 때 그냥 StringUtils라고 만들지 말고, Apache Commons나 Spring Framework 등에 존재하는 StringUtils 등을 상속해서 ProjectSpecificStringUtils를 만든다. 그리고서 원하는 기능을 하는 함수를 ProjectSpecificStringUtils에 추가하면 다른 프로그래머들은 일관성있게 ProjectSpecificStringUtils 만 사용하면 되게 된다. 이미 세상에는 대부분의 기능에 관한 유틸리티성 static 함수 모음 클래스들이 존재한다.
  2. static import 사용성을 높이려면 지나치게 보편적인 이름을 사용하는 것은 피하는게 낫다. 다시 말해 나는 StringUtils.contains() 보다는 StringUtils.strContains()를 선호한다.

덧글

  • devfuner 2015/07/01 17:44 # 삭제

    프로그래밍을 하는 방법에 대해서 좋은 참고가 되었습니다.
    개발언어는 공부해도 프로그래밍은 공부(참고)할 자료나 선배들이 부족한 상황에서 저에겐 소중한 자료가 되겠네요.
    감사합니다.
  • 지나가는개발자 2016/11/24 10:19 # 삭제

    잘 읽고 갑니다~ㅎ
※ 로그인 사용자만 덧글을 남길 수 있습니다.