From c26428ba78607e433e7df9e088fe1e80612fdf90 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sat, 29 Nov 2025 18:19:50 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 11 + README.md | 3 + plugin.lock.json | 53 ++ skills/code-style-reviewer/EXAMPLES.md | 837 +++++++++++++++++++++++ skills/code-style-reviewer/PRINCIPLES.md | 445 ++++++++++++ skills/code-style-reviewer/SKILL.md | 140 ++++ 6 files changed, 1489 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 plugin.lock.json create mode 100644 skills/code-style-reviewer/EXAMPLES.md create mode 100644 skills/code-style-reviewer/PRINCIPLES.md create mode 100644 skills/code-style-reviewer/SKILL.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..7b3c6c8 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,11 @@ +{ + "name": "code-style-plugin", + "description": "코드 스타일 원칙 기반 AI 코드 리뷰 Skill - 단일책임원칙, DRY, 단순화 우선, YAGNI, 타입 안전성을 검사합니다", + "version": "1.0.0", + "author": { + "name": "Code Style Team" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..df4b76f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# code-style-plugin + +코드 스타일 원칙 기반 AI 코드 리뷰 Skill - 단일책임원칙, DRY, 단순화 우선, YAGNI, 타입 안전성을 검사합니다 diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..23cd426 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,53 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:devstefancho/claude-plugins:code-style-plugin", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "44cf038caa797cf758a53847702a8a88ccf42973", + "treeHash": "e238d4889d5504ca93878b1322665f5f41bbf2726bd58855fd17ed427955109f", + "generatedAt": "2025-11-28T10:16:20.071150Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "code-style-plugin", + "description": "코드 스타일 원칙 기반 AI 코드 리뷰 Skill - 단일책임원칙, DRY, 단순화 우선, YAGNI, 타입 안전성을 검사합니다", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "5de827debe9df419106b041dc8f32039c36160a541c1e0339cea5a48e638ffd8" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "620630d4c3723acaace9a20247d1d2ff6a243e8eb14b9ff40516197c19270346" + }, + { + "path": "skills/code-style-reviewer/EXAMPLES.md", + "sha256": "92a84246e8c7f23c984f987c146e1dd075fc5dc7872842ceb200a4becf0b85df" + }, + { + "path": "skills/code-style-reviewer/PRINCIPLES.md", + "sha256": "7e0148681d7303a5efc91b812fb1f800e17247e3ac8a6f9536edb4d5f217cf8b" + }, + { + "path": "skills/code-style-reviewer/SKILL.md", + "sha256": "4048fc6d1e33bc28f1a0b5d1d6b68cb5f2b2723e63eb7b73adf1020fe2268f03" + } + ], + "dirSha256": "e238d4889d5504ca93878b1322665f5f41bbf2726bd58855fd17ed427955109f" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/code-style-reviewer/EXAMPLES.md b/skills/code-style-reviewer/EXAMPLES.md new file mode 100644 index 0000000..8149afc --- /dev/null +++ b/skills/code-style-reviewer/EXAMPLES.md @@ -0,0 +1,837 @@ +# Code Style Examples - 좋은 코드 vs 나쁜 코드 + +## TypeScript / JavaScript 예제 + +### 예제 1: 단일책임원칙 (SRP) + +#### ❌ 나쁜 예 +```typescript +class UserManager { + // 여러 책임을 가짐: 사용자 데이터, 검증, 저장, 이메일 발송 + + async createUser(userData: any) { + // 1. 검증 + if (!userData.email) throw new Error("Email required"); + if (!userData.name) throw new Error("Name required"); + if (userData.email.length < 5) throw new Error("Invalid email"); + + // 2. 데이터 변환 + const user = { + id: Math.random().toString(), + email: userData.email, + name: userData.name, + createdAt: new Date(), + role: 'user' + }; + + // 3. 저장 + const db = require('./db'); + db.save('users', user); + + // 4. 이메일 발송 + const mailer = require('nodemailer'); + await mailer.send({ + to: user.email, + subject: 'Welcome', + html: `

Welcome ${user.name}

` + }); + + // 5. 로깅 + console.log(`User created: ${user.id}`); + const logger = require('./logger'); + logger.log('user_created', { userId: user.id, email: user.email }); + + return user; + } +} +``` + +#### ✅ 좋은 예 +```typescript +// 각각의 책임을 분리 +class UserValidator { + validate(userData: UserInput): void { + if (!userData.email) throw new Error("Email required"); + if (!userData.name) throw new Error("Name required"); + if (!this.isValidEmail(userData.email)) throw new Error("Invalid email"); + } + + private isValidEmail(email: string): boolean { + return email.includes('@') && email.length >= 5; + } +} + +class UserFactory { + create(userData: UserInput): User { + return { + id: this.generateId(), + email: userData.email, + name: userData.name, + createdAt: new Date(), + role: 'user' + }; + } + + private generateId(): string { + return Math.random().toString(); + } +} + +class UserRepository { + async save(user: User): Promise { + const db = require('./db'); + await db.save('users', user); + } +} + +class UserNotificationService { + async notifyNewUser(user: User): Promise { + const mailer = require('nodemailer'); + await mailer.send({ + to: user.email, + subject: 'Welcome', + html: `

Welcome ${user.name}

` + }); + } +} + +class UserLogger { + logCreation(user: User): void { + const logger = require('./logger'); + logger.log('user_created', { userId: user.id, email: user.email }); + } +} + +class UserService { + constructor( + private validator: UserValidator, + private factory: UserFactory, + private repository: UserRepository, + private notificationService: UserNotificationService, + private logger: UserLogger + ) {} + + async createUser(userData: UserInput): Promise { + this.validator.validate(userData); + const user = this.factory.create(userData); + await this.repository.save(user); + await this.notificationService.notifyNewUser(user); + this.logger.logCreation(user); + return user; + } +} +``` + +--- + +### 예제 2: DRY (Don't Repeat Yourself) + +#### ❌ 나쁜 예 +```typescript +// 반복되는 검증 로직 +function validateUserEmail(email: string): boolean { + if (!email) return false; + if (email.trim().length === 0) return false; + if (!email.includes('@')) return false; + return true; +} + +function validateProductEmail(email: string): boolean { + if (!email) return false; + if (email.trim().length === 0) return false; + if (!email.includes('@')) return false; + return true; +} + +function validateOrganizationEmail(email: string): boolean { + if (!email) return false; + if (email.trim().length === 0) return false; + if (!email.includes('@')) return false; + return true; +} + +// 반복되는 API 호출 패턴 +async function getUsersFromAPI(apiKey: string): Promise { + const response = await fetch('https://api.example.com/users', { + headers: { 'Authorization': `Bearer ${apiKey}` } + }); + if (!response.ok) throw new Error('API Error'); + return response.json(); +} + +async function getProductsFromAPI(apiKey: string): Promise { + const response = await fetch('https://api.example.com/products', { + headers: { 'Authorization': `Bearer ${apiKey}` } + }); + if (!response.ok) throw new Error('API Error'); + return response.json(); +} + +async function getOrdersFromAPI(apiKey: string): Promise { + const response = await fetch('https://api.example.com/orders', { + headers: { 'Authorization': `Bearer ${apiKey}` } + }); + if (!response.ok) throw new Error('API Error'); + return response.json(); +} +``` + +#### ✅ 좋은 예 +```typescript +// 공통 검증 함수 +function isValidEmail(email: string): boolean { + if (!email) return false; + if (email.trim().length === 0) return false; + return email.includes('@'); +} + +// 모든 곳에서 재사용 +const isUserEmailValid = isValidEmail(userEmail); +const isProductEmailValid = isValidEmail(productEmail); +const isOrgEmailValid = isValidEmail(orgEmail); + +// 공통 API 클라이언트 +class ApiClient { + constructor(private apiKey: string, private baseUrl: string) {} + + async fetch(endpoint: string): Promise { + const response = await fetch(`${this.baseUrl}${endpoint}`, { + headers: { 'Authorization': `Bearer ${this.apiKey}` } + }); + + if (!response.ok) { + throw new Error(`API Error: ${response.statusText}`); + } + + return response.json(); + } +} + +// 사용 +const apiClient = new ApiClient(apiKey, 'https://api.example.com'); +const users = await apiClient.fetch('/users'); +const products = await apiClient.fetch('/products'); +const orders = await apiClient.fetch('/orders'); +``` + +--- + +### 예제 3: 단순화 우선 (Simplicity First) + +#### ❌ 나쁜 예 +```typescript +// 불필요한 추상화와 복잡한 로직 +interface DataProcessor { + process(data: any[]): any[]; +} + +class ComplexDataProcessor implements DataProcessor { + process = (data: any[]): any[] => + [data] + .reduce((acc: any, val: any) => + [val] + .reduce((a: any, item: any) => + [item] + .reduce((b: any, i: any) => + ({ + ...b, + ...Object.entries(i).reduce((c: any, [k, v]: any) => + ({ + ...c, + [k]: typeof v === 'number' ? v * 2 : v + }), {}) + }), {}), [])[0], {}); +} + +// 깊은 중첩 +function checkPermissions(user: User): boolean { + if (user) { + if (user.isActive) { + if (user.roles) { + if (user.roles.length > 0) { + if (user.roles.some((role: Role) => role.name === 'admin')) { + if (user.roles[0].permissions) { + if (user.roles[0].permissions.includes('create_user')) { + return true; + } + } + } + } + } + } + } + return false; +} +``` + +#### ✅ 좋은 예 +```typescript +// 단순하고 명확한 구현 +function doubleNumbers>(data: T[]): T[] { + return data.map(item => { + const result = { ...item }; + Object.entries(result).forEach(([key, value]) => { + if (typeof value === 'number') { + result[key] = value * 2; + } + }); + return result; + }); +} + +// Early return으로 중첩 제거 +function hasCreateUserPermission(user: User): boolean { + if (!user?.isActive) return false; + if (!user.roles || user.roles.length === 0) return false; + + const adminRole = user.roles.find(role => role.name === 'admin'); + return adminRole?.permissions?.includes('create_user') ?? false; +} + +// Optional chaining과 nullish coalescing +function canCreateUser(user: User): boolean { + return user?.isActive && + user?.roles?.some(r => r.name === 'admin' && r.permissions?.includes('create_user')) ?? false; +} +``` + +--- + +### 예제 4: YAGNI (You Aren't Gonna Need It) + +#### ❌ 나쁜 예 +```typescript +// 사용되지 않는 기능들 +interface UserService { + // 현재 사용: 필수 + getUser(id: string): Promise; + createUser(email: string, name: string): Promise; + + // 사용 안 함: "나중에 필요할 것 같아서" 추가 + archiveUser(id: string): Promise; + restoreUser(id: string): Promise; + bulkCreateUsers(users: UserInput[]): Promise; + exportUsersToCSV(filter?: any): Promise; + generateUserReport(startDate: Date, endDate: Date): Promise; + predictChurn(userId: string): Promise; +} + +// 사용되지 않는 매개변수 +function fetchUserData( + userId: string, + includeProfilePicture?: boolean, // 사용 안 함 + includeFollowers?: boolean, // 사용 안 함 + analyzeActivity?: boolean, // 사용 안 함 + includeFuturePreferences?: boolean // 사용 안 함 +): User { + const user = getUser(userId); + // userId와 기본 정보만 사용 + return user; +} + +// 주석 처리된 코드 +function saveData(data: any) { + // const oldWay = JSON.stringify(data); + // localStorage.setItem('data', oldWay); + + // if (someLegacyCondition) { + // sendToOldServer(data); + // } + + sendToNewServer(data); +} +``` + +#### ✅ 좋은 예 +```typescript +// 필요한 기능만 정의 +interface UserService { + getUser(id: string): Promise; + createUser(email: string, name: string): Promise; + // 필요하면 나중에 추가 +} + +// 필요한 매개변수만 +function fetchUserData(userId: string): User { + return getUser(userId); +} + +// 필요하면 나중에 새 함수로 추가 +function fetchUserWithFollowers(userId: string): UserWithFollowers { + const user = getUser(userId); + const followers = getFollowers(userId); + return { ...user, followers }; +} + +// 깔끔한 코드: 주석 처리 제거 +function saveData(data: any) { + sendToNewServer(data); +} +``` + +--- + +### 예제 5: 타입 안전성 (Type Safety) + +#### ❌ 나쁜 예 +```typescript +// any 타입 남용 +function processData(data: any): any { + return data.map((item: any) => { + return { + name: item.name, + value: item.value * 2 + }; + }); +} + +// 타입이 없는 함수 +function calculateTotal(items) { + return items.reduce((sum, item) => sum + item.price, 0); +} + +// 암묵적 any +function filterData(array, key, value) { + return array.filter(item => item[key] === value); +} + +// 잘못된 타입 사용 +type UserData = { + [key: string]: any // 이것도 any와 마찬가지 +}; + +const user: UserData = { + name: 'John', + age: 30, + email: true // 타입 체크 불가 +}; +``` + +#### ✅ 좋은 예 +```typescript +// 명확한 타입 정의 +interface DataItem { + name: string; + value: number; +} + +function processData(data: DataItem[]): DataItem[] { + return data.map(item => ({ + name: item.name, + value: item.value * 2 + })); +} + +// 명시적 타입과 반환형 +interface CartItem { + price: number; + quantity: number; +} + +function calculateTotal(items: CartItem[]): number { + return items.reduce((sum, item) => sum + item.price * item.quantity, 0); +} + +// 제네릭으로 유연성 유지 +function filterByProperty( + array: T[], + key: keyof T, + value: T[keyof T] +): T[] { + return array.filter(item => item[key] === value); +} + +// 명확한 타입 정의 +interface User { + name: string; + age: number; + email: string; +} + +const user: User = { + name: 'John', + age: 30, + email: 'john@example.com' +}; + +// unknown으로 안전하게 처리 +function process(data: unknown): void { + if (typeof data === 'string') { + console.log(data.toUpperCase()); + } else if (typeof data === 'number') { + console.log(data * 2); + } else if (Array.isArray(data)) { + console.log(data.length); + } +} +``` + +--- + +### 예제 6: 명명규칙 (Naming Conventions) + +#### ❌ 나쁜 예 +```typescript +// 의미없는 이름 +const x = 10; +const temp = user; +const tmp1 = calculateTotal(items); +const a = getData(); +const fn = (b) => b * 2; + +// 일관성 없는 명명 +const max_count = 100; +const itemTotal = 50; +const MaxValue = 200; +const user_status = 'active'; + +// 너무 긴 이름 +const userDataThatWillBeProcessedAndStoredButNotDeletedOrModifiedInAnyWayShapeOrForm = user; + +// 약어 남용 +const usr = getUser(); +const adr = getAddress(); +const phn = getPhone(); +const nps = calculateScore(); + +// 부정확한 이름 +const userData = items; // userData인데 items를 저장? +const getTotalPrice = () => items.length; // getTotalPrice인데 길이를 반환? +``` + +#### ✅ 좋은 예 +```typescript +// 명확한 이름 +const maxRetries = 10; +const userProfile = user; +const cartTotal = calculateTotal(items); +const userData = getData(); +const doubleValue = (value: number) => value * 2; + +// 일관성 있는 명명 (camelCase) +const maxCount = 100; +const itemTotal = 50; +const maxValue = 200; +const userStatus = 'active'; + +// 적절한 길이의 이름 +const userForProcessing = user; + +// 완전한 이름 사용 +const user = getUser(); +const address = getAddress(); +const phone = getPhone(); +const netPromoterScore = calculateScore(); + +// 정확한 이름 +const cartItems = items; +const totalPrice = items.reduce((sum, item) => sum + item.price, 0); +``` + +--- + +## 함수 크기 비교 + +### ❌ 나쁜 예 - 너무 큰 함수 +```typescript +// 함수가 50줄 이상 +async function handleUserRegistration(request: any) { + const { email, password, firstName, lastName, phone, address, city, state, zip, country, dateOfBirth, acceptTerms } = request.body; + + if (!email) throw new Error('Email is required'); + if (!password) throw new Error('Password is required'); + // ... 더 많은 검증 + + const hashedPassword = await hashPassword(password); + const user = { + email, + password: hashedPassword, + firstName, + lastName, + phone, + address, + city, + state, + zip, + country, + dateOfBirth, + acceptTerms, + createdAt: new Date(), + lastLogin: null, + isActive: true, + preferences: {} + }; + + const existingUser = await db.findUser(email); + if (existingUser) throw new Error('User already exists'); + + await db.save('users', user); + + const emailToken = generateToken(); + await db.save('email_tokens', { userId: user.id, token: emailToken }); + + const mailer = require('nodemailer'); + await mailer.send({ + to: email, + subject: 'Verify your email', + html: `Verify` + }); + + // ... 더 많은 코드 + + return { success: true, userId: user.id }; +} +``` + +### ✅ 좋은 예 - 작고 집중된 함수들 +```typescript +async function handleUserRegistration(request: RegisterRequest): Promise { + const userData = request.body; + + validateUserInput(userData); + const existingUser = await userRepository.findByEmail(userData.email); + if (existingUser) throw new Error('User already exists'); + + const user = await userService.registerUser(userData); + await emailService.sendVerificationEmail(user); + + return { success: true, userId: user.id }; +} + +// 각 함수는 한 가지 일만 함 +function validateUserInput(userData: UserInput): void { + if (!userData.email) throw new Error('Email is required'); + if (!userData.password) throw new Error('Password is required'); +} + +async function registerUser(userData: UserInput): Promise { + const hashedPassword = await hashPassword(userData.password); + return userRepository.save({ ...userData, password: hashedPassword }); +} + +async function sendVerificationEmail(user: User): Promise { + const token = generateToken(); + await tokenRepository.save({ userId: user.id, token }); + await mailer.send({ + to: user.email, + subject: 'Verify your email', + html: `Verify` + }); +} +``` + +--- + +## 종합 예제: 실제 개선 사례 + +### 요구사항 +"상품 주문 처리 함수를 작성하세요" + +#### ❌ 나쁜 구현 (30줄 이상) +```typescript +async function processOrder(orderData: any) { + // SRP 위반: 여러 책임 + // DRY 위반: 반복되는 검증 + // YAGNI 위반: 불필요한 기능 + // 명명규칙: 일관성 없음 + // 타입 안전: any 사용 + + if (!orderData) throw new Error("Order data required"); + if (!orderData.userId) throw new Error("User ID required"); + if (!orderData.items || orderData.items.length === 0) throw new Error("Items required"); + + let total = 0; + for (let i = 0; i < orderData.items.length; i++) { + if (!orderData.items[i].productId) throw new Error("Product ID required"); + if (!orderData.items[i].quantity || orderData.items[i].quantity <= 0) throw new Error("Quantity must be > 0"); + + const product = await getProduct(orderData.items[i].productId); + total += product.price * orderData.items[i].quantity; + } + + if (total <= 0) throw new Error("Total must be > 0"); + + const user = await getUser(orderData.userId); + if (!user) throw new Error("User not found"); + + const order = { + id: Math.random().toString(), + userId: user.id, + items: orderData.items, + total: total, + status: 'pending', + createdAt: new Date(), + completedAt: null + }; + + const result = await db.insert('orders', order); + + const message = `Order created: ${order.id}. Total: $${total}`; + console.log(message); + + return { orderId: order.id, total: total, message: message }; +} +``` + +#### ✅ 좋은 구현 (SRP, DRY, 단순화, YAGNI, 타입 안전) +```typescript +// === 타입 정의 === +interface OrderItem { + productId: string; + quantity: number; +} + +interface CreateOrderRequest { + userId: string; + items: OrderItem[]; +} + +interface Order { + id: string; + userId: string; + items: OrderItem[]; + total: number; + status: 'pending' | 'completed' | 'cancelled'; + createdAt: Date; +} + +interface CreateOrderResponse { + orderId: string; + total: number; +} + +// === 검증 계층 (SRP: 검증만 담당) === +class OrderValidator { + validateCreateRequest(request: CreateOrderRequest): void { + this.validateNotEmpty(request, 'Order data'); + this.validateNotEmpty(request.userId, 'User ID'); + this.validateItems(request.items); + } + + private validateNotEmpty(value: T, fieldName: string): void { + if (!value) throw new Error(`${fieldName} is required`); + } + + private validateItems(items: OrderItem[]): void { + if (!Array.isArray(items) || items.length === 0) { + throw new Error('At least one item is required'); + } + + items.forEach((item, index) => { + if (!item.productId) throw new Error(`Item ${index}: Product ID is required`); + if (!item.quantity || item.quantity <= 0) throw new Error(`Item ${index}: Quantity must be > 0`); + }); + } +} + +// === 가격 계산 계층 (SRP: 계산만 담당, DRY: 중복 로직 제거) === +class OrderPricingService { + async calculateTotal(items: OrderItem[]): Promise { + let total = 0; + + for (const item of items) { + const product = await this.getProduct(item.productId); + total += product.price * item.quantity; + } + + return total; + } + + private async getProduct(productId: string): Promise<{ price: number }> { + return getProduct(productId); // 외부 함수 호출 + } +} + +// === 주문 생성 계층 (SRP: 주문 생성만 담당) === +class OrderFactory { + create(userId: string, items: OrderItem[], total: number): Order { + return { + id: this.generateId(), + userId, + items, + total, + status: 'pending', + createdAt: new Date() + }; + } + + private generateId(): string { + return `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } +} + +// === 저장소 계층 (SRP: 저장만 담당) === +class OrderRepository { + async save(order: Order): Promise { + await db.insert('orders', order); + } + + async findById(orderId: string): Promise { + return db.findById('orders', orderId); + } +} + +// === 메인 서비스 (조율만 담당) === +class OrderService { + constructor( + private validator: OrderValidator, + private pricingService: OrderPricingService, + private factory: OrderFactory, + private repository: OrderRepository + ) {} + + async createOrder(request: CreateOrderRequest): Promise { + // 1. 검증 + this.validator.validateCreateRequest(request); + + // 2. 사용자 확인 + const user = await getUser(request.userId); + if (!user) throw new Error('User not found'); + + // 3. 가격 계산 + const total = await this.pricingService.calculateTotal(request.items); + + // 4. 주문 생성 + const order = this.factory.create(request.userId, request.items, total); + + // 5. 저장 + await this.repository.save(order); + + // 6. 응답 + return { + orderId: order.id, + total: order.total + }; + } +} + +// === 사용 예 === +const orderService = new OrderService( + new OrderValidator(), + new OrderPricingService(), + new OrderFactory(), + new OrderRepository() +); + +const response = await orderService.createOrder({ + userId: 'user123', + items: [ + { productId: 'prod1', quantity: 2 }, + { productId: 'prod2', quantity: 1 } + ] +}); +``` + +**개선 사항:** + +1. **SRP**: 각 클래스가 하나의 책임만 가짐 +2. **DRY**: 검증 로직이 한 곳에서만 정의됨 +3. **단순화**: 메인 로직이 명확하고 읽기 쉬움 +4. **YAGNI**: 필요한 기능만 구현 +5. **타입 안전**: 모든 함수에 명확한 타입 정의 +6. **명명규칙**: 일관성 있는 네이밍 +7. **테스트 용이**: 각 클래스를 독립적으로 테스트 가능 + +--- + +이 예제들을 참고하여 코드를 작성하고 검토해보세요! diff --git a/skills/code-style-reviewer/PRINCIPLES.md b/skills/code-style-reviewer/PRINCIPLES.md new file mode 100644 index 0000000..409ccfe --- /dev/null +++ b/skills/code-style-reviewer/PRINCIPLES.md @@ -0,0 +1,445 @@ +# Code Style Principles - 상세 가이드 + +## 1. 단일책임원칙 (Single Responsibility Principle - SRP) + +### 개념 +"하나의 클래스나 함수는 하나의 변경 이유만 가져야 한다" + +### 왜 중요한가? +- 코드 이해와 유지보수가 쉬워집니다 +- 테스트가 간단해집니다 +- 재사용성이 높아집니다 +- 변경 영향도가 최소화됩니다 + +### 체크리스트 +- [ ] 함수가 한 가지 작업만 수행하는가? +- [ ] 함수가 여러 이유로 변경될 가능성이 있는가? +- [ ] 클래스가 여러 책임을 가지고 있는가? +- [ ] 함수의 이름이 무엇을 하는지 정확히 설명하는가? +- [ ] 함수를 한 문장으로 설명할 수 있는가? + +### 나쁜 예 +```typescript +// 여러 책임을 가진 함수 +function processUserOrder(userId: string, items: Item[]) { + // 1. 사용자 검증 + const user = validateUser(userId); + + // 2. 주문 생성 + const order = createOrder(user, items); + + // 3. 결제 처리 + processPayment(order); + + // 4. 이메일 발송 + sendEmail(user.email, "Order confirmed"); + + // 5. 로깅 + logTransaction(order); +} +``` + +### 좋은 예 +```typescript +// 각 함수가 하나의 책임만 가짐 +function processUserOrder(userId: string, items: Item[]) { + const user = validateUser(userId); + const order = createOrder(user, items); + handlePayment(order); + notifyUser(user, order); + recordTransaction(order); +} + +// 결제만 담당 +function handlePayment(order: Order): void { + processPayment(order); +} + +// 알림만 담당 +function notifyUser(user: User, order: Order): void { + sendEmail(user.email, "Order confirmed"); +} + +// 로깅만 담당 +function recordTransaction(order: Order): void { + logTransaction(order); +} +``` + +--- + +## 2. DRY (Don't Repeat Yourself) + +### 개념 +"같은 코드는 한 번만 작성해야 한다" + +### 왜 중요한가? +- 버그 수정이 한 곳에서만 필요합니다 +- 코드가 간결해집니다 +- 유지보수 비용이 감소합니다 +- 일관성이 보장됩니다 + +### 체크리스트 +- [ ] 반복되는 패턴이나 로직이 있는가? +- [ ] 유사한 함수들이 통합될 수 있는가? +- [ ] 공통 로직이 유틸리티 함수로 추출되었는가? +- [ ] 복사-붙여넣기한 코드가 있는가? +- [ ] 같은 정규식이나 로직이 여러 곳에 있는가? + +### 나쁜 예 +```typescript +// 반복되는 검증 로직 +function createUser(email: string, name: string) { + if (!email || email.length === 0) { + throw new Error("Email is required"); + } + if (!name || name.length === 0) { + throw new Error("Name is required"); + } + // 사용자 생성... +} + +function createPost(title: string, content: string) { + if (!title || title.length === 0) { + throw new Error("Title is required"); + } + if (!content || content.length === 0) { + throw new Error("Content is required"); + } + // 포스트 생성... +} +``` + +### 좋은 예 +```typescript +// 검증 로직을 한 곳에서 관리 +function validateRequired(value: string, fieldName: string): void { + if (!value || value.length === 0) { + throw new Error(`${fieldName} is required`); + } +} + +function createUser(email: string, name: string) { + validateRequired(email, "Email"); + validateRequired(name, "Name"); + // 사용자 생성... +} + +function createPost(title: string, content: string) { + validateRequired(title, "Title"); + validateRequired(content, "Content"); + // 포스트 생성... +} +``` + +--- + +## 3. 단순화 우선 (Simplicity First) + +### 개념 +"복잡한 추상화보다는 이해하기 쉬운 단순한 코드를 우선한다" + +### 왜 중요한가? +- 새로운 팀원이 빨리 코드를 이해합니다 +- 버그가 적어집니다 +- 유지보수가 쉬워집니다 +- 과도한 설계를 피합니다 + +### 체크리스트 +- [ ] 코드를 읽고 쉽게 이해할 수 있는가? +- [ ] 불필요한 추상화가 있는가? +- [ ] 깊은 중첩이 있는가? (3단계 이상) +- [ ] 과도하게 우아한(clever) 코드가 있는가? +- [ ] 한 줄의 코드로 너무 많은 작업을 하는가? + +### 나쁜 예 +```typescript +// 불필요한 추상화와 복잡성 +interface Strategy { + execute(data: any): any; +} + +class ComplexStrategy implements Strategy { + execute = (data) => { + return [data].reduce((acc, val) => ({ + ...acc, + ...Object.entries(val).reduce((a, [k, v]) => + ({ ...a, [k]: typeof v === 'number' ? v * 2 : v }), {}) + }), {}); + }; +} + +// 깊은 중첩 +if (user) { + if (user.isActive) { + if (user.permissions) { + if (user.permissions.includes('admin')) { + // 실제 작업 + } + } + } +} +``` + +### 좋은 예 +```typescript +// 단순하고 명확한 구현 +function doubleNumbers(obj: Record): Record { + return Object.entries(obj).reduce((acc, [key, value]) => { + acc[key] = typeof value === 'number' ? value * 2 : value; + return acc; + }, {} as Record); +} + +// Early return으로 중첩 제거 +function checkAdminPermission(user: User): boolean { + if (!user) return false; + if (!user.isActive) return false; + if (!user.permissions) return false; + return user.permissions.includes('admin'); +} + +// 더 나은 방식 +function isAdmin(user: User): boolean { + return user?.isActive && user?.permissions?.includes('admin') ?? false; +} +``` + +--- + +## 4. YAGNI (You Aren't Gonna Need It) + +### 개념 +"현재 필요하지 않은 기능은 구현하지 않는다" + +### 왜 중요한가? +- 코드 복잡도를 낮춥니다 +- 유지보수해야 할 코드의 양을 줄입니다 +- 미래 변경에 더 유연하게 대응합니다 +- 불필요한 버그의 원인을 제거합니다 + +### 체크리스트 +- [ ] 사용되지 않는 코드가 있는가? +- [ ] 사용되지 않는 매개변수가 있는가? +- [ ] "나중에 필요할 것 같아서" 추가한 기능이 있는가? +- [ ] 테스트되지 않는 코드가 있는가? +- [ ] 주석 처리된 코드가 있는가? + +### 나쁜 예 +```typescript +// 사용되지 않는 기능과 매개변수 +interface UserService { + // 현재는 사용하지 않지만, 나중에 필요할 것 같아서 추가 + createUser(email: string, name: string, preferredLanguage?: string): User; + updateUser(id: string, data: Partial): User; + deleteUser(id: string): void; + // 나중에 필요할 것 같아서 추가 + suspendUser(id: string, reason: string): User; + archiveUser(id: string): User; +} + +function getUserFullInfo(userId: string, includeAnalytics?: boolean, includeHistory?: boolean, includeFuturePredictions?: boolean) { + // 현재는 includeAnalytics와 includeFuturePredictions를 사용하지 않음 + const user = getUser(userId); + // ... +} +``` + +### 좋은 예 +```typescript +// 필요한 기능만 구현 +interface UserService { + createUser(email: string, name: string): User; + updateUser(id: string, data: Partial): User; + deleteUser(id: string): void; + // 필요해지면 그때 추가 +} + +function getUserFullInfo(userId: string) { + const user = getUser(userId); + // 필요한 정보만 반환 + return { + id: user.id, + email: user.email, + name: user.name + }; +} + +// 미래에 필요하면 그때 추가 +function getUserWithAnalytics(userId: string) { + const user = getUser(userId); + const analytics = getAnalytics(userId); + return { ...user, analytics }; +} +``` + +--- + +## 5. 타입 안전성 (Type Safety) + +### 개념 +"`any` 타입을 피하고, 명확한 타입 정의를 사용한다" (TypeScript) + +### 왜 중요한가? +- 런타임 에러를 개발 단계에서 발견합니다 +- IDE의 자동완성이 정확해집니다 +- 코드 리팩토링이 안전합니다 +- 의도가 명확해집니다 + +### 체크리스트 +- [ ] `any` 타입이 사용되었는가? +- [ ] 함수의 매개변수에 타입이 모두 정의되었는가? +- [ ] 함수의 반환 타입이 명시되었는가? +- [ ] 객체의 모양이 `interface`로 정의되었는가? +- [ ] `unknown` 타입을 사용할 때 타입 가드가 있는가? + +### 나쁜 예 +```typescript +// any 타입 남용 +function processData(data: any): any { + return data.map((item: any) => { + return { + ...item, + value: item.value * 2 + }; + }); +} + +// 타입이 없는 반환값 +function getUserData(id) { + // ... + return user; +} + +// 암묵적 any +function filterItems(items, predicate) { + return items.filter(predicate); +} +``` + +### 좋은 예 +```typescript +// 명확한 타입 정의 +interface DataItem { + id: string; + value: number; + name: string; +} + +function processData(data: DataItem[]): DataItem[] { + return data.map(item => ({ + ...item, + value: item.value * 2 + })); +} + +// 명시적 반환 타입 +interface User { + id: string; + email: string; + name: string; +} + +function getUserData(id: string): User { + // ... + return user; +} + +// 제네릭으로 유연성 유지 +function filterItems(items: T[], predicate: (item: T) => boolean): T[] { + return items.filter(predicate); +} + +// unknown을 사용할 때는 타입 가드 필수 +function process(data: unknown): void { + if (typeof data === 'string') { + console.log(data.toUpperCase()); + } else if (Array.isArray(data)) { + console.log(data.length); + } +} +``` + +--- + +## 6. 명명규칙 (Naming Conventions) + +### 개념 +"코드는 인간을 위해 작성되며, 명확한 이름이 코드의 의도를 전달한다" + +### 체크리스트 +- [ ] 변수명이 의미있고 명확한가? +- [ ] 함수명이 동사로 시작하는가? +- [ ] 클래스명이 명사이고 PascalCase인가? +- [ ] 상수가 UPPER_SNAKE_CASE인가? +- [ ] 명명규칙이 일관성 있는가? + +### 나쁜 예 +```typescript +// 의미없는 이름 +const x = 10; +const temp = userData; +const fn = (a) => a * 2; + +// 일관성 없는 명명 +const max_count = 100; +const itemTotal = 50; +const MaxValue = 200; + +// 너무 긴 이름 +const userDataForProcessingAndStorageButNotForDeletionOrModification = user; +``` + +### 좋은 예 +```typescript +// 명확한 이름 +const maxRetries = 10; +const userProfile = userData; +const doubleNumber = (value: number) => value * 2; + +// 일관성 있는 명명 (camelCase for variables/functions) +const maxCount = 100; +const itemTotal = 50; +const maxValue = 200; + +// 적절한 길이 +const userForProcessing = user; +``` + +### 명명 규칙 요약 + +| 대상 | 규칙 | 예시 | +|------|------|------| +| 변수 | camelCase | `userName`, `isActive`, `itemCount` | +| 함수 | camelCase + 동사 | `getUserData`, `validateEmail`, `calculateTotal` | +| 클래스 | PascalCase | `UserService`, `ValidationHelper`, `ApiClient` | +| 상수 | UPPER_SNAKE_CASE | `MAX_RETRY`, `DEFAULT_TIMEOUT`, `API_KEY` | +| 파일 | lowercase + kebab-case | `user-service.ts`, `api-client.ts` | +| 인터페이스 | PascalCase + I prefix (선택) | `IUser` 또는 `User` | +| Enum | PascalCase | `UserStatus`, `PaymentMethod` | + +--- + +## 종합 체크리스트 + +코드 리뷰 시 확인할 항목들: + +### 구조 (Structure) +- [ ] 함수/클래스가 하나의 책임만 가지는가? (SRP) +- [ ] 반복되는 코드가 추출되었는가? (DRY) +- [ ] 코드가 간단하고 이해하기 쉬운가? (Simplicity) + +### 기능 (Functionality) +- [ ] 필요한 기능만 구현되었는가? (YAGNI) +- [ ] 사용되지 않는 코드가 없는가? +- [ ] 타입이 안전하게 정의되었는가? (Type Safety) + +### 스타일 (Style) +- [ ] 명명규칙이 일관성 있는가? +- [ ] 코드 형식이 일관성 있는가? +- [ ] 주석이 적절히 작성되었는가? + +### 테스트 (Testing) +- [ ] 함수가 테스트 가능한 크기인가? +- [ ] 엣지 케이스가 처리되었는가? +- [ ] 에러 처리가 적절한가? diff --git a/skills/code-style-reviewer/SKILL.md b/skills/code-style-reviewer/SKILL.md new file mode 100644 index 0000000..097a4af --- /dev/null +++ b/skills/code-style-reviewer/SKILL.md @@ -0,0 +1,140 @@ +--- +name: code-style-reviewer +description: 코드 스타일 원칙 기반 리뷰 - 단일책임원칙(SRP), DRY(Don't Repeat Yourself), 단순화 우선, YAGNI(You Aren't Gonna Need It), 타입 안전성을 검사합니다. 코드 구조와 명명규칙도 함께 평가합니다. 코드 리뷰가 필요할 때 자동으로 사용됩니다. +allowed-tools: Read, Grep, Glob +--- + +# Code Style Reviewer + +코드 스타일 원칙에 따른 전문적인 코드 리뷰를 제공하는 Skill입니다. Claude가 직접 코드를 분석하여 5가지 핵심 원칙을 중심으로 상세한 리포트를 생성합니다. + +## 검사 원칙 + +### 1. 단일책임원칙 (Single Responsibility Principle) +클래스, 함수, 모듈은 하나의 책임만 가져야 합니다. 복잡한 함수는 여러 작은 함수로 분리되어야 합니다. + +### 2. DRY (Don't Repeat Yourself) +같은 로직이 반복되면 안 됩니다. 공통 로직은 별도의 함수나 유틸리티로 추출해야 합니다. + +### 3. 단순화 우선 +복잡한 추상화보다는 이해하기 쉬운 단순한 코드를 우선합니다. 과도한 설계는 피합니다. + +### 4. YAGNI (You Aren't Gonna Need It) +현재 필요하지 않은 기능은 추가하지 않습니다. 미래를 대비한 불필요한 코드는 제거해야 합니다. + +### 5. 타입 안전성 +`any` 타입 사용을 최소화합니다. TypeScript를 사용할 때는 명확한 타입을 정의해야 합니다. + +## Instructions + +### 리뷰 프로세스 + +1. **대상 파일 파악** + - 검토할 코드 파일을 Read 도구로 읽습니다 + - 파일 구조와 전체 범위를 파악합니다 + +2. **원칙별 분석** + - 각 파일에 대해 5가지 원칙을 체계적으로 검토합니다 + - Grep을 사용하여 반복되는 패턴을 찾습니다 + - 명명규칙의 일관성을 확인합니다 + +3. **상세 리포트 생성** + - 파일별로 구분된 리포트를 작성합니다 + - 각 문제점에 대해 구체적인 개선 방안을 제시합니다 + - 우선순위를 표시합니다: + - **Critical**: 반드시 수정해야 함 + - **Warning**: 개선이 필요함 + - **Suggestion**: 고려해볼 만함 + +4. **코드 예시 제공** + - 각 문제에 대해 "문제 코드" vs "개선 코드" 예시를 제시합니다 + - 변경의 이유를 명확히 설명합니다 + +## 리뷰 체크리스트 + +### 단일책임원칙 검사 +- [ ] 함수가 하나의 작업만 수행하는가? +- [ ] 클래스가 하나의 책임만 가지는가? +- [ ] 복잡한 로직이 작은 함수로 분리되어 있는가? +- [ ] 함수의 길이가 적절한가? (권장: 20줄 이하) + +### DRY 검사 +- [ ] 반복되는 코드가 있는가? +- [ ] 공통 로직이 추출되었는가? +- [ ] 설정 값이 하드코딩되지 않았는가? +- [ ] 유사한 구조의 코드가 통합될 수 있는가? + +### 단순화 우선 검사 +- [ ] 불필요한 추상화가 있는가? +- [ ] 복잡한 문법 대신 단순한 표현을 사용했는가? +- [ ] 깊은 중첩 구조가 있는가? (권장: 3단계 이내) +- [ ] 과도하게 우아한(overly clever) 코드가 있는가? + +### YAGNI 검사 +- [ ] 사용되지 않는 코드가 있는가? +- [ ] 미래를 대비한 불필요한 기능이 있는가? +- [ ] 제거할 수 있는 매개변수가 있는가? +- [ ] 죽은(dead) 코드나 주석이 있는가? + +### 타입 안전성 검사 (TypeScript) +- [ ] `any` 타입이 사용되었는가? +- [ ] 모든 함수의 매개변수에 타입이 정의되었는가? +- [ ] 반환 타입이 명시적인가? +- [ ] `interface`와 `type`을 적절히 사용했는가? + +### 명명규칙 검사 +- [ ] 변수명이 명확하고 의미있는가? +- [ ] 함수명이 동사로 시작하는가? +- [ ] 클래스명이 명사이고 PascalCase인가? +- [ ] 상수가 UPPER_SNAKE_CASE인가? +- [ ] 명명규칙이 일관성 있는가? + +## 예시 + +자세한 예시와 패턴은 [EXAMPLES.md](EXAMPLES.md) 참고 +상세한 원칙 설명은 [PRINCIPLES.md](PRINCIPLES.md) 참고 + +## 리뷰 출력 형식 + +``` +# Code Style Review Report + +## 📄 파일: [filename] + +### ✅ 좋은 점 +- [좋은 사례들] + +### ⚠️ Critical Issues +**문제 1: [제목]** +- 위치: [라인 또는 함수명] +- 원칙: [해당 원칙] +- 설명: [상세 설명] +- 개선 방법: + ``` + // Before + [현재 코드] + + // After + [개선된 코드] + ``` + +### 📢 Warnings +[경고 수준 문제들] + +### 💡 Suggestions +[제안 수준의 개선 사항들] + +## 📊 종합 평가 +- 전체 코드 품질 점수: [X/10] +- 가장 중요한 개선 사항: [상위 3개] +``` + +## 사용 시나리오 + +이 Skill은 다음 상황에 자동으로 활용됩니다: + +- 코드 리뷰 요청 시 +- 코드 품질 분석 요청 시 +- 코드 구조 개선 조언 필요 시 +- 새로운 파일의 스타일 검사 시 +- 기존 코드의 리팩토링 제안 시