나는 500개의 AI 코딩 실수를 분석하고 이를 잡아내는 ESLint 플러그인을 만들었다

발행: (2026년 4월 4일 AM 10:49 GMT+9)
10 분 소요
원문: Dev.to

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 %
환각된 객체/API9.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 규칙은 더 평탄한 패턴을 권장합니다.

규칙 뒤의 연구

RuleBug Pattern Addressed
no-async-array-callbacks누락된 Promise.all, 잘못된 async 흐름
no-empty-catch오류를 조용히 무시함
no-magic-numbers유지보수가 어려운 상수
prefer-early-return깊은 중첩, 불명확한 제어 흐름
prefer-unknown-in-catchany 타입의 catch 매개변수
throw-error-objectsError 인스턴스 대신 문자열을 던짐
structured-logging일관되지 않은 로그 형식
consistent-exports기본/명명된 내보내기의 혼합
explicit-export-types공개 함수에 반환 타입이 없음
no-commented-out-code죽은 코드 누적

전체 규칙 문서: github.com/pertrai1/eslint-plugin-llm-core#rules

typescript-eslint만 사용하지 않을까요?

좋은 질문입니다. typescript-eslint는 훌륭합니다 — 이 플러그인은 이를 보완하도록 설계되었으며, 대체하기 위한 것이 아닙니다.

typescript-eslinteslint-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

이거 만들었나요? 마음에 안 드나요? 놓친 규칙에 대한 아이디어가 있나요? 이슈를 열거나 연락 주세요. 저는 AI가 이상한 코드를 작성하는 것을 본 기여자를 적극적으로 찾고 있습니다.

0 조회
Back to Blog

관련 글

더 보기 »

Docker Build Output: 필요 없는 50줄

Docker 빌드는 설계상 자세히 출력됩니다: 레이어 ID, 다운로드 진행 표시줄, SHA‑256 해시, 그리고 캐시 상태가 모든 단계마다 나타납니다. docker build를 내부에서 실행할 때…