왜 AI-Generated 코드는 이상하게 느껴지는가
I’m happy to translate the article for you, but I need the full text of the post (the content you’d like translated). Could you please paste the article’s body here? I’ll keep the source line and all formatting exactly as you requested.
“AI‑like” 코드 느낌
아마도 한 번쯤은 느껴보셨을 겁니다. 풀 리퀘스트를 검토하고 있거나, 새로운 모듈을 인계받았을 때, 코드가 뭔가… 어색하게 느껴지는 순간 말이죠. 코드는 정상적으로 실행되고 로직도 맞지만, 마치 교과서에서 그대로 베껴 온 듯한, 인간 개발자들이 흔히 만들지 않는 그런 깔끔하고 정형화된 분위기가 있습니다. 소프트웨어의 언캐니 밸리—인간이 만든 것처럼 보이지만 실제로는 아닌 코드죠.
수천 줄의 인간 코드와 LLM이 만든 코드를 오랫동안 바라보면, 몇 가지 패턴이 눈에 띕니다. 아래는 현장에서 직접 체감한 “징후”들을 깊이 파헤친 내용입니다.
1. 포맷팅 — “완벽히 대칭”
어떤 모습인가
파일을 열었을 때 모든 라인이 정확히 80 문자에 맞춰 깔끔히 줄바꿈되고, 들여쓰기가 정확히 4 스페이스이며, 연산자 주변과 쉼표 뒤에 대칭적인, 거의 리듬감 있는 공백이 들어가 있는 모습을 상상해 보세요. 중괄호는 항상 같은 라인에 있거나(또는 항상 다음 라인에) 있고, 파일 구조는 딱딱하고 일관된 템플릿을 따릅니다.
왜 눈에 띄는가
이건 스타일 가이드를 학습한 모델이 만든 출력물이며, 마감 시간에 쫓기는 사람의 타이핑과는 다릅니다. 인간은 일관되지 않습니다. 어떤 파일에서는 2 스페이스, 다른 파일에서는 4 스페이스를 쓰기도 하고, 급할 때는 라인을 길게 늘리기도 합니다. 함수 정의에서는 중괄호를 새 줄에 두지만, 짧은 if 문에서는 같은 줄에 두는 식으로 상황에 따라 다르게 작성합니다. 이런 “과도하게 일관된” 포맷은 컨텍스트 전환, 피로, 혹은 일회성 스크립트에 대한 미적 완벽주의가 부족한 자연스러운 흐름이 결여되어 있습니다.
사람이 보통 다르게 하는 방식
사람이 작성한 코드베이스는 파일마다 약간씩 포맷이 다릅니다. 특히 여러 사람이 손을 댔을 경우 탭 너비가 섞여 있거나, 가끔씩 남는 공백이 있거나, 빈 줄이 일관되지 않을 수 있습니다. 마치 “살아있는” 코드처럼 보입니다.
JavaScript 예시
// AI‑like: 완벽히 대칭적이고, 규칙적.
function calculateTotal(items) {
let total = 0;
for (let item of items) {
total += item.price;
}
return total;
}
// Human‑like: 조금은 느슨하고, 실용적.
function calcTotal(items) {
let t = 0;
for (const item of items) t += item.price;
return t;
}
2. 주석 — “명백한 문서화”
어떤 모습인가
아무리 사소한 함수라도 형식적인 docstring 블록이 있습니다. 라인‑별 주석은 무엇을 하는지 설명하고, 왜 하는지는 거의 다루지 않습니다. 간단한 더하기 함수에도 소설 같은 설명이 달려 있습니다:
"""Adds two numbers together and returns the result."""
주석은 마치 코드를 큰소리로 읽어주는 자막처럼 느껴집니다.
왜 눈에 띄는가
AI는 구문을 “문서화”하도록 학습되었기 때문에, 의도를 설명하기보다 문법을 설명합니다. 언제 주석이 불필요한지 판단하는 인간의 감각이 부족합니다. 전체 코드베이스에 걸쳐 완벽하고 균일한 주석이 존재한다면 큰 레드 플래그가 됩니다. 사람은 보통 주석을 아끼며, 비직관적인 왜 를 설명하거나, 까다로운 워크어라운드, 혹은 // TODO: 같은 메모를 남길 때만 사용합니다.
사람이 보통 다르게 하는 방식
사람이 작성한 코드는 주석이 부족한 경우가 많습니다. 로직이 복잡하거나 비즈니스 규칙이 애매하거나, 미래의 자신에게 경고를 남겨야 할 때만 주석을 달죠. 일회성 스크립트나 내부 헬퍼 함수는 주석이 전혀 없을 때가 흔합니다.
Python 예시
# AI‑like: 명백한 내용까지 주석 처리.
# Create a list of numbers from 0 to 9
numbers = list(range(10))
# Loop through the list
for num in numbers:
# Print the current number
print(num)
# Human‑like: “왜”를 설명하는 주석.
# Use a fixed seed for reproducible test failures
random.seed(42)
# TODO: This breaks on February 29th, needs proper date lib
def is_weekday(date_str):
...
3. 네이밍 — “과도하게 설명적이거나 애매하게 일반적”
어떤 모습인가
변수와 함수 이름이 지나치게 설명적(calculate_total_amount_from_items_list)이거나, 같은 파일 안에서 극히 일반적(temp, data, value1)인 경우가 섞여 있습니다. 이름 자체는 문법적으로는 완벽하지만, 인간 코드에서 흔히 보이는 은어, 약어, 개인적인 특이점이 전혀 없습니다(예: getStuff() 혹은 final_final_parser_v3).
왜 눈에 띄는가
LLM은 기존에 학습한 네이밍 패턴을 조합해 이름을 만들기 때문에, 지나치게 길거나 지나치게 짧은 이름이 동시에 나타날 수 있습니다. 인간은 보통 컨텍스트와 팀 문화에 맞춰 이름을 짓고, 가끔은 유머나 약어를 섞어 사용합니다. 이런 “극단적인” 네이밍은 인위적인 느낌을 줍니다.
Source:
from their training data, which ranges from meticulously named open‑source projects to hastily written snippets. Without true intent, the names lack a consistent “voice.” They’re either robotically precise or oddly vague, and the style can shift dramatically within a single file.
How humans usually differ
Humans develop a personal or team naming culture. We might be consistently terse (getX) or consistently descriptive, but we’re rarely both in the same scope. We also break our own rules when we’re tired, leading to foo, bar, or thingy in a quick prototype.
Python Example
# AI‑like: Inconsistent, overly verbose, or oddly generic.
def process_user_input_data(input_data_string):
parsed_data = json.loads(input_data_string)
result = perform_calculation(parsed_data)
return result
def helper(a, b): # Suddenly super generic.
return a + b
# Human‑like: More consistent tone with accepted shorthand.
def parse_input(json_str):
data = json.loads(json_str)
return calc(data)
def add(a, b):
return a + b
4. Over‑Abstraction — “Design‑Pattern Overkill”
What it looks like
A simple 20‑line script is refactored into a class with three helper methods and an abstract base “just in case.” There’s an overuse of design patterns like singletons or factories for problems that don’t need them. You’ll see enumerate() where a simple loop would do, or unnecessary try…except blocks wrapping every operation.
Why it stands out
AI models are trained on “best‑practice” examples, which often emphasize abstraction, reusability, and defensive patterns. They tend to over‑apply these patterns, resulting in code that feels academic and over‑engineered for the task at hand. It’s solving for textbook correctness, not for shipping.
How humans usually differ
Senior developers know when to abstract and when to keep it simple. We often write straightforward, slightly duplicative code first and only refactor when duplication becomes a real problem. We avoid creating classes for things that are just functions.
JavaScript Example
// AI‑like: Over‑abstracted for a simple task.
class DataProcessor {
constructor(data) {
this.data = data;
}
validate() { /* ... */ }
normalize() { /* ... */ }
process() {
this.validate();
this.normalize();
// ... actual processing logic
}
}
// Human‑like: Simple functional approach.
function processData(data) {
// Validate and normalize inline when needed.
if (!Array.isArray(data)) throw new Error('Invalid data');
return data.map(item => /* ... */);
}
요약
“AI‑like” 분위기는 코드가 무엇을 하는가가 아니라 어떻게 작성되었는가에 달려 있습니다. 지나치게 일관된 포맷팅, 방대하지만 얕은 주석, 초정밀과 일반적인 이름 사이를 오가는 네이밍, 불필요한 추상화는 해당 코드가 실제 제약 조건 하에서 일하는 인간 개발자보다 모델에 의해 생성되었을 가능성을 강하게 시사합니다. 이러한 패턴을 인식하면 AI‑생성 코드를 찾아낼 수 있을 뿐만 아니라, 더 중요한 것은 코드가 실제로 사용되는 느낌이 나고, 유지보수가 용이하며, 진정으로 인간적인 코드를 작성하도록 안내합니다.
처리
// Human‑like: a plain function that gets the job done.
function processData(data) {
if (!data) return null;
// quick validation and normalization inline
const cleaned = data.map(item => ({ ...item, value: Number(item.value) }));
// ... process
return cleaned;
}
AI‑같은 방어적 스타일
보이는 모습: 모든 함수가
try…catch로 감싸져 있어 “An error occurred.” 와 같은 일반적인 오류 메시지를 로그합니다. 제어된 환경에서 한 번만 실행되는 스크립트라도 엣지 케이스를 집착적으로 처리합니다. 간단한 CLI 도구에도 사용자 정의 오류 클래스를 볼 수 있습니다.두드러지는 이유: 모델은 견고한 코드를 목표로 학습되었기 때문에 기본적으로 “버블‑랩” 접근법을 사용합니다. 결제 서비스처럼 방어적 코딩이 필수적인 경우와 일회성 데이터 마이그레이션 스크립트처럼 과도한 경우를 구분할 컨텍스트 이해가 부족합니다.
인간은 보통 어떻게 다른가: 우리는 실용적입니다. 운영 코드에서는 흔히 발생할 오류만 구체적으로 처리하고, 스크립트에서는 오류가 발생하면 바로 크래시하고 스택 트레이스를 확인합니다.
// assuming the input is valid for now혹은// FIXME: add proper error handling와 같은 주석을 남겨 두기도 합니다.
Python – AI‑같은 vs 인간‑같은
# AI‑like: Defensive to a fault.
def read_config(path):
try:
with open(path, 'r') as f:
data = json.load(f)
return data
except FileNotFoundError:
print("File not found.")
return None
except json.JSONDecodeError:
print("Invalid JSON.")
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
# Human‑like: often more direct, handling errors where it matters.
def read_config(path):
with open(path) as f:
return json.load(f) # Let it crash if the file is missing or malformed.
임포트 과다 vs 간결한 임포트
보이는 모습: 코드는 가장 안전하고 보수적인 기본값을 사용합니다. JavaScript에서 실행을 연기하기 위해
setTimeout(..., 0)같은 “해킹”적이지만 흔히 쓰이는 해결책을 피합니다. 임포트는 종종 과다하게 포함되어, 실제로 필요 없는 라이브러리까지 파일 상단에 선언됩니다.두드러지는 이유: AI 모델은 올바르고 널리 받아들여지는 코드를 생성하도록 최적화돼 있습니다. 기술적으로는 불순하지만 실용적인 관용구를 회피합니다. “만일을 대비”하는 접근법 때문에 모든 가능한 의존성을 확보하려다 보니 임포트가 과다해집니다.
인간은 보통 어떻게 다른가: 우리는 언어별 관용구와 때때로 영리한 해킹(그 이유를 설명하는 주석과 함께)을 사용합니다. 필요성을 확인한 뒤에만 라이브러리를 추가하고, 임포트는 과감히 정리합니다.
Python – AI‑무거운 vs 인간‑가벼운 임포트
# AI‑like: Conservative and import‑heavy.
import numpy as np
import pandas as pd
import re, json, os, sys, time, datetime # Many unused.
def find_pattern(text):
return re.search(r'\d+', text)
# Human‑like: lean imports, idiomatic hack.
import re
def find_pattern(text):
# re.search returns None if not found, which is falsy.
return re.search(r'\d+', text) or 0
일관성 vs 인간의 “피로감”
보이는 모습: 동일한 AI가 만든 여러 파일을 보면 문구, 구조, 변수명 선택까지도 기이하게 반복되는 것을 볼 수 있습니다. 인간이 흔히 보이는 급하게 만든, 2 am에 작성된 서투른 부분이 전혀 없습니다.
두드러지는 이유: AI는 피곤함, 지루함, 조급함을 느끼지 않으며, 가장 일반적인 패턴을 통계적으로 규칙적으로 출력합니다. 인간은 좋은 날과 나쁜 날이 있어 코드에 그 차이가 드러납니다.
인간은 보통 어떻게 다른가: 우리의 코드는 이야기를 담고 있습니다. 프로젝트 초기에 신중하고 주석이 풍부한 함수가 있다가, 시간이 지나면서 급하게 작성된 부분이 섞이기도 합니다.
Source: …
rasts with the messy, hard‑coded function added during a late‑night bug fix. This variation is a signature of human authorship.
차이는 종종 의도에 달려 있습니다. 주니어 개발자는 난잡한 코드를 쓸 수 있지만, 그 난잡함에는 개인적인 흔적이 있습니다. 시니어 개발자는 깔끔한 코드를 작성하지만, 그것은 목적이 있는 깔끔함이며—완벽함은 아닙니다.
왜 프로덕션 코드는 더 난잡한가
실제 현장의 코드는 상충되는 요구사항, 촉박한 일정, 레거시 제약, 그리고 버그 수정에 의해 형성됩니다. 그것은 흔적(// HACK:)과 임시 해결책, 그리고 여러 층의 역사를 쌓아갑니다. 반면 AI가 만든 코드는 마치 유지보수의 혼란을 겪지 않은 그린필드 프로젝트처럼 느껴집니다.
AI 코드가 “너무 신중하게” 느껴지는 경우
교과서를 외운 학생이지만 실용적인 이득을 위해 언제 규칙을 깨야 하는지 아직 배우지 못한 상황과 같습니다. 코드는 정확하지만, 무시해야 할 것을 아는 경험 많은 개발자가 가진 자신감 있고 때로는 무자비한 실용주의가 부족합니다.
AI‑생성 코드를 식별하는 방법
AI‑생성 코드를 식별하는 것은 버그를 찾는 것이 아니라 인간의 흔적이 없는 것을 찾는 것입니다: 일관성 부족, 과도한 설명, 그리고 교과서식 패턴의 과잉 적용.
“Any sufficiently advanced technology is indistinguishable from magic.”
— Arthur C. Clarke
추가: AI처럼 보이는 코드는 종종 여러 AI가 함께 작업하여 생성되며, 이는 “과도하게 설계된” 느낌을 더욱 증폭시킬 수 있습니다.