코드 표준
Elftia는 점진적 적용 전략과 함께 엄격한 ESLint 구성(typescript-eslint/strict)을 사용합니다.
도구 체인
| 도구 | 목적 | 구성 파일 |
|---|---|---|
| ESLint | 코드 품질 검사(strict 모드) | eslint.config.js |
| Prettier | 코드 형식 지정 | .prettierrc |
| Husky | Git Hooks 관리 | .husky/pre-commit |
| lint-staged | 스테이징된 파일 검사 | package.json |
일반 명령
# Run ESLint checks
npm run lint:eslint
# Check Prettier formatting
npm run lint:prettier
# Auto-format code
npm run format
# Complete lint check (ESLint + TypeScript)
npm run lint
# Check a single file
npx eslint path/to/file.ts
# Auto-fix a single file
npx eslint path/to/file.ts --fix
Pre-Commit 자동 검사
코드를 커밋할 때 Husky는 스테이징된 .ts/.tsx 파일에 대해 eslint --fix로 lint-staged를 자동 실행합니다.
- error 수준: 커밋을 차단하며 반드시 수정해야 합니다
- warn 수준: 경고를 표시하지만 커밋은 허용합니다
- auto-fix: import 정렬처럼 자동 수정 가능한 문제를 자동으로 수정합니다
ESLint 규칙 빠른 참조
심각도 수준
| 수준 | 의미 | 커밋 동작 |
|---|---|---|
error | 반드시 수정 | 커밋 차단 |
warn | 수정 권장 | 커밋 허용 |
off | 비활성화 | - |
Error 수준 규칙(커밋 차단)
// simple-import-sort/imports — Auto-fixable
// Imports must be ordered as specified, eslint --fix will auto-fix
// prefer-const
let value = 1; // Never reassigned, should use const
// no-var
var x = 1; // var usage is prohibited
// eqeqeq
if (a == b) { } // Use == instead of === (except for null checks)
// react-hooks/rules-of-hooks
if (condition) { useState(); } // Hooks can only be called at top level
Warn 수준 규칙(커밋 허용)
TypeScript 규칙
| 규칙 | 설명 | 수정 방법 |
|---|---|---|
@typescript-eslint/no-unused-vars | 사용하지 않는 변수 | 삭제하거나 _ 접두사를 붙입니다 |
@typescript-eslint/no-explicit-any | any 타입 사용 | 구체적인 타입이나 unknown으로 변경합니다 |
@typescript-eslint/consistent-type-imports | 타입 전용이 아닌 imports | import { type Foo }로 변경합니다 |
@typescript-eslint/no-non-null-assertion | Non-null assertion ! | Optional chaining ?. 또는 type guard를 사용합니다 |
React 규칙
| 규칙 | 설명 | 수정 방법 |
|---|---|---|
react-hooks/exhaustive-deps | 불완전한 Hook 의존성 | 의존성 배열을 완성합니다 |
react/jsx-no-leaked-render | 누출된 렌더링(count && <X />) | count > 0 && <X />로 변경합니다 |
react/self-closing-comp | 빈 태그가 self-close되지 않음 | <Comp></Comp> → <Comp /> |
react/jsx-curly-brace-presence | 불필요한 중괄호 | prop={"val"} → prop="val" |
react/jsx-boolean-value | 중복된 boolean 값 | disabled={true} → disabled |
react/hook-use-state | State 이름이 camelCase가 아님 | [Value, setV] → [value, setV] |
기타 규칙
| 규칙 | 설명 | 수정 방법 |
|---|---|---|
no-console | Renderer 프로세스에서 console.log 사용 | console.warn/error/info/debug로 변경합니다 |
jsx-a11y/* | 접근성 문제 | Design System을 참조하세요 |
Import 정렬 규칙
Imports는 다음 순서로 정렬해야 합니다(eslint --fix로 자동 수정 가능).
// 1. Node.js built-in modules
import path from 'node:path';
import fs from 'node:fs';
// 2. External packages
import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
// 3. Internal alias @/
import { Button } from '@/components/ui/button';
import { useAppState } from '@/shared/hooks/useAppState';
// 4. @shared/ alias
import type { Message } from '@shared/contracts';
// 5. @main/ alias
import { AppPaths } from '@main/services/infra/paths/paths';
// 6. Parent directory imports
import { utils } from '../utils';
// 7. Same directory imports
import { helper } from './helper';
// 8. Style imports
import './styles.css';
Prettier 구성
| 옵션 | 값 | 설명 |
|---|---|---|
semi | true | 세미콜론 사용 |
singleQuote | true | 작은따옴표 사용 |
tabWidth | 2 | 들여쓰기 너비 |
trailingComma | 'es5' | 후행 쉼표 |
printWidth | 100 | 줄 너비 제한 |
파일 크기 제한
황금 규칙: 단일 파일은 코드 600줄을 초과하지 않아야 합니다.
| 파일 유형 | 권장 제한 | 경고 줄 수 | 하드 제한 |
|---|---|---|---|
| React 컴포넌트 | 400줄 | 600줄 | 800줄 |
| Custom Hook | 300줄 | 400줄 | 600줄 |
| 유틸리티/헬퍼 함수 | 150줄 | 200줄 | 300줄 |
| 타입 정의 | 100줄 | 150줄 | 200줄 |
| Service/API | 300줄 | 400줄 | 600줄 |
결과:
- 경고 줄 수 초과 → 현재 작업에서 반드시 분리하고 미루지 마세요
- 하드 제한 초과 → 즉시 중단하고 먼저 리팩터링한 뒤 계속하세요
명명 규칙
파일 명명
| 유형 | 규칙 | 예시 |
|---|---|---|
| React 컴포넌트 | PascalCase.tsx | UserMessage.tsx |
| Hooks | use + PascalCase.ts | useChatActions.ts |
| 유틸리티 함수 | camelCase.ts | messageHelpers.ts |
| 타입 정의 | camelCase.ts 또는 types.ts | chatTypes.ts |
| 상수 | UPPER_CASE.ts | API_CONSTANTS.ts |
| Services | PascalCase + Service.ts | ChatService.ts |
| 디렉터리 | kebab-case | chat-messages/ |
컴포넌트 명명
// Good: Clear and descriptive
export function UserMessage() { }
export function EditTool() { }
export function ChatComposer() { }
// Bad: Too generic
export function Message() { }
export function Tool() { }
export function Input() { }
Hook 명명
// Good: use + feature description
export function useChatActions() { }
export function useMessageEffects() { }
// Bad: Does not follow React Hook naming convention
export function chatActions() { }
export function messageHook() { }
디렉터리 구조 표준
컴포넌트 디렉터리
components/
├── ComponentName/
│ ├── index.ts # Unified export
│ ├── ComponentName.tsx # Main component
│ ├── types.ts # Type definitions
│ ├── utils.ts # Utility functions
│ ├── SubComponent.tsx # Sub-component
│ └── __tests__/
│ └── ComponentName.test.tsx
Hooks 디렉터리
hooks/
├── domain/
│ ├── index.ts # Unified export
│ ├── useDomainState.ts # State
│ ├── useDomainActions.ts # Actions
│ └── useDomainEffects.ts # Side effects
Services 디렉터리
services/
├── ServiceName/
│ ├── index.ts # Unified export
│ ├── ServiceName.ts # Main service
│ ├── types.ts # Type definitions
│ └── subdomain/ # Sub-domain
TypeScript 모범 사례
타입 Imports
// Use type-only imports
import { type SomeType } from './types';
import type { AnotherType } from '@shared/contracts';
any 피하기
// Bad
function handle(data: any) { }
// Good: Use specific types
function handle(data: Message) { }
// Good: Use generics
function handle<T extends BaseMessage>(data: T) { }
// Good: Use unknown + type guard
function handle(data: unknown) {
if (isMessage(data)) { /* data: Message */ }
}
Barrel Export
// components/chat/messages/index.ts
export { MessageItem } from './MessageItem';
export { MessageList } from './MessageList';
export type { MessageItemProps } from './MessageItem';
코드 구성 순서
React 컴포넌트 구조
// 1. Imports
// 2. Type definitions
// 3. Constants
// 4. Helper functions
// 5. Main component
// 5.1 Hooks
// 5.2 Derived state (useMemo)
// 5.3 Event handlers (useCallback)
// 5.4 Effects (useEffect)
// 5.5 Render (return)
// 6. Sub-components (if simple)
단일 책임 원칙
각 파일은 변경되어야 할 이유가 하나뿐이어야 합니다. 컴포넌트는 다음과 같은 경우 분리해야 합니다.
- 코드가 300줄을 초과합니다
- 독립적인 로직 블록이 3개 이상 포함되어 있습니다
- 재사용 가능한 부분이 있습니다
- 서로 다른 부분의 변경 빈도가 다릅니다
- 테스트가 어려워집니다
분리 전략:
| 전략 | 사용 시점 | 예시 |
|---|---|---|
| 기능별 분리 | UI 블록이 독립적일 때 | Toolbar / Message list / Input box → 별도 컴포넌트 |
| 데이터 흐름별 분리 | 복잡한 상태 로직 | State / Actions / Side effects → 별도 Hooks |
| 도메인별 분리 | 유사한 항목이 여러 개일 때 | EditTool / WriteTool / BashTool → 별도 파일 |
Git 워크플로
커밋 규칙
Conventional Commits 형식을 사용하세요.
feat: New feature description
fix: Bug fix description
refactor: Refactoring description
docs: Documentation update
style: Code formatting changes (no logic changes)
test: Test-related changes
chore: Build tool or auxiliary tool changes
Pre-Commit 체크리스트
npm run lint실행 — 오류가 없는지 확인합니다- 모든 error 수준 문제를 수정합니다
- 가능한 경우 warn 수준 문제를 수정합니다
npm run format실행 — 코드를 형식 지정합니다- 새 파일은 warning이 0개여야 합니다
- 기존 파일을 수정할 때는 해당 파일의 warning도 함께 수정합니다