언어·프레임워크/JavaScript

[JavaScript] 객체 유효성 검사로 Proxy 이해하기

DandyNow 2025. 4. 23. 09:54
728x90
반응형

객체 유효성 검사로 Proxy 이해하기

JavaScript로 개발할 때 객체 데이터의 무결성을 지키는 것은 중요하다. 예를 들어 사용자 정보 객체에서 나이는 양수여야 하고, 이름은 빈 문자열이 아니어야 하는 규칙이 있을 수 있다. 이런 유효성 검사 로직을 깔끔하게 구현하는 방법이 필요하다.

이 글에서는 JavaScript의 Proxy 객체를 사용하여 객체 유효성 검사를 구현하는 방법을 소개하고자 한다. Proxy를 이용하면 객체 접근을 중간에서 제어하여 유효성 검사 로직을 더 체계적이고 우아하게 작성할 수 있다.

1. JavaScript Proxy란 무엇인가?

Proxy 객체는 다른 객체(대상 target)를 감싸는 래퍼(wrapper)이다. 이 래퍼를 통해 대상 객체에 대한 기본적인 동작(속성 접근, 할당, 함수 호출 등)을 가로채고(intercept) 개발자가 정의한 방식으로 동작을 재정의할 수 있다. 즉, 객체와 상호작용하는 모든 과정을 중간에서 대리인(proxy)이 먼저 확인하고 처리하도록 만드는 것이다.

2. Proxy와 유효성 검사: set 핸들러 활용

객체의 속성에 특정 값을 할당하는 시점에 그 값이 유효한지 검사하고 싶을 때, Proxyset 핸들러(트랩)가 매우 유용하다. set 핸들러는 객체의 속성에 값을 할당하려고 시도할 때 자동으로 호출되는 함수이다.

set 핸들러는 다음 네 개의 인자를 받는다.

  1. target: 원본 대상 객체이다.
  2. property: 값을 할당하려는 속성의 이름이다.
  3. value: 할당하려는 새로운 값이다.
  4. receiver: 최초 작업이 수행된 객체 (대개 프록시 객체 자신)이다.

set 핸들러는 반드시 불리언 값을 반환해야 한다. true를 반환하면 할당이 성공적으로 처리되었음을 나타낸다. false를 반환하면 할당이 실패했음을 나타내며, 엄격 모드(strict mode)에서는 TypeError가 발생한다. 또는, 유효성 검사에 실패했을 때 직접 에러를 발생시킬 수도 있다.

3. 예제: 사용자 정보 유효성 검사 (switch 사용)

이제 사용자 정보 객체(name, age)에 대해 Proxy를 이용한 유효성 검사를 구현한다. name은 비어 있지 않은 문자열이어야 하고, age는 0 이상 150 미만의 정수여야 한다는 규칙을 switch 문을 사용하여 적용해 본다.

// 1. 유효성 검사 핸들러 정의이다.
const userValidator = {
  set(target, property, value) {
    // console.log는 설명을 위해 유지한다. 실제 코드에서는 필요에 따라 제거하거나 로깅 라이브러리를 사용한다.
    console.log(`[Proxy] 속성 '${property}'에 값 '${value}' 할당 시도.`);

    // 속성 이름(property)에 따라 유효성 검사를 분기한다.
    switch (property) {
      case 'name':
        if (typeof value !== 'string' || value.trim() === '') {
          // 유효하지 않으면 에러를 발생시킨다.
          throw new Error('유효성 오류: 이름(name)은 비어 있지 않은 문자열이어야 한다.');
        }
        console.log(`[Proxy] 이름(name) 유효성 검사 통과.`);
        break; // switch 문을 탈출한다.

      case 'age':
        if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value >= 150) {
          // 유효하지 않으면 에러를 발생시킨다.
          throw new Error('유효성 오류: 나이(age)는 0 이상 150 미만의 정수여야 한다.');
        }
        console.log(`[Proxy] 나이(age) 유효성 검사 통과.`);
        break; // switch 문을 탈출한다.

      default:
        // 'name', 'age' 외 다른 속성은 별도 검사 없이 통과시킨다.
        console.log(`[Proxy] '${property}' 속성은 별도 유효성 검사 없다.`);
        break;
    }

    // 모든 유효성 검사를 통과했거나 해당 없으면 실제 값 할당을 한다.
    // Reflect.set을 사용하는 것이 권장된다.
    console.log(`[Proxy] '${property}' 속성에 값 할당 완료.`);
    return Reflect.set(target, property, value); // 성공 시 true를 반환한다.
  }
};

// 2. 원본 데이터 객체이다.
const user = {
  name: '김개발',
  age: 28
};

// 3. Proxy 객체 생성이다.
const validatedUser = new Proxy(user, userValidator);

// 4. Proxy를 통한 사용 테스트이다.
console.log("--- 유효한 할당 테스트 ---");
try {
  validatedUser.name = '이코드'; // 성공이다.
  validatedUser.age = 35;      // 성공이다.
  validatedUser.email = 'lee.code@example.com'; // 'email'은 검사 규칙 없으므로 성공이다.
  console.log("현재 validatedUser:", validatedUser);
  console.log("현재 원본 user:", user); // 원본 객체도 변경된다.
} catch (e) {
  console.error(e.message);
}

console.log("\n--- 유효하지 않은 할당 테스트 ---");
try {
  console.log("\n테스트: 빈 이름 할당 시도.");
  validatedUser.name = '   '; // 공백 문자열 -> 에러 발생이다.
} catch (e) {
  console.error(e.message);
}

try {
  console.log("\n테스트: 음수 나이 할당 시도.");
  validatedUser.age = -10; // 음수 -> 에러 발생이다.
} catch (e) {
  console.error(e.message);
}

try {
  console.log("\n테스트: 너무 큰 나이 할당 시도.");
  validatedUser.age = 150; // 150 이상 -> 에러 발생이다.
} catch (e) {
  console.error(e.message);
}

console.log("\n--- 에러 발생 후 객체 상태 확인 ---");
// 에러가 발생한 할당은 적용되지 않는다.
console.log("최종 validatedUser:", validatedUser);

4. 코드 해설

  • userValidator 객체: 이것은 Proxy의 핸들러 객체이다. 핵심 로직은 set 메서드 안에 있다.
  • set 메서드: 속성 할당 시도가 있을 때마다 호출된다.
    • switch (property): 할당하려는 속성 이름(property)에 따라 검사 로직을 분기한다.
    • case 'name', case 'age': 각 속성에 맞는 유효성 검사를 수행한다. 조건에 맞지 않으면 throw new Error()를 통해 즉시 에러를 발생시키고 실행을 중단한다.
    • default: name이나 age가 아닌 다른 속성이 들어오면 별다른 검사 없이 통과시킨다.
    • Reflect.set(target, property, value): 모든 검사를 통과하면 Reflect.set을 사용하여 원본 객체(target)에 안전하게 값을 할당한다. 이 메서드는 할당 성공 여부를 불리언 값으로 반환하며, Proxyset 핸들러에서 이 반환 값을 그대로 사용하는 것이 좋다.
  • user 객체: 유효성 검사의 대상이 되는 평범한 JavaScript 객체이다.
  • validatedUser: new Proxy(user, userValidator)를 통해 생성된 프록시 객체이다. 앞으로 validatedUser를 통해 user 객체에 접근하면 userValidator의 규칙이 적용된다.
  • 테스트 코드: validatedUser에 유효한 값과 유효하지 않은 값을 할당해보며 프록시가 잘 작동하는지, 에러 처리가 올바른지 확인한다. 유효하지 않은 값 할당 시 try...catch를 사용하여 에러 메시지를 출력한다.

5. 결론

일반적인 데이터 구조 및 타입 검증에는 스키마 라이브러리(Zod 등)가 더 보편적으로 사용된다. Proxy는 런타임 중 객체와의 상호작용 시점에 유효성 규칙을 동적으로 적용해야 하는 특정 요구사항이 있을 때 강력한 도구가 된다. Proxy를 사용하면 객체의 속성 할당과 같은 기본적인 동작에 개입하여 유효성 검사 로직을 객체 자체의 코드와 분리하여 더 깔끔하고 중앙 집중적으로 관리할 수 있다. 이는 코드의 유지보수성을 높이고 데이터의 무결성을 보장하는 데 큰 도움이 된다.

Proxy에는 set 외에도 get(속성 읽기), has(in 연산자), deleteProperty(속성 삭제) 등 다양한 동작을 가로챌 수 있는 핸들러들이 있다. 필요에 따라 다른 핸들러들도 활용하여 더욱 강력한 객체 제어를 구현해 보기 바란다.

728x90
반응형