๐ค GPT-5.4 vs Claude Sonnet 4.6 vs Gemini 3.1 Pro โ 4๊ฐ์ง ์ค์ ์๋๋ฆฌ์ค์์์ ์์ด์ ํธ ์ฝ๋ฉ ๋ฅ๋ ฅ ๋น๊ต
์์ฝ
GPT-5.4, Claude Sonnet 4.6, Gemini 3.1 Pro๋ฅผ ๋์์ผ๋ก 4๊ฐ์ง ํ๋ก๊ทธ๋๋ฐ ์คํ์์์ ์์ด์ ํธ ์ฝ๋ฉ ๋ฅ๋ ฅ์ ๋น๊ต ๋ถ์ํ ์ฐ๊ตฌ์ ๋๋ค. ๋์ผํ ํ๋กฌํํธ ํ์์ ๋ชจ๋ธ์ด ์ฝ๋์ ์ ํ์ฑ, ์๋ฌ ์ฒ๋ฆฌ, ์ ์ง๋ณด์์ฑ ๋ฑ์ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์ค์ง์ ์ธ ์๋๋ฆฌ์ค๋ฅผ ํตํด ๊ฒ์ฆํ์ต๋๋ค.
ํต์ฌ ํฌ์ธํธ
- Go, Python, Node.js, React ๋ฑ 4๊ฐ์ง ์คํ ๊ธฐ๋ฐ ๋น๊ต
- ์ ํ์ฑ, ์๋ฌ ์ฒ๋ฆฌ, ๊ด์ฉ์ ์คํ์ผ ๋ฑ ์๋์ด ๋ฆฌ๋ทฐ์ด ๊ธฐ์ค ํ๊ฐ
- Claude Sonnet 4.6์ด ์์ฑ ์๋ ๋ฉด์์ ์๋์ ์ธ ์ฑ๋ฅ ๊ธฐ๋ก
- ๋ชจ๋ธ๋ณ ์ฌ์ ์ง์(Priors)์ ๋ฐ๋ฅธ ์ฝ๋ ๊ตฌ์ฑ ์ฐจ์ด ํ์ธ
์ธ ๊ฐ์ง ์ต์ฒจ๋จ ์ฝ๋ฉ ๋ชจ๋ธ์ด Go, Python, Node.js (vanilla http), ๊ทธ๋ฆฌ๊ณ React + TypeScript๋ผ๋ ๋ค ๊ฐ์ง ์คํ์ ์ฌ์ฉํ์ฌ ๋์ผํ ์์ ์ ํ(TODO REST API ๋ฐ TODO UI)์ ์ฒ์๋ถํฐ ์์ฑํ๋ ์ ๋ฉด ๋น๊ต์
๋๋ค.
์ด๊ฒ์ ํฉ์ฑ ๋ฒค์น๋งํฌ(Synthetic benchmark)๊ฐ ์๋๋๋ค. ๊ฐ ๋ชจ๋ธ์๋ ๋์ผํ ํ์ดํ ์์ด ํ๋กฌํํธ(Prompt)๊ฐ ์ฃผ์ด์ก์ผ๋ฉฐ, ํ๋์ ํ์ผ์ ์์ฑํ์ต๋๋ค. ๊ทธ ๊ฒฐ๊ณผ๋ฌผ์ ์๋์ด ๋ฆฌ๋ทฐ์ด๊ฐ PR(Pull Request)์์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํ ๊ธฐ์ค์ธ ์ ํ์ฑ(Correctness), HTTP ์๋ฏธ๋ก (HTTP semantics), ์๋ฌ ์ฒ๋ฆฌ(Error handling), ์ ํจ์ฑ ๊ฒ์ฌ(Validation), ๊ด์ฉ์ ์คํ์ผ(Idiomatic style), ๊ทธ๋ฆฌ๊ณ ์ ์ง๋ณด์์ฑ(Maintainability)์ ๋ฐํ์ผ๋ก ํ๊ฐ๋์์ต๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ฃ๏ธ ํ๋กฌํํธ (The Prompt)
- โ๏ธ ์ค์ (Setup)
- ๐น ์๋๋ฆฌ์ค 1 โ Go REST API
- ๐ ์๋๋ฆฌ์ค 2 โ Python REST API
- ๐จ ์๋๋ฆฌ์ค 3 โ Node.js REST API
- โ๏ธ ์๋๋ฆฌ์ค 4 โ React + TypeScript UI
- ๐ ์ข ํฉ ์ค์ฝ์ด๋ณด๋ (Aggregate Scoreboard)
- ๐ ๋ํ๋ ํจํด๋ค (Patterns That Emerged)
- ๐ฏ ๋ชจ๋ธ ์ ํ์ ์ฃผ๋ ์๋ฏธ (What This Means for Picking a Model)
๐ฃ๏ธ ํ๋กฌํํธ (The Prompt)
๋ชจ๋ ์๋๋ฆฌ์ค์ ๋ชจ๋ ๋ชจ๋ธ์ ์ธ์ด ํ ํฐ๋ง ๊ต์ฒด๋ ์ ํํ ๋์ผํ ํ ์ค์ ์ง์นจ์ ๋ฐ์์ต๋๋ค:
"100์ค์ ์ฝ๋ ์ด๋ด๋ก todo ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ [golang / python / nodejs / reactjs] ํ์ผ์ ์์ฑํด์ค"
๊ทธ๊ฒ ์ ๋ถ์ ๋๋ค. ๋ช ์ธ(Spec)๋, ์๋ํฌ์ธํธ(Endpoints) ๋ชฉ๋ก๋, ์ ํจ์ฑ ๊ฒ์ฌ(Validation), CORS, REST ์๋ฏธ๋ก (REST semantics), ๋๋ ์ ๊ทผ์ฑ(Accessibility)์ ๋ํ ํํธ๋ ์์์ต๋๋ค. 100์ค ์ ํ์ ์๋์ ์ธ ๊ฒ์ด์์ต๋๋ค. ์ด๋ ๋ชจ๋ธ์ด _๋ฌด์์ ํฌํจํ๊ณ ๋ฌด์์ ์๋ตํ ์ง_์ ๋ํด ์ค์ค๋ก ํ๋จํ๋๋ก ๊ฐ์ ํ๋ฉฐ, ๋ฐ๋ก ์ด ์ง์ ์์ ๋ชจ๋ธ์ ์ฌ์ ์ง์(Priors)์ด ๋๋ฌ๋ฉ๋๋ค. ๋ชจ๋ ๊ฒ์ ์ถ๊ฐํ ์ฌ์ ๋ ์์ต๋๋ค. ์ ํํด์ผ๋ง ํฉ๋๋ค.
โ๏ธ ์ค์ (Setup)
์์ค ์ ์ฅ์: truongpx396/gpt-5.4_claude-sonnet-4.6_gemini-3.1-pro-coding-capability โ ์์ฑ๋ ๋ชจ๋ ํ์ผ์
gencode_golang/,gencode_python/,gencode_node/, ๊ทธ๋ฆฌ๊ณgencode_reactjs/์๋์ ์ ๋ฆฌ๋์ด ์์ต๋๋ค.
์ธ ๊ฐ์ง ๊ฒฝ์ ๋ชจ๋ธ ๋ชจ๋ GitHub Copilot์ ํตํด ์ ์๋์์ผ๋ฉฐ, ๊ฐ ๋ชจ๋ธ์ ๊ธฐ๋ณธ ์ถ๋ก (Reasoning) ์ค์ ์ผ๋ก ์คํ๋์์ต๋๋ค.
| ๋ชจ๋ธ | ์ถ๋ก ๋ชจ๋ (Reasoning mode) | ์ปจํ ์คํธ ์๋์ฐ (Context window) | ์์ฑ ์๋ (Generation speed)* | ์ ์ ๋ฐฉ์ (Access) |
|---|---|---|---|---|
| GPT-5.4 | ์ค๊ฐ (๊ธฐ๋ณธ๊ฐ) | 400k | ~24 tok/s | GitHub Copilot |
| ... |
- ๋ณธ ํ ์คํธ ์ค์ ์ธก์ ๋จ โ ๊ฐ ์์ ์ ์ฝ 100ํ / ์ฝ 700๊ฐ์ ์ถ๋ ฅ ํ ํฐ์ ์์ฑํ์ต๋๋ค. Claude Sonnet 4.6์ด ์๋์ ์ธ ์ฐจ์ด๋ก ๊ฐ์ฅ ๋นจ๋์ผ๋ฉฐ, GPT-5.4๋ณด๋ค ์ฝ 42% ๋น ๋ฅด๊ณ Gemini 3.1 Pro๋ณด๋ค ์ฝ 13% ๋นจ๋์ต๋๋ค. ์ค์ ๋ก ์ด๋ 20์ด ๋๊ธฐ์ 29์ด ๋๊ธฐ์ ์ฐจ์ด๋ฅผ ์๋ฏธํ๋ฉฐ, ๋จ์ผ ์์ฑ (one-shot generation) ์์๋ ๋์ ๋์ง๋ง ๊ฒฐ์ ์ ์ธ ์ฐจ์ด๋ ์๋๋๋ค. ํ์ง๋ง ๋ง์ ์์ฐจ์ ํธ์ถ์ด ๋ฐ์ํ๋ ์์ด์ ํธ ๋ฃจํ (agentic loops)์์๋ ์ด ์ฐจ์ด๊ฐ ํฌ๊ฒ ๋์ ๋ ๊ฒ์ ๋๋ค.
๊ฐ ์ถ๋ ฅ๋ฌผ์ ๋ํด ์๋์ด ๋ฆฌ๋ทฐ์ด(senior-reviewer)๊ฐ ๊ฒํ ํ๋ ๋ฐฉ์์ ํ๊ฒฐ ์์ฒด๋ Claude Code ๋ด๋ถ์์ ์คํ๋๋ 1M-ํ ํฐ ์ปจํ ์คํธ ์๋์ฐ๋ฅผ ๊ฐ์ง Claude Sonnet 4.7์ ์ํด ์์ฑ๋์์ต๋๋ค. ํด๋น ๋ชจ๋ธ์ ํ๊ฐ ๋์์ด ๋ ์ฝ๋๋ฅผ ์ง์ ์์ฑํ์ง ์์์ผ๋ฉฐ, ์ค์ง ์ฝ๊ณ ์ฑ์ ๋ง ์ํํ์ต๋๋ค.
๋ฆฌ๋ทฐ ๋ชจ๋ธ์ ์ ๊ณต๋ ํ๋กฌํํธ๋ ๋ชจ๋ ์๋๋ฆฌ์ค์์ ๋์ผํ์ผ๋ฉฐ, ํด๋ ์ด๋ฆ๋ง ๊ต์ฒด๋์์ต๋๋ค:
"
gencode_golang/gencode_python/gencode_node/gencode_reactjsํด๋์ ์๋ 3๊ฐ์ ํ์ผ์ ํ์ธํ๊ณ , ์ด๋ค ์ฝ๋๊ฐ ๋ ๋์์ง ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ด์ ๊ฐ ๋ฌด์์ธ์ง ์๋ ค์ฃผ์ธ์."
์ด ์คํ์์ "์ปจํ ์คํธ ์๋์ฐ (context window)" ์ด์ ์๊ฐ๋ณด๋ค ์ค์๋๊ฐ ๋ฎ์ต๋๋ค. ๊ฐ ์์ ์ ์๋ฐฑ ๊ฐ์ ํ ํฐ ๋ด์ ๋ค์ด์ค๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋์ ์ด ์งํ๋ ๊ฐ ๋ฒค๋๊ฐ Copilot ๋ด์์ ์์ ์ ๋ชจ๋ธ์ ์ด๋ป๊ฒ ํฌ์ง์ ๋ํ๋์ง๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฐ ๋ ์ค์ํฉ๋๋ค. GPT-5.4๋ ํค๋น๊ธ (heavyweight), Sonnet 4.6์ ์ํฌํธ์ค (workhorse, ์ค๋ฌด ์ค์ฌํ), Gemini 3.1 Pro๋ ํ๋ฆฌ๋ทฐ ํฐ์ด (preview tier)๋ก ์๋ฆฌ ์ก๊ณ ์์ต๋๋ค.
๊ฒฉ๋ฆฌ ๋ฐ ํธํฅ ๋ฐฉ์ง (Isolation & Bias Prevention)
๊ฐ ํ์ผ์ ์ ์ฉ์ ๊นจ๋ํ๊ณ ์๋ก์ด ์ปจํ
์คํธ (dedicated, clean, fresh context) โ ์ฆ, ์ด์ ๋ํ ๊ธฐ๋ก์ด ์๊ณ , ๊ณต์ ๋ ์ฑํ
์ธ์
์ด ์์ผ๋ฉฐ, ๋ชจ๋ธ ๊ฐ์ ์ํธ ์ฐธ์กฐ๊ฐ ์๋ ๋ณ๋์ ๋ฆฌํฌ์งํ ๋ฆฌ(repo)์์ ์์ฑ๋์์ต๋๋ค. ์์ฑ๋ ํ ๊ฐ ์ถ๋ ฅ๋ฌผ์ ๊ฒํ ๋ฅผ ์ํด ๋ณ๋์ ๋ชฉ์ ์ง ๋ฆฌํฌ์งํ ๋ฆฌ๋ก ์ด๋๋์์ต๋๋ค. ๊ฒฐ์ ์ ์ผ๋ก, ์์ฑ ๊ณผ์ ์ค์๋ ์ฌ์ ์ค์ ๋ ๊ท์น, ์ฌ์ฉ์ ์ ์ ์ง์นจ (custom instructions), ์์คํ
ํ๋กฌํํธ (system prompts), ๋๋ .github/copilot-instructions.md ํ์ผ์ด ์ ํ ์กด์ฌํ์ง ์์์ต๋๋ค โ ๋ชจ๋ ๋ชจ๋ธ์ ์์ ๊ธฐ๋ณธ ์ค์ (bare defaults) ์ํ๋ก ์คํ๋์์ต๋๋ค. ์ด๋ ๋ค์์ ์๋ฏธํฉ๋๋ค:
- ์ด๋ค ๋ชจ๋ธ๋ ์์ฑ ์ ์ด๋ ์์ฑ ์ค์ ๋ค๋ฅธ ๋ชจ๋ธ์ ์ถ๋ ฅ์ ๋ณด์ง ๋ชปํ์ต๋๋ค.
- ๊ณต์ ๋ ์ปจํ ์คํธ ์๋์ฐ (Context Window)๋ฅผ ํตํด ๊ฒฝ์ ๋ชจ๋ธ ๊ฐ์ ์คํ์ผ, ๊ตฌ์กฐ ๋๋ ๊ฒฐ์ ์ฌํญ์ด ์ ์ถ๋ ์ ์์์ต๋๋ค.
- ์ด๋ค ์ปค์คํ ์์คํ ํ๋กฌํํธ (Custom System Prompt)๋ ํน์ ํจํด์ ๋ฐ๋ฅด๋๋ก ๋ชจ๋ธ์ ์ ๋ํ๊ฑฐ๋ ๋ฉ๋ฆฌํ๊ฒ ํ์ง ์์์ต๋๋ค.
- ๊ฒํ ์ (Sonnet 4.7)๋ ์ค์ง ์๋ณธ ํ์ผ๋ง์ ์ ๋ฌ๋ฐ์์ผ๋ฉฐ, ์ด๋ค ๋ชจ๋ธ์ด ์ด๋ค ํ์ผ์ ์์ฑํ๋์ง์ ๋ํ ํํธ๋ ์ ํ ๋ฐ์ง ๋ชปํ์ต๋๋ค.
- ํ์ผ ์ด๋ฆ ์ ๋ฏธ์ฌ (
_gpt-5.4,_claude-sonet-4.6,_gemini-3.1-pro)๋ ๋ชจ๋ ํ์ ์ด ์๋ฃ๋ ํ์๋ง ์ ์ฉ๋์์ต๋๋ค. ์์ฑ ๋ฐ ๊ฒํ ๋จ๊ณ ๋์ ํ์ผ์ ์ค์ง ๋ฒํธ๋ก๋ง ์๋ณ๋์์ต๋๋ค (todo_1_,todo_2_,todo_3_). ๊ฐ๋ ์ฑ์ ์ํด ์ฌํ์ ์ผ๋ก ๊ท์ ์ ๋ณด๊ฐ ์ถ๊ฐ๋์์ต๋๋ค.
๋ชฉํ๋ ์ต์ปค๋ง ํธํฅ (Anchoring Bias, ํ๋์ ์๋ฃจ์ ์ ๋ณธ ํ ๋ค๋ฅธ ์๋ฃจ์ ์ ์์ฑํ๋ ๊ฒ), ์ปจํ ์คํธ ์ ์ถ (Context Bleed), ๊ทธ๋ฆฌ๊ณ ๋ชจ๋ธ์ ์๊ธฐ ํธํฅ (Model Self-favoritism)๊ณผ ๊ฐ์ ํธํฅ์ ์์ธ์ ๊ฐ๋ฅํ ํ ์ ๊ฑฐํ๋ ๊ฒ์ด์์ต๋๋ค.
๐น ์๋๋ฆฌ์ค 1 โ Go REST API
์์: Sonnet 4.6 > GPT-5.4 > Gemini 3.1 Pro
์ฐ์น์: Claude Sonnet 4.6
Sonnet 4.6์ Go 1.22+์ ๋ฉ์๋ ์ธ์ ๋ผ์ฐํ
(Method-aware Routing)์ ๋๋จธ์ง ๊ธฐ๋ณธ ์ฌํญ๋ค๊ณผ ๊ฒฐํฉํ ์ ์ผํ ๋ชจ๋ธ์ด์์ต๋๋ค. ์ด ๋ชจ๋ธ์ r.PathValue("id")๋ฅผ ์ฌ์ฉํ๋ mux.HandleFunc("/todos/{id}", ...)๋ฅผ ์ฌ์ฉํ์๊ณ , ์ผ๋ฐ์ ์ธ Content-Type / WriteHeader / Encode 3๋จ๊ณ ๊ณผ์ ์ ์ ๊ฑฐํ๋ jsonResponse() ํฌํผ ํจ์, ๊ตฌ์กฐํ๋ JSON ์๋ฌ ๋ฐ๋, ๋์คํจ์น(Dispatch)๋ฅผ ์ํ switch r.Method, ๊ทธ๋ฆฌ๊ณ โ ๊ฐ์ฅ ์ค์ํ ์ ์ผ๋ก์ โ ๋๋ฝ๋ ํ๋๊ฐ ์กฐ์ฉํ ์ ๋ก ๊ฐ(Zero value)์ผ๋ก ์ด๊ธฐํ๋์ง ์๋๋ก ๋ถ๋ถ ์
๋ฐ์ดํธ๋ฅผ ์ํ ํฌ์ธํฐ ํ๋ (Pointer Fields)๋ฅผ ์ฌ์ฉํ์ต๋๋ค:
gencode_golang/todo_2_claude-sonet-4.6.go:83-97
case http.MethodPut:
var body struct {
Title *string `json:"title"`
...
๋ํ ์ฃผ๋ชฉํ ์ ์ body.Title == ""๋ฅผ ๊ฒ์ฆํ๊ณ , ๊ธฐ๋ณธ mux ๋์ ๋ช
์์ ์ธ http.NewServeMux()๋ฅผ ์ฌ์ฉํ๋ฉฐ, ์ค์ GET /todos/{id} ๊ฒฝ๋ก๋ฅผ ๋
ธ์ถํ๋ค๋ ๊ฒ์
๋๋ค.
2์: GPT-5.4 โ ์ฌ๋ฐ๋ฅธ ์๋ฏธ๋ก , ๊ตฌ์ ๋ผ์ฐํ
GPT-5.4๋ _์๋ฏธ(meaning)_๋ ์ ํํ๊ฒ ํ์
ํ์ต๋๋ค. ๋ถ๋ถ ์
๋ฐ์ดํธ๋ฅผ ์ํ ํฌ์ธํฐ ํ๋๋ฅผ ํฌํจํ PATCH ๋ฐฉ์, strings.TrimSpace ๊ฒ์ฆ ๋ฑ์ ์ฌ์ฉํ์ต๋๋ค. ํ์ง๋ง Go 1.22 ์ด์ ์ ํจํด์ ์ฌ์ฉํ์ต๋๋ค. ๊ฒฝ๋ก ํ์ฑ์ ์ํด ์๋์ผ๋ก strings.TrimPrefix(r.URL.Path, "/todos/")๋ฅผ ์ฌ์ฉํ๊ณ , ์ผ๋ฐ ํ
์คํธ ์๋ฌ ๋ณธ๋ฌธ์ ๊ฐ์ง http.Error๋ฅผ ์ฌ์ฉํ๋ฉฐ, ์กฐํ์ ๋ฉ์๋ ๋์คํจ์น(method dispatch)๊ฐ ๋ค์์ธ ํ๋์ ๊ฑฐ๋ํ ํธ๋ค๋ฌ๋ฅผ ์์ฑํ์ต๋๋ค. ๋ง์น 2020๋
์ Go ์ฝ๋๋ฅผ ๋ณด๋ ๋ฏํฉ๋๋ค.
์ตํ์: Gemini 3.1 Pro โ ํ๋์ ์ธ ๊ฒ๋ชจ์ต, ๋ฌด๋์ง ๊ธฐ๋ณธ๊ธฐ
Gemini 3.1 Pro์ ์ฝ๋๋ ๊ฐ์ฅ ํ๋์ ์ผ๋ก ๋ณด์์ง๋ง ("GET /todos" ์คํ์ผ์ ๋ผ์ฐํ
), ๊ธฐ์ด์ ์ธ ๋ถ๋ถ์์ ์คํจํ์ต๋๋ค:
strconv.Atoi(r.PathValue("id"))๋ฐjson.NewDecoder(r.Body).Decode(&t)์์ ๋ฐ์ํ๋ ์๋ฌ๋ฅผ ๋ฌด์ํ์ต๋๋ค. โ ์๋ชป๋ ์ ๋ ฅ์ด ๋ค์ด์ค๋ฉด 400 ์๋ฌ ๋์id=0์ด ๋ฉ๋๋ค.map[int]Todo๋ฅผ ์ ์ฅ์๋ก ์ฌ์ฉํฉ๋๋ค. โGET /todos๋ฅผ ํธ์ถํ ๋๋ง๋ค ์์ดํ ์ด ๋ฌด์์ ์์๋ก ๋ฐํ๋ฉ๋๋ค. ์ด๊ฒ์ API๊ฐ ์๋๋ผ ์ฌ๋กฏ๋จธ์ ์ ๋๋ค.- ์ ๋ ฅ ๊ฒ์ฆ(input validation)์ด ์์ผ๋ฉฐ, ๋น ์ ๋ชฉ์ ๋ํ ๋ฐฉ์ด ๋ก์ง(empty-title guard)๋ ์์ต๋๋ค.
- PUT ์์ฒญ ์ ์ ์ฒด ๋ ์ฝ๋๋ฅผ ๋ฎ์ด์๋๋ค.
completedํ๋๋ฅผ ์๋ตํ๋ฉดfalse๋ก ๋ฐ๋๋๋ค.
๊ณ ์ ์ ์ธ ์ค์ ์ ๋ฐ ์์(foot-guns)๋ฅผ ํ๋์ ์ธ ๋ฌธ๋ฒ์ผ๋ก ๊ฐ์ธ๋์ ํํ์ ๋๋ค.
๐ ์๋๋ฆฌ์ค 2 โ Python REST API (ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ http.server)
์์: GPT-5.4 > Sonnet 4.6 > Gemini 3.1 Pro
์ด ์๋๋ฆฌ์ค๋ GPT-5.4๊ฐ ์๋์ ์ผ๋ก 1์๋ฅผ ์ฐจ์งํ ์ ์ผํ ์ฌ๋ก์ ๋๋ค.
์ฐ์น: GPT-5.4 โ ๊ฐ์ฅ ์์ ํ ์ ๋ ฅ ์ฒ๋ฆฌ
GPT-5.4๋ ์ง๋ฃจํ์ง๋ง ์ค์ํ ์ธ๋ถ ์ฌํญ๋ค์ ์๋ฒฝํ๊ฒ ์ฒ๋ฆฌํ์ต๋๋ค. ํญ์ CORS ํค๋๋ฅผ ์ก์ถํ๋ send() ํฌํผ, Content-Length ๋๋ฝ์ ๋ํด ๋ฐฉ์ด ๋ก์ง์ ๊ฐ์ถ read_json() (๋ค๋ฅธ ๋ชจ๋ธ๋ค์ int(None)์์ ์ถฉ๋์ด ๋ฐ์ํฉ๋๋ค), UUID ID, createdAt ํ์์คํฌํ, OPTIONS ์์ฒญ์ ๋ํ 204 No Content, ๊ธฐ๋ณธ ์์ฒญ ๋ก๊ทธ ์ต์ , ๊ทธ๋ฆฌ๊ณ **์ง์ ํ PATCH ์๋ฏธ๋ก (semantics)**์ ๊ตฌํํ์ต๋๋ค:
gencode_python/todo_1_gpt-5.4.py:21-24
def read_json(handler):
size = int(handler.headers.get("Content-Length", "0"))
raw = handler.rfile.read(size) if size else b"{}"
...
gencode_python/todo_1_gpt-5.4.py:52-61
def do_PATCH(self):
todo = self.find_todo()
if not todo:
...
์ค์ง ๋์น ๋ถ๋ถ๋ง: GET /todos/{id}๊ฐ ์๊ณ , ์ ์ฅ์๊ฐ ๋์
๋๋ฆฌ ๋์ ๋ฆฌ์คํธ์
๋๋ค (O(n) ์กฐํ).
์ฐจ์์๊ถ: Sonnet 4.6 โ ๋ ๋์ ๋ฐ์ดํฐ ๋ชจ๋ธ, ์ฝํ ์๋ฏธ๋ก
Sonnet 4.6์ ์ฌ๋ฐ๋ฅธ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ธ dict ์ ์ฅ์ ์ ํํ์ต๋๋ค. ์ด๋ O(1) ์กฐํ๋ฅผ ์ ๊ณตํ๋ฉฐ ์ญ์ ์ ๊น๋ํ dict.pop()์ ๊ตฌํํ๊ณ ์ ์ฉํ ์์ ๋ฐฐ๋๊น์ง ์ถ๊ฐํ์ต๋๋ค. ํ์ง๋ง ๋ถ๋ถ ์
๋ฐ์ดํธ๋ฅผ **PUT**์ผ๋ก ๋ ์ด๋ธ๋งํ๋๋ฐ, ์ด๋ RFC 7231์ ๋ฐ๋ฅด๋ฉด ์๋ฏธ๋ก ์ ์ผ๋ก ํ๋ฆฝ๋๋ค (PUT์ ์ ์ฒด ๊ต์ฒด ์๋ฏธ). ๋ํ title์ด ๋ฌธ์์ด์ด ์๋ ๊ฒฝ์ฐ body["title"].strip()์์ ์ ์ฌ์ ์ธ AttributeError๊ฐ ๊ธฐ๋ค๋ฆฌ๊ณ ์์ต๋๋ค.
์ตํ์๊ถ: Gemini 3.1 Pro โ ์ข์ ์์ด๋์ด๋ ํ๋, ํ๊ท(regressions)๊ฐ ๋ง์
Gemini 3.1 Pro๋ ๋จ ํ๋์ ์ง์ ์ผ๋ก ์ข์ ์์ด๋์ด๋ฅผ ์ ๊ณตํ์ต๋๋ค. ๋ฐ๋ก end_headers ์ค๋ฒ๋ผ์ด๋๋ฅผ ํตํ CORS ๊ตฌํ์ธ๋ฐ, ์ด๋ ์ธ ๋ชจ๋ธ ์ค ๊ฐ์ฅ DRYํ ์ ๊ทผ ๋ฐฉ์์
๋๋ค. ๊ทธ ์ธ ๋ชจ๋ ๊ฒ์ ํ๊ท์ ์
๋๋ค: ์ ์ญ ์นด์ดํฐ์์ ์์ธก ๊ฐ๋ฅํ ์ ์ ID, ์ ํจ์ฑ ๊ฒ์ฌ ๋ถ์ฌ (๋น ๋ฌธ์์ด "" ์ ๋ชฉ์ด ์กฐ์ฉํ ์ ์ฅ๋จ), Content-Length ๋๋ฝ ์ ์ถฉ๋ ๋ฐ์ (int(None) โ TypeError), DELETE๊ฐ ์ธํ๋ ์ด์ค ์์ ๋์ ์ ์ญ ๋ฆฌ์คํธ๋ฅผ ์ฌ๋ฐ์ธ๋ฉํ๋ ๋ฌธ์ (๋ค๋ฅธ ๋ชจ๋ ์ฐธ์กฐ๋ฅผ ๊นจ๋จ๋ฆผ), OPTIONS์ ์๋ชป๋ ์ํ ์ฝ๋ (204 ๋์ 200), ๊ธฐ๋ณธ stderr ๋ก๊ทธ ์คํธ.
๐จ ์๋๋ฆฌ์ค 3 โ Node.js REST API (์์ node:http)
์์: GPT-5.4 > Sonnet 4.6 > Gemini 3.1 Pro
์ฐ์น์: GPT-5.4 โ ๊ฐ์ฅ ๊น๋ํ ์ถ์ํ
GPT-5.4์ Node ๋ฒ์ ์ด ์ค์ ๋ก ๋ฐฐํฌํ ๋งํ ์ฝ๋์
๋๋ค. ์ด ์ฝ๋๋ ESM import(์ต์ Node์ ์ผ์น), ์ถฉ๋ ๋ฐฉ์ง ID๋ฅผ ์ํ randomUUID() ์ฌ์ฉ, ์ํ + CORS + content-type์ ํ ๋ฒ์ ํธ์ถ๋ก ์ ์กํ๋ ๋จ์ผ send() ํฌํผ, PATCH์ ๋ํ ์๊ฒฉํ ํ๋๋ณ ํ์
๊ฒ์ฆ, ๊ทธ๋ฆฌ๊ณ ์๋ชป๋ JSON์ ๋ํด 400 (500์ด ์๋)์ ๋ฐํํ๋ ์ต์์ try/catch๋ฅผ ์ฌ์ฉํฉ๋๋ค:
gencode_node/todo_1_gpt-5.4.js:42-48
if (url.pathname.startsWith('/todos/') && req.method === 'PATCH') {
if (!todo) return send(res, 404, { error: 'todo not found' })
const { text, completed } = await readBody(req)
...
typeof completed === 'boolean' ๊ฒ์ฌ๋ ์ฅ๋๊ฐ ์ฝ๋๋ฅผ ์ค์ ์ ํ ์ฝ๋์ ๊ตฌ๋ถํ๋ ์ข
๋ฅ์ ๋ํ
์ผ์
๋๋ค. Gemini๊ฐ ์ฌ์ฉํ๋ ์ ๊ฐ ๋ฐ ์์๋ฐฉํธ์ ์ ๊ทผ ๋ฐฉ์({ ...todos[index], ...data, id })์ ํด๋ผ์ด์ธํธ๊ฐ completed: "yes"๋ฅผ ์์ฑํ์ฌ ๋ชจ๋ ์ฌ๋์ ์ํ ์คํค๋ง๋ฅผ ๊นจ๋จ๋ฆฌ๊ฒ ๋ง๋ญ๋๋ค.
์ฐจ์์: Sonnet 4.6 โ ๋ธ๋ผ์ฐ์ ์์ ์ฌ์ฉํ๊ธฐ๋ ๊น๋ํ์ง๋ง ๋ถ๊ฐ๋ฅํจ
Sonnet 4.6์ Node ์ฝ๋๋ ๋ผ์ฐํธ๋ณ JSON ํ์ฑ ์ค๋ฅ ์ฒ๋ฆฌ๊ฐ ๊ฐ์ฅ ์ข๊ณ , DELETE ์์ ์ ํํ 204 No Content๋ฅผ ๋ฐํํฉ๋๋ค. ํ์ง๋ง CORS ํค๋๋ฅผ ์ ํ ์ ์กํ์ง ์์ ํ๋ก์ ์์ด ๋ธ๋ผ์ฐ์ ํ๋ก ํธ์๋์์ ์ฌ์ฉํ๊ธฐ๋ ๋ถ๊ฐ๋ฅํฉ๋๋ค. TODO ์ฑ์ ๊ด์ ์์ ์ด๋ ์น๋ช
์ ์ธ ์ ํ ๊ฒฐํจ์
๋๋ค.
์ตํ์: Gemini 3.1 Pro โ ์ฅํฉํ๊ณ ์๋ฏธ์ ์ผ๋ก ํ๋ฆผ
Go์ ๊ฐ์ ํจํด์ ๋ณด์
๋๋ค: PATCH๊ฐ ์๋๋ ๊ณณ์ PUT์ด ์ฌ์ฉ๋์๊ณ , ์๋ชป๋ JSON์ 400 ๋์ 500์ ๋ฐํํ๋ฉฐ, ์
๋ ฅ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์๊ณ , ์ ๋ชฉ์ trim()์ด ์์ด (" "๋ ์ ํจํ TODO์), ๊ทธ๋ฆฌ๊ณ ESM import ๋์ require๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ด๋ 2025๋
๋ฒ์ ์ Node ์์ ๋ก๋ ์ด์ํฉ๋๋ค. ์ข์ ์ ์ ํ๋ ์์ต๋๋ค: for await (const chunk of req)๋ ์ธ ๊ฐ์ง ์ค ๊ฐ์ฅ ๊ด์ฉ์ ์ธ ๋ฐ๋ ๋ฆฌ๋์
๋๋ค. ์์ ์น๋ฆฌ, ๋ง์ ํจ๋ฐฐ์
๋๋ค.
โ๏ธ ์๋๋ฆฌ์ค 4 โ React + TypeScript UI
์์: Sonnet 4.6 > Gemini 3.1 Pro > GPT-5.4
์ด ์๋๋ฆฌ์ค๋ ๋ชจ๋ ์ฐจ์์์ ๋จ ํ๋์ ์น์๊ฐ ์กด์ฌํ์ง ์๊ธฐ ๋๋ฌธ์ ๊ฐ์ฅ ํฅ๋ฏธ๋ก์ด ์๋๋ฆฌ์ค์ ๋๋ค. ๊ฐ ๋ชจ๋ธ์ ๋ค๋ฅธ ๋ชจ๋ธ์ด ๋ถ์กฑํ๋ ๋ฌด์ธ๊ฐ๋ฅผ ๋ณด์ฌ์ฃผ์์ต๋๋ค.
์ข ํฉ ์ฐ์น์: Sonnet 4.6 โ ์ต๊ณ ์ ์ํคํ ์ฒ ๋ฐ ๊ธฐ๋ฅ ์ธํธ
Sonnet 4.6์ ๊ฐ์ฅ ์์ ํ TODO ๊ธฐ๋ฅ์ ์์ฑํ์ต๋๋ค: ์ถ๊ฐ(add), ํ ๊ธ(toggle), ์ญ์ (delete), ํํฐ(์ ์ฒด/ํ์ฑ/์๋ฃ)(filter (all/active/done)), ๋จ์ ํญ๋ชฉ ์นด์ดํฐ(items-left counter), ๋น ์ํ(empty state), ๊ทธ๋ฆฌ๊ณ ์๋ฃ ํญ๋ชฉ ์ง์ฐ๊ธฐ(clear-completed). ๋ํ ํธ๋ค๋ฌ(handlers)๋ฅผ ์์ ์ด๋ฆ์ด ์ง์ ๋ ํจ์(named functions)๋ก ๋ถ๋ฆฌํ์๊ณ , ๋ชจ๋ ์คํ์ผ๋ง์ ๋จ์ผ s ๊ฐ์ฒด๋ก ๋ชจ์ JSX๊ฐ ์คํ์ผ๋ง ๋
ธ์ด์ฆ๊ฐ ์๋ ๊ตฌ์กฐ์ฒ๋ผ ์ฝํ๋๋ก ํ์ต๋๋ค. ํํฐ ๋ก์ง์ ์ํ(state)๊ฐ ์๋ ํ์๋ ๊ฐ(derived value)์ผ๋ก ์ฒ๋ฆฌ๋์๋๋ฐ, ์ด๋ React์ ๊ด์ฉ์ ์ธ(idiomatic) ๋ฐฉ์์
๋๋ค:
gencode_reactjs/todo_2_claude-sonet-4.6.tsx:10-26
const add = () => {
const text = input.trim();
if (!text) return;
...
AI ์๋ ์์ฑ ์ฝํ ์ธ
๋ณธ ์ฝํ ์ธ ๋ Dev.to AI tag์ ์๋ฌธ์ AI๊ฐ ์๋์ผ๋ก ์์ฝยท๋ฒ์ญยท๋ถ์ํ ๊ฒ์ ๋๋ค. ์ ์ ์๊ถ์ ์์ ์์์๊ฒ ์์ผ๋ฉฐ, ์ ํํ ๋ด์ฉ์ ๋ฐ๋์ ์๋ฌธ์ ํ์ธํด ์ฃผ์ธ์.
์๋ฌธ ๋ฐ๋ก๊ฐ๊ธฐ