Files
TK-FB-Project/docs/guides/CODING_STYLE.md
Hyungi Ahn 1e7155b864 refactor: Phase 1 - 긴급 보안 및 중복 제거
## 🚨 보안 강화
- 하드코딩된 비밀번호를 환경변수로 전환
- .env.example 생성 및 보안 가이드 추가
- docker-compose.yml 환경변수 적용
- README.md에서 실제 비밀번호 제거

## 🗑️ 중복 제거
- synology_deployment/ 디렉토리 제거 (268MB)
- synology_deployment*.tar.gz 아카이브 제거 (234MB)
- 총 502MB의 중복 파일 삭제

## 🧹 백업 파일 정리
- *.backup 파일 제거 (10개)
- *복사본* 파일 제거
- *이전* 파일 제거
- json(백업)/ 디렉토리 제거

## 📋 .gitignore 업데이트
- 백업 파일 패턴 추가
- 보안 파일 제외 (.env, *.pem, *.key)
- 임시 파일 제외 (*.tmp, *.new)
- 빌드 아티팩트 제외 (*.tar.gz)

## 📚 문서화
- docs/ 디렉토리 구조 생성
- 리팩토링 분석 및 계획 문서 작성
- 코딩 스타일 가이드 작성
- 개발 환경 설정 가이드 작성
- 시스템 아키텍처 문서 작성

## 변경된 파일
- .env.example (신규)
- .gitignore (업데이트)
- docker-compose.yml (환경변수 적용)
- README.md (보안 정보 제거)
- docs/* (신규 문서 7개)

## 보안 개선 효과
 비밀번호 노출 위험 제거
 Git 히스토리에서 민감 정보 분리
 환경별 설정 분리 가능
 배포 보안 강화

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-11 10:16:57 +09:00

14 KiB

코딩 스타일 가이드

TK-FB-Project 코드 작성 규칙 및 베스트 프랙티스

📚 목차

  1. 일반 원칙
  2. JavaScript/Node.js
  3. HTML/CSS
  4. SQL
  5. Git 커밋 메시지
  6. 파일 및 디렉토리 구조

일반 원칙

1. 가독성 우선

// ❌ 나쁜 예
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. 명확한 네이밍

// ❌ 나쁜 예
const d = new Date();
const arr = [];
const temp = getUserData();

// ✅ 좋은 예
const currentDate = new Date();
const activeWorkers = [];
const userData = getUserData();

3. 단일 책임 원칙

// ❌ 나쁜 예 - 하나의 함수가 너무 많은 일을 함
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)

// ❌ 나쁜 예 - 중복 코드
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

변수 선언

// ✅ const 우선, 재할당 필요시 let, var 사용 금지
const API_URL = 'http://api.example.com';
let currentPage = 1;

// ❌ var 사용 금지
var x = 10;  // NO!

함수 작성

// ✅ 화살표 함수 사용 (콜백, 간단한 함수)
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;
  }
}

에러 처리

// ❌ 나쁜 예
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);
}

객체 및 배열

// ✅ 구조 분해 할당
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 } 대신

비동기 처리

// ❌ 콜백 지옥
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()
]);

조건문

// ✅ 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');
  }
}

주석

// ✅ JSDoc 사용
/**
 * 작업 보고서를 생성합니다
 * @param {Object} reportData - 보고서 데이터
 * @param {number} reportData.worker_id - 작업자 ID
 * @param {string} reportData.work_content - 작업 내용
 * @param {number} userId - 생성하는 사용자 ID
 * @returns {Promise<Object>} 생성된 보고서
 * @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: 임시 해결책, 나중에 리팩토링 필요

모듈 구조

// ✅ 명확한 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

<!-- ✅ 시맨틱 태그 사용 -->
<header>
  <nav>
    <ul>
      <li><a href="/"></a></li>
    </ul>
  </nav>
</header>

<main>
  <section>
    <article>
      <h1>제목</h1>
      <p>내용</p>
    </article>
  </section>
</main>

<footer>
  <p>&copy; 2025 TK-FB</p>
</footer>

<!-- ✅ 들여쓰기 2칸 -->
<div class="container">
  <div class="row">
    <div class="col">
      내용
    </div>
  </div>
</div>

<!-- ✅ 속성 순서 -->
<!-- 1. class 2. id 3. data-* 4. 기타 -->
<button
  class="btn btn-primary"
  id="submit-btn"
  data-action="submit"
  type="submit"
  disabled
>
  제출
</button>

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

쿼리 작성

-- ✅ 대문자 키워드, 명시적 컬럼 지정
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!

테이블/컬럼 네이밍

-- ✅ 스네이크 케이스
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 커밋 메시지

형식

<type>(<scope>): <subject>

<body>

<footer>

Type

  • feat: 새로운 기능
  • fix: 버그 수정
  • refactor: 리팩토링
  • style: 코드 포맷팅
  • docs: 문서 수정
  • test: 테스트 추가/수정
  • chore: 빌드, 설정 변경

예시

# ✅ 좋은 커밋 메시지
feat(report): 작업 보고서 엑셀 내보내기 기능 추가

사용자가 작업 보고서를 엑셀 파일로 내보낼 수 있도록 기능 추가
- xlsx 라이브러리 사용
- 날짜 범위 선택 가능
- 필터링 옵션 제공

Closes #123

# ✅ 리팩토링
refactor(api): index.js를 여러 파일로 분리

- config/database.js: DB 연결 설정
- config/cors.js: CORS 설정
- middlewares/auth.js: 인증 미들웨어
- controllers/userController.js: 사용자 관리 API

index.js 파일 크기: 889줄 → 95줄

# ✅ 버그 수정
fix(calendar): 주말 날짜 선택 시 오류 수정

주말 날짜 클릭 시 TypeError 발생하는 문제 해결
Date 객체 null 체크 추가

Fixes #456

# ❌ 나쁜 커밋 메시지
update
fix bug
asdf
ㅁㄴㅇㄹ

파일 및 디렉토리 구조

백엔드

api.hyungi.net/
├── index.js              # 진입점 (간결하게)
├── config/               # 설정 파일
│   ├── database.js
│   ├── cors.js
│   └── routes.js
├── controllers/          # 라우트 핸들러
│   ├── userController.js
│   └── reportController.js
├── services/            # 비즈니스 로직
│   ├── userService.js
│   └── reportService.js
├── models/              # 데이터 모델
│   ├── User.js
│   └── Report.js
├── middlewares/         # 미들웨어
│   ├── auth.js
│   └── errorHandler.js
├── utils/               # 유틸리티
│   ├── errors.js
│   └── logger.js
└── tests/               # 테스트
    ├── unit/
    └── integration/

프론트엔드

web-ui/
├── js/
│   ├── modules/         # 모듈화된 코드
│   │   ├── common/
│   │   │   ├── api-client.js
│   │   │   └── utils.js
│   │   ├── calendar/
│   │   │   ├── CalendarView.js
│   │   │   └── CalendarAPI.js
│   │   └── dashboard/
│   └── pages/           # 페이지별 스크립트
├── css/
│   ├── base/
│   ├── components/
│   ├── layouts/
│   └── pages/
└── pages/               # HTML 파일

파일 네이밍

// ✅ 케이스 규칙
// 파일명: kebab-case
user-controller.js
daily-work-report.js

// 클래스/컴포넌트: PascalCase
UserController.js
CalendarView.js

// 일반 함수/변수: camelCase
const getUserData = () => {};
const reportService = {};

코드 리뷰 체크리스트

제출 전 확인

  • 코드가 스타일 가이드를 따르는가?
  • 주석이 적절한가?
  • 테스트가 작성되었는가?
  • 에러 처리가 되어있는가?
  • 보안 취약점이 없는가?
  • 성능 이슈가 없는가?
  • 문서가 업데이트되었는가?

리뷰어 확인사항

  • 코드 로직이 명확한가?
  • 중복 코드가 없는가?
  • 변수명이 의미있는가?
  • 함수가 단일 책임을 가지는가?
  • 테스트가 충분한가?

도구 및 린터 설정

ESLint

{
  "extends": ["eslint:recommended"],
  "env": {
    "node": true,
    "es6": true
  },
  "rules": {
    "indent": ["error", 2],
    "quotes": ["error", "single"],
    "semi": ["error", "always"],
    "no-var": "error",
    "prefer-const": "error",
    "no-console": "warn"
  }
}

Prettier

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80
}

참고 자료


마지막 업데이트: 2025-12-11