테이블이 필요한 순간들

웹 개발을 하다 보면 데이터를 정리해서 보여줘야 하는 상황이 자주 발생한다.

사용자 목록, 제품 비교표, 통계 데이터 등... 이럴 때 가장 적합한 HTML 요소가 바로 테이블이다.

CSS Grid나 Flexbox가 대세가 된 지금도, 진짜 표 형태의 데이터를 표현할 때는 테이블만한 게 없다.

HTML 테이블 기본 구조

핵심 태그들의 역할

  • <table>: 테이블 전체를 감싸는 컨테이너다
  • <tr>: Table Row, 테이블의 행(가로줄)이다
  • <th>: Table Header, 헤더 셀(제목 셀)이다
  • <td>: Table Data, 데이터 셀(내용 셀)이다

기본 구조 예시

html
<table>
  <tr>
    <th>이름</th>
    <th>나이</th>
    <th>직업</th>
  </tr>
  <tr>
    <td>김개발</td>
    <td>28</td>
    <td>프론트엔드 개발자</td>
  </tr>
  <tr>
    <td>이디자인</td>
    <td>26</td>
    <td>UI/UX 디자이너</td>
  </tr>
</table>

의미론적 테이블 구조

thead, tbody, tfoot 활용

html
<table>
  <thead>
    <tr>
      <th>상품명</th>
      <th>가격</th>
      <th>재고</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>맥북 프로</td>
      <td>2,590,000원</td>
      <td>5개</td>
    </tr>
    <tr>
      <td>아이패드</td>
      <td>429,000원</td>
      <td>12개</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td>총합</td>
      <td>3,019,000원</td>
      <td>17개</td>
    </tr>
  </tfoot>
</table>

caption으로 테이블 제목 추가

html
<table>
  <caption>2024년 4분기 매출 현황</caption>
  <thead>
    <tr>
      <th>월</th>
      <th>매출액</th>
      <th>전년 대비</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>10월</td>
      <td>1,250만원</td>
      <td>+15%</td>
    </tr>
  </tbody>
</table>

실무에서 자주 사용하는 패턴들

1. 사용자 관리 테이블

html
<table class="user-table">
  <thead>
    <tr>
      <th>ID</th>
      <th>이름</th>
      <th>이메일</th>
      <th>가입일</th>
      <th>상태</th>
      <th>관리</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>001</td>
      <td>홍길동</td>
      <td>hong@example.com</td>
      <td>2024.01.15</td>
      <td><span class="status active">활성</span></td>
      <td>
        <button class="btn-edit">수정</button>
        <button class="btn-delete">삭제</button>
      </td>
    </tr>
  </tbody>
</table>

2. 제품 비교표

html
<table class="comparison-table">
  <thead>
    <tr>
      <th>기능</th>
      <th>기본 플랜</th>
      <th>프로 플랜</th>
      <th>엔터프라이즈</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>사용자 수</td>
      <td>최대 5명</td>
      <td>최대 50명</td>
      <td>무제한</td>
    </tr>
    <tr>
      <td>저장 공간</td>
      <td>10GB</td>
      <td>100GB</td>
      <td>1TB</td>
    </tr>
    <tr>
      <td>가격</td>
      <td>무료</td>
      <td>월 29,000원</td>
      <td>월 99,000원</td>
    </tr>
  </tbody>
</table>

3. 대시보드 통계표

html
<table class="dashboard-stats">
  <thead>
    <tr>
      <th>구분</th>
      <th>오늘</th>
      <th>어제</th>
      <th>증감률</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>방문자</td>
      <td>1,247명</td>
      <td>1,156명</td>
      <td class="increase">+7.9%</td>
    </tr>
    <tr>
      <td>페이지뷰</td>
      <td>3,891회</td>
      <td>4,012회</td>
      <td class="decrease">-3.0%</td>
    </tr>
  </tbody>
</table>

colspan과 rowspan으로 셀 병합하기

colspan: 가로 셀 병합

html
<table>
  <tr>
    <th colspan="3">2024년 분기별 실적</th>
  </tr>
  <tr>
    <th>1분기</th>
    <th>2분기</th>
    <th>3분기</th>
  </tr>
  <tr>
    <td>1,200만원</td>
    <td>1,450만원</td>
    <td>1,380만원</td>
  </tr>
</table>

rowspan: 세로 셀 병합

html
<table>
  <tr>
    <th rowspan="2">지역</th>
    <th colspan="2">매출</th>
  </tr>
  <tr>
    <th>온라인</th>
    <th>오프라인</th>
  </tr>
  <tr>
    <td>서울</td>
    <td>2,300만원</td>
    <td>1,800만원</td>
  </tr>
  <tr>
    <td>부산</td>
    <td>1,200만원</td>
    <td>900만원</td>
  </tr>
</table>

현대적인 테이블 스타일링

기본 테이블 스타일

css
table {
  width: 100%;
  border-collapse: collapse;
  margin: 2rem 0;
  font-size: 0.9rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

th, td {
  padding: 12px 15px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

th {
  background-color: #f8f9fa;
  font-weight: 600;
  color: #333;
  text-transform: uppercase;
  font-size: 0.8rem;
  letter-spacing: 0.5px;
}

tr:hover {
  background-color: #f5f5f5;
}

반응형 테이블

css
.table-container {
  overflow-x: auto;
  margin: 1rem 0;
}

@media (max-width: 768px) {
  table {
    font-size: 0.8rem;
  }

  th, td {
    padding: 8px 6px;
  }

/* 모바일에서 일부 열 숨기기 */
  .hide-mobile {
    display: none;
  }
}

스트라이프 테이블

css
.stripe-table tbody tr:nth-child(even) {
  background-color: #f8f9fa;
}

.stripe-table tbody tr:nth-child(odd) {
  background-color: white;
}

모던 카드 스타일 테이블

css
.card-table {
  border: none;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}

.card-table th {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  padding: 20px 15px;
}

.card-table td {
  border: none;
  padding: 15px;
  border-bottom: 1px solid #eee;
}

.card-table tbody tr:last-child td {
  border-bottom: none;
}

접근성을 고려한 테이블

scope 속성으로 헤더-데이터 관계 명시

html
<table>
  <thead>
    <tr>
      <th scope="col">이름</th>
      <th scope="col">수학</th>
      <th scope="col">영어</th>
      <th scope="col">평균</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">김학생</th>
      <td>90</td>
      <td>85</td>
      <td>87.5</td>
    </tr>
  </tbody>
</table>

headers 속성으로 복잡한 테이블 접근성 개선

html
<table>
  <tr>
    <th id="name">이름</th>
    <th id="math">수학</th>
    <th id="eng">영어</th>
  </tr>
  <tr>
    <td headers="name">홍길동</td>
    <td headers="math">95</td>
    <td headers="eng">88</td>
  </tr>
</table>

자바스크립트로 테이블 기능 확장

정렬 기능 구현

javascript
function sortTable(columnIndex) {
  const table = document.querySelector('table');
  const tbody = table.querySelector('tbody');
  const rows = Array.from(tbody.rows);

  const sortedRows = rows.sort((a, b) => {
    const aText = a.cells[columnIndex].textContent.trim();
    const bText = b.cells[columnIndex].textContent.trim();

// 숫자인지 확인
    if (!isNaN(aText) && !isNaN(bText)) {
      return Number(aText) - Number(bText);
    }

    return aText.localeCompare(bText);
  });

// 기존 행들 제거하고 정렬된 행들 추가
  tbody.innerHTML = '';
  sortedRows.forEach(row => tbody.appendChild(row));
}

// 헤더 클릭 시 정렬
document.querySelectorAll('th').forEach((header, index) => {
  header.addEventListener('click', () => sortTable(index));
  header.style.cursor = 'pointer';
});

검색 필터 기능

javascript
function filterTable(searchTerm) {
  const table = document.querySelector('table');
  const rows = table.querySelectorAll('tbody tr');

  rows.forEach(row => {
    const text = row.textContent.toLowerCase();
    const shouldShow = text.includes(searchTerm.toLowerCase());
    row.style.display = shouldShow ? '' : 'none';
  });
}

// 검색 입력 필드 연결
document.getElementById('search').addEventListener('input', (e) => {
  filterTable(e.target.value);
});

행 선택 기능

javascript
document.querySelectorAll('tbody tr').forEach(row => {
  row.addEventListener('click', function() {
// 기존 선택 해제
    document.querySelectorAll('tbody tr.selected').forEach(r => {
      r.classList.remove('selected');
    });

// 현재 행 선택
    this.classList.add('selected');
  });
});

반응형 테이블 고급 기법

모바일에서 카드 형태로 변환

css
@media (max-width: 768px) {
  .responsive-table thead {
    display: none;
  }

  .responsive-table tbody,
  .responsive-table tr,
  .responsive-table td {
    display: block;
  }

  .responsive-table tr {
    border: 1px solid #ccc;
    margin-bottom: 10px;
    padding: 10px;
    border-radius: 8px;
  }

  .responsive-table td {
    border: none;
    position: relative;
    padding-left: 50%;
  }

  .responsive-table td:before {
    content: attr(data-label) ": ";
    position: absolute;
    left: 6px;
    width: 45%;
    font-weight: bold;
  }
}

html
<table class="responsive-table">
  <thead>
    <tr>
      <th>이름</th>
      <th>이메일</th>
      <th>전화번호</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td data-label="이름">김개발</td>
      <td data-label="이메일">kim@example.com</td>
      <td data-label="전화번호">010-1234-5678</td>
    </tr>
  </tbody>
</table>

성능 최적화 팁

큰 테이블의 가상 스크롤링

javascript
class VirtualTable {
  constructor(container, data, rowHeight = 50) {
    this.container = container;
    this.data = data;
    this.rowHeight = rowHeight;
    this.visibleRows = Math.ceil(container.clientHeight / rowHeight) + 5;
    this.init();
  }

  init() {
    this.container.innerHTML = `
      <div class="virtual-scroll" style="height: ${this.data.length * this.rowHeight}px">
        <table class="virtual-table"></table>
      </div>
    `;

    this.virtualScroll = this.container.querySelector('.virtual-scroll');
    this.table = this.container.querySelector('.virtual-table');

    this.container.addEventListener('scroll', () => this.updateVisibleRows());
    this.updateVisibleRows();
  }

  updateVisibleRows() {
    const scrollTop = this.container.scrollTop;
    const startIndex = Math.floor(scrollTop / this.rowHeight);
    const endIndex = Math.min(startIndex + this.visibleRows, this.data.length);

    this.renderRows(startIndex, endIndex);
    this.table.style.transform = `translateY(${startIndex * this.rowHeight}px)`;
  }

  renderRows(start, end) {
    const rows = this.data.slice(start, end).map(item => `
      <tr>
        <td>${item.name}</td>
        <td>${item.email}</td>
        <td>${item.phone}</td>
      </tr>
    `).join('');

    this.table.innerHTML = `
      <thead>
        <tr><th>이름</th><th>이메일</th><th>전화번호</th></tr>
      </thead>
      <tbody>${rows}</tbody>
    `;
  }
}

주의사항과 베스트 프랙티스

레이아웃 용도로 테이블 사용 금지

html
<!-- 잘못된 사용 - 레이아웃 목적 -->
<table>
  <tr>
    <td>사이드바</td>
    <td>메인 컨텐츠</td>
  </tr>
</table>

<!-- 올바른 사용 - 표 형태 데이터 -->
<table>
  <tr>
    <th>상품명</th>
    <th>가격</th>
  </tr>
  <tr>
    <td>노트북</td>
    <td>1,200,000원</td>
  </tr>
</table>

빈 셀 처리

html
<!-- 빈 셀에는 &nbsp; 또는 - 사용 -->
<table>
  <tr>
    <td>데이터 있음</td>
    <td>&nbsp;</td><!-- 빈 셀 -->
    <td>-</td><!-- 해당 없음을 나타내는 셀 -->
  </tr>
</table>

테이블 캡션과 요약 정보

html
<table>
  <caption>
    2024년 월별 매출 현황
    <span class="table-summary">총 12개월 데이터, 단위: 만원</span>
  </caption>
<!-- ... -->
</table>

마무리: 테이블의 올바른 활용

테이블은 단순해 보이지만 올바르게 사용하면 강력한 도구가 된다.

div와 CSS로 모든 걸 해결하려 하지 말고, 진짜 표 형태의 데이터를 다룰 때는 테이블을 사용하는 것이 의미론적으로도, 접근성 측면에서도 훨씬 좋다.

 

특히 관리자 페이지나 대시보드 같은 데이터 중심의 인터페이스를 만들 때는 테이블의 진가가 발휘된다. 정렬, 필터링, 페이지네이션 등의 기능을 추가하면 사용자 경험도 크게 향상시킬 수 있다.

 

테이블을 단순히 '구식 기술'로 생각하지 말고, 적재적소에 활용할 수 있는 개발자가 되자.

 

테이블과 함께 HTML의 구조적 데이터 표현을 담당하는 또 다른 요소들도 있다.

용어와 설명을 체계적으로 정리할 때 유용한 HTML dt, dd, dl 태그 완전 정복 - 레거시 코드에서 발견한 숨겨진 보석 글도 정리했다.

FAQ나 용어사전을 만들 때 테이블보다 더 적합한 선택이 될 수 있다.

레거시 코드에서 만난 낯선 태그들

오래된 프로젝트의 HTML 코드를 리팩토링하던 중, 생소한 태그들을 발견했다.
<dl>, <dt>, <dd>... 이 태그들은 무엇이고, 왜 사용되었을까? 알고 보니 이들은 HTML의 숨겨진 보석 같은 존재였다.

HTML Definition List 태그란?

Definition List(정의 목록) 태그는 용어와 그 설명을 구조적으로 표현하는 HTML 요소다.

기본 구조

<dl><!-- Definition List -->
  <dt>용어1</dt><!-- Definition Term -->
  <dd>용어1에 대한 설명</dd><!-- Definition Description -->

  <dt>용어2</dt>
  <dd>용어2에 대한 설명</dd>
</dl>

각 태그의 역할

  • <dl>: Definition List의 컨테이너다
  • <dt>: Definition Term, 정의할 용어다
  • <dd>: Definition Description, 용어에 대한 설명이다

실무에서 활용할 수 있는 사례들

1. FAQ 섹션

<dl class="faq-list">
  <dt>회원가입은 어떻게 하나요?</dt>
  <dd>상단 메뉴의 '회원가입' 버튼을 클릭하여 필요한 정보를 입력하면 된다.</dd>

  <dt>비밀번호를 잊어버렸어요</dt>
  <dd>로그인 페이지의 '비밀번호 찾기'를 통해 이메일로 재설정 링크를 받을 수 있다.</dd>
</dl>

2. 제품 사양 정보

<dl class="product-specs">
  <dt>CPU</dt>
  <dd>Intel Core i7-12700K</dd>

  <dt>메모리</dt>
  <dd>32GB DDR4-3200</dd>

  <dt>저장장치</dt>
  <dd>1TB NVMe SSD</dd>
</dl>

3. 용어사전 또는 API 문서

<dl class="api-docs">
  <dt>GET /api/users</dt>
  <dd>
    <p>모든 사용자 목록을 조회한다.</p>
    <p><strong>응답:</strong> 사용자 배열</p>
  </dd>

  <dt>POST /api/users</dt>
  <dd>
    <p>새로운 사용자를 생성한다.</p>
    <p><strong>요청 본문:</strong> 사용자 정보 객체</p>
  </dd>
</dl>

CSS 스타일링으로 현대적으로 만들기

기본 스타일 초기화

css
dl {
  margin: 0;
  padding: 0;
}

dt {
  font-weight: bold;
  margin-top: 1rem;
  margin-bottom: 0.5rem;
}

dd {
  margin: 0 0 1rem 1rem;
  color: #666;

모던한 FAQ 스타일

.faq-list {
  max-width: 800px;
  margin: 2rem auto;
}

.faq-list dt {
  background: #f8f9fa;
  padding: 1rem;
  border-left: 4px solid #007bff;
  font-size: 1.1rem;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.faq-list dt:hover {
  background: #e9ecef;
}

.faq-list dd {
  padding: 1rem;
  margin: 0;
  border-left: 4px solid #dee2e6;
  background: white;
  line-height: 1.6;
}

카드 스타일 제품 사양

.product-specs {
  display: grid;
  gap: 1rem;
  margin: 2rem 0;
}

.product-specs dt,
.product-specs dd {
  margin: 0;
  padding: 1rem;
}

.product-specs dt {
  background: #343a40;
  color: white;
  font-weight: 600;
  border-radius: 8px 8px 0 0;
}

.product-specs dd {
  background: #f8f9fa;
  border: 1px solid #dee2e6;
  border-top: none;
  border-radius: 0 0 8px 8px;
}

접근성과 SEO 관점에서의 장점

의미론적 HTML의 중요성

<!-- 좋지 않은 예 -->
<div class="faq-item">
  <div class="question">질문내용</div>
  <div class="answer">답변내용</div>
</div>

<!-- 권장하는 예 -->
<dl class="faq-list">
  <dt>질문내용</dt>
  <dd>답변내용</dd>
</dl>

스크린 리더 지원

Definition List 태그는 스크린 리더가 용어와 설명의 관계를 명확히 인식할 수 있도록 도와준다.

<dl>
  <dt id="term1">API</dt>
  <dd aria-describedby="term1">
    Application Programming Interface의 줄임말로,
    서로 다른 소프트웨어 간의 통신을 가능하게 하는 인터페이스다.
  </dd>
</dl>

자바스크립트와 함께 활용하기

아코디언 FAQ 구현

document.querySelectorAll('.faq-list dt').forEach(term => {
  term.addEventListener('click', function() {
    const description = this.nextElementSibling;
    const isVisible = description.style.display !== 'none';

// 모든 답변 숨기기
    document.querySelectorAll('.faq-list dd').forEach(dd => {
      dd.style.display = 'none';
    });

// 클릭한 항목만 토글
    if (!isVisible) {
      description.style.display = 'block';
    }
  });
});

검색 기능 추가

function searchDefinitions(query) {
  const terms = document.querySelectorAll('dl dt');
  const descriptions = document.querySelectorAll('dl dd');

  terms.forEach((term, index) => {
    const termText = term.textContent.toLowerCase();
    const descText = descriptions[index].textContent.toLowerCase();
    const searchQuery = query.toLowerCase();

    if (termText.includes(searchQuery) || descText.includes(searchQuery)) {
      term.style.display = 'block';
      descriptions[index].style.display = 'block';
    } else {
      term.style.display = 'none';
      descriptions[index].style.display = 'none';
    }
  });
}

브라우저 호환성과 주의사항

브라우저 지원

Definition List 태그는 모든 주요 브라우저에서 지원되며, HTML4부터 표준으로 포함되어 있다.

주의할 점

<!-- 잘못된 사용 -->
<dl>
  <dd>설명만 있고 용어가 없음</dd><!-- dt 없이 dd만 사용하면 안 된다 -->
</dl>

<!-- 올바른 사용 -->
<dl>
  <dt>용어</dt>
  <dd>설명</dd>
</dl>

중첩 구조 활용

<dl>
  <dt>프론트엔드 기술스택</dt>
  <dd>
    <dl>
      <dt>HTML</dt>
      <dd>마크업 언어다</dd>
      <dt>CSS</dt>
      <dd>스타일시트 언어다</dd>
      <dt>JavaScript</dt>
      <dd>프로그래밍 언어다</dd>
    </dl>
  </dd>
</dl>

레거시에서 배우는 교훈

오래된 코드에서 발견한 dt, dd, dl 태그는 단순히 구식 기술이 아니라, 의미론적 HTML의 중요성을 보여주는 좋은 예시였다.

현대 웹 개발에서도 적절한 곳에 이 태그들을 활용하면 접근성과 SEO 측면에서 큰 이점을 얻을 수 있다.

특히 FAQ, 용어사전, 제품 사양 등을 구현할 때는 div와 span의 조합보다 훨씬 의미 있는 마크업을 작성할 수 있다.

레거시 코드를 단순히 '오래된 것'으로 치부하지 말고, 그 안에 숨겨진 지혜를 발견해보는 것도 개발자로서 성장하는 좋은 방법이라고 생각한다.

HTML의 구조적 데이터 표현에 관심이 있다면, 표 형태 데이터를 다루는 HTML 테이블 태그 완벽 가이드 - table, tr, td, th 실전 활용법 글도 함께 정리했다. Definition List와 마찬가지로 의미론적 HTML의 중요성을 보여주는 좋은 예시다.

입사 첫날의 실수

입사 첫날, 평소 습관대로 바탕화면에 프로젝트 폴더들을 만들어 작업하고 있었다.

그런데 사수분이 내 화면을 보시더니 살짝 식겁하셨다.

"너, 잘 모르는구나?" 하시며 바로 D드라이브 설정을 도와주셨다. (사실 원래도 큰기대는 안 하셨다고...ㅋ)

그때 배운 윈도우 개발 환경 설정을 정리한다.

왜 바탕화면 작업이 문제일까?

C드라이브의 한계

  • 시스템 드라이브: 윈도우 OS가 설치된 메인 드라이브다
  • 용량 부족: 프로그램 설치, 임시 파일, 시스템 파일로 금세 가득 찬다
  • 성능 저하: 용량이 부족하면 시스템 전체가 느려진다
  • 백업 위험: 시스템 오류 시 데이터 손실 가능성이 높다

바탕화면 작업의 위험성

C:\Users\사용자명\Desktop\
├── 프로젝트1\
├── 프로젝트2\
├── node_modules\  ← 이게 문제다
├── dist\
└── .git\

바탕화면에 프로젝트를 만들면 모든 파일이 C드라이브에 저장된다. 특히 node_modules 폴더는 수만 개의 파일을 생성하며 용량을 급격히 늘린다.

한글 경로 문제: 숨겨진 복병

실제 발생하는 문제들

C:\Users\김개발\Desktop\프로젝트\  ← 이런 경로가 문제다

Node.js 관련 오류

bash
# npm install 시 발생하는 오류
npm ERR! ENOENT: no such file or directory, scandir 'C:\Users\김개발\Desktop\프로젝트\node_modules'

# Webpack 빌드 오류
Error: ENOENT: no such file or directory, open 'C:\Users\김개발\바탕화면\소스\index.js'

Git 관련 문제

bash
# 한글 파일명 인식 오류
git add 새파일.js
fatal: pathspec '새파일.js' did not match any files

# 한글 경로에서 clone 실패
git clone <https://github.com/user/repo.git> 프로젝트
warning: could not create directory '프로젝트'

인코딩 충돌의 원인

  • 윈도우: CP949 (EUC-KR) 인코딩
  • 개발 도구: UTF-8 인코딩
  • 결과: 경로 인식 실패 및 빌드 오류

늘 이런 오류가 뜨는 건 아니지만 이와 같은 오류의 가능성이 있다고 한다.

D드라이브 생성 및 설정 가이드

1단계: 디스크 관리 접근

  1. Windows + R 키를 눌러 실행 창 열기
  2. diskmgmt.msc 입력 후 엔터
  3. 디스크 관리 창이 열린다

2단계: 드라이브 최적화 및 메모리 정리

사수분이 해주신 작업 순서:
1. 기존 드라이브 최적화
2. 임시 파일 및 캐시 정리
3. 새 파티션 생성
4. 포맷 후 설정

메모리 정리 작업

cmd
# 관리자 권한으로 CMD 실행
# 디스크 정리
cleanmgr /sagerun:1

# 임시 파일 삭제
del /q /f /s %TEMP%\*
del /q /f /s C:\Windows\Temp\*

3단계: 파티션 생성

기존 C드라이브 용량 축소 방법:
1. C드라이브 우클릭 → "볼륨 축소" 선택
2. 축소할 공간 크기 입력 (예: 100GB = 102400MB)
3. "축소" 버튼 클릭
4. 할당되지 않은 공간 생성 확인

4단계: 새 파티션 포맷

  1. 할당되지 않은 공간 우클릭
  2. "새 단순 볼륨" 선택
  3. 마법사 따라 진행
  4. 드라이브 문자: D 선택
  5. 파일 시스템: NTFS 선택
  6. 할당 단위 크기: 기본값 유지
  7. 볼륨 레이블: "Dev" 또는 "Work" 등 영문 이름 사용

5단계: 드라이브 최적화

cmd
# 관리자 권한으로 CMD 실행
# 새로 만든 D드라이브 최적화
defrag D: /O

개발 환경 폴더 구조 설정

권장 폴더 구조 (영문 경로 필수)

D:\
├── Dev\
│   ├── Projects\
│   │   ├── personal\
│   │   ├── company\
│   │   └── study\
│   ├── Tools\
│   │   ├── nodejs\
│   │   ├── git\
│   │   └── vscode-portable\
│   └── Backup\
├── Downloads\
└── Documents\

실제 설정 방법

batch
# D드라이브에 개발 폴더 생성 (영문만 사용)
mkdir D:\Dev
mkdir D:\Dev\Projects
mkdir D:\Dev\Projects\personal
mkdir D:\Dev\Projects\company
mkdir D:\Dev\Projects\study
mkdir D:\Dev\Tools
mkdir D:\Dev\Backup

한글 경로 문제 완전 해결

Git 한글 지원 설정

bash
# Git에서 한글 파일명 지원
git config --global core.quotepath false
git config --global core.precomposeunicode true

# 한글 커밋 메시지 지원
git config --global i18n.logoutputencoding utf-8
git config --global i18n.commitencoding utf-8

시스템 UTF-8 설정

제어판 → 지역 → 관리 → 시스템 로케일 변경
"Unicode UTF-8 사용" 체크 (윈도우 10 1903 이상)
※ 재부팅 필요

Node.js 한글 경로 오류 해결

bash
# npm 설정
npm config set cache D:\Dev\Tools\npm-cache
npm config set prefix D:\Dev\Tools\npm-global

# 한글 경로 오류 방지를 위한 환경 변수
set NODE_OPTIONS=--max_old_space_size=4096

시스템 폴더 경로 변경

사용자 폴더 이동

  1. 내 PC사용자 폴더 진입
  2. 다운로드 폴더 우클릭 → 속성
  3. 위치 탭 → 이동 클릭
  4. D:\Downloads 경로 선택 (영문 경로)
  5. 기존 파일 이동 여부 확인

환경 변수 설정

환경 변수 편집:
1. Windows + R → sysdm.cpl
2. 고급 탭 → 환경 변수
3. 새로 만들기:
   - 변수 이름: DEV_HOME
   - 변수 값: D:\Dev
   - 변수 이름: PROJECT_PATH
   - 변수 값: D:\Dev\Projects

개발 도구별 설정 변경

VS Code 설정

// settings.json
{
  "terminal.integrated.cwd": "D:\\Dev\\Projects",
  "explorer.compactFolders": false,
  "workbench.startupEditor": "newUntitledFile",
  "git.defaultCloneDirectory": "D:\\Dev\\Projects",
  "files.encoding": "utf8",
  "terminal.integrated.env.windows": {
    "LANG": "ko_KR.UTF-8"
  }
}

터미널 한글 설정

bash
# PowerShell 프로필 설정
notepad $PROFILE

# 다음 내용 추가
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding

성능 최적화 추가 팁

SSD 최적화

cmd
# SSD 트림 활성화 확인
fsutil behavior query DisableDeleteNotify

# 결과가 0이면 트림 활성화됨
# 1이면 다음 명령어로 활성화
fsutil behavior set DisableDeleteNotify 0

인덱싱 서비스 제외

  1. 제어판인덱싱 옵션
  2. 수정 클릭
  3. D드라이브 체크 해제 (개발 파일은 인덱싱 불필요)
  4. 확인 후 재구성

자동 정리 스크립트

batch
# cleanup.bat 파일 생성
@echo off
echo 개발 환경 정리 중...
del /q /f /s %TEMP%\*
del /q /f /s C:\Windows\Temp\*
del /q /f /s D:\Dev\Tools\npm-cache\*
echo 정리 완료!
pause

백업 전략 수립

자동 백업 스크립트

batch
# backup.bat
@echo off
set SOURCE=D:\Dev\Projects
set BACKUP=D:\Dev\Backup\%date:~0,4%%date:~5,2%%date:~8,2%

echo 프로젝트 백업 시작...
xcopy "%SOURCE%" "%BACKUP%" /E /I /Y /EXCLUDE:exclude.txt
echo 백업 완료: %BACKUP%

제외할 파일 목록 (exclude.txt)

node_modules\
.git\
dist\
build\
*.log

주의사항과 문제 해결

권한 문제 해결

D드라이브 권한 설정:
1. D드라이브 우클릭 → 속성
2. 보안 탭 → 편집
3. 사용자 계정 선택 → 모든 권한 체크
4. 확인 → 적용

한글 경로 레거시 도구 대응

bash
# 구버전 Webpack에서 한글 경로 오류 시# webpack.config.js에서 경로 처리
const path = require('path');

module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname), // 절대 경로 사용
    }
  }
};

파일 경로 길이 제한 해결

cmd
# 긴 파일 경로 지원 활성화 (관리자 권한 필요)
reg add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f

실무 팁: 프로젝트 생성 템플릿

새 프로젝트 생성 스크립트

batch
# new-project.bat
@echo off
set /p PROJECT_NAME="프로젝트명 (영문): "
set PROJECT_PATH=D:\Dev\Projects\company\%PROJECT_NAME%

mkdir "%PROJECT_PATH%"
cd "%PROJECT_PATH%"

echo 프로젝트 폴더 생성: %PROJECT_PATH%
echo Git 초기화 중...
git init
echo README.md 생성...
echo # %PROJECT_NAME% > README.md

echo 프로젝트 설정 완료!
code .

개발 환경의 기초

사수분이 D드라이브 설정을 도와주신 덕분에 개발 환경의 중요성을 깨달았다.

단순히 폴더 하나 옮기는 것 같지만, 실제로는 전체 시스템의 성능과 안정성에 큰 영향을 미친다.

특히 한글 경로 문제는 한국 개발자들이 자주 겪는 고질적인 문제다.

node_modules 폴더의 수만 개 파일과 한글 경로가 만나면 각종 빌드 오류와 성능 저하를 일으킨다.

신입 개발자라면 작업 환경 설정부터 제대로 하는 것이 좋다.

영문 경로 사용, D드라이브 분할, 시스템 UTF-8 설정까지... 이런 기본기가 탄탄해야 나중에 고생하지 않는다.


핵심 포인트

  • 바탕화면 작업은 C드라이브 용량 부족과 한글 경로 문제의 주요 원인이다
  • D드라이브 분할로 시스템과 개발 환경을 분리해야 한다
  • 모든 폴더와 파일명은 영문으로 작성하는 것이 안전하다
  • Git과 Node.js의 한글 지원 설정을 미리 해두자
  • 시스템 UTF-8 설정으로 인코딩 문제를 근본적으로 해결할 수 있다
  • 환경 변수와 권한 설정을 통해 개발 효율성을 높일 수 있다

모노레포란?

모노레포(Monorepo)를 간단히 말하면, 서버와 클라이언트 코드가 한 저장소(Repository)에 같이 있는 것을 뜻한다

각각의 코드베이스를 별도의 저장소로 관리하는 멀티레포(Multirepo)와는 달리, 하나의 저장소에서 모든 관련 프로젝트를 통합적으로 관리하는 방식이다

모노레포의 장점

  • 코드 공유: 서버와 클라이언트가 같은 저장소에 있으므로, 공통된 코드를 쉽게 재사용
  • 일관된 버전 관리: 한 저장소에서 관리되므로 의존성 버전 충돌을 줄임
  • 일관된 개발 환경: 서버와 클라이언트 간의 개발 환경을 동일하게 유지
  • 간편한 CI/CD: 한 저장소에서 빌드와 테스트 파이프라인 설정 과정 단순화

모노레포의 단점

  • 저장소 크기 증가: 대규모 프로젝트에서 저장소 크기가 커지고 클론, 빌드, 테스트 시간이 증가
  • 의존성 관리 복잡성: 하나의 프로젝트에서 특정 의존성을 업데이트시 다른 프로젝트에 영향을 미칠 수 있음
  • 권한 관리 어려움: 모든 프로젝트가 한 저장소에 있으므로 프로젝트별 접근 권한 관리가 어려움

해결 방안

  • Monorepo 도구 사용: Nx, Bazel 같은 도구를 사용해 필요한 부분만 빌드, 테스트
  • 의존성 관리: Lerna, Yarn Workspaces 등을 통해 독립적인 의존성 관리 구현
  • 권한 분리: GitHub CODEOWNERS 파일로 디렉토리별 권한 설정

멀티레포란?

멀티레포(Multirepo)는 각 프로젝트를 독립적으로 별도의 저장소에서 관리하는 방식을 뜻한다

서버와 클라이언트 코드, 공통 라이브러리 등이 각기 다른 저장소에 저장되며, 필요할 때만 의존성을 가져와 사용하는 형태이다

멀티레포의 장점

  • 독립적인 배포 및 개발: 특정 프로젝트만 선택적으로 업데이트하거나 배포하기 쉬움 (각 팀이 다른 팀의 코드베이스에 의존하지 않고 빠르게 개발 및 배포 가능)
  • 프로젝트 분리: 각 코드베이스가 독립적으로 관리되므로 프로젝트 간 영향도 낮아짐 하나의 저장소에서 발생한 문제가 다른 프로젝트에 영향을 주지 않음
  • 다양한 기술 스택 관리: 서로 다른 언어나 빌드 도구를 사용하는 프로젝트 간 충돌을 방지

멀티레포의 단점

  • 공통 코드 관리 어려움: 공통 라이브러리나 설정을 동기화하기 까다로움
  • 의존성 관리 복잡: 공통으로 사용하는 라이브러리나 설정 파일을 여러 저장소에 중복 관리해야 할 때 버전 충돌 가능성 있음 (A와 B 프로젝트가 동일한 공통 라이브러리를 사용하는데, A는 라이브러리의 최신 버전을 요구하고 B는 이전 버전을 유지해야 할때)

해결방안

  • 공통 코드 관리: 각 저장소에서 사용하는 공통 코드를 별도의 라이브러리로 추출하고, NPM 패키지나 PyPI 같은 패키지 관리 시스템에 배포하여 버전 관리를 체계적으로 수행
  • 의존성 관리 자동화: Dependabot이나 Renovate 같은 도구를 사용해 각 저장소의 의존성을 자동으로 업데이트하고 충돌을 최소화

모노레포 vs 멀티레포

  모노레포 멀티레포
저장소 구조 하나의 저장소 여러 개의 저장소
공통 코드 관리 쉬움 어려움
프로젝트 독립성 낮음 높음
CI/CD 설정 간단 프로젝트별 설정 필요

모노레포를 사용하는 회사

  • Google: 대규모 코드를 모노레포 방식으로 관리하며, Bazel 같은 빌드 도구를 사용
  • Facebook: 모든 프로젝트를 모노레포 방식으로 관리하여 일관된 코드베이스 유지
  • Uber: 여러 플랫폼에서 공통 코드를 효율적으로 관리하기 위해 모노레포를 채택

멀티레포를 사용하는 회사

  • Amazon: 서비스가 분리된 독립적인 팀 구조(소위 "Two Pizza Team")를 기반으로 멀티레포를 사용
  • Netflix: 독립된 마이크로서비스 아키텍처를 채택하여 각 서비스가 별도의 저장소에서 관리됨
  • Microsoft: Windows, Office 등 여러 제품군을 개별적으로 관리하기 위해 멀티레포를 활용

모노레포와 멀티레포의 선택 기준

  • 모노레포: 프로젝트 간 강한 연관성이 있거나, 공통 코드를 많이 공유해야 하는 경우
  • 멀티레포: 각 프로젝트가 독립적이며, 팀별로 자유로운 기술 선택과 관리가 필요한 경우

장단점이 명확하기 때문에, 회사의 팀 구조와 프로젝트 요구 사항에 따라 선택하는것이 좋겠다!

(상세한 내용은 참고 자료 링크 모던 프론트엔드 프로젝트 구성 기법 - 모노레포 개념 편에 더 자세히 나와있다)

 

참고자료, 이미지 출처

https://d2.naver.com/helloworld/0923884

기상청 공공 API 쓰는 법

  1. 행정안전부 공공데이터 포털 접속
    https://www.data.go.kr/
  2. 기상청 단기예보 조회서비스 검색, 오픈 API 목록에 있다
    https://www.data.go.kr/data/15084084/openapi.do
  3. 활용 신청 및 승인
    1. 로그인 / 회원가입
    2. API 활용 사유만 쓰면 활용신청 가능, 조금 기다리면 바로 승인됨
  4. 엔드 포인트와 인증키 메모
  5. API 호출 함수 생성
    fetchWeather() 함수를 만들었다
  6. 날씨 데이터를 표시할 모달 컴포넌트 생성
  7. 아이콘 클릭시 API 호출 및 모달 표시

현재 위치에 따라 날씨 데이터 가져오기

아래는 내 프로젝트에서 사용된 WeatherModal 컴포넌트 코드로 리액트, 타입스크립트, 테일윈드를 사용하고 있다

주석에 설명을 추가하였다

import React, { useEffect, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faTimes,
  faMapMarkerAlt,
  faThermometerHalf,
  faTint,
  faWind,
} from "@fortawesome/free-solid-svg-icons";

interface WeatherModalProps {
  isOpen: boolean;
  onClose: () => void;
}

const WeatherModal: React.FC<WeatherModalProps> = ({ isOpen, onClose }) => {
  const [weatherData, setWeatherData] = useState<any>(null);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const [location, setLocation] = useState<{ nx: number; ny: number } | null>(
    null
  );

  useEffect(() => {
    if (isOpen) {
      navigator.geolocation.getCurrentPosition(
        (position) => {
          const { latitude, longitude } = position.coords;
          const { nx, ny } = convertToGridCoords(latitude, longitude);
          setLocation({ nx, ny });
        },
        (error) => {
          console.error("Error getting location:", error);
          setError("위치를 가져올 수 없습니다.");
        }
      );
    }
  }, [isOpen]);

  useEffect(() => {
    if (isOpen && location) {
      fetchWeather(location.nx, location.ny);
    }
  }, [isOpen, location]);

  const fetchWeather = async (nx: number, ny: number) => {
    setIsLoading(true);
    setError(null);
    try {
      const apiKey =
        "vk4aWLTlPOiSCrGPCqW9+RPDwCEbd2qZroyXnqkjhfKAQM15ca8W2DHua28iBIE0OaNWNEPaaAR2suzllAcotg==";
      const encodedKey = encodeURIComponent(apiKey);

      const today = new Date(); // 오늘의 날짜 객체 생성
      const hour = today.getHours(); // 현재 시각 불러오기

      let formattedTime = hour.toString().padStart(2, "0") + "00"; // API에서 요구하는 HH00 형식으로 변환

      const formattedDate = today.toISOString().slice(0, 10).replace(/-/g, "");

      const baseUrl =
        "https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst";
      const queryParams = `numOfRows=10&pageNo=1&base_date=${formattedDate}&base_time=${formattedTime}&nx=${nx}&ny=${ny}&dataType=JSON`;

      const url = `${baseUrl}?serviceKey=${encodedKey}&${queryParams}`;

      const response = await fetch(url);
      const data = await response.json();

      if (data.response && data.response.body && data.response.body.items) {
        const items = data.response.body.items.item;
        const processedData = processWeatherData(items);
        setWeatherData(processedData);
      } else {
        throw new Error("예상치 못한 API 응답 구조");
      }
    } catch (error) {
      console.error("날씨 데이터를 가져오는 중 오류 발생:", error);
      setError(
        error instanceof Error
          ? error.message
          : "알 수 없는 오류가 발생했습니다."
      );
    } finally {
      setIsLoading(false);
    }
  };

  const processWeatherData = (items: any[]) => {
    const processed: any = {};
    items.forEach((item) => {
      switch (item.category) {
        case "T1H": // 기온
          processed.temperature = parseFloat(item.obsrValue);
          break;
        case "RN1": // 강수량
          processed.rainfall = parseFloat(item.obsrValue);
          break;
        case "REH": // 습도
          processed.humidity = parseFloat(item.obsrValue);
          break;
        case "WSD": // 풍속
          processed.windSpeed = parseFloat(item.obsrValue);
          break;
      }
    });
    return processed;
  };

	// 위치 변환 함수 : 사용자의 현재 위치(위도 및 경도)를 기상청 API가 요구하는 격자 좌표(nx, ny)로 변환하는 함수
  const convertToGridCoords = (lat: number, lon: number) => {
    return { nx: 55, ny: 127 }; // 격자 좌표로 변환하는 예시 좌표 반환
  };

  if (!isOpen) {
    return null;
  }

  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div className="bg-white p-6 rounded-lg max-w-sm w-full">
        <div className="flex justify-between items-center">
          <h2 className="text-xl font-bold">현재 날씨</h2>
          <button
            onClick={onClose}
            className="text-gray-500 hover:text-gray-700"
          >
            <FontAwesomeIcon icon={faTimes} />
          </button>
        </div>
        <FontAwesomeIcon icon={faMapMarkerAlt} className="mr-2" />
        <span>현재 위치</span>
        {isLoading ? (
          <p>날씨 데이터를 불러오는 중...</p>
        ) : error ? (
          <p className="text-red-500">오류: {error}</p>
        ) : weatherData ? (
          <div>
            <div className="flex justify-between gap-2 mt-2">
              <div className="flex-grow basis-0 bg-blue-100 p-3 rounded-lg">
                <FontAwesomeIcon
                  icon={faThermometerHalf}
                  className="mr-2 text-blue-500"
                />
                <span className="font-bold">{weatherData.temperature}°C</span>
              </div>
              <div className="flex-grow basis-0 bg-blue-100 p-3 rounded-lg">
                <FontAwesomeIcon icon={faTint} className="mr-2 text-blue-500" />
                <span className="font-bold">{weatherData.humidity}%</span>
              </div>
              <div className="flex-grow basis-0 bg-blue-100 p-3 rounded-lg">
                <FontAwesomeIcon icon={faWind} className="mr-2 text-blue-500" />
                <span className="font-bold">{weatherData.windSpeed}m/s</span>
              </div>
              {weatherData.rainfall > 0 && (
                <div className="bg-blue-100 p-3 rounded-lg">
                  <FontAwesomeIcon
                    icon={faTint}
                    className="mr-2 text-blue-500"
                  />
                  <span className="font-bold">{weatherData.rainfall} mm</span>
                </div>
              )}
            </div>
          </div>
        ) : (
          <p>날씨 데이터가 없습니다.</p>
        )}
      </div>
    </div>
  );
};

export default WeatherModal;

근데 이렇게 하면 날씨가 개떡같이 나올 것이다

현재 위치를 00시 00구 00동으로 나타내려면 역지오코딩(Reverse Geocoding)이라는 기술을 사용해야한다

그건 다음 포스팅에서...

간단함

마이페이지에서 찾지말고 자격증. 확인서 메뉴에서 확인서 발급 신청을 하면 된다

마이페이지에서 자격증 접수내역과 같이 확인서를 발급 받도록 하면 훨씬 편할텐데

이 화면 다음으로 넘어가면 확인서 발급이 가능한 봤던 시험 목록이 나온다

체크 박스가 있는데, 한번에 1개만 선택 가능해서 여러개 발급받으려면 이 과정을 반복해야한다

참고로 응시확인서는 합격자 발표 이후 가능하므로 10월에 본 실기의 발표는 12월이라 글 작성 시점인 11월에는 응시확인서를 발급 받을 수 없었다 (시험접수확인서는 가능하나, 응시 확인서라는게합격 확인서가 아닌데 왜 이런 시스템인진 모르겠다)

대체로 좋지 못한 UX였다

정보처리기사 진법 변환을 공부하다 문득 든생각

애초에 뭔 진법이 이렇게 많냐 이거 왜쓰는거야?

10진수

우리의 손가락을 열개다

그 덕분에 우리는 숫자를 0부터 9까지 쉽게 셀 수 있다

이게 우리가 십진수를 쓰게 된 유래다

2진수

컴퓨터는 0과1로만 이루어져있다

는 사실을 알고는 있지만 컴퓨터로 0123456789 텍스트를 치면 다 나오는데

근본적으로 0과 1로 변환되어야 할 이유는 무엇일까

컴퓨터가 데이터를 보는 방법이 그 이유다

컴퓨터는 전압을 올리거나 내리는 방식으로 데이터를 표현한다

전압이 있을 때는 1, 없을 때는 0으로 표시하는 방식이 가장 간단하고 안정적이다

이렇게 그래프 상으로는 전류가 끊긴 것 같지만

사실은 이런식으로 되어있는 형태이다

 

그리고 각각의 자릿수를 Bit 비트 라고 한다

즉, 1 bit 로 표현할 수 있는것은 참, 거짓 딱 두가지이다

 

여기서 2비트로 늘리면 표현할수 있는 가지수는 2의 2승으로 4가지로 늘어난다 (0부터 3)

4비트는 2의 4승으로 16가지의 표현이 가능하다 (0부터 15)

8비트는 2의 8승으로 256가지의 표현이 가능하다 (0부터 255)

 

컴퓨터는 이 8비트를 데이터가 저장되는 최소 단위로 삼기로 했다

4개로 나뉘어진 비트의 단위를 Nibble이라하며, 앞4비트와 뒤4비트가 합쳐진 8비트를 1Byte 라고 한다

1 byte = 8bits
           = 2nibble ( 4bits : 4bits)
           = 2 ^ 8 = 256 (0~255)

16진수

앞서 4비트는 16가지의 표현이 가능하다 했다

기존의 10진수로는 0부터 9까지 한자리수로 표현이 가능한데, 10이상의 숫자까지 한자리로 표현할 수가 없었다. 여기서 16진수가 등장한다

10이상의 숫자가 나오면 10 = a, 11 = b, 12 = c, 13 = d, 14 = e, 15 = f 이렇게 알파벳으로 표현하게 되었다

예를 들어, 10진수 255는 2진수로 11111111, 16진수로는 FF로 표현된다

왜 굳이 다양한 진법을 사용할까?

16진수를 사용하면 큰 수를 더 간결하고 알아보기 쉽게 표현할 수 있기 때문에 메모리 주소, 색상 코드, 파일 형식 등에서 자주 사용된다

2진수는 컴퓨터 내부에서 데이터를 처리하는 데 필수적이지만, 사람이 직접 읽고 쓰기에는 불편하다

그래서 8진수16진수처럼 더 큰 단위를 사용해 데이터를 간단히 표현할 수 있다

특히 16진수는 4비트 단위를 깔끔하게 나눌 수 있어 데이터의 경계를 명확히 볼 수 있다

8bits = 1 byte : C언어로 char
16bits = 2 byte : C언어로 short int
32bits = 4 byte : C언어로 int
64bits = 8 byte : C언어로 long int

더 큰 단위 또한 있다

1 byte
1000 byte = 1 Kbyte (10^3)
1000 Kyte = 1 Mbyte (10^6)
1000 Myte = 1 Gbyte (10^9)
1000 Gyte = 1 Tbyte (10^12)

8진수

8진수는 2진수보다 더 짧고 간결하게 데이터를 표현할 수 있는 방법이다

예를 들어, 3비트0부터 7까지 8개의 값을 표현할 수 있다

8진수는 예전 컴퓨터 시스템에서 메모리나 데이터 표현을 단순화하기 위해 자주 사용되었다

 

정리하자면 진법이 다양한 이유는 결국 컴퓨터의 효율성사람의 편의성을 동시에 만족시키기 위해서라고 볼 수 있겠다

 

참고 자료

https://youtu.be/lZKej5s3T_w?si=Da7ZiAMwnwQFkbcg

서버 배포 후 클라이언트의 로컬 환경에서 세션을 이용한 로그인 기능이 제대로 동작하지 않는 문제가 발생했다

확인해 보니, 서버에 세션 쿠키가 저장되지 않는 것 같았다

이 문제를 해결하기 위해 쿠키의 속성에 대해 더 자세히 알아보고 설정을 수정하게 되었다

Cookie

쿠키는 웹 애플리케이션에서 중요한 정보를 저장하고 전송할 때 사용되며, 이를 안전하게 다루기 위해 다양한 속성들을 설정할 수 있다

Secure

Secure 속성은 HTTPS 통신에서만 쿠키에 접근할 수 있도록 제한한다

이를 통해 악의적인 네트워크 공격(예: 중간자 공격)을 방지할 수 있다

(localhost 환경에서는 Secure 속성을 지정해도 HTTP로 통신이 가능하지만, 실제 배포 환경에서는 HTTPS를 사용해야한다)

SameSite

SameSite 속성은 크로스 사이트 요청에서 쿠키 전송을 제어하는 역할을 한다

이를 통해 사이트 외부에서 발생한 요청에 대한 쿠키 전송을 제한함으로써, CSRF(Cross-Site Request Forgery) 공격을 방지할 수 있다

Strict

Strict로 설정된 쿠키는 해당 도메인 내에서 발생한 요청(퍼스트 파티 쿠키)만 전송된다

즉, 외부에서 오는 모든 크로스 사이트 요청에서는 쿠키가 전송되지 않는다

예를 들어, 외부 링크나 다른 도메인에서의 접근을 통해 사용자가 사이트로 돌아올 때 쿠키가 전송되지 않는다

Lax

Lax 모드는 기본적으로 Strict와 비슷하지만, 일부 안전한 요청 (GET 메서드를 통한 외부 링크 클릭 등)에서는 쿠키 전송이 허용된다

예를 들어, 사용자가 다른 사이트에서 내 사이트로 링크를 클릭할 때는 쿠키가 전송된다

None

None으로 설정하면 동일 사이트와 크로스 사이트 모두에서 쿠키 전송이 가능하다

즉, 퍼스트 파티 쿠키와 서드 파티 쿠키가 모두 전송될 수 있어, CSRF 공격에 취약하다

크롬 80 버전 이후부터는 SameSite=None으로 설정된 쿠키는 반드시 Secure 속성과 함께 사용해야 하며, 그렇지 않으면 브라우저에서 쿠키를 차단한다

HttpOnly

HttpOnly 속성은 클라이언트의 자바스크립트로 쿠키에 접근할 수 없도록 막아준다

이를 통해 XSS(Cross-Site Scripting) 공격으로부터 쿠키 탈취를 방지할 수 있다

브라우저에서만 쿠키를 처리할 수 있도록 하여, 클라이언트 측 코드가 쿠키를 조작하지 못하게 하는 것이다

 

Hyper Text Transfer Protocol HTTP

HTTP 상태 코드(HTTP Status Codes)는 클라이언트와 서버 간의 통신 상태를 나타내는 3자리 숫자로 구성된 코드로, 클라이언트가 다음에 취해야 할 동작을 결정하는 중요한 역할을 한다

상태 코드는 1XX부터 5XX까지 다양한 범주로 나뉘며, 각각 클라이언트와 서버 간의 통신 과정에서 발생하는 특정 상황을 나타낸다

1XX: 정보 응답

  • 100 (계속): 클라이언트가 요청의 나머지 부분을 계속 전송해야 함을 의미
  • 101 (프로토콜 전환): 서버가 클라이언트의 프로토콜 전환 요청을 승인
  • 102 (처리 중): 요청을 처리 중이며 응답이 지연됨을 알림

2XX: 성공 응답

  • 200 (성공): 요청이 성공적으로 처리됨
  • 201 (작성됨): 요청이 성공적으로 처리되었고, 서버가 새로운 리소스를 생성함 ex) 회원 가입 후 새로운 사용자 계정 생성
  • 202 (허용됨): 요청을 접수했지만 아직 처리되지 않음
  • 204 (콘텐츠 없음): 요청이 성공했으나 응답 본문이 없음 ex) 데이터 삭제 요청 후 별도의 응답 데이터가 필요 없을 때

3XX: 리다이렉션 응답

  • 301 (영구 이동): 요청한 리소스가 영구적으로 새로운 URL로 이동됨
  • 302 (임시 이동): 요청한 리소스가 일시적으로 다른 URL에서 제공됨 ex) 사용자를 다른 페이지로 임시 리다이렉트할 때
  • 304 (수정되지 않음): 리소스가 변경되지 않았으며, 캐시된 버전을 사용 가능

4XX: 클라이언트 오류

  • 400 (잘못된 요청): 서버가 요청을 이해할 수 없거나 잘못된 데이터를 받았을 때 사용
  • 401 (권한 없음): 인증이 필요하지만 제공되지 않았거나 실패한 경우 ex) 로그인 없이 보호된 리소스에 접근 시도
  • 403 (금지됨): 서버가 요청을 거부, 인증은 성공했으나, 클라이언트에게 해당 리소스에 대한 접근 권한이 없는 경우
  • 404 (찾을 수 없음): 요청한 리소스를 찾을 수 없음 ex) 잘못된 URL 요청

5XX: 서버 오류

  • 500 (내부 서버 오류): 서버에서 처리 중 예기치 않은 오류 발생
  • 502 (잘못된 게이트웨이): 게이트웨이 또는 프록시 서버가 상위 서버로부터 잘못된 응답을 받음
  • 503 (서비스 이용 불가): 서버가 일시적으로 요청을 처리할 수 없음 (과부하 상태이거나 유지보수 중일 때)

보통 API 응답에서 사용되는 주요 상태 코드들만 다루었다

오버로딩과 오버라이딩은 객체지향 프로그래밍(OOP)에서 메서드를 다루는 두 가지 중요한 방법이다. 이 두 개념을 제대로 이해하려면 객체지향의 몇 가지 특징을 먼저 짚고 넘어가는 게 도움이 된다.

객체 지향 프로그래밍의 특징

상속 Inheritance

부모 클래스의 멤버를 자식 클래스가 상속받아 사용할 수 있으며, 이를 통해 코드의 재사용성이 높아지고 유지보수도 편리해진다

단, 부모 클래스의 private 멤버는 상속받을 수 없으며, protected 멤버는 자식 클래스에서 접근할 수 있다 상속은 extends 키워드를 사용하여 구현한다.

자식 클래스는 부모 클래스의 메서드나 변수를 그대로 쓸 수도 있고, 필요하면 재정의(오버라이딩)할 수도 있다

오버로딩 vs 오버라이딩

메서드 오버로딩 Method Overloading

같은 이름의 메서드를 매개변수의 개수나 타입을 달리하여 중복 정의하는 것

메서드 이름은 동일하지만, 인자의 개수나 타입이 다르다

이는 컴파일 시간에 결정되며, 코드의 가독성과 유연성을 높여준다.

class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }
}

메서드 오버라이딩 Method Overriding

상속 관계에서 부모 클래스의 메서드를 자식 클래스에서 재정의하는 것

부모 메서드와 동일한 이름, 리턴 타입, 매개변수를 가져야 하며, 이를 통해 자식 클래스에서 부모의 동작을 변경할 수 있다

이 동작은 런타임에 결정된다

class Animal {
    void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Dog barks");
    }
}

추상클래스와 인터페이스

추상 클래스와 인터페이스는 객체지향 프로그래밍에서 상속과 오버라이딩을 활용할 때 많이 등장하는 개념이다

추상 클래스 Abstract Class

하나 이상의 추상 메서드(구현되지 않은 메서드)를 포함하는 클래스

추상 클래스 자체로는 인스턴스를 만들 수 없다

따라서, 이를 상속받은 자식 클래스에서 모든 추상 메서드를 반드시 오버라이딩(구현)해야 한다

즉, 추상 클래스는 기본 설계도만 제공하고, 세부 구현은 자식 클래스에서 하도록 강제하는 구조다.

인터페이스 Interface

모든 메서드가 추상 메서드인 클래스

인터페이스는 구현체가 없기 때문에 인스턴스를 생성할 수 없으며, 클래스는 extends로 상속하고 인터페이스는 implements를 사용하여 구현한다

클래스는 하나만 상속할 수 있지만, 인터페이스는 여러 개를 구현할 수 있어서 다양한 기능을 한 번에 추가할 때 유용하다. 인터페이스를 구현한 클래스는 반드시 그 안의 모든 메서드를 구현해야 한다

이 두 개념을 이해하면, 오버라이딩이 왜 중요한지 더 쉽게 파악할 수 있다

특히, 추상 클래스나 인터페이스에서 정의된 메서드들을 재정의하는 방식으로 자식 클래스에서 자신만의 기능을 추가하거나 변경할 수 있기 때문이다.

+ Recent posts