# 코딩 스타일 가이드 > TK-FB-Project 코드 작성 규칙 및 베스트 프랙티스 ## 📚 목차 1. [일반 원칙](#일반-원칙) 2. [JavaScript/Node.js](#javascriptnodejs) 3. [HTML/CSS](#htmlcss) 4. [SQL](#sql) 5. [Git 커밋 메시지](#git-커밋-메시지) 6. [파일 및 디렉토리 구조](#파일-및-디렉토리-구조) --- ## 일반 원칙 ### 1. 가독성 우선 ```javascript // ❌ 나쁜 예 const x = a.filter(i => i.active).map(i => ({...i, name: i.n})); // ✅ 좋은 예 const activeWorkers = workers .filter(worker => worker.is_active) .map(worker => ({ ...worker, name: worker.name })); ``` ### 2. 명확한 네이밍 ```javascript // ❌ 나쁜 예 const d = new Date(); const arr = []; const temp = getUserData(); // ✅ 좋은 예 const currentDate = new Date(); const activeWorkers = []; const userData = getUserData(); ``` ### 3. 단일 책임 원칙 ```javascript // ❌ 나쁜 예 - 하나의 함수가 너무 많은 일을 함 async function processReport(data) { // 검증 if (!data.worker_id) throw new Error('Invalid'); // DB 저장 await db.query('INSERT INTO ...'); // 이메일 발송 await sendEmail(); // 알림 전송 await sendNotification(); } // ✅ 좋은 예 - 책임 분리 async function processReport(data) { validateReport(data); const report = await saveReport(data); await notifyReportCreation(report); return report; } function validateReport(data) { if (!data.worker_id) { throw new ValidationError('작업자를 선택해주세요'); } } async function saveReport(data) { return await reportRepository.create(data); } async function notifyReportCreation(report) { await emailService.send(report); await notificationService.send(report); } ``` ### 4. DRY (Don't Repeat Yourself) ```javascript // ❌ 나쁜 예 - 중복 코드 if (!req.user || !['admin', 'system'].includes(req.user.access_level)) { return res.status(403).json({ error: 'Forbidden' }); } // ✅ 좋은 예 - 재사용 가능한 미들웨어 const requireRole = (...roles) => { return (req, res, next) => { if (!req.user || !roles.includes(req.user.access_level)) { throw new ForbiddenError(); } next(); }; }; // 사용 router.get('/admin', requireRole('admin', 'system'), getAdminData); ``` --- ## JavaScript/Node.js ### 변수 선언 ```javascript // ✅ const 우선, 재할당 필요시 let, var 사용 금지 const API_URL = 'http://api.example.com'; let currentPage = 1; // ❌ var 사용 금지 var x = 10; // NO! ``` ### 함수 작성 ```javascript // ✅ 화살표 함수 사용 (콜백, 간단한 함수) const double = (n) => n * 2; const sum = (a, b) => a + b; // ✅ 일반 함수 (메서드, 복잡한 로직) function calculateTotalHours(reports) { let total = 0; for (const report of reports) { total += report.work_hours; } return total; } // ✅ async/await 사용 (Promise보다 선호) async function fetchUserData(userId) { try { const user = await userModel.findById(userId); const reports = await reportModel.findByUser(userId); return { user, reports }; } catch (error) { logger.error('Failed to fetch user data', { userId, error }); throw error; } } ``` ### 에러 처리 ```javascript // ❌ 나쁜 예 try { await saveData(); } catch (error) { console.log(error); // 단순 로그만 } // ✅ 좋은 예 try { await saveData(data); } catch (error) { logger.error('데이터 저장 실패', { data, error: error.message, stack: error.stack }); throw new AppError('데이터 저장에 실패했습니다', 500); } ``` ### 객체 및 배열 ```javascript // ✅ 구조 분해 할당 const { name, email, phone } = user; const [first, second, ...rest] = items; // ✅ 스프레드 연산자 const newUser = { ...user, is_active: true }; const allItems = [...items1, ...items2]; // ✅ 단축 속성 const name = 'John'; const age = 30; const user = { name, age }; // { name: name, age: age } 대신 ``` ### 비동기 처리 ```javascript // ❌ 콜백 지옥 getData(function(a) { getMoreData(a, function(b) { getMoreData(b, function(c) { console.log(c); }); }); }); // ✅ async/await async function processData() { const a = await getData(); const b = await getMoreData(a); const c = await getMoreData(b); return c; } // ✅ 병렬 처리가 가능한 경우 const [users, projects, reports] = await Promise.all([ fetchUsers(), fetchProjects(), fetchReports() ]); ``` ### 조건문 ```javascript // ✅ Early Return 패턴 function processUser(user) { if (!user) { throw new ValidationError('User is required'); } if (!user.is_active) { throw new ValidationError('User is not active'); } // 메인 로직 return processActiveUser(user); } // ❌ 중첩된 조건문 function processUser(user) { if (user) { if (user.is_active) { // 메인 로직 return processActiveUser(user); } else { throw new Error('Not active'); } } else { throw new Error('No user'); } } ``` ### 주석 ```javascript // ✅ JSDoc 사용 /** * 작업 보고서를 생성합니다 * @param {Object} reportData - 보고서 데이터 * @param {number} reportData.worker_id - 작업자 ID * @param {string} reportData.work_content - 작업 내용 * @param {number} userId - 생성하는 사용자 ID * @returns {Promise} 생성된 보고서 * @throws {ValidationError} 검증 실패 시 */ async function createReport(reportData, userId) { // ... } // ✅ 복잡한 로직 설명 // NOTE: MySQL 8.0에서는 GROUP BY 동작이 다르므로 명시적으로 컬럼 지정 const query = ` SELECT worker_id, COUNT(*) as count FROM reports GROUP BY worker_id ORDER BY count DESC `; // ⚠️ TODO, FIXME 등 명확히 표시 // TODO: 캐싱 로직 추가 필요 // FIXME: 날짜 범위 검증 개선 필요 // HACK: 임시 해결책, 나중에 리팩토링 필요 ``` ### 모듈 구조 ```javascript // ✅ 명확한 import/export // 파일 상단에 모든 import const express = require('express'); const { ValidationError } = require('../utils/errors'); const reportService = require('../services/reportService'); // 함수 정의 function createReport(req, res, next) { // ... } function getReports(req, res, next) { // ... } // 파일 하단에 export module.exports = { createReport, getReports }; ``` --- ## HTML/CSS ### HTML ```html

제목

내용

내용
``` ### CSS ```css /* ✅ 클래스 네이밍: BEM 방식 */ .block {} .block__element {} .block--modifier {} /* 예시 */ .card {} .card__header {} .card__body {} .card__footer {} .card--large {} .card--primary {} /* ✅ 속성 순서 */ .element { /* 1. Positioning */ position: absolute; top: 0; left: 0; z-index: 10; /* 2. Box Model */ display: block; width: 100px; height: 100px; margin: 10px; padding: 10px; border: 1px solid #000; /* 3. Typography */ font-family: Arial; font-size: 14px; line-height: 1.5; color: #333; /* 4. Visual */ background-color: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* 5. Other */ cursor: pointer; transition: all 0.3s ease; } /* ✅ CSS 변수 사용 */ :root { --color-primary: #007bff; --color-success: #28a745; --spacing-md: 1rem; } .button { background-color: var(--color-primary); padding: var(--spacing-md); } /* ✅ 중첩 최소화 (3단계 이하) */ .nav {} .nav__item {} .nav__link {} /* ❌ 과도한 중첩 */ .nav ul li a span {} /* NO! */ ``` --- ## SQL ### 쿼리 작성 ```sql -- ✅ 대문자 키워드, 명시적 컬럼 지정 SELECT id, name, email, created_at FROM users WHERE is_active = 1 AND role = 'admin' ORDER BY created_at DESC LIMIT 10; -- ❌ SELECT * 사용 금지 SELECT * FROM users; -- NO! -- ✅ 조인 명시적 작성 SELECT r.id, r.work_content, w.name AS worker_name, p.name AS project_name FROM daily_work_reports r INNER JOIN workers w ON r.worker_id = w.id LEFT JOIN projects p ON r.project_id = p.id WHERE r.report_date BETWEEN ? AND ?; -- ✅ 파라미터 바인딩 사용 const query = 'SELECT * FROM users WHERE id = ? AND email = ?'; const [rows] = await db.query(query, [userId, email]); -- ❌ 문자열 연결 금지 (SQL Injection 위험) const query = `SELECT * FROM users WHERE email = '${email}'`; -- NO! ``` ### 테이블/컬럼 네이밍 ```sql -- ✅ 스네이크 케이스 CREATE TABLE daily_work_reports ( id INT PRIMARY KEY AUTO_INCREMENT, worker_id INT NOT NULL, report_date DATE NOT NULL, work_content TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- ✅ 복수형 테이블명 users, workers, projects, reports -- ✅ 외래키 명확히 worker_id, project_id (테이블명_id 형식) ``` --- ## Git 커밋 메시지 ### 형식 ``` ():