스택오버플로우 복붙러 & ChatGPT 의존러 — 검색의 달인들

스택오버플로우 복붙러 & ChatGPT 의존러 — 검색의 달인들

"개발자의 진짜 실력은 구글링 실력이다 — 라고 자위하던 시절이 있었음"


복붙의 황금기: 스택오버플로우 시대

2008년 스택오버플로우가 탄생하면서 개발자 생태계에 혁명이 일어남. 그 전에는 뭐 하나 모르면 600쪽짜리 O'Reilly 책을 뒤져야 했는데, 이제 구글에 에러 메시지만 복붙하면 답이 나오는 세상이 된 거임.

개발자 진화의 역사:

1990년대: man page 읽기 → 동료한테 물어보기 → 포기
2000년대: 구글링 → 포럼 뒤지기 → 아직도 모름
2010년대: 구글링 → 스택오버플로우 → 복붙 → 작동함 → 왜 되는지 모름
2020년대: ChatGPT한테 물어봄 → 복붙 → 작동함 → 왜 되는지 모름
2025년대: AI가 코드 다 짬 → 복붙 → 안 됨 → AI한테 왜 안 되냐고 물어봄
스택오버플로우 재미있는 통계
  • 가장 많이 조회된 질문: "How do I undo the most recent local commits in Git?" (1400만+ 조회)
  • 즉, 전 세계 개발자 1400만 명이 git commit을 잘못 했다는 뜻
  • 개발자는 실수하는 존재. 우리 모두 평등하게 무능함.

스택오버플로우 복붙러의 하루

typescript
// 09:00 - 출근. 어제 머지된 코드에서 버그 발견
// Google: "TypeError: Cannot read property 'map' of undefined"
// 스택오버플로우 검색 결과 37개 중 2012년 답변 복붙

// 09:30 - 복붙한 코드가 jQuery 기반이라 React 프로젝트에 안 맞음
// Google: "react equivalent of jquery each"
// 또 복붙

// 10:00 - 이제 다른 에러 발생
// Google: "Uncaught TypeError: this.setState is not a function"
// 2016년 답변 발견: bind(this) 하라고 함
// 우리 프로젝트는 함수형 컴포넌트인데... 복붙함

// 10:30 - 클래스 컴포넌트 문법이 섞여서 더 안 됨
// Google: "convert class component to functional component react"
// 변환 가이드 복붙

// 11:00 - 원래 버그는 아직 안 고침. 새로운 버그 3개 추가됨

복붙의 수준 분류

typescript
// Level 1: 순수 복붙 (이해 0%)
// 스택오버플로우에서 가장 좋아요 많은 답변을 그대로 복붙
// "되긴 되는데 왜 되는지 모름"

// Level 2: 변수명만 바꾸는 복붙 (이해 10%)
// 원본: const foo = bar.filter(x => x.active)
// 변형: const users = data.filter(x => x.active)
// "이해했음" 이라고 착각하는 단계

// Level 3: 여러 답변을 프랑켄슈타인 복붙 (이해 30%)
// 답변 A에서 함수 구조 가져오고
// 답변 B에서 에러 처리 가져오고
// 답변 C에서 타입 정의 가져옴
// 세 개가 서로 호환이 안 됨

// Level 4: 복붙 후 커스터마이징 (이해 60%)
// 원리를 어느 정도 이해하고 프로젝트에 맞게 수정
// 사실 이 정도면 "참고"라고 부르는 게 맞음

// Level 5: 답변을 읽고 영감을 받아 직접 작성 (이해 90%)
// 이건 복붙이 아니라 "학습"임
// 여기까지 오면 졸업한 거임

ChatGPT 의존러의 탄생

2022년 11월, ChatGPT가 등장하면서 개발자 생태계가 다시 한번 뒤집어짐. 스택오버플로우 트래픽이 폭락하고, 새로운 종족이 탄생했음.

ChatGPT 의존러 — 일명 "프롬프트 엔지니어"

ChatGPT 의존러의 위험 신호
  • 간단한 for 루프도 ChatGPT한테 물어봄
  • "ChatGPT가 이렇게 하라고 했어요"를 코드 리뷰 코멘트에 씀
  • AI가 생성한 코드를 검증 없이 프로덕션에 배포함
  • 에러가 나면 에러 메시지를 AI한테 던지고 나온 답을 또 복붙함
  • AI가 만든 코드의 로직을 설명하지 못함

ChatGPT 의존러의 코딩 패턴

typescript
// ChatGPT 의존러의 전형적인 대화:

// 프롬프트 1: "React에서 사용자 목록을 보여주는 컴포넌트 만들어줘"
// → AI가 완벽한 코드를 줌
// → 복붙. 작동함. 좋아.

// 프롬프트 2: "여기서 페이지네이션 추가해줘"
// → AI가 기존 코드 무시하고 새로운 코드를 줌
// → 복붙. 기존 코드와 충돌. 안 됨.

// 프롬프트 3: "이 에러 고쳐줘: TypeError: Cannot read property..."
// → AI가 또 다른 코드를 줌
// → 복붙. 다른 에러 발생.

// 프롬프트 4: "이 에러도 고쳐줘"
// → 무한 루프 진입

// 결과물: AI가 3번 리라이트한 프랑켄슈타인 코드
// 아무도 이해 못 함. AI도 이제 컨텍스트 잃어버림.

스택오버플로우 복붙 vs ChatGPT 복붙 비교

typescript
// 같은 문제: "배열에서 중복 제거"

// 스택오버플로우 복붙 (2015년 답변)
// → 검증됨 (좋아요 3,847개)
// → 댓글에 엣지 케이스 경고 있음
// → 성능 벤치마크까지 있음
const unique = [...new Set(array)];

// ChatGPT 복붙
// → 장황한 설명과 함께 옴
// → 보통은 맞지만 가끔 환각함
// → 존재하지 않는 라이브러리를 추천하기도 함

// ChatGPT가 실제로 추천한 적 있는 존재하지 않는 것들:
// - npm 패키지 "array-unique-deep" (없음)
// - Array.prototype.unique() (이런 메서드 없음)
// - lodash.deduplicate (이런 함수 없음)
// 자신감 있게 추천하니까 더 무서움

복붙의 함정들

1. 라이선스 지뢰밭

typescript
// 스택오버플로우의 코드는 CC BY-SA 4.0 라이선스임.
// 이게 뭔 뜻이냐면:

// 1. 출처를 밝혀야 함 (Attribution)
// → "이 코드는 스택오버플로우에서 가져왔습니다"
// → 솔직히 아무도 안 함

// 2. 같은 라이선스로 공유해야 함 (ShareAlike)
// → 니 프로젝트가 MIT 라이선스인데 CC BY-SA 코드를 넣으면
// → 라이선스 충돌 발생
// → 법적으로는 문제가 될 수 있음
// → "에이 설마 누가 고소하겠어" → 실제로 고소당한 사례 있음

// 회사 법무팀이 이걸 알면 경악할 내용:
// 프로덕션 코드의 약 30%가 스택오버플로우 출처라는 연구 결과가 있음
실제 사례: 라이선스 문제

2022년, 한 기업이 오픈소스 라이선스 감사를 받다가 스택오버플로우에서 가져온 코드 조각이 GPL 오염을 일으킨 걸 발견했음. 해당 모듈 전체를 다시 작성해야 했고, 3개월이 걸렸음. "그냥 복붙한 건데..." 가 3개월의 재작업으로 돌아온 사례.

2. 보안 취약점 복붙

typescript
// 스택오버플로우에서 가장 많이 복붙되는 보안 취약점 코드들

// 취약점 1: SQL 인젝션
// 2011년 답변이 아직도 복붙되고 있음
const query = `SELECT * FROM users WHERE name = '${userName}'`;
// userName에 "'; DROP TABLE users; --" 넣으면 테이블 날아감
// 올바른 방법: parameterized query 사용

// 취약점 2: XSS (Cross-Site Scripting)
// 사용자 입력을 검증 없이 DOM에 직접 렌더링하면
// 악성 스크립트가 삽입될 수 있음
// 올바른 방법: 입력 값 sanitize + 안전한 렌더링 API 사용

// 취약점 3: 인증서 검증 비활성화
// "SSL 에러 나요" 질문의 최다 좋아요 답변이
// TLS 검증을 비활성화하라고 함
// "야 이거 하니까 됨!!" ← 보안 구멍을 뚫어서 된 거임

// 취약점 4: CORS 전부 허용
// "CORS 에러 나요" 질문의 답변
// app.use(cors({ origin: '*' }));
// "오 됐다!" ← 전 세계 누구나 니 API 호출 가능하게 만든 거임

3. 버전 호환성 시한폭탄

typescript
// 2019년 답변을 2026년 프로젝트에 복붙하면 생기는 일

// 답변 (2019년, React 16 기준):
class UserList extends React.Component {
  componentWillMount() {  // React 17에서 deprecated, 18에서 제거됨
    this.fetchUsers();
  }

  componentWillReceiveProps(nextProps) { // 이것도 제거됨
    if (nextProps.filter !== this.props.filter) {
      this.fetchUsers(nextProps.filter);
    }
  }

  render() {
    return (
      <div>
        {this.state.users.map(user => (
          <div key={user.id}>{user.name}</div>
        ))}
      </div>
    );
  }
}

// 2026년 프로젝트에 이걸 넣으면:
// - componentWillMount → 존재하지 않음
// - componentWillReceiveProps → 존재하지 않음
// - 클래스 컴포넌트 → 팀원들의 따가운 눈초리

// 복붙러: "스택오버플로우에서 되는 코드인데 왜 안 되죠?"
// 팀원: "그 답변 7년 전 거잖아요..."

검색도 실력이다: 올바른 검색 방법

복붙 자체가 나쁜 건 아님. 문제는 생각 없이 복붙하는 것. 검색을 잘 하는 것도 진짜 실력임.

좋은 검색의 기술

typescript
// 나쁜 검색 (0.1x 검색법)
// Google: "react not working"
// → 결과 3억 개. 도움 안 됨.

// Google: "error in my code"
// → ???

// Google: "javascript why"
// → 이건 철학적 질문임

// 좋은 검색 (효과적인 검색법)
// Google: "React useEffect cleanup function not called unmount"
// → 구체적인 기술 + 구체적인 증상 + 구체적인 상황

// Google: "TypeScript generic constraint extends keyof"
// → 정확한 키워드 조합

// Google: "Next.js 16 app router fetch cache revalidate"
// → 버전 명시 (이게 중요!)

스택오버플로우 답변 평가법

typescript
// 좋은 답변의 신호:
// - 좋아요 수가 높음 (100+)
// - 최근에 수정됨 (3년 이내)
// - 댓글에서 엣지 케이스 토론이 있음
// - 왜 이렇게 하는지 설명이 있음
// - 대안 접근법도 언급됨
// - 답변자의 reputation이 높음 (10k+)

// 나쁜 답변의 신호:
// - 답변이 2015년 이전이고 수정 이력 없음
// - "이거 해보세요" 한 줄짜리
// - 댓글에 "이거 더 이상 안 됨" 이 달려있음
// - any 를 남발함
// - 보안 관련 설정을 비활성화함
// - "내 프로젝트에서는 됨" ← 니 프로젝트가 특이한 거임
효과적인 검색 프레임워크

검색할 때 이 순서로 접근하면 됨:

  1. 에러 메시지를 정확히 읽기 — 놀랍게도 대부분 답이 에러 메시지에 있음
  2. 공식 문서를 먼저 확인 — 구글 검색에 "site:nextjs.org" 추가
  3. 버전을 명시 — "React 19" 를 검색어에 포함
  4. GitHub Issues 확인 — 라이브러리 버그일 수도 있음
  5. 스택오버플로우/AI는 마지막 — 위 4단계로 해결 안 될 때

이 순서를 지키면 복붙해도 양질의 코드를 복붙하게 됨.

AI를 제대로 활용하는 방법

AI 도구를 아예 안 쓰는 건 비효율적임. 잘 쓰는 게 중요함.

typescript
// 나쁜 AI 활용
// "사용자 관리 시스템 만들어줘" → 500줄 코드 받음 → 전부 복붙

// 좋은 AI 활용
// 1. 구체적인 질문
// "TypeScript에서 discriminated union 타입으로
//  API 응답을 모델링하려는데, 에러 케이스 처리 패턴 보여줘"

// 2. AI 답변을 검증
type ApiResponse<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; error: { code: string; message: string } };

// → 이 패턴이 우리 프로젝트에 맞는지 확인
// → 타입 추론이 제대로 되는지 직접 테스트
// → 기존 코드와 일관성 있는지 확인

// 3. 학습 도구로 활용
// "이 코드에서 exhaustive check가 왜 필요한지 설명해줘"
// → 이해한 후에 적용. 이해 없이 복붙하지 않음.

function handleResponse<T>(response: ApiResponse<T>) {
  switch (response.status) {
    case 'success':
      return response.data;
    case 'error':
      throw new Error(response.error.message);
    default:
      // exhaustive check
      const _exhaustive: never = response;
      throw new Error(`Unhandled status: ${_exhaustive}`);
  }
}

복붙러에서 개발자로: 졸업 가이드

복붙에서 벗어나는 5단계

1단계: 인정 — "나 복붙 좀 많이 하는 것 같음" 인정하기

2단계: 이해 — 복붙하기 전에 코드가 뭘 하는지 한 줄씩 읽기. 모르는 메서드가 있으면 MDN이나 공식 문서에서 확인.

3단계: 변형 — 복붙한 코드를 프로젝트 스타일에 맞게 수정하기. 변수명, 에러 처리, 타입 등을 직접 고치기.

4단계: 재작성 — 복붙한 코드를 참고만 하고, 직접 처음부터 작성하기. 이 과정에서 진짜 이해가 됨.

5단계: 기여 — 내가 겪은 문제와 해결법을 블로그/스택오버플로우에 공유하기. 복붙 당하는 입장이 되어보기.

마지막으로: 모든 개발자는 검색한다

시니어 개발자도 검색함. 차이는:

typescript
// 주니어: 검색 → 첫 번째 결과 복붙 → 안 되면 두 번째 결과 복붙
// 시니어: 검색 → 여러 결과 비교 → 원리 이해 → 프로젝트에 맞게 적용

// 진짜 시니어가 검색하는 것들:
// - 라이브러리의 소스 코드 (GitHub)
// - RFC 문서 (표준 스펙)
// - 벤치마크 결과 (성능 비교)
// - 릴리즈 노트 (변경사항 확인)
// - 보안 어드바이저리 (취약점 확인)

// 주니어가 검색하는 것들:
// - "how to center a div"
// - "왜 안됨"
// - 에러 메시지 전체 (따옴표 없이)

검색은 부끄러운 게 아님. 기억력에는 한계가 있고, 기술은 계속 변하니까 전부 외울 수도 없음.

중요한 건 검색 결과를 어떻게 소화하느냐임. 복붙은 시작점이지, 도착점이 아님.


"나는 스택오버플로우에서 복사했고, ChatGPT에서 붙여넣기 했으며, 그것을 내 코드라 불렀다." — 현대 개발자의 고백