개발자가 놓치기 쉬운 닷넷의 기본 원리 #1: 객체 지향의 구멍 static

‘개발자가 놓치기 쉬운 자바의 기본 원리’ 라는 글의 내용을 보고 같은 내용을 닷넷을 기준으로 서술한다면 재미있을 것 같다는 생각에 곧바로 글을 옮겨적어봅니다. 좋은 소스를 제공해주신 다음 커뮤니티본부 커뮤니티개발1팀의 전성호님께 감사드립니다. 이 글이 닷넷에 입문하시는 분들이나 기본적인 원리때문에 고민하셨던 분들께 좋은 역할을 해주기를 바랍니다.


출처: http://dna.daum.net/technote/java/PrincipleOfJavaInternalForDeveloperEasyToLost


요약: 개발자가 놓치기 쉬운 닷넷의 기본 원리에 대하여 기본적이긴 하지만 개발하면서 느끼고 경험한 내용을 정리하였다.


1. 객체 지향의 구멍 static


1.1 닷넷은 객체 지향 환경이다?


“닷넷은 자바와 마찬가지로 객체지향 언어이다” 라는 주장을 자주 접하게 된다. 만일 이것이 사실이라면 닷넷을 사용하는 한 “기존의 절차 지향 프로그래밍을 전혀 할 수 없을게 아닌가?” 라는 생각이 들지만 사실은 그렇지 않다. static을 이용하면 비 객체지향 언어처럼 코딩할 수 있다.


static method, static property, static event는 instance가 아닌 클래스에 속하는 method로, class method, class property, class event라고 부른다. 반대로 static이 아닌 method, property, event은 instance method, instance property, instance event라고 부른다.


static method, static property에는 this가 없다. instance method, property에는 숨겨진 파라미터로 this가 건네진다. (아래 “객체지향에 흔히 있는 오해” 참고) 하지만 static method, static property는 절차 지향의 함수와 동일하므로 숨겨진 파라미터 this는 없다. 그래서 static method, static property에서는 전달한 this가 없으므로 instance method, instance property를 호출하거나 instance field를 참조할 수 없는 것이다.


(참고) 객체지향에 흔히 있는 오해



  • 오해 1. “객체 지향에서는 객체 끼리 서로 메시지를 주고 받으며 동작한다.”라는 말을 듣고 다음과 같이 생각할 수 있다. “객체 지향에서는 객체가 각각 독립하여 움직인다는 것인가, 그러면 각 객체에 독립된 thread가 할당되어 있단 말인가?” 그렇지 않다. “메세지를 보낸다”라는 것은 단순히 각 객체의 함수 호출에 불과하다.
  • 덧. 메세지를 보낸다라는 개념을 같은 컴퓨터 내에서가 아닌 네트워크 상의 관점으로 확장을 하였을 때, 우리가 흔히 알고 있는 XML Web Service, DCOM, CORBA와 같은 Remote Procedure Call 기반 통신 기법으로 발전하게 되는 것이다.
  • 오해 2. “객체 지향에서는 method, property, event가 class에 부속되어 있다”는 말을 듣고 다음과 같이 생각할 수 있다. “그러면 instance별로 method의 실행 코드가 복제되고 있는 것이 아닌가?” 물론 이것도 오해다. method의 실행 코드는 종래의 함수와 동일한 어딘가 다른 곳 (CLR의 메모리 영역 내)에 존재하며 그 첫 번째 파라미터로 객체의 포인터 this가 건네질 뿐이다.
  • 오해 3. “그렇다면 각 instance가 method의 실행 코드를 통째로 갖고 있지 않는 것은 확실하지만, method의 실행 코드의 포인터는 각 instance별로 보관하고 있는 것이 아닌가?” 이것은 약간 애매한 오해이긴 하다. CLR 스펙에서는 JVM 스펙에서와 마찬가지로 별도의 메모리 영역 내에 실행 코드를 가지고 있는 상황에서 각 메서드 호출 시 Stack Frame이 생성되어 실행되고 실행 완료 시 복귀 주소를 전달한다.

1.2 전역 변수


static에서 public field는 전역 변수 (global variable, 글로벌 변수)이다. 여기서 “글로벌 변수는 왜 안되는가”에 대해 잠깐 생각해 본다. 우리는 흔히 “글로벌 변수는 될수있는한 사용하지 않는 것이 좋다”라고 한다. 그 이유는 글로벌 변수는 어디서든 참조할 수 있고 값을 변경할 수 있기 때문이다.


또한 파라미터나 리턴값으로 교환해야할 정보를 글로벌 변수를 경유(사용)하여 건네주면 함수의 역할이 불분명해지고 흐름도 애매해진다. 마지막 이유로는 “글로벌 변수는 하나밖에 없다”는 것이다. 이는 어디서 이 값을 변경했는지 알 수 없게 하는 지름길이고 실무에서도 간혹 발생하긴 하지만, 이 하나밖에 없는 변수가 버전업으로 두개가 필요하게 되었을 때 확장도 대형 프로젝트에서는 힘들어진다.


따라서 static에서 public은 readonly나 const를 붙여 상수나 읽기 전용 변수로 사용해야지 그 외의 용도는 자제하는 것이 좋을 것이다.


(참고) readonly 초기화에서의 주의점. 예를 들어 다음과 같은 코드를 보았을 때 우려되는 점은 무엇인가?


public static readonly System.Drawing.Color White = new System.Drawing.Color(255, 255, 255);

readonly 변수는 인라인 문장과 생성자 권역 내에서 한 번 초기화되면 변경이 불가능한데 object로 초기화할 경우 WHITE라는 필드가 변경될 수 없는 것이지 그것이 가리키는 객체는 아니라는 점이다.


과거 신규 서비스 개발시 readonly 변수 필드에 설정파일을 읽어 cache하는 singleton class의 특정 member를 이용하여 초기화할 경우 이 멤버값이 변경되면 readonly 변수의 값이 변경되었는데 프로그램에서는 이상한 짓을 하는 원인을 찾기가 상당히 어려웠던 경험을 하고난 후 부터 이런 코드는 냄새나는 코드로 여겨지게 되었다.


static은 글로벌 변수와 동일하므로 남발해서는 안된다. static을 사용할 경우 다음 두 가지는 최소한 기억한다.



  1. static field는 readonly의 경우와 달리 정말 “하나여도 되는지” 여부를 잘 생각해야 한다.
  2. static method는 주저하지 말고 쓰되 다음 두 가지의 경우 매우 활용적이다.
  3. 다른 많은 클래스에서 사용하는 Utility Method 군을 만드는 경우. (주로 Utility Class의 method)
  4. 클래스 안에서만 사용하는 “하청 메서드 (private method)”. 이유를 예를 들어 설명하면, 아래와 같은 조금은 과장된 클래스가 있다고 하자.

public class T
{
    private int a;
    private int b;
    private int c;

    private int Calc()
    {
        c = a + b;
        return c * c;
    }

    // ….other method or getter/setter…
}

위의 클래스 T의 경우 내부에서는 Calc라는 instance 함수를 사용하게 되면 c의 값이 매번 변하게 된다. 이는 무심코 하는 실수로 클래스 내에서 private method는 모든 멤버 instance 변수에 접근 가능하게 되면서 발생하게 된다. c의 값이 변하지 않기를 바랄 수 있다. 이 때 안전한 방법은 다음과 같이 Calc 하청 메서드를 static method로 수정하면 안전하다.


private static int Calc(int a, int b)
{
    int c = a + b;
    return c * c;
}

여기서 a와 b는 멤버 변수를 접근할 수 없어 전달해야 한다. (static에는 this가 없어 instance field를 참조할 수 없다는 것은 이미 위에서 설명했다.) 또한 c도 같은 이유로 사용할 수 없어 로컬 변수로 선언하여 사용하고 있다. 이럴 경우 메서드가 약간 커질 수 있지만 instance member 변수를 안전하게 사용할 수 있다는 장점이 있다. 이것은 static을 다시한번 생각하게 하는 좋은 예가 되었을 것이다.

댓글 남기기