내가 매번 다시 쓰는 패턴들

프로젝트가 바뀌어도 손이 먼저 가는 코드가 있다. 한 번 짜면 잘 안 바뀌고, 다음 프로젝트에서도 그대로 다시 쓰게 되는 작은 조각들이다. 모아 두면 나만의 표준 라이브러리가 되는데, 결국엔 다 머릿속에 들어 있어서 매번 새로 친다.

이 글은 그런 조각 네 개에 대한 메모다.

1. 타입이 박힌 환경변수

환경변수는 모두 string이다. 그런데 코드에서는 boolean이고, number이고, URL이고, enum이다. 이 간극을 매 프로젝트마다 똑같이 한 번 메우고 시작한다.

import { z } from 'zod';

const Env = z.object({
  PORT: z.coerce.number().default(3000),
  DEBUG: z.enum(['true', 'false']).transform(v => v === 'true').default('false'),
  DATABASE_URL: z.string().url(),
  ALLOWED_EMAIL: z.string().email(),
});

export const env = Env.parse(process.env);

이 한 블록 덕분에 process.env.PORT * 2 같은 코드가 깨지지 않는다. 그리고 더 중요하게, 누락된 환경변수가 런타임 한참 후가 아니라 부팅 직후에 비명을 지른다.

2. "그룹별 최신 한 건"

SQL을 쓸 때 가장 자주 마주치는 패턴 하나. 사용자별 최신 주문, 포스트별 최신 댓글, 디바이스별 마지막 로그인. 처음엔 매번 새로 짜다가, 결국 window function 하나로 굳어졌다.

SELECT *
FROM (
  SELECT
    *,
    ROW_NUMBER() OVER (
      PARTITION BY user_id
      ORDER BY created_at DESC
    ) AS rn
  FROM orders
) ranked
WHERE rn = 1;

GROUP BY + JOIN 조합보다 읽기 쉽고, 인덱스만 잘 깔리면 빠르다. SQLite도 D1도 PostgreSQL도 같은 문법이다.

3. find를 잊는 데 일주일

맥에서 find로 파일 찾는 걸 그만뒀다. fd로 바꾸면 .gitignore/node_modules 자동 제외, 멀티스레드, 정규식 default. 다시 돌아갈 이유가 없다.

# 옛날
find . -name "*.tsx" -not -path "*/node_modules/*" | head -20

# 지금
fd '\.tsx$' | head -20

타이핑이 1/3로 줄고, 결과는 더 빠르다. 새 머신 셋업할 때 가장 먼저 까는 도구 셋 중 하나(나머지는 rg, bat).

4. JSON으로 끝내는 설정

설정 파일을 JS/TS로 짤지 JSON/YAML로 짤지 항상 고민했다. 결론: 변경 가능한 데이터는 JSON, 동작이 들어가면 TS. 둘을 섞지 않는다.

{
  "site": {
    "title": "Blog",
    "author": "esc5221",
    "baseUrl": "https://blog.example.com"
  },
  "build": {
    "perPage": 10,
    "feedLimit": 30
  }
}

JSON은 다른 언어에서도 읽힌다. 백업 스크립트, CI 워크플로우, 외부 도구. 한 번의 추상화 비용이 평생 환영받는다.

마지막

이 패턴들은 천재적이지 않다. 어느 시니어 엔지니어한테 물어봐도 비슷한 답이 나올 거다. 하지만 "이걸 매번 다시 짠다"는 사실 자체가 의미 있는 신호다 — 자주 쓰는 것은 결국 본인 도구가 된다.