Featured Post

AI 바이브 코딩 시대, Zod 하나쯤은 꼭 챙겨둬야 하는 이유

Zod - 스키마 검증 관련 이미지

AI가 짜준 코드, 편하긴 한데… 그냥 믿고 쓰기엔 좀 찜찜하죠

요즘 개발하다 보면 솔직히 AI 바이브 코딩 없이 일하는 게 오히려 어색할 때가 있어요. 저도 개발자로 밥 먹고 산 지 20년쯤 됐는데, 요즘은 LLM한테 “이 API 응답 TypeScript로 파싱하는 코드 만들어줘” 하고 던지면 진짜 금방 뚝딱 나오잖아요. 예전 같으면 문서 뒤지고, 샘플 응답 맞춰보고, 타입 만들고, 예외 케이스 생각하고… 은근히 손이 많이 갔는데 말이죠.

근데요. 편한 거랑 믿을 수 있는 건 또 다른 문제더라고요.

AI가 만들어준 코드를 보고 있으면 “오, 제법인데?” 싶은 순간이 많아요. 그런데 막상 실제 서비스 코드에 넣으려고 하면 마음 한구석이 살짝 불편합니다. 뭐랄까, 겉보기엔 멀쩡한데 실제 데이터가 들어왔을 때도 정말 괜찮을까 싶은 느낌? 특히 외부 API 응답이나 사용자 입력값처럼 내 손을 떠난 데이터라면 더 그렇고요.

사실 저도 처음에는 이런 생각을 했어요.

“TypeScript 쓰고 있는데 굳이 런타임 검증까지 해야 하나?”

“AI가 타입까지 잘 만들어주는데 Zod까지 쓰면 좀 과한 거 아닌가?”

근데 프로젝트 몇 번 굴려보니까, 그 생각이 꽤 위험하다는 걸 금방 알게 됐습니다. TypeScript는 어디까지나 개발할 때 도와주는 친구지, 실제 런타임에 들어오는 데이터까지 책임져주진 않거든요. 서버에서 갑자기 필드명이 바뀌거나, 문서에는 string이라고 되어 있는데 null이 들어오거나, 배열이라고 믿었는데 객체 하나가 툭 들어오는 상황… 이런 거 생각보다 자주 생깁니다. 진짜로요.

예를 들어 AI가 유저 정보를 받아오는 코드를 만들어줬다고 해볼게요. 응답 타입은 대충 이렇게 가정했겠죠. name은 string, email도 string, age는 number. 그런데 어느 날 백엔드에서 age에 null을 보내기 시작합니다. 아니면 테스트 계정에는 email이 빠져 있습니다. 그러면 프론트에서는 별생각 없이 user.email.toLowerCase() 같은 코드를 실행하다가 그대로 뻗는 거예요. 에러 로그 보면 한숨 나오죠. “아… 이걸 왜 내가 못 막았지?” 하고요.

바로 이런 지점에서 Zod가 되게 든든한 안전망이 됩니다.

Zod는 TypeScript랑 궁합이 좋은 스키마 검증 라이브러리예요. 쉽게 말하면 “이 데이터는 이런 모양이어야 해”라고 미리 정의해두고, 실제 런타임에서 그 모양이 맞는지 확인해주는 도구입니다. AI가 코드를 빠르게 만들어주는 시대일수록 이런 검증 레이어가 더 중요해져요. AI는 그럴듯한 코드를 정말 잘 만들지만, 현실의 데이터는 생각보다 예의 바르지 않거든요.

저는 요즘 AI한테 코드를 맡길 때 이런 식으로 생각합니다. AI는 속도를 내는 역할, Zod는 안전벨트 채워주는 역할. 자동차로 치면 AI가 액셀이고, Zod는 브레이크이자 에어백 같은 느낌이에요. 액셀만 밟으면 빠르긴 한데, 언젠가 벽에 박습니다. 좀 과격한 표현이지만, 현업에서는 진짜 그런 일이 생기더라고요.

AI한테 Zod 코드를 시킬 때는 길게 설명하지 않아도 됩니다

AI 프롬프트를 작성하다 보면 자꾸 설명이 길어질 때가 있어요. 저도 처음에는 그랬습니다. “이 API는 어떤 서비스에서 쓰고, 어떤 페이지에서 호출되고, 응답은 대략 이런 의미고…” 이렇게 배경 설명을 잔뜩 넣었죠. 물론 필요한 경우도 있지만, Zod 스키마 하나 만들 때는 그렇게까지 장황할 필요가 없더라고요.

LLM을 쓰다 보면 결국 토큰 절약도 실력입니다. 특히 여러 번 질문하고 수정하면서 개발할 때는 토큰이 생각보다 빨리 녹아요. “아니 방금 충전했는데 벌써?” 싶은 순간이 옵니다. 그래서 저는 Zod 관련 요청을 할 때 최대한 짧고 명확하게 던지는 편이에요.

예를 들면 이런 식입니다.

TypeScript용 Zod 스키마 작성. UserProfile 응답: name(string), age(number optional), email(string email), createdAt(string datetime). z.infer 타입도 함께.

이 정도면 충분합니다. 굳이 “당신은 뛰어난 TypeScript 개발자입니다” 같은 말도 매번 넣을 필요 없고, “아름답고 유지보수 가능한 코드로 작성해주세요” 같은 문장도 상황에 따라선 그냥 토큰 낭비일 때가 많아요. 물론 코드 스타일이 중요한 팀이라면 별도로 규칙을 줘야겠지만, 단순 스키마 생성에서는 필드와 제약 조건이 핵심입니다.

제가 자주 쓰는 방식은 필드를 최대한 구조적으로 적는 거예요.

    • 스키마 이름: UserProfileSchema
    • 필드: name string min 1, email string email, age number optional
    • 출력: Zod schema, z.infer type, parse 예시
    • 주의: 설명 짧게, 코드 위주

이렇게 던지면 AI도 헛소리를 덜 합니다. 신기하게도 프롬프트가 짧아질수록 결과가 더 깔끔해지는 경우가 있어요. 물론 짧기만 하면 안 되고, 필요한 정보가 딱 들어가야 합니다. 짧고 흐릿한 프롬프트는 별로고, 짧고 선명한 프롬프트가 좋습니다.

예전에 제가 실제로 겪은 일인데요. 한 번은 중첩 객체가 많은 API 응답을 검증하려고 AI한테 “복잡한 주문 데이터 Zod 스키마 만들어줘”라고 너무 대충 던졌어요. 그랬더니 AI가 주문, 배송, 결제, 쿠폰, 주소, 사용자 정보까지 자기 마음대로 상상해서 엄청 긴 코드를 만들어주더라고요. 보고 나서 “아… 얘도 모르면 지어내는구나” 싶었습니다. 그 뒤로는 필드 목록을 제가 먼저 정리하고, “없는 필드는 만들지 말 것”이라는 문장을 꼭 붙입니다. 이거 은근히 효과 좋아요.

제가 자주 쓰는 Zod 프롬프트 문장들

요즘은 아예 손에 익은 문장들이 몇 개 있습니다. 필요할 때 복붙해서 쓰면 편해요. 거창한 건 아니고, 그냥 현업에서 덜 피곤해지는 문장들입니다.

    • 아래 JSON 응답을 기준으로 Zod 스키마를 만들어줘. 없는 필드는 추가하지 마.
    • TypeScript에서 사용할 수 있게 z.infer 타입도 같이 만들어줘.
    • nullable과 optional을 구분해서 처리해줘.
    • 외부 API 응답 검증용이니 parse 대신 safeParse 예시를 보여줘.
    • 설명은 짧게 하고 코드 위주로 작성해줘.
    • 중첩 객체는 별도 Schema로 분리해줘.
    • transform은 필요한 경우에만 사용하고, 원본 데이터 검증을 우선해줘.

여기서 특히 중요한 건 nullable과 optional을 구분하라는 말입니다. 이거 정말 자주 틀려요. optional은 필드가 아예 없을 수 있는 거고, nullable은 필드는 있는데 값이 null일 수 있는 거잖아요. AI가 이걸 대충 섞어버리면 나중에 실제 데이터에서 묘하게 문제가 생깁니다. 버그도 아주 얄밉게 생겨요. 바로 터지면 차라리 고치기 쉬운데, 특정 계정이나 특정 날짜 데이터에서만 터지는 식으로요.

커스텀 인스트럭션에 Zod 규칙을 박아두면 은근히 삶이 편해집니다

AI 바이브 코딩을 자주 한다면, 매번 같은 말을 프롬프트에 쓰는 것도 귀찮습니다. 귀찮은 건 자동화해야죠. 개발자는 원래 귀찮음을 견디지 못해서 도구를 만드는 사람들이니까요.

저는 개인적으로 AI 도구의 커스텀 인스트럭션이나 프로젝트 설정에 Zod 관련 규칙을 미리 넣어두는 걸 추천합니다. 특히 TypeScript 기반으로 개발하는 분이라면 꽤 체감이 큽니다.

제가 넣어두는 문장은 대충 이런 느낌이에요.

TypeScript 코드 생성 시 외부 API 응답, 사용자 입력값, 환경 변수처럼 런타임에 들어오는 데이터는 Zod 스키마를 먼저 정의하고 검증한 뒤 사용한다. 타입은 z.infer로 추론한다. nullable과 optional을 명확히 구분한다.

이거 하나 넣어두면 AI가 코드를 만들 때 습관처럼 Zod 스키마를 먼저 제안하는 경우가 많아집니다. 물론 항상 완벽하진 않아요. AI가 만든 스키마도 사람이 봐야 합니다. 그래도 출발점이 달라져요. 예전에는 제가 “여기 검증 빠졌네”, “여기 null 들어오면 죽겠네”, “이 타입은 런타임에서 보장 안 되는데?” 이런 잔소리를 계속해야 했다면, 이제는 기본 뼈대가 어느 정도 갖춰진 상태에서 리뷰를 시작하게 됩니다.

솔직히 이 차이가 큽니다. 코드 리뷰할 때 에너지가 덜 빨려요.

특히 팀에서 여러 명이 AI를 쓰는 상황이라면 이런 규칙은 더 중요합니다. 사람마다 AI를 쓰는 방식이 다르거든요. 어떤 사람은 타입만 만들고 끝내고, 어떤 사람은 try-catch를 잔뜩 감싸고, 어떤 사람은 그냥 any로 밀고 갑니다. 그러다 보면 코드베이스가 금방 들쭉날쭉해져요. 그럴 때 “외부 데이터는 Zod로 검증한다”는 간단한 룰 하나만 있어도 팀의 코드 품질이 꽤 안정됩니다.

parse와 safeParse, 이 둘은 상황에 맞게 골라 쓰는 게 좋습니다

Zod를 쓸 때 parse()safeParse()를 자주 보게 됩니다. 둘 다 검증을 해주지만 느낌이 좀 달라요.

parse()는 데이터가 스키마와 맞지 않으면 바로 에러를 던집니다. 그래서 “여기서 데이터가 틀리면 그냥 실패하는 게 맞다” 싶은 곳에 쓰기 좋아요. 예를 들어 내부적으로 이미 검증된 설정값을 다시 확인한다거나, 서버 초기화 단계에서 환경 변수를 검증할 때는 parse가 깔끔합니다.

반면 safeParse()는 성공과 실패를 객체로 돌려줍니다. 그래서 외부 API 응답처럼 실패 가능성을 자연스럽게 처리해야 하는 경우에 좋아요. 저는 프론트엔드에서 외부 데이터 받을 때는 safeParse를 더 선호하는 편입니다. 에러를 사용자에게 보여줄 수도 있고, 로그를 남기고 fallback UI를 띄울 수도 있으니까요.

    • parse: 실패하면 에러를 던짐, 강하게 검증하고 바로 중단할 때 좋음
    • safeParse: 실패해도 결과 객체로 반환, 사용자 경험을 부드럽게 처리하기 좋음

AI한테 요청할 때도 이걸 명확히 말해주면 결과물이 훨씬 좋아집니다. “외부 API 응답이니까 safeParse로 처리해줘”라고 하면 try-catch 범벅 코드 대신 좀 더 다루기 쉬운 형태로 만들어주는 경우가 많아요. 별거 아닌 것 같지만, 이런 작은 문장이 토큰도 아끼고 수정 시간도 줄여줍니다.

z.infer를 쓰면 타입과 검증이 따로 놀지 않아서 좋습니다

제가 Zod를 좋아하는 가장 큰 이유 중 하나는 z.infer입니다. 이거 처음 제대로 써보면 약간 시원한 느낌이 있어요. “아, 타입을 따로 만들 필요가 없구나” 하는 그 느낌이요.

보통 TypeScript에서 API 응답 타입을 만들면 이런 식으로 하잖아요.

interface User { name: string; email: string; age?: number; }

그리고 Zod 스키마를 또 따로 만들면 비슷한 정보를 두 번 적게 됩니다. 문제는 시간이 지나면서 둘이 어긋난다는 거예요. 인터페이스에는 age가 optional인데, 스키마에서는 필수로 되어 있다거나. 스키마에는 nullable을 허용했는데 타입에는 반영이 안 되어 있다거나. 이런 거 정말 자주 생깁니다. 특히 바쁜 일정에 치이면 더 그렇고요.

그래서 저는 가능하면 스키마를 기준으로 타입을 뽑습니다.

type User = z.infer<typeof UserSchema>;

이렇게 해두면 런타임 검증과 TypeScript 타입이 같은 출처에서 나옵니다. 말하자면 진실의 원천이 하나가 되는 거죠. 이게 유지보수할 때 정말 편합니다. 필드 하나 바꾸면 스키마만 수정하면 되고, 타입은 알아서 따라옵니다.

AI한테도 이렇게 요청하면 좋아요.

Zod 스키마를 먼저 만들고, 해당 스키마에서 z.infer로 타입을 추론해줘. 별도 interface는 만들지 마.

이 문장을 넣어두면 AI가 쓸데없이 interface와 schema를 따로 만들어서 중복 코드를 생산하는 걸 꽤 줄일 수 있습니다. 물론 프로젝트 스타일에 따라 interface를 따로 유지해야 하는 경우도 있겠지만, 특별한 이유가 없다면 저는 z.infer 쪽이 더 낫다고 봅니다. 중복은 언젠가 사고를 부르거든요. 조용히 있다가 배포 전날 튀어나오는 그런 사고요.

실전에서 Zod가 한 번 막아준 버그는 오래 기억에 남습니다

얼마 전에 사이드 프로젝트로 외부 결제 API를 붙인 적이 있어요. AI 도움을 받아서 연동 코드를 꽤 빠르게 만들었습니다. 문서 보고 요청/응답 타입 만들고, 성공 케이스 처리하고, 실패 케이스도 어느 정도 넣고요. “이 정도면 됐네” 싶었죠.

근데 배포 직전에 Zod로 응답 검증을 돌려봤더니, 생각 못 한 값이 하나 걸렸습니다. 결제 응답의 currency 필드가 어떤 경우에는 소문자로 오고, 어떤 경우에는 대문자로 오더라고요. AI가 만들어준 코드는 소문자만 가정하고 있었고요. 문서도 애매했습니다. 이런 거 보면 참… 현실 데이터는 늘 한 수 위예요.

그때 Zod 스키마에서 허용 값을 다시 정리하고, 필요한 곳에는 .transform(val => val.toUpperCase())를 넣어서 처리했습니다. 만약 그 검증을 안 하고 그냥 배포했다면? 아마 특정 결제 수단에서만 에러가 났을 거예요. 그리고 고객 문의 들어오고, 로그 뒤지고, 재현 안 돼서 머리 긁고… 상상만 해도 피곤합니다.

이런 경험을 한 번 하고 나면 생각이 조금 바뀝니다.

“AI가 코드를 잘 짜느냐”보다 더 중요한 질문이 생겨요.

“이 코드가 현실의 이상한 데이터 앞에서도 버틸 수 있느냐?”

저는 이 질문에 답하기 위해 Zod를 씁니다. AI를 못 믿어서라기보다는, 현실을 너무 많이 봐서요. 20년 개발하다 보면 데이터가 문서대로만 오지 않는다는 걸 몸으로 배우게 됩니다. 참 씁쓸하지만, 그게 또 실무죠.

AI 바이브 코딩에서 Zod를 더 잘 쓰는 현실적인 습관들

Zod를 그냥 설치해서 쓰는 것도 좋지만, AI와 같이 쓸 때는 몇 가지 습관을 들이면 훨씬 편해집니다. 대단한 철학은 아니고요. 삽질을 조금 덜 하기 위한 생활 팁에 가깝습니다.

샘플 JSON을 먼저 확보하고 AI에게 던지세요

가능하면 API 문서만 보고 스키마를 만들지 말고, 실제 응답 JSON을 같이 확보하는 게 좋습니다. 문서는 이상적이고, 실제 응답은 현실적입니다. 둘이 다르면 현실을 믿어야 합니다. 슬프지만 대체로 그래요.

AI한테는 이렇게 요청하면 좋습니다.

아래 실제 API 응답 JSON을 기준으로 Zod 스키마를 만들어줘. 문서에 없는 필드는 추가하지 말고, null 값은 nullable로 처리해줘.

이렇게 하면 AI가 추측하는 범위가 확 줄어듭니다. 특히 “문서에 있을 법한 필드”를 마음대로 만들어내는 일을 막는 데 도움이 됩니다.

스키마 이름 규칙을 정해두면 나중에 덜 헷갈립니다

작은 프로젝트에서는 대충 해도 됩니다. 근데 파일이 늘어나기 시작하면 이름 규칙이 은근히 중요해져요. 저는 보통 이런 식으로 씁니다.

    • UserSchema: 도메인 모델에 가까운 기본 스키마
    • UserResponseSchema: API 응답 검증용 스키마
    • CreateUserInputSchema: 사용자 입력 또는 요청 바디 검증용 스키마
    • UserQuerySchema: 검색 조건이나 쿼리 파라미터 검증용 스키마

AI에게도 “위 네이밍 규칙을 따라줘”라고 한 번 알려주면 꽤 잘 맞춰줍니다. 이런 사소한 통일감이 쌓이면 코드 읽는 속도가 빨라져요. 나중에 내가 내 코드를 봐도 덜 낯섭니다. 이거 중요합니다. 한 달 전의 나는 거의 남이거든요.

너무 많은 transform은 조심하는 게 좋습니다

Zod의 transform은 편합니다. 들어온 값을 원하는 형태로 바꿀 수 있으니까요. 문자열 날짜를 Date로 바꾸거나, 대소문자를 정리하거나, 숫자 문자열을 number로 변환할 수 있습니다.

근데 너무 많이 쓰면 스키마가 검증 도구인지 변환 파이프라인인지 헷갈리기 시작합니다. 저는 개인적으로 transform은 꼭 필요한 곳에만 씁니다. 검증은 검증답게, 데이터 가공은 별도 함수에서 하는 게 읽기 편할 때가 많아요.

AI는 가끔 친절이 과해서 이것저것 transform을 넣어버립니다. 그래서 저는 프롬프트에 이런 말을 종종 붙입니다.

transform은 꼭 필요한 경우에만 사용하고, 기본은 원본 데이터 검증 중심으로 작성해줘.

별거 아닌 문장이지만, 결과 코드가 훨씬 담백해집니다.

환경 변수 검증에도 Zod는 꽤 쓸 만합니다

AI 바이브 코딩 이야기하다가 API 응답만 말하면 조금 아쉽죠. 저는 환경 변수 검증에도 Zod를 자주 씁니다. 이건 특히 Node.js나 Next.js 프로젝트에서 효과가 좋아요.

환경 변수는 이상하게 방심하기 쉽습니다. 로컬에서는 잘 되는데 배포 환경에서 값이 빠져 있다거나, 문자열 "false"를 boolean처럼 착각한다거나, 숫자 포트를 string으로 받아놓고 이상한 비교를 한다거나. 이런 문제들, 한 번쯤 겪어보셨을 거예요.

Zod로 환경 변수를 검증해두면 앱 시작 시점에 바로 문제를 잡을 수 있습니다. 늦게 터지는 에러보다 빨리 터지는 에러가 훨씬 착합니다. 특히 배포 직후에 조용히 잘못 동작하는 것보다, 시작부터 “이 환경 변수 빠졌습니다” 하고 알려주는 게 낫죠.

AI한테는 이렇게 시키면 됩니다.

process.env를 검증하는 Zod 스키마를 만들어줘. DATABASE_URL은 url string 필수, NODE_ENV는 development/test/production enum, PORT는 number로 변환.

이런 식으로요. 그러면 AI가 대체로 꽤 괜찮은 기본 코드를 만들어줍니다. 다만 여기서도 변환 로직은 꼭 한 번 직접 봐야 합니다. 환경 변수는 전부 string으로 들어오는 경우가 많아서, 숫자나 boolean 변환에서 실수가 생기기 쉽거든요.

AI가 빨라질수록, 개발자는 검증 기준을 더 잘 세워야 합니다

요즘 AI 도구들을 보면 속도가 정말 무섭습니다. 예전에는 반나절 걸리던 코드가 몇 분 만에 나오기도 하고, 익숙하지 않은 라이브러리도 대충 방향을 잡아줍니다. 저도 도움 많이 받습니다. 괜히 고집부리면서 손으로 다 치는 시대는 지났다고 봐요.

근데 속도가 빨라질수록 개발자의 역할이 사라지는 건 아니더라고요. 오히려 달라집니다. 이제는 코드를 한 줄 한 줄 직접 치는 시간보다, 무엇을 검증해야 하는지, 어디에 안전장치를 둘지, AI가 만든 결과물을 어떤 기준으로 받아들일지를 판단하는 시간이 더 중요해지는 것 같아요.

Zod는 그 판단을 코드로 남기는 좋은 방법입니다. “이 데이터는 이런 모양이어야 한다”는 내 기준을 명확하게 적어두는 거니까요. 그리고 그 기준은 AI에게도 좋은 가이드가 됩니다. 스키마가 있으면 AI가 이후 코드를 만들 때도 훨씬 덜 헤맵니다. 타입도 추론되고, 검증도 되고, 예외 케이스도 드러나니까요.

저는 그래서 요즘 새로운 기능을 만들 때 이런 흐름을 자주 탑니다.

    • 실제 API 응답 또는 입력값 예시를 먼저 확인합니다.
    • AI에게 Zod 스키마를 만들게 합니다.
    • nullable, optional, enum, array 조건을 직접 확인합니다.
    • z.infer로 타입을 뽑습니다.
    • 비즈니스 로직은 검증된 타입을 기준으로 작성합니다.
    • 실패 케이스는 safeParse로 처리할지, parse로 중단할지 정합니다.

이 흐름이 익숙해지면 AI가 만든 코드도 훨씬 덜 불안합니다. 그냥 “AI가 해줬겠지”가 아니라, 내가 세운 울타리 안에서 AI를 쓰는 느낌이랄까요. 저는 그게 꽤 중요하다고 봅니다. 도구에 끌려가는 게 아니라, 도구를 데리고 일하는 느낌.

Zod는 거창한 유행이 아니라, AI 시대의 기본 안전장치에 가깝습니다

개발 도구는 유행이 빠릅니다. 어떤 라이브러리는 반짝 뜨고 사라지고, 어떤 패턴은 몇 년 뒤에 보면 좀 민망하기도 하죠. 그런데 Zod가 주는 가치는 꽤 단순하고 오래갑니다. 데이터가 맞는지 확인한다. 타입과 런타임 사이의 간극을 줄인다. 외부에서 들어오는 값을 믿기 전에 검사한다. 이건 유행이라기보다 기본기에 가까워요.

AI 바이브 코딩을 한다면 더더욱 그렇습니다. AI가 코드를 빠르게 만들어줄수록, 우리는 그 코드가 기대하는 데이터 구조를 명확히 해야 합니다. 안 그러면 빠르게 만든 만큼 빠르게 깨집니다. 그리고 깨진 코드는 늘 바쁜 날, 이상한 시간, 재현 어려운 조건에서 나타나죠. 참 얄밉게도요.

그래서 저는 TypeScript로 개발하는 분들, 특히 AI를 자주 쓰는 분들이라면 Zod 하나쯤은 꼭 익혀두셨으면 합니다. 엄청 깊게 파지 않아도 됩니다. z.object(), z.string(), z.number(), optional(), nullable(), safeParse(), z.infer 정도만 손에 익어도 실무에서 꽤 많은 문제를 막을 수 있어요.

저도 앞으로 AI를 계속 쓸 겁니다. 안 쓸 이유가 없어요. 생산성이 너무 좋거든요. 다만 AI가 만든 코드를 그대로 믿기보다는, Zod 같은 도구로 한 번 더 확인하고 제 기준 안에 넣어두려고 합니다. 뭐랄까, 빠르게 가되 허술하게 가진 않겠다는 마음이죠.

개발 오래 하다 보면 결국 남는 건 그런 습관인 것 같아요. 멋진 기술도 좋고, 새로운 도구도 좋지만, 사고를 줄이는 작은 습관들. Zod는 제게 그런 도구입니다. 조용하지만 든든한, 가방 한쪽에 늘 넣어두는 멀티툴 같은 녀석이요.

댓글