프로그래밍/javascript

javascript setter/getter naming convention

콘파냐 2022. 4. 12. 12:46

기본적인 자바스크립트 객체의 속성에 접근은 아무런 제한없이 이루어지면 속성의 변환도 자유롭다.
이런 자유로운 접근은 간혹 잘못된 입력을하는 등의 실수를 초래한다.
어떤 객체의 속성에 넣을 값의 범위를 알거나 제한할 필요가 있다면 속성에 직접 접근은 제한하여 최대한 안전하게 속성값을 사용하도록 만드는 것이 중요한다.
이런 역할을 하는 것이 setter/getter이다.
setter/getter는 대부분의 언어들이 문법적으로 지원하며 사용방법도 비슷한다.
이렇게 setter/getter를 사용하면 속성을 비교적 안전하게 사용하도록 하고 좀 더 정교한 추가적인 처리를 할 수 있다.
이 글에서는 setter/getter를 사용하는 핵심을 객체 리터럴클래스 방식 두가지로 설명한다.
먼저 property를 가진 객체를 생각해보자.

javascript 객체 생성 2가지 방법

// 객체 리터럴
console.log('객체 리터럴')
const obj = {
    count:0,
}

console.log(obj.count); //0

console.log("----------")

// 클래스
console.log('클래스')
class Cobj {
    constructor() {
        this.count = 0
    }
}

const obj1 = new Cobj()
console.log(obj1.count) //0
  • 결과
방법1
0
----------
방법2
0

이 코드에 setter/getter를 추가해보자.

// 객체 리터럴
console.log('객체 리터럴')
const obj = {
    count:0,
    /* getter / setter 추가 */
    get count() {
        return this.count
    },
    set count(count) {
        if (count < 0) {
            console.log('not allow negative value');
        } else {
            this.count = count
        }
    }

}

console.log(obj.count); 

console.log("----------")

// 클래스
console.log('클래스')
class Cobj {
    constructor() {
        this.count = 0
    }
    /* getter / setter 추가 */
    get count() {
        return this.count
    }
    set count(count) {
        if (count < 0) {
            console.log('not allow negative value');
        } else {
            this.count = count
        }
    }
}

const obj1 = new Cobj()
console.log(obj1.count) 

위 코드는 기존 코드에 아무런 수정없이 getter/setter를 추가하였다. 그런데 기존 count 속성과 같은 이름은 setter/getter를 추가한 것이 문제가 된다.
이 코드를 실행하면 문제가 발생한다.

  • 결과
...
RangeError: Maximum call stack size exceeded
...

recursive 호출이 일어난다. 말 그대로 스택 오버플로우다. 해설하면 obj.countcount는 애초에 몇 개인지를 기록하는 number 형 속성으로 사용했어야 한다. 그런데 gettersetter 함수를 같은 이름인 count로 한 것 때문에 문제가 발생한 것아다.
그런데 gettersetter는 외부에서 접근할 이름이므로 count로 이름 짓는 것이 상식상 자연스럽기 때문에 이렇게 한 것이다. setter/getter의 이름은 외부에서 사용할 때 애초에 의도한 number 속성인 count인 것 처럼 보이도록하는 것이 좋기 때문이다.
이런 경우 외부에 보여지는 getter/setter의 이름은 count로 이름 짓고 실제 number 속성인 count_count와 같이 언더바를 붙여 준다. 일종의 naming convention으로 봐도 좋으며 대부분의 다른 언어들에서도 각각의 네이밍 컨벤션을 가지고 있다.
다음 코드는 네이밍 컨벤션에 맞게 실제 number 속성인 count_count로 변경한 코드다.

네이밍 컨벤션을 사용하여 stack size 에러 해결

// 객체 리터럴
console.log('객체 리터럴')
const obj = {
    _count:0,
    /* getter / setter 추가 */
    get count() {
        return this._count
    },
    set count(value) {
        if (value < 0) {
            console.log('not allow negative value');
        } else {
            this._count = value
        }
    }

}

console.log(obj.count); //0
obj.count = 33
console.log(obj.count); //0

console.log("----------")

// 클래스
console.log('클래스')
class Cobj {
    constructor() {
        this._count = 0
    }
    /* getter / setter 추가 */
    get count() {
        return this._count
    }
    set count(value) {
        if (value < 0) {
            console.log('not allow negative value');
        } else {
            this._count = value
        }
    }
}

const obj1 = new Cobj()
console.log(obj1.count) //0
obj1.count = 33
console.log(obj1.count) //33

결과

객체 리터럴
0
33
----------
클래스
0
33

다시 setter/getter의 사용 의미를 곰곰히 생각해보면, 대상이 되는 속성에 직접 접근을 막고 제한을 두기 위함이다.
그럼에도 위 코드에서

obj._count = 999; 

와 같은 코드도 _count의 값에 접근, 변경시킬 수 있다.
애초에 본래의 목적을 이루기 위헤서는 _count에 접근을 막아야 한다.
언어적인 한계로 인해 python, 과 javascript와 같은 언어는 이를 네이밍 컨벤션을 통해서 어느정도 해결하는 것이다.
또한 자바스크림트에서 이를 막기 위한 최소한의 노력은 typescript를 사용하는 것이다. constructor 에서 _count를 private로 선언하고 외부 접근 시 문법 에러를 알려주는 것이다. 물론 타입스크립트는 자바스크립트에 타입 가이드를 입힌 형태일 뿐 결국 나중에 생성되는 것은 자바스크립트이므로 이런 접근을 언어적 차원에서 막는 것은 아니다.

클래스 객체 생성 방식 코드를 더 자연스럽게 수정해 보기.

이 방식은 javascriptpython과 같이 동적으로 객체에 속성을 추가할 수 있는 언어에서 유효한 방식이다.
앞서 코드는 네이밍 컨벤션에 의해 _count라는 속성을 객체 내부에 직접 정의했다.
이 방식을 내부에서도 setter/getter를 사용하여 정의할 것이다.

// 클래스
console.log('클래스')
class Cobj {
    constructor() {
        this.count = 0
    }
    /* getter / setter 추가 */
    get count() {
        return this._count
    }
    set count(value) {
        if (value <= 0) {
            console.log('not allow negative value');
        } else {
            this._count = value
        }
    }
}

const obj1 = new Cobj()
console.log(obj1.count) //1
obj1.count = 33
console.log(obj1.count) //33

obj1._count=999;
console.log(obj1._count) // 999

이전 코드에서 변한 부분은 객체 내부에서 count 속성을 초기화 하는 부분이다.

class Cobj {
    constructor() {
        this.count = 0 // 기존 this._count = 0;
    }
  • 결과
    클래스
    1
    33
    999

이렇게 하면 this.countcountsetter를 사용하게 된다. 그리고 setter 내부에서 _count를 동적으로 생성하는 것이다. 좀 더 세련된 코드로 변경된 것 같다.

객체 리터럴 방식의 count 초기화 문제

주의할 점은 객체 리터럴 방식의 경우 앞서 방식으로 사용하지 말아야 한다. 초가화 동작 방식이 다르다. 해보면 알겠지만 클래스의 경우처럼 초기화가 이루어지지 않는다.

const obj = {
  count: 1 // setter를 경우하지 않고 속성 count가 1로 초기화
  ...
  get count() {
    ...
  }
  set count(value) {
    ...
  }
  • 결과
    객체 리터럴
    undefined
    33
    777
    객체 리터럴로 객체를 초기화 하면 속성 countsetter/getter count 가 따로 객체에 존재하게 된다. 외부에서는 getter count가 있으므로 초기화된 속성값 count에 접근 할 수 없다.
    초기화 방식의 차이는 있으나 초기화 후의 동작은 클래스와 동일하게 동작한다. 따라서 객체 리터럴 방식을 사용할 때는 객체 속성을 _count와 같은 형식으로 쓰는 것이 좋다.
    또한, 이 두 방식이 헷갈린다면 객체 속성은 _count로 통일하여 언더바를 붙여 사용하자.
반응형