본문 바로가기
Tech-BYOD

AI가 코드를 짜줬는데, 왜 퇴근은 제가 못하죠?

by simhead-peterkim 2026. 8. 1.

AI가 코드를 짜줬는데, 왜 퇴근은 제가 못하죠?

 

Introduction

한때 저는 순진했습니다. 아니, 순진했다기보다 조금 눈이 반짝였습니다. “이제 AI가 코드를 다 짜주니까 개발자는 프롬프트만 잘 치면 되는 시대가 왔다.” 이 문장을 마음속으로 세 번쯤 외우던 시절이 있었습니다. 개발자는 더 이상 반복문과 보일러플레이트에 갇히지 않고, 우아하게 요구사항을 말하며, AI는 옆에서 커피도 안 마시고 불평도 안 하고 코드를 척척 뽑아주는 그런 미래 말입니다. 저는 그 미래가 거의 도착했다고 믿었습니다. 그리고 그날 밤, 터미널은 제게 현실을 알려주었습니다.

처음 AI가 3초 만에 200줄짜리 코드를 뽑아냈을 때의 감동은 아직도 선명합니다. 화면에는 클래스가 있었고, 타입 힌트가 있었고, 예외 처리처럼 보이는 무언가가 있었고, 심지어 주석까지 있었습니다. 저는 잠시 신성한 장면을 본 사람처럼 의자에 기대어 생각했습니다. “오늘 칼퇴각이다.” 그 순간만큼은 AI가 코드 생성 도구가 아니라 퇴근의 수호신처럼 보였습니다. 인간은 드디어 야근이라는 원죄에서 해방되는가 싶었습니다.

하지만 AI는 3초 만에 코드를 짰고, 그 코드가 일으킨 버그를 잡는 데 3시간이 걸렸습니다. AI가 만든 건 코드가 아니라 고급형 수수께끼였습니다. 함수 이름은 너무 아름다웠고, 구조는 너무 그럴듯했고, 설명은 너무 자신만만했습니다. 문제는 실행하면 틀렸다는 것입니다. 더 무서운 점은 틀렸는데도 코드는 예뻤다는 것입니다. 개발자에게 가장 잔인한 코드는 지저분한 코드가 아닙니다. 읽을수록 설득력 있는데 실제 비즈니스 로직은 반대로 가는 코드입니다.

이 글은 AI 코딩 도구를 쓰며 느낀 환상과 현실, 그리고 “AI가 다 해준다더니 왜 내가 아직 사무실 불을 끄고 있나”라는 현대 개발자의 근본적인 의문을 다룹니다. 농담처럼 시작하지만, 핵심은 꽤 진지합니다. AI는 개발자의 시간을 줄여줄 수 있습니다. 다만 아무렇게나 쓰면 줄어든 시간보다 더 많은 디버깅 청구서를 들고 옵니다. 결국 AI 시대의 개발자는 코드를 덜 치는 사람이 아니라, 생성된 코드를 더 정확하게 판단하고 운영 환경에 맞게 길들이는 사람이 됩니다.

AI 코딩의 3대 환각과 야근의 전조 증상

AI 코딩을 처음 접하면 가장 먼저 느끼는 것은 속도입니다. 두 번째로 느끼는 것은 불안입니다. 세 번째로 느끼는 것은 “내가 지금 뭘 승인한 거지?”라는 뒤늦은 자아 성찰입니다. AI가 만들어준 코드는 종종 완성품처럼 보이지만, 실제로는 검증되지 않은 초안입니다. 특히 개발자를 야근으로 인도하는 전조 증상은 대체로 세 가지 형태로 나타납니다.

1. 존재하지 않는 라이브러리와 완벽한 메소드 이름

가장 허탈한 순간은 AI가 너무 자연스럽게 존재하지 않는 라이브러리를 추천할 때입니다. 패키지 이름이 그럴듯하고, 메소드 이름도 완벽합니다. 공식 문서에 있을 것 같은 냄새가 납니다. 그런데 설치하려고 하면 없습니다. 검색해도 없습니다. 깃허브에도 없습니다. 마치 평행우주에서는 유명한 라이브러리인데, 우리 우주에는 아직 배포되지 않은 느낌입니다.

AI는 마치 면접장에서 엄청나게 당당하게 거짓말하는 인턴 같습니다. 말은 너무 잘하는데, 실제로 일을 시켜보면 존재하지 않는 패키지를 import하고 있어요. 더 얄미운 것은 그 코드가 “딱 내가 원하던 모양”이라는 점입니다. 이름도 좋고, API도 직관적이고, 예제도 깔끔합니다. 다만 현실에 없습니다. 현실에 없다는 건 소프트웨어 세계에서 꽤 치명적인 단점입니다.

2. 문법은 맞지만 비즈니스 로직은 반대로 달리는 코드

두 번째 환각은 더 위험합니다. 코드는 실행됩니다. 테스트도 일부 통과합니다. 린터도 조용합니다. 그런데 실제 요구사항과 반대로 움직입니다. 예를 들어 “임계값 이상이면 알림을 보내라”고 했는데, AI가 “임계값 미만이면 정상 처리” 구조를 만들면서 알림 조건을 뒤집어버립니다. 문법 오류는 친절합니다. 빨간 줄이라도 그어줍니다. 하지만 비즈니스 로직 오류는 조용히 웃으며 운영 환경으로 걸어 들어갑니다.

AI는 문장의 확률을 잘 맞추지만, 조직의 암묵지까지 자동으로 이해하지는 못합니다. “활성 사용자”가 우리 회사에서는 최근 30일 로그인 사용자인지, 결제 중인 사용자인지, 관리자 승인까지 끝난 사용자인지 알 수 없습니다. 그런 맥락을 인간이 주지 않으면 AI는 가장 일반적으로 보이는 정의를 가져옵니다. 그리고 일반적인 정의는 우리 서비스에서는 틀린 정의일 수 있습니다.

3. 성공 케이스만 빛나는 Edge Case 무시

세 번째 환각은 성공 케이스 편애입니다. AI가 만들어준 코드는 처음 시연할 때 아주 예쁩니다. 정상 입력을 넣으면 정상 출력이 나옵니다. README의 예제처럼 세상이 착하면 문제없이 동작합니다. 하지만 사용자는 README처럼 살지 않습니다. 네트워크는 끊기고, JSON은 비어 있고, 토큰은 만료되고, 하드웨어는 바쁘고, 환경 변수는 빠지고, 요청은 두 번 들어오고, 날짜는 타임존을 타고 어딘가로 사라집니다.

AI는 종종 “성공 케이스”만 예쁘게 짜주고, 예외 처리와 에러 로깅은 쿨하게 생략합니다. 마치 “실패는 마음가짐의 문제입니다”라고 말하는 사람 같습니다. 하지만 운영 환경에서 실패는 마음가짐이 아니라 알림입니다. 그리고 그 알림은 보통 퇴근 직전에 옵니다.

 

환각 유형 겉보기 증상 실제 위험 퇴근 전 확인법
존재하지 않는 라이브러리 API가 너무 깔끔하고 설명이 친절함 설치, 배포, 보안 검토 단계에서 막힘 공식 문서, 패키지 저장소, 버전 호환성 확인
가짜 로직 문법과 타입은 맞지만 결과가 이상함 정상 동작처럼 보이는 장애 발생 요구사항을 테스트 케이스로 먼저 고정
Edge Case 생략 데모 입력에서는 잘 동작함 운영 환경의 비정상 입력에서 장애 빈 값, 타임아웃, 재시도, 권한 오류 점검

AI 코드는 “그럴듯함”의 밀도가 높습니다. 그래서 개발자는 더 천천히 읽어야 합니다. 못생긴 코드는 의심하기 쉽지만, 예쁜 코드는 믿고 싶어지기 때문입니다.

AI 시대에 개발자의 진짜 역할: 트러블슈팅 사례

실무에서 가장 크게 느끼는 차이는 하드웨어와 운영 환경입니다. AI는 일반적인 클라우드 서버나 넉넉한 노트북 환경을 기준으로 답할 때가 많습니다. 하지만 실제 프로젝트는 늘 제약을 가집니다. 메모리는 부족하고, 네트워크는 불안정하고, 장치는 특정 포트에 물려 있고, 드라이버 버전은 오래됐고, 배포 환경은 문서처럼 깨끗하지 않습니다. 특히 ESP32, Jetson, Raspberry Pi 같은 보드나 카메라, 센서, 시리얼 장치가 얽힌 프로젝트에서는 AI의 일반론이 아주 빠르게 벽에 부딪힙니다.

예를 들어 ESP32-S3에서 간단한 센서 값을 HTTPS API로 보내는 코드를 만든다고 해봅시다. 저는 AI에게 “Wi-Fi 연결 후 센서 데이터를 서버로 POST하고 실패하면 재시도하는 코드”를 부탁했습니다. AI는 멋진 C++ 예제를 줬습니다. 문제는 그 코드가 큰 JSON 라이브러리와 동적 문자열 조합을 과하게 쓰고, 매 루프마다 클라이언트를 새로 만들고, 타임아웃을 명확히 설정하지 않았다는 점입니다. 개발 PC에서 읽기에는 괜찮았지만, 제한된 메모리와 불안정한 Wi-Fi 환경에서는 몇 시간 뒤 힙 메모리가 줄어들고 재부팅이 발생했습니다.

그때 떠오른 문장이 있습니다. “AI가 자꾸 메모리 8MB짜리 보드에 100MB짜리 라이브러리를 쓰라고 추천합니다. AI에게 내 보드는 양자 컴퓨터인 모양입니다.” 물론 실제로는 AI가 악의적으로 그런 것이 아닙니다. 제가 환경 제약을 충분히 주지 않았고, AI는 흔하고 편한 라이브러리 중심으로 답했을 뿐입니다. 하지만 운영자는 저입니다. 보드가 재부팅되면 AI가 현장에 와서 USB 케이블을 다시 꽂아주지 않습니다.

Before: 그럴듯하지만 현장에서는 불안한 코드

아래 코드는 AI가 자주 제안하는 형태의 축약 예시입니다. 문법만 보면 괜찮아 보이지만, 임베디드 환경에서는 동적 문자열 증가, 타임아웃 누락, 응답 처리 누락, 연결 실패 시 자원 정리 불명확 같은 문제가 쌓일 수 있습니다.

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

void sendReading(float temperature, float humidity) {
  HTTPClient http;
  DynamicJsonDocument doc(4096);

  doc["temperature"] = temperature;
  doc["humidity"] = humidity;
  doc["device"] = "esp32-sensor-01";

  String body;
  serializeJson(doc, body);

  http.begin("https://api.example.com/readings");
  http.addHeader("Content-Type", "application/json");
  http.POST(body);
}

After: 제약을 반영해 고친 코드

수정 방향은 단순했습니다. 보드의 메모리 여유를 전제로 하지 않고, 요청 수명을 명확히 닫고, 타임아웃을 걸고, 실패 로그를 남기고, JSON 버퍼를 작게 제한했습니다. 실제 프로젝트라면 인증 토큰, TLS 인증서, 백오프 정책까지 더 점검해야 합니다.

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

bool sendReading(float temperature, float humidity) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("[api] wifi disconnected");
    return false;
  }

  StaticJsonDocument<192> doc;
  doc["t"] = temperature;
  doc["h"] = humidity;
  doc["id"] = "esp32-s3-a01";

  char body[192];
  size_t len = serializeJson(doc, body, sizeof(body));
  if (len == 0 || len >= sizeof(body)) {
    Serial.println("[api] json buffer too small");
    return false;
  }

  HTTPClient http;
  http.setTimeout(3000);

  if (!http.begin("https://api.example.com/readings")) {
    Serial.println("[api] http begin failed");
    return false;
  }

  http.addHeader("Content-Type", "application/json");
  int status = http.POST((uint8_t*)body, len);
  http.end();

  if (status < 200 || status >= 300) {
    Serial.printf("[api] post failed: %d\n", status);
    return false;
  }

  return true;
}

이 사례에서 개발자의 역할은 단순히 코드를 손으로 다시 쓰는 것이 아니었습니다. 진짜 역할은 “AI가 놓친 문맥”을 찾아내는 일이었습니다. 보드의 메모리 제약, Wi-Fi 끊김, TLS 연결 비용, JSON 버퍼 크기, 실패 시 로그의 밀도, 재시도 간격, 장시간 실행 후 힙 변화 같은 운영 조건은 프롬프트에 없으면 잘 반영되지 않습니다. AI는 대체로 가장 깔끔한 답을 줍니다. 하지만 현장은 가장 지저분한 입력을 줍니다.

 

점검 항목 AI 초안의 문제 수정한 기준
메모리 DynamicJsonDocument와 String 사용 작은 StaticJsonDocument와 고정 버퍼 사용
네트워크 Wi-Fi 상태와 타임아웃 확인 부족 연결 상태 확인, 3초 타임아웃 설정
자원 정리 POST 이후 정리 흐름이 불명확 성공과 실패 경로 모두에서 http.end() 호출
운영 로그 실패 원인을 알기 어려움 각 실패 지점에 원인 로그 추가

AI를 쓸 때 퇴근 시간을 앞당기는 3가지 프롬프트 생존 법칙

AI를 잘 쓰면 퇴근 시간이 앞당겨질 수 있습니다. 다만 “코드를 짜줘” 한 문장으로 시작하면 운이 좋을 때는 초안이 나오고, 운이 나쁠 때는 야근의 씨앗이 나옵니다. AI에게 일을 시킬 때는 부장님한테 보고서 쓰듯 아주 구체적이고 치사할 정도로 제약 조건을 달아야 합니다. 안 그러면 혼자 상상의 나래를 펼칩니다. 상상력은 창작에는 좋지만, 배포 전날에는 조금 무섭습니다.

1. “코드를 짜줘” 대신 “리뷰해줘”부터 시작하기

처음부터 구현을 맡기면 AI는 빈칸을 채우기 시작합니다. 문제는 그 빈칸이 당신의 업무 맥락이라는 점입니다. 그래서 먼저 “이 설계에서 빠진 위험을 찾아줘”, “이 요구사항을 구현하기 전에 질문해야 할 내용을 정리해줘”, “이 구조가 현재 코드베이스에 맞는지 리뷰해줘”라고 물어보는 편이 좋습니다. AI를 타자수로 쓰기 전에 리뷰어로 쓰면, 나중에 되돌릴 변경이 줄어듭니다.

좋은 시작 프롬프트 예시

이 기능을 바로 구현하지 말고 먼저 리뷰해줘.
현재 환경은 ESP32-S3, FreeRTOS, PSRAM 사용 가능, OTA 업데이트 있음.
센서 데이터는 10초마다 HTTPS POST로 전송하고, 네트워크가 끊겨도 메인 루프가 막히면 안 돼.

1. 요구사항에서 빠진 질문을 먼저 제시해줘.
2. 메모리, 네트워크, 재시도, 로그 관점의 위험을 표로 정리해줘.
3. 구현은 내가 승인한 뒤에 최소 변경으로 제안해줘.

2. 제약 조건과 경계선을 명확히 주기

AI는 모르는 것을 모른다고 말하기보다 일반적인 상황을 가정하는 경우가 많습니다. 그래서 제약 조건을 앞에 세워야 합니다. “Python으로 API 클라이언트 만들어줘”보다 “Python 3.8, 외부 의존성 추가 불가, 요청 타임아웃 5초, Flask request thread를 막지 않게, 기존 로깅 포맷 유지”가 훨씬 낫습니다. “ESP32-S3 FreeRTOS 환경이고 PSRAM을 써야 해”처럼 하드웨어와 런타임 제약을 명시하면, AI의 답변이 현실 쪽으로 내려옵니다.

특히 운영 환경에서는 금지 조건이 중요합니다. “새 라이브러리 추가하지 마”, “공개 API 바꾸지 마”, “기존 설정 파일 덮어쓰지 마”, “카메라 장치 인덱스 변경하지 마”, “시리얼 포트는 타임아웃을 반드시 둬” 같은 문장은 AI의 상상력을 줄이는 대신 내 퇴근 가능성을 올립니다.

3. 예외 처리와 에러 로그를 먼저 물어보기

대부분의 사람은 AI에게 “성공하는 코드”를 먼저 부탁합니다. 하지만 실전에서는 “어디서 터지는지”를 먼저 묻는 편이 더 유용합니다. “이 코드가 터질 수 있는 3가지 이유를 말해줘”, “각 실패 지점에서 어떤 로그를 남겨야 해?”, “네트워크가 30초 동안 끊기면 어떤 상태가 돼?”, “메모리가 부족하면 어떻게 복구하지?”처럼 질문을 뒤집어보면 AI가 놓친 구멍이 드러납니다.

 

나쁜 요청 더 나은 요청 기대 효과
코드 짜줘 먼저 설계 리뷰와 위험 목록을 줘 불필요한 구현 폭주를 줄임
알아서 예외 처리해줘 실패 가능한 경로 5개와 로그 메시지를 먼저 제안해줘 운영 중 원인 추적이 쉬워짐
최신 라이브러리로 해줘 현재 런타임에서 검증된 의존성만 써줘 배포 환경 호환성 위험 감소
전체 리팩터링해줘 공개 동작 유지, 파일 2개 이하, 테스트 포함으로 고쳐줘 변경 범위와 리뷰 비용 감소

AI 코딩 체크리스트: 퇴근 전 마지막 방어선

AI가 만든 코드를 받으면 바로 “LGTM” (Looks Good To Me) 을 외치고 싶어집니다. 하지만 퇴근 전에는 짧은 체크리스트가 필요합니다. 체크리스트는 개발자를 귀찮게 하려고 있는 것이 아니라, 미래의 나를 구하려고 있습니다. 특히 AI가 만든 코드는 변경 속도가 빠르기 때문에 검증 속도도 함께 올라가야 합니다. 빠르게 만들고 느리게 검증하면 결국 전체 일정은 느려집니다.

# AI가 수정한 뒤 최소 확인 루틴
git diff --stat
git diff -- blogs/ core/ app.py
python3 -m py_compile app.py core/*.py

# 하드웨어나 외부 API가 필요한 테스트는
# 로컬에서 가능한 범위와 실제 장비 검증 범위를 분리해서 기록합니다.

검증할 때는 “작동하나?”보다 “어떻게 실패하나?”를 봐야 합니다. 성공 경로는 대개 데모에서 이미 확인됩니다. 진짜 문제는 빈 입력, 느린 응답, 권한 없음, 중복 요청, 타임아웃, 메모리 부족, 장치 점유, 설정 누락 같은 경계 조건입니다. AI에게 이 목록을 먼저 만들게 하고, 사람은 그 목록이 실제 운영 환경과 맞는지 확인하는 방식이 가장 현실적입니다.

Troubleshooting / Common Gotchas

증상 가능한 원인 먼저 볼 것 AI에게 다시 물을 질문
로컬에서는 되는데 배포에서 실패 런타임 버전, 환경 변수, 의존성 차이 버전 출력, 설정 파일, 배포 로그 이 코드가 Python 3.8과 ARM64에서 깨질 지점을 찾아줘
몇 시간 뒤 느려짐 메모리 누수, 연결 해제 누락, 버퍼 증가 메모리 사용량, 열린 연결, 반복 루프 장시간 실행 시 누적되는 자원을 찾아줘
가끔 요청이 멈춤 타임아웃 누락, 블로킹 I/O, 재시도 폭주 요청 제한 시간, 스레드 상태, 네트워크 로그 모든 외부 호출에 타임아웃과 실패 경로를 추가해줘
테스트는 통과하는데 사용자 플로우가 이상함 happy path만 테스트, 비즈니스 조건 오해 요구사항 문서, 실제 사용자 입력, 경계값 이 테스트가 놓친 반대 조건과 경계값을 제안해줘

Frequently Asked Questions

AI가 만든 코드는 어느 정도까지 믿어도 되나요?

초안으로는 충분히 믿을 만하지만, 검증 없는 완성품으로 믿기는 어렵습니다. 특히 인증, 결제, 권한, 데이터 삭제, 하드웨어 제어, 장시간 실행 서비스는 사람이 반드시 diff와 실패 경로를 확인해야 합니다.

프롬프트를 잘 쓰면 버그가 없어질까요?

버그가 사라지지는 않지만, 버그의 종류와 범위는 줄일 수 있습니다. 환경 제약, 금지 조건, 성공 기준, 예외 처리 요구사항을 명확히 주면 AI가 일반론 대신 실제 상황에 가까운 코드를 제안할 가능성이 높아집니다.

AI 코딩에서 가장 먼저 물어봐야 할 질문은 무엇인가요?

“이 코드가 터질 수 있는 이유는 무엇인가?”입니다. 성공 경로보다 실패 경로를 먼저 보면 로그, 타임아웃, 재시도, 메모리, 권한 같은 운영 품질을 초반부터 챙길 수 있습니다.

임베디드나 하드웨어 프로젝트에서도 AI 코딩이 유용한가요?

유용합니다. 다만 하드웨어 제약을 아주 구체적으로 줘야 합니다. 보드 종류, 메모리, OS, 드라이버, 포트, 타임아웃, 장치 점유 가능성, 장시간 실행 조건을 명시하지 않으면 AI가 데스크톱처럼 넉넉한 환경을 가정할 수 있습니다.

Conclusion

AI는 개발자를 대체한다기보다 개발자의 생산성을 높여주는 지치지 않는 자웅동체 인턴에 가깝습니다. 질문하면 답하고, 시키면 만들고, 밤새 불평하지 않습니다. 문제는 가끔 너무 자신 있게 틀리고, 존재하지 않는 라이브러리를 가져오고, 내 작은 보드를 데이터센터 서버처럼 대한다는 것입니다. 그래서 운전대는 여전히 사람이 잡아야 합니다. AI가 가속 페달이라면, 개발자는 브레이크와 핸들과 보험 약관을 함께 들고 있는 사람입니다.

결국 최종 책임을 지고 버그를 잡는 것은 인간 개발자의 통찰력과 경험입니다. 이것이 검색 엔진이 말하는 E-E-A-T와도 맞닿아 있습니다. Experience, Expertise, Authoritativeness, Trustworthiness는 그럴듯한 문장만으로 생기지 않습니다. 실제로 터진 코드를 보고, 로그를 읽고, 원인을 좁히고, 다시 같은 실수를 막는 과정에서 쌓입니다. AI는 그 과정을 빠르게 도와줄 수 있지만, 대신 살아주지는 않습니다.

오늘도 AI가 싼 똥(?)을 치우며 퇴근길 지하철에 몸을 실읍니다. 그래도 사랑한다 Copilot... 덕분에 저는 더 빨리 코드를 만들고, 더 깊게 의심하고, 더 자주 테스트하게 되었습니다. 퇴근은 못 했지만, 적어도 오늘의 저는 어제보다 한 단계 더 성장했습니다. 약간의 다크서클과 함께요.

여러분은 AI가 준 코드 때문에 몇 시에 퇴근해보셨나요? 댓글로 슬픈 사연을 나눠주세요!

AI 코드 디버깅에 지친 개발자 야근 풍경