[프롬프트] 특정 언어(Python ➔ Go) 코드 변환 프롬프트 - 최적 프롬프트 레시피
Python의 동적 비동기 코드를 Go의 고성능 정적 타입 동시성 파이프라인으로 무결점 전환하는 Staff 레벨 프롬프트
"Python으로 프로토타이핑한 마이크로서비스를 Go로 전환할 때, LLM에 단순 변환을 요청하면 interface{} 남발, panic 남용, 제어되지 않는 Goroutine이 생성되어 컴파일만 되는 불안정한 코드가 양산됩니다. 구조화된 지침이 필수적입니다."
1단계: 페인포인트 & 작업 목표
단순 기계 번역 시 마주치는 치명적 안티패턴
- 타입 시스템 붕괴: Python의
dict를map[string]interface{}나any로 무분별하게 변환하여 런타임 타입 단언 패닉 초래 - 예외 처리 왜곡: Python의
try-except를 흉내 내려panic/recover를 남용하거나 에러 리턴을 무시 - 동시성 모델 오염:
asyncio.gather를 제한 없는go func()로 변환해 메모리 OOM 및 고루틴 누수 발생 - 리소스 누수: Go의 필수적인
context.Context생명주기 및defer resp.Body.Close()누락
완벽한 포팅을 위한 엔지니어링 목표
- 관용적 Go(Idiomatic Go): 정적 타입 Struct와 명시적
(T, error)다중 반환 구조 확립 - 포인터 vs 값 리시버의 명확한 분리: 불필요한 힙 할당 방지 및 상태 뮤테이션 제어
- 구조화된 동시성(Structured Concurrency): Worker Pool, 버퍼드 채널,
errgroup적용 - 견고한 수명주기:
context.Context를 통한 타임아웃 및 Graceful Cancellation 보장
2단계: 프롬프트 레시피 비교 (3단 분리 표기)
3단계: 실행 결과 비교
❌ 레시피 A 실행 한계 분석
단순 지시 프롬프트로 생성된 코드는 공유 슬라이스(results = append(results, res))에 락 없이 무제한 고루틴으로 접근하여 데이터 레이스(Data Race) 패닉을 발생시킵니다. 또한 recover() 흉내와 무제한 HTTP 요청으로 프로덕션 서버를 즉시 다운시킵니다.
⚡ 레시피 B 실전 사례: 비동기 데이터 수집기 (Worker Pool + Context)
- 1단계 (도메인 모델): "Python 코드의 입출력 JSON을 분석해 Go 구조체(Struct)를 선언해 줘."
- 2단계 (워커 풀 제약): "레시피 B를 적용하여 동시 작업 수를 제어하는 Worker Pool 구조로 작성해 줘."
- 3단계 (에러 & 타임아웃): "
context.WithTimeout과fmt.Errorf(%w)래핑을 추가해 줘." - 4단계 (테스트 검증): "
httptest.Server를 사용한 단위 테스트 코드를 작성해 줘."
package scraper
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"time"
)
type TargetResponse struct {
ID int64 `json:"id"`
Title string `json:"title"`
FetchedAt time.Time `json:"fetched_at"`
}
type Scraper struct {
client *http.Client
workers int
}
func NewScraper(workers int, timeout time.Duration) *Scraper {
return &Scraper{
workers: workers,
client: &http.Client{
Timeout: timeout,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
IdleConnTimeout: 90 * time.Second,
},
},
}
}
func (s *Scraper) FetchAll(ctx context.Context, urls []string) ([]TargetResponse, []error) {
jobs := make(chan string, len(urls))
results := make(chan TargetResponse, len(urls))
errs := make(chan error, len(urls))
var wg sync.WaitGroup
for w := 0; w < s.workers; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case url, ok := <-jobs:
if !ok {
return
}
data, err := s.fetchOne(ctx, url)
if err != nil {
errs <- fmt.Errorf("fetch failed for %s: %w", url, err)
} else {
results <- data
}
}
}
}()
}
for _, u := range urls {
jobs <- u
}
close(jobs)
go func() {
wg.Wait()
close(results)
close(errs)
}()
var finalResults []TargetResponse
var finalErrors []error
for res := range results {
finalResults = append(finalResults, res)
}
for err := range errs {
finalErrors = append(finalErrors, err)
}
return finalResults, finalErrors
}
func (s *Scraper) fetchOne(ctx context.Context, targetURL string) (TargetResponse, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
if err != nil {
return TargetResponse{}, fmt.Errorf("create req: %w", err)
}
resp, err := s.client.Do(req)
if err != nil {
return TargetResponse{}, fmt.Errorf("do req: %w", err)
}
defer func() {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return TargetResponse{}, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
var data TargetResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return TargetResponse{}, fmt.Errorf("decode: %w", err)
}
data.FetchedAt = time.Now()
return data, nil
}
🚀 레시피 C 실전 사례: 고성능 스트리밍 파이프라인 (Streaming Decoder + sync.Pool)
package pipeline
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"sync"
"golang.org/x/sync/errgroup"
)
type SourceRecord struct {
ID string `json:"id"`
Value float64 `json:"value"`
}
type TransformedRecord struct {
UID string `json:"uid"`
Score float64 `json:"score"`
}
func StreamProcessJSON(ctx context.Context, r io.Reader, w io.Writer, concurrency int) error {
dec := json.NewDecoder(r)
token, err := dec.Token()
if err != nil || token != json.Delim('[') {
return fmt.Errorf("expected json array opening: %w", err)
}
recordChan := make(chan SourceRecord, 500)
g, gCtx := errgroup.WithContext(ctx)
g.SetLimit(concurrency)
// Producer Stage
g.Go(func() error {
defer close(recordChan)
for dec.More() {
var rec SourceRecord
if err := dec.Decode(&rec); err != nil {
return fmt.Errorf("streaming decode: %w", err)
}
select {
case <-gCtx.Done():
return gCtx.Err()
case recordChan <- rec:
}
}
dec.Token()
return nil
})
outChan := make(chan TransformedRecord, 500)
var workerWg sync.WaitGroup
for i := 0; i < concurrency; i++ {
workerWg.Add(1)
go func() {
defer workerWg.Done()
for item := range recordChan {
res := TransformedRecord{
UID: strings.ToUpper(item.ID),
Score: item.Value * 1.5,
}
select {
case <-gCtx.Done():
return
case outChan <- res:
}
}
}()
}
go func() {
workerWg.Wait()
close(outChan)
}()
enc := json.NewEncoder(w)
for out := range outChan {
if err := enc.Encode(out); err != nil {
return fmt.Errorf("stream encode: %w", err)
}
}
return g.Wait()
}
4단계: 메커니즘 심층 분석
1. 동적 타이핑 ➔ 정적 구조체 컴파일 검증
Python 런타임의 덕 타이핑은 Go에서 컴파일 타임 메모리 레이아웃으로 변환되어야 합니다. 프롬프트에 Strictly typed Go structs 제약을 부여해야 interface{}를 통한 불필요한 힙 이스케이프 및 타입 단언 런타임 패닉을 차단할 수 있습니다.
2. 단일 스레드 비동기(GIL) ➔ 진정한 병렬 동시성(M:N Goroutines)
Python asyncio는 단일 스레드 협력형 멀티태스킹이므로 공유 메모리 Race Condition이 상대적으로 적습니다. 하지만 Go는 실제 멀티코어에 스케줄링되므로, 버퍼드 채널과 워커 풀 제약 없이 단순 번역하면 데이터 레이스가 발생합니다.
3. HTTP 커넥션 풀링과 defer 리소스 관리
Go의 http.Response.Body는 닫는 것뿐만 아니라 내부 데이터를 완전히 소진(io.Copy(io.Discard, ...))해야 Keep-Alive 소켓을 재사용합니다. 프롬프트가 이 패턴을 규정해야 소켓 고갈을 방지합니다.
5단계: 실전 응용 팁 & 플레이스홀더 템플릿
컴파일 및 런타임 필수 검증 명령어
go test -race ./...: 채널 및 공유 자원 Race Condition 정밀 추적golangci-lint run:errcheck,contextcheck등 정적 분석go test -bench=. -benchmem: 할당당 바이트(B/op)와 allocs/op 제로 할당 여부 측정