Articlestack.convex.dev·2026년 7월 8일·0

When to and when not to use return validators

Quick Summary

Convex의 return validator는 유용하지만 모든 쿼리·뮤테이션에 기계적으로 붙일 도구가 아니라, 정확한 런타임 반환 계약이 필요할 때 선택적으로 써야 한다는 지침 변경이다.

When to and when not to use return validators 관련 대표 이미지

🖼️ 인포그래픽

When to and when not to use return validators 내용을 설명하는 본문 이미지

🖼️ 4컷 인포그래픽

When to and when not to use return validators 내용을 설명하는 본문 이미지

💡 한 줄 요약

Convex의 return validator는 유용하지만 모든 쿼리·뮤테이션에 기계적으로 붙일 도구가 아니라, 정확한 런타임 반환 계약이 필요할 때 선택적으로 써야 한다는 지침 변경이다.

📌 핵심 요약

  • 저자는 Convex AI rules에서 LLM에게 항상 returns validator를 추가하라고 권장해 왔지만, 이제 그 “항상”이라는 규칙을 철회한다고 설명한다. return validator 자체가 나쁜 것이 아니라, 무조건 적용할 때 실제 코드베이스와 에이전트형 개발 흐름에서 비용이 커지기 때문이다.
  • return validator는 Convex 함수의 반환값 형태를 선언하고 런타임에 검사하는 장치다. 특히 object validator는 추가 속성을 허용하지 않기 때문에, 예상하지 않은 필드가 반환되면 조용히 통과시키지 않고 런타임 오류를 낸다.
  • 처음에 항상 사용하라고 한 이유는 주로 TypeScript의 순환 타입 문제와 코드 생성 환경의 어려움을 완화하려는 목적이었다. 그러나 실제로는 반환 타입 추론 의존을 줄여도 도움이 되는 상황이 제한적이어서 보편 규칙으로 삼기에는 부작용이 컸다.
  • 무조건적인 return validator 사용은 인라인 스키마 복붙, 리팩터링 부담, 토큰 낭비, LLM의 필드 환각·누락·잘못된 validator 선택 가능성을 키운다. 시스템 필드인 _id와 _creationTime까지 반복 선언하게 되는 중복 문제도 생긴다.
  • 저자의 새 결론은 기본적으로 TypeScript 타입과 추론을 선호하되, Convex가 정확한 런타임 계약을 강제해야 하는 경우에는 returns를 사용하라는 것이다. 컴포넌트 코드 생성, static codegen, OpenAPI 생성, any 또는 검증되지 않은 외부 데이터가 섞이는 경로가 대표적인 적용 대상이다.

🧩 주요 포인트

  1. 저자는 Convex AI rules에서 LLM에게 항상 returns validator를 추가하라고 권장해 왔지만, 이제 그 “항상”이라는 규칙을 철회한다고 설명한다. return validator 자체가 나쁜 것이 아니라, 무조건 적용할 때 실제 코드베이스와 에이전트형 개발 흐름에서 비용이 커지기 때문이다.
  2. return validator는 Convex 함수의 반환값 형태를 선언하고 런타임에 검사하는 장치다. 특히 object validator는 추가 속성을 허용하지 않기 때문에, 예상하지 않은 필드가 반환되면 조용히 통과시키지 않고 런타임 오류를 낸다.
  3. 처음에 항상 사용하라고 한 이유는 주로 TypeScript의 순환 타입 문제와 코드 생성 환경의 어려움을 완화하려는 목적이었다. 그러나 실제로는 반환 타입 추론 의존을 줄여도 도움이 되는 상황이 제한적이어서 보편 규칙으로 삼기에는 부작용이 컸다.
  4. 무조건적인 return validator 사용은 인라인 스키마 복붙, 리팩터링 부담, 토큰 낭비, LLM의 필드 환각·누락·잘못된 validator 선택 가능성을 키운다. 시스템 필드인 _id와 _creationTime까지 반복 선언하게 되는 중복 문제도 생긴다.
  5. 저자의 새 결론은 기본적으로 TypeScript 타입과 추론을 선호하되, Convex가 정확한 런타임 계약을 강제해야 하는 경우에는 returns를 사용하라는 것이다. 컴포넌트 코드 생성, static codegen, OpenAPI 생성, any 또는 검증되지 않은 외부 데이터가 섞이는 경로가 대표적인 적용 대상이다.

🧠 상세 정리

1. 지침 변경의 핵심

글은 Convex 쿼리와 뮤테이션에 return validator를 항상 추가하라고 하던 기존 권장사항을 수정하는 데서 출발한다. 저자는 이전 조언을 따랐다고 해서 잘못한 것은 아니지만, “항상”이라는 단어가 실제 개발에서 문제를 만든다고 본다. 즉 return validator가 나쁜 도구라는 뜻이 아니라, 모든 함수에 일괄 적용할 만큼 비용 없는 기본값은 아니라는 판단이다. 새 지침은 더 세밀하다. 기본적으로는 TypeScript 타입과 추론을 활용하고, Convex가 반환값의 정확한 런타임 계약을 강제해야 할 때만 returns를 쓰라는 방향이다.

2. return validator가 하는 일

Convex에서는 args로 함수에 들어오는 인자를 검증하고, returns로 함수에서 나가는 반환값을 검증할 수 있다. return validator는 반환값의 모양을 선언해 두면 Convex가 런타임에 실제 반환값이 그 선언과 맞는지 확인하는 기능이다. 예시의 getUserPreview 함수처럼 name 문자열만 반환하도록 선언하면, 그 형태와 맞지 않는 값은 오류가 된다. 특히 중요한 점은 object validator가 “정확한” 객체 형태를 요구한다는 것이다. 선언되지 않은 추가 속성이 반환되면 허용되지 않고 런타임 검증 실패가 발생하므로, 예상 밖 데이터를 조용히 내보내는 일을 막을 수 있다.

3. 처음에 항상 권장했던 이유

초기 권장의 동기는 런타임 정확성 자체보다 TypeScript와 코드 생성 환경에서 겪는 타입 문제에 가까웠다. Convex 프로젝트에서는 함수가 생성된 api나 internal 객체를 참조하고, 그 참조가 다시 생성 타입의 일부가 되면서 순환 타입 문제가 생길 수 있다. 이런 상황에서는 타입이 타입을 계속 참조하다가 TypeScript가 처리하지 못하는 경우가 발생한다. 그래서 LLM이 항상 return validator를 선언하면 추론 반환 타입에 덜 의존하게 되어 순환을 끊는 데 도움이 될 것이라는 기대가 있었다. 하지만 실제로는 특정 조건에서만 유효했고, 보편적 해결책으로 쓰기에는 과한 규칙이었다.

4. 무조건 사용이 만든 유지보수 비용

실제 코드베이스에서 “항상 추가” 규칙은 먼저 장황함을 만든다. 모델이 validator를 재사용하기보다 반환 형태를 함수 안에 인라인으로 복붙하는 패턴이 흔하기 때문이다. 그러면 같은 형태가 여러 함수에 거의 비슷하게 흩어지고, 스키마가 바뀔 때 한 곳만 고치는 일이 아니라 여러 validator를 추적해 수정하는 일이 된다. 컴파일 오류를 고친 뒤에도 런타임 검증 오류를 다시 따라가야 할 수 있어 리팩터링 비용이 커진다. 저자는 이것을 개발자가 기대하는 즐거운 타입 안정성이 아니라, 거의 같지만 조금씩 다른 선언들을 계속 관리해야 하는 부담으로 본다.

5. AI 워크플로에서의 토큰 낭비와 환각 위험

에이전트형 개발 흐름에서는 장황한 return validator가 단순한 미관 문제가 아니라 모델 성능과 반복 속도에 영향을 주는 비용이 된다. 모델은 코드를 읽고 다시 읽으며 다단계 변경을 계획해야 하므로, 매번 반복되는 수백 토큰의 validator는 작업 기억을 압박한다. 이런 장황함은 느린 반복, 필드 누락, “아차, 이 필드를 잊었다” 같은 실수로 이어질 수 있다. 또한 모델에게 스키마를 validator로 재현하라고 요구하면 존재하지 않는 필드를 만들거나, 필요한 필드를 빠뜨리거나, 잘못된 validator 타입을 고를 가능성도 커진다. TypeScript가 일부를 잡아주더라도, 애초에 그런 문제를 덜 만들도록 하는 편이 더 빠르다는 주장이다.

6. 정확한 런타임 계약이 필요한 경우

그럼에도 return validator의 가치는 분명하다. TypeScript는 구조적 타입 시스템이라 진짜 의미의 exact type을 제공하지 않으며, 특히 any가 끼거나 타입이 없는 외부 API 데이터를 다룰 때 선언보다 많은 필드를 반환할 수 있다. 글의 예시처럼 User를 반환한다고 해도 email 같은 추가 필드가 섞여 나갈 수 있고, return validator는 이를 런타임 오류로 막아 의도치 않은 데이터 노출을 줄인다. 따라서 컴포넌트 코드 생성처럼 추론이 불가능한 경우, static codegen에서 반환 validator가 없으면 v.any()로 떨어지는 경우, OpenAPI 생성처럼 서버 계약을 명확히 하고 싶은 경우에는 적합하다. 외부 API 데이터는 가능하면 가져온 직후 경계에서 검증하는 편이 좋지만, 반환 단계에서도 정확성을 한 번 더 강제하고 싶다면 returns 사용이 타당하다.

🧾 핵심 주장 / 시사점

  • return validator의 핵심 가치는 타입 주석을 반복하는 데 있지 않고, TypeScript가 보장하지 못하는 “추가 필드 없음”을 런타임에서 강제하는 데 있다.
  • LLM이 코드를 작성하는 환경에서는 장황한 명시성이 항상 안전으로 이어지지 않는다. 반복된 validator는 오히려 토큰 비용, 스키마 불일치, 환각 가능성을 높이는 입력이 될 수 있다.
  • 좋은 기본값은 “항상 명시”가 아니라 “필요한 계약만 명시”에 가깝다. 일반 경로는 TypeScript 추론과 helper 타입으로 단순하게 유지하고, 외부 데이터·any·코드 생성·OpenAPI처럼 경계가 중요한 지점에 runtime validation을 집중하는 편이 더 균형적이다.

✅ 액션 아이템

  • Convex 설계 원칙을 TypeScript 타입 추론 중심으로 유지하되, 정확한 런타임 반환 계약이 필요한 함수에만 returns validator를 적용한다.
  • 적용 대상은 컴포넌트 코드 생성, static codegen, OpenAPI 출력, any 또는 검증되지 않은 외부 데이터 유입 경로로 한정해 범위를 정한다.
  • object validator의 strict한 필드 검사 때문에 _id·_creationTime 중복 선언이 늘어나는 지점을 찾아 스키마 재사용 패턴으로 정비한다.

❓ 열린 질문

  • 무조건 적용을 중단했을 때 기존에 필요한 반환 안전성이 사라질 위험은 어느 지점에서 드러나는가?
  • returns validator를 꼭 써야 하는 경계는 어떤 함수군에서 어떻게 구분해 문서로 고정할 수 있는가?
  • 예상치 못한 필드 반환·환각을 줄이면서도 개발 생산성 손실을 막는 대안은 어떤 방식이 가능한가?

관련 문서

공통 태그와 주제 흐름을 기준으로 같이 보면 좋은 문서를 이어서 제안합니다.