# 리팩토링 실행 계획 > **작성일**: 2025-12-11 > **목표**: 코드 품질 개선, 유지보수성 향상, 보안 강화 ## 📋 전체 로드맵 ``` Phase 1 (1-2주) → Phase 2 (1개월) → Phase 3 (2-3개월) → Phase 4 (6개월) 긴급 조치 코드 구조화 아키텍처 개선 현대화 ``` --- ## 🚨 Phase 1: 긴급 조치 (1-2주) ### 목표 보안 취약점 제거 및 명백한 문제 해결 ### 작업 항목 #### 1.1 중복 디렉토리 제거 (2시간) **현재 문제**: - `synology_deployment/` 전체 프로젝트 중복 (268MB) - `synology_deployment_v3.tar.gz`, `synology_deployment.tar.gz` 불필요한 아카이브 **작업**: ```bash # 1. 백업 확인 git status # 2. 중복 디렉토리 제거 rm -rf synology_deployment/ rm synology_deployment_v3.tar.gz rm synology_deployment.tar.gz rm TK-FB-Project_Synology_Volume2_20251105_120332.tar.gz # 3. 배포 스크립트 생성 mkdir deployment # deployment/deploy.sh 생성 ``` **검증**: - [ ] 디렉토리 삭제 확인 - [ ] .gitignore 업데이트 - [ ] 배포 스크립트 테스트 #### 1.2 보안 정보 환경변수화 (4시간) **현재 문제**: - docker-compose.yml에 평문 비밀번호 - README.md에 실제 비밀번호 노출 **작업**: **Step 1**: .env.example 생성 ```bash # .env.example DB_HOST=db DB_PORT=3306 DB_NAME=hyungi DB_USER=hyungi DB_PASSWORD= DB_ROOT_PASSWORD= JWT_SECRET= JWT_REFRESH_SECRET= API_PORT=20005 NODE_ENV=production ``` **Step 2**: docker-compose.yml 수정 ```yaml services: db: environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: ${DB_NAME} MYSQL_USER: ${DB_USER} MYSQL_PASSWORD: ${DB_PASSWORD} api: environment: DB_HOST: ${DB_HOST} DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} DB_NAME: ${DB_NAME} JWT_SECRET: ${JWT_SECRET} JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET} PORT: ${API_PORT} NODE_ENV: ${NODE_ENV} ``` **Step 3**: .gitignore 업데이트 ```gitignore # 환경 변수 .env .env.local .env.*.local # 보안 정보 secrets/ *.pem *.key ``` **Step 4**: README.md 업데이트 ```markdown ## 환경 설정 1. `.env.example`을 복사하여 `.env` 파일 생성 2. 실제 비밀번호로 값 변경 3. `.env` 파일은 절대 Git에 커밋하지 않음 ``` **검증**: - [ ] .env 파일로 정상 동작 확인 - [ ] README에서 실제 비밀번호 제거 - [ ] .env가 .gitignore에 포함됨 #### 1.3 백업 파일 정리 (1시간) **작업**: ```bash # 1. 백업 파일 확인 find . -name "*.backup" -o -name "*복사본*" -o -name "*이전*" # 2. 제거 rm api.hyungi.net/index.js.backup rm "api.hyungi.net/controllers/dailyWorkReportController 이전.js" rm hyungi.sql.backup rm docker-compose.yml.backup rm docker-compose.yml.new rm "web-ui/js/daily-report-viewer 복사본.js" rm -rf "json(백업)" # 3. .gitignore 업데이트 echo "*.backup" >> .gitignore echo "*복사본*" >> .gitignore echo "*이전*" >> .gitignore echo "*.old" >> .gitignore ``` **검증**: - [ ] 백업 파일 모두 제거 - [ ] .gitignore 적용 확인 #### 1.4 Git Commit & Push (0.5시간) ```bash git add . git commit -m "refactor: Phase 1 - 긴급 보안 및 중복 제거 - synology_deployment 중복 디렉토리 제거 - 비밀번호 환경변수화 (.env) - 백업/임시 파일 정리 - .gitignore 업데이트 🤖 Generated with Claude Code" git push origin master ``` **Phase 1 완료 체크리스트**: - [ ] 중복 디렉토리 제거 - [ ] 보안 정보 환경변수화 - [ ] 백업 파일 정리 - [ ] .gitignore 업데이트 - [ ] Git 커밋 & 푸시 - [ ] refactoring/LOG.md에 기록 --- ## ⚙️ Phase 2: 코드 구조화 (1개월) ### 목표 index.js 분리, 파일 크기 축소, 코드 정리 ### 2.1 백엔드 구조 개선 (2주) #### 2.1.1 index.js 분리 (3일) **현재**: 889줄의 거대한 파일 **목표 구조**: ``` api.hyungi.net/ ├── index.js # 100줄 이하 ├── config/ │ ├── database.js # DB 연결 설정 │ ├── cors.js # CORS 설정 │ ├── middleware.js # 미들웨어 설정 │ └── routes.js # 라우트 등록 ├── middlewares/ │ ├── auth.js # 인증 │ ├── permission.js # 권한 │ ├── errorHandler.js # 에러 처리 │ └── requestLogger.js # 요청 로깅 └── utils/ ├── errors.js # 커스텀 에러 클래스 └── logger.js # 로거 ``` **작업 순서**: 1. **config/database.js 생성** (347-377줄 이동) ```javascript const mysql = require('mysql2/promise'); const pool = mysql.createPool({ host: process.env.DB_HOST || 'localhost', user: process.env.DB_USER || 'hyungi', password: process.env.DB_PASSWORD, database: process.env.DB_NAME || 'hyungi', port: process.env.DB_PORT || 3306, waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); module.exports = pool; ``` 2. **config/cors.js 생성** (183-228줄 이동) ```javascript const allowedOrigins = [ 'http://localhost:3000', 'http://localhost:8080', 'http://192.168.0.6:8080', 'http://hyungi.net', 'https://hyungi.net' ]; const corsOptions = { origin: (origin, callback) => { if (process.env.NODE_ENV === 'development' && !origin) { return callback(null, true); } if (allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('CORS policy violation')); } }, credentials: true }; module.exports = corsOptions; ``` 3. **middlewares/auth.js 생성** (240-274줄 이동) ```javascript const jwt = require('jsonwebtoken'); const { AuthenticationError } = require('../utils/errors'); const authenticateToken = (req, res, next) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { throw new AuthenticationError('토큰이 필요합니다'); } jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) { throw new AuthenticationError('유효하지 않은 토큰입니다'); } req.user = user; next(); }); }; module.exports = { authenticateToken }; ``` 4. **controllers/userController.js 생성** (347-631줄 이동) - 인라인 사용자 관리 API를 별도 컨트롤러로 분리 5. **index.js 재구성** ```javascript require('dotenv').config(); const express = require('express'); const cors = require('cors'); const db = require('./config/database'); const corsOptions = require('./config/cors'); const { errorHandler } = require('./middlewares/errorHandler'); const routes = require('./config/routes'); const app = express(); const PORT = process.env.PORT || 20005; // 미들웨어 app.use(cors(corsOptions)); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 라우트 app.use('/api', routes); // 에러 핸들러 (마지막에 위치) app.use(errorHandler); // 서버 시작 app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); module.exports = app; ``` **검증**: - [ ] 서버 정상 시작 - [ ] 모든 API 엔드포인트 동작 확인 - [ ] 에러 처리 정상 동작 - [ ] index.js가 100줄 이하 #### 2.1.2 에러 처리 통일 (2일) **utils/errors.js 생성**: ```javascript class AppError extends Error { constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') { super(message); this.statusCode = statusCode; this.code = code; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } } class ValidationError extends AppError { constructor(message, details = {}) { super(message, 400, 'VALIDATION_ERROR'); this.details = details; } } class AuthenticationError extends AppError { constructor(message = '인증이 필요합니다') { super(message, 401, 'AUTHENTICATION_ERROR'); } } class ForbiddenError extends AppError { constructor(message = '권한이 없습니다') { super(message, 403, 'FORBIDDEN'); } } class NotFoundError extends AppError { constructor(message = '리소스를 찾을 수 없습니다') { super(message, 404, 'NOT_FOUND'); } } module.exports = { AppError, ValidationError, AuthenticationError, ForbiddenError, NotFoundError }; ``` **middlewares/errorHandler.js 생성**: ```javascript const { AppError } = require('../utils/errors'); const logger = require('../utils/logger'); const errorHandler = (err, req, res, next) => { // 로깅 logger.error({ message: err.message, stack: err.stack, code: err.code, path: req.path, method: req.method, userId: req.user?.id }); // 운영 에러 (예상된 에러) if (err.isOperational) { return res.status(err.statusCode).json({ success: false, error: { message: err.message, code: err.code, details: err.details } }); } // 프로그래밍 에러 (예상치 못한 에러) return res.status(500).json({ success: false, error: { message: process.env.NODE_ENV === 'development' ? err.message : '서버 오류가 발생했습니다', code: 'INTERNAL_ERROR' } }); }; module.exports = { errorHandler }; ``` **기존 코드 변경**: ```javascript // Before if (!user) { return res.status(401).json({ error: 'Unauthorized' }); } // After const { AuthenticationError } = require('../utils/errors'); if (!user) { throw new AuthenticationError(); } ``` **검증**: - [ ] 모든 컨트롤러에 에러 클래스 적용 - [ ] 에러 응답 형식 통일 - [ ] 로깅 정상 동작 #### 2.1.3 SELECT * 쿼리 개선 (3일) **작업 파일**: - models/*.js (14개 파일) - controllers/*.js (17개 파일) **변경 예시**: ```javascript // Before const [workers] = await db.query('SELECT * FROM workers'); // After const [workers] = await db.query(` SELECT id, name, email, phone, position, department, is_active, created_at, updated_at FROM workers WHERE is_active = 1 ORDER BY name ASC `); ``` **검증**: - [ ] 모든 SELECT * 제거 확인 - [ ] 쿼리 성능 테스트 - [ ] API 응답 확인 ### 2.2 프론트엔드 모듈화 (2주) #### 2.2.1 공통 모듈 추출 (5일) **목표 구조**: ``` web-ui/js/ ├── modules/ │ ├── common/ │ │ ├── api-client.js # API 호출 통합 │ │ ├── utils.js # 유틸리티 함수 │ │ ├── validator.js # 폼 검증 │ │ └── logger.js # 로깅 │ ├── calendar/ │ │ ├── CalendarView.js # UI 렌더링 │ │ ├── CalendarAPI.js # API 호출 │ │ └── CalendarUtils.js # 유틸리티 │ └── dashboard/ │ ├── DashboardView.js │ └── DashboardData.js ├── pages/ # 기존 파일 (모듈 사용) └── legacy/ # 임시 백업 ``` **1단계: api-client.js 생성** ```javascript // modules/common/api-client.js export class ApiClient { constructor(baseUrl) { this.baseUrl = baseUrl || this.getBaseUrl(); } getBaseUrl() { const protocol = window.location.protocol; const hostname = window.location.hostname; return `${protocol}//${hostname}:20005/api`; } async request(endpoint, options = {}) { const token = localStorage.getItem('token'); const headers = { 'Content-Type': 'application/json', ...options.headers }; if (token) { headers['Authorization'] = `Bearer ${token}`; } try { const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, headers }); const data = await response.json(); if (!response.ok) { throw new Error(data.error?.message || '요청 실패'); } return data; } catch (error) { console.error('[API Error]', error); throw error; } } get(endpoint) { return this.request(endpoint, { method: 'GET' }); } post(endpoint, data) { return this.request(endpoint, { method: 'POST', body: JSON.stringify(data) }); } put(endpoint, data) { return this.request(endpoint, { method: 'PUT', body: JSON.stringify(data) }); } delete(endpoint) { return this.request(endpoint, { method: 'DELETE' }); } } export default new ApiClient(); ``` **2단계: 기존 파일에서 API 호출 변경** ```javascript // Before const response = await fetch(`${baseUrl}/workers`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); const data = await response.json(); // After import api from './modules/common/api-client.js'; const data = await api.get('/workers'); ``` **검증**: - [ ] 모든 API 호출이 api-client 사용 - [ ] 토큰 자동 포함 확인 - [ ] 에러 처리 통일 #### 2.2.2 큰 파일 분리 (5일) **우선순위**: 1. work-report-calendar.js (1,720줄) → 3-4개 파일 2. modern-dashboard.js (1,299줄) → 3-4개 파일 3. daily-work-report.js (1,137줄) → 3개 파일 **검증**: - [ ] 각 파일 500줄 이하 - [ ] 기능 정상 동작 - [ ] 모듈 간 의존성 명확 **Phase 2 완료 체크리스트**: - [ ] index.js 100줄 이하로 축소 - [ ] 에러 처리 통일 - [ ] SELECT * 모두 제거 - [ ] 공통 모듈 추출 - [ ] 큰 파일 분리 (각 500줄 이하) - [ ] Git 커밋 & 푸시 - [ ] refactoring/LOG.md 업데이트 --- ## 🏗️ Phase 3: 아키텍처 개선 (2-3개월) ### 3.1 서비스 레이어 완성 (4주) **목표**: 비즈니스 로직을 컨트롤러에서 서비스로 분리 **작업 대상**: - DailyWorkReport (851줄 컨트롤러) - Worker 관리 - Project 관리 - Attendance 관리 - Issue 관리 **패턴**: ``` Controller → Service → Repository → Model ↓ ↓ ↓ 라우팅 비즈니스 로직 데이터 접근 ``` ### 3.2 권한 체크 미들웨어 (1주) **middlewares/permission.js**: ```javascript const requireAuth = (req, res, next) => { ... }; const requireRole = (...roles) => { ... }; const requireOwnerOrAdmin = (getOwnerId) => { ... }; ``` ### 3.3 테스트 코드 작성 (4주) **목표**: 50% 이상 커버리지 **도구**: - Jest (백엔드) - Testing Library (프론트엔드) **우선순위**: 1. 서비스 레이어 (비즈니스 로직) 2. API 엔드포인트 (통합 테스트) 3. 유틸리티 함수 ### 3.4 API 문서화 (2주) **도구**: Swagger/OpenAPI **목표**: - 모든 엔드포인트 문서화 - 요청/응답 예시 - 에러 코드 정의 **Phase 3 완료 체크리스트**: - [ ] 서비스 레이어 완성 - [ ] 권한 미들웨어 통일 - [ ] 테스트 커버리지 50% 이상 - [ ] API 문서 완성 - [ ] 성능 최적화 (인덱스, 캐싱) --- ## 🚀 Phase 4: 현대화 (6개월) ### 4.1 TypeScript 마이그레이션 (8주) **순서**: 1. tsconfig.json 설정 2. 유틸리티 함수부터 변환 3. 모델 → 서비스 → 컨트롤러 순서 4. 프론트엔드 변환 ### 4.2 프론트엔드 프레임워크 (12주) **옵션**: - React + TypeScript - Vue 3 + TypeScript - Svelte + TypeScript **마이그레이션 전략**: - 점진적 마이그레이션 - 기존 페이지와 공존 - 중요 페이지부터 전환 ### 4.3 ORM 도입 (4주) **옵션**: - TypeORM - Prisma - Sequelize **장점**: - 타입 안전성 - 마이그레이션 관리 - 쿼리 빌더 ### 4.4 CI/CD 파이프라인 (2주) **GitHub Actions**: ```yaml name: CI/CD on: push: branches: [master, develop] pull_request: branches: [master] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run tests run: npm test deploy: needs: test runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' steps: - name: Deploy to production run: ./deploy.sh ``` **Phase 4 완료 체크리스트**: - [ ] TypeScript 100% 전환 - [ ] 프론트엔드 프레임워크 도입 - [ ] ORM 적용 - [ ] CI/CD 구축 - [ ] 모니터링 시스템 --- ## 📊 진행 상황 추적 ### 주간 체크포인트 매주 금요일: - [ ] 완료된 작업 리뷰 - [ ] 다음 주 계획 수립 - [ ] 블로커 식별 및 해결 - [ ] refactoring/LOG.md 업데이트 ### 월간 리뷰 매월 말: - [ ] 코드 메트릭스 측정 - [ ] 성능 벤치마크 - [ ] 기술 부채 평가 - [ ] 로드맵 조정 --- ## 🎯 성공 기준 ### Phase 1 - ✅ 보안 취약점 0개 - ✅ 코드 중복 <10% - ✅ 백업 파일 0개 ### Phase 2 - ✅ 평균 파일 크기 <500줄 - ✅ 에러 처리 통일률 100% - ✅ SELECT * 사용 0개 ### Phase 3 - ✅ 서비스 레이어 커버리지 100% - ✅ 테스트 커버리지 >50% - ✅ API 문서화 100% ### Phase 4 - ✅ TypeScript 전환율 100% - ✅ 프레임워크 마이그레이션 완료 - ✅ CI/CD 파이프라인 운영 --- ## 📝 참고 문서 - [코드 분석 리포트](ANALYSIS.md) - [리팩토링 로그](LOG.md) - [코딩 스타일 가이드](../guides/CODING_STYLE.md) - [아키텍처 개요](../architecture/OVERVIEW.md) --- **다음**: [리팩토링 시작하기](LOG.md)