나는 500개의 AI 코딩 실수를 분석하고 이를 잡아내는 ESLint 플러그인을 만들었다
Source: Dev.to
(번역할 텍스트를 제공해 주시면 한국어로 번역해 드리겠습니다.)
아마 보셨을 법한 패턴
const results = items.map(async (item) => {
return await fetchItem(item);
});
괜찮아 보이죠? 여러분의 AI 비서가 작성했습니다. 테스트도 통과하고 코드 리뷰도 승인했습니다.
그런데 프로덕션에 배포되면서 results가 값이 아닌 Promise들의 배열이라는 사실을 알게 됩니다. 2번째 줄의 await는 아무런 효과가 없습니다. Promise.all(items.map(...)) 혹은 for…of 루프가 필요했습니다.
이것은 TypeScript 버그가 아니라 일반적인 LLM 코딩 실수입니다 — 제가 AI‑생성 코드 품질을 연구하기 시작했을 때 발견한 수백 가지 실수 중 하나죠.
문제: AI는 동작하는 코드를 작성하지만 올바른 코드를 작성하지는 않는다
LLM은 테스트를 통과하는 코드를 작성하는 데는 뛰어나지만, 엣지 케이스를 처리하고 일관성을 유지하며 내부적으로 모범 사례를 따르는 코드를 작성하는 데는 형편없다.
333개의 버그 분석과 PromptHub의 558개 잘못된 스니펫 연구 등 여러 실증 연구를 검토한 결과, 명확한 패턴이 드러났다:
| 버그 유형 | 빈도 |
|---|---|
| 누락된 코너 케이스 | 15.3 % |
| 오해·잘못 해석 | 20.8 % |
| 환각된 객체/API | 9.6 % |
| 잘못된 조건 | 높음 |
| 누락된 코드 블록 | 40 %+ |
가장 답답한 점은? 이러한 문제들의 대부분이 lint 단계에서 예방 가능하다는 것이다.
해결책: AI‑생성 코드용 ESLint 규칙
저는 eslint-plugin-llm-core 를 만들었습니다 — AI 코딩 어시스턴트가 가장 자주 저지르는 실수를 잡아내기 위해 설계된 20개의 규칙을 포함한 ESLint 플러그인입니다.
이 규칙들은 단순히 일반적인 베스트 프랙티스 규칙이 아니라, AI‑생성 코드베이스에서 반복해서 보던 패턴들을 목표로 합니다:
- Async/await 오용
- 일관성 없는 오류 처리
- 누락된 null 검사
- 이름이 지정된 상수 대신 사용되는 매직 넘버
- 조기 반환 대신 깊은 중첩
- 오류를 삼키는 빈
catch블록 - 의도를 흐리게 하는 일반적인 변수명
예시: Async 배열 콜백 함정
// ❌ AI가 자주 작성함
const userIds = users.map(async (user) => {
return await db.getUser(user.id);
});
// userIds는 Promise[] — User[]가 아님
// ✅ 실제로 필요한 코드
const userIds = await Promise.all(
users.map((user) => db.getUser(user.id))
);
플러그인은 no-async-array-callbacks 규칙으로 이를 잡아냅니다:
57:27 error Avoid passing async functions to array methods llm-core/no-async-array-callbacks
This pattern returns an array of Promises, not the resolved values.
Consider using Promise.all() or a for...of loop instead.
오류 메시지를 보셨나요? 이 메시지는 불평만 하는 것이 아니라 가르치기 위해 설계되었습니다. 목표는 개발자와 그들의 AI 어시스턴트가 왜 잘못된 것인지 이해하도록 돕는 것입니다.
예시: 빈 catch 안티패턴
// ❌ AI가 자주 생성함
try {
await processData(data);
} catch (e) {
// TODO: handle error
}
no-empty-catch 규칙이 이를 잡아냅니다:
63:11 error Empty catch block silently swallows errors llm-core/no-empty-catch
Unhandled errors make debugging difficult and can hide critical failures.
Either handle the error, rethrow it, or log it with context.
예시: 조기 반환 대신 깊은 중첩
// ❌ AI는 중첩을 좋아함
function processData(data: Data | null) {
if (data) {
if (data.items) {
if (data.items.length > 0) {
return data.items.map(processItem);
}
}
}
return [];
}
// ✅ 조기 반환이 더 깔끔함
function processData(data: Data | null) {
if (!data?.items?.length) return [];
return data.items.map(processItem);
}
prefer-early-return 규칙은 더 평탄한 패턴을 권장합니다.
규칙 뒤의 연구
| Rule | Bug Pattern Addressed |
|---|---|
no-async-array-callbacks | 누락된 Promise.all, 잘못된 async 흐름 |
no-empty-catch | 오류를 조용히 무시함 |
no-magic-numbers | 유지보수가 어려운 상수 |
prefer-early-return | 깊은 중첩, 불명확한 제어 흐름 |
prefer-unknown-in-catch | any 타입의 catch 매개변수 |
throw-error-objects | Error 인스턴스 대신 문자열을 던짐 |
structured-logging | 일관되지 않은 로그 형식 |
consistent-exports | 기본/명명된 내보내기의 혼합 |
explicit-export-types | 공개 함수에 반환 타입이 없음 |
no-commented-out-code | 죽은 코드 누적 |
전체 규칙 문서: github.com/pertrai1/eslint-plugin-llm-core#rules
왜 typescript-eslint만 사용하지 않을까요?
좋은 질문입니다. typescript-eslint는 훌륭합니다 — 이 플러그인은 이를 보완하도록 설계되었으며, 대체하기 위한 것이 아닙니다.
typescript-eslint | eslint-plugin-llm-core | |
|---|---|---|
| 초점 | TypeScript 언어 정확성 | AI 코딩 패턴 방지 |
| 오류 메시지 | 기술적이며, 사양 중심 | 교육적이며, 맥락이 풍부함 |
| 규칙 설계 | 언어 사양 준수 | 관찰된 LLM 버그 패턴 |
두 플러그인을 모두 사용해야 합니다. typescript-eslint는 TypeScript‑특화 문제를 잡아냅니다. llm-core는 LLM이 반복해서 틀리는 패턴을 포착합니다 — 기술적으로 유효한 TypeScript인지 여부와 관계없이.
시작하기
npm install -D eslint-plugin-llm-core
// eslint.config.js
import llmCore from 'eslint-plugin-llm-core';
export default [
{
plugins: {
'llm-core': llmCore,
},
rules: {
// 권장 설정 활성화
...llmCore.configs.recommended.rules,
},
},
];
이제 평소처럼 ESLint를 실행하세요; 플러그인이 AI‑특화 함정들을 찾아내고 더 안전하고 유지보수가 쉬운 코드를 배포하도록 도와줍니다.
module.exports = [
{
files: ["**/*.js"],
rules: {
// ...여기에 규칙을 추가하세요
"llm-core/no-async-array-callback": "error",
// 기타 규칙…
},
},
];
그게 전부입니다. 권장 규칙 세트에 대한 설정이 필요 없습니다.
더 큰 그림: AI에게 더 나은 습관 가르치기
이 규칙들은 단순히 실수를 잡아내는 것이 아니라 가르칩니다.
AI 어시스턴트가 다음 오류 메시지를 볼 때:
Avoid passing async functions to array methods.
This pattern returns an array of Promises, not the resolved values.
Consider using Promise.all() or a for...of loop instead.
그것은 학습합니다. 다음 번에는 올바른 패턴을 작성합니다.
반복되는 에이전트 워크플로우에서는 — AI가 코드를 반복적으로 작성하고, 테스트하고, 수정하는 과정 — 이 피드백 루프가 누적됩니다. 각 린트 오류가 교육의 순간이 됩니다.
다음 단계
- Auto‑fixes for fixable rules → 수정 가능한 규칙에 대한 Auto‑fixes
- More logging library detection (Pino, Winston, Bunyan) → 더 많은 로깅 라이브러리 감지 (Pino, Winston, Bunyan)
- Additional rules based on ongoing research → 진행 중인 연구를 기반으로 한 Additional rules
- Evidence gathering on whether rules actually improve AI‑generated code quality → 규칙이 실제로 AI‑생성 코드 품질을 향상시키는지에 대한 Evidence gathering
AI 코딩 어시스턴트(CURSOR, Claude Code, Copilot 등)를 사용하고 계시다면, 그들이 잘못하는 패턴에 대해 여러분의 피드백을 듣고 싶습니다.
Try It
npm install -D eslint-plugin-llm-core
- GitHub: https://github.com/pertrai1/eslint-plugin-llm-core
- npm: https://www.npmjs.com/package/eslint-plugin-llm-core
이거 만들었나요? 마음에 안 드나요? 놓친 규칙에 대한 아이디어가 있나요? 이슈를 열거나 연락 주세요. 저는 AI가 이상한 코드를 작성하는 것을 본 기여자를 적극적으로 찾고 있습니다.