[클린코드] 주석: 숙명인가 함정인가? (feat. 코드로 말해요, 우리)
코드가 어려우면 주석을 달면 되지!"라는 말, 개발자라면 누구나 한 번쯤 들어봤을 겁니다.
하지만, 주석과의 관계는 참으로 복잡미묘합니다.

처음엔 친절한 메시지처럼 보이지만, 시간이 지나면서 코드와 따로 놀고,
오히려 이해를 방해하는 경우가 너무 많았습니다.

클린 코드 9장 "주석"에서는 주석을 잘못 사용하는 경우를 지적하며,
이상적으로는 코드 자체로 설명이 가능해야 한다고 강조합니다.
하지만, 현실은 그렇게 이상적이지만은 않죠.

그렇다면 우리는 주석과 어떻게 공존해야 할까요? 🤔

1. 나쁜 주석: 우리를 괴롭히는 악당들 👿
1.1 중복된 주석: 코드만 봐도 아는데... 🤦♀️
흔히 볼 수 있는 예시죠. 접근성을 위해 aria-label 속성까지 추가했습니다.
// components/SearchInput.tsx
import React from 'react';
import './SearchInput.scss';
const SearchInput: React.FC = () => (
// 검색 입력 컴포넌트
<input
type="text"
className="search-input"
placeholder="검색어를 입력하세요"
aria-label="검색어 입력"
/>
);
export default SearchInput;
// components/SearchInput.scss
.search-input {
// 입력 필드 스타일
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
&:focus {
// 포커스 스타일
outline: none;
border-color: #007bff;
}
}
코드만으로도 충분히 의미가 명확한데 굳이 "// 검색 입력 컴포넌트", "// 입력 필드 스타일"과 같은 주석이 필요할까요? 오히려 코드 분석 흐름만 방해할 뿐입니다.
1.2 주석 처리된 코드: 망령의 흔적을 지워주세요! 👻
// components/DropdownMenu.tsx
import React, { useState } from 'react';
import './DropdownMenu.scss';
const DropdownMenu: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
// const handleMouseEnter = () => {
// setIsOpen(true);
// };
return (
<div className="dropdown-menu">
<button
className="dropdown-button"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
>
메뉴
</button>
{isOpen && (
<ul className="dropdown-list" role="menu">
{/* <li role="menuitem">메뉴 1</li> */}
<li role="menuitem">메뉴 2</li>
</ul>
)}
</div>
);
};
export default DropdownMenu;
주석 처리된 코드는 마치 과거의 망령처럼 코드베이스를 떠돌며 유지보수를 방해합니다. 과감하게 삭제하고 깨끗한 코드를 유지하세요! 버전 관리 시스템은 우리의 든든한 아군입니다. 😉
1.3 잘못된 주석: 거짓말은 나빠요! 🤥
// components/Modal.tsx
import React, { useState } from 'react';
import './Modal.scss';
interface ModalProps {
children: React.ReactNode;
onClose: () => void; // 모달 닫기 콜백 함수 (실제로는 열림 상태 업데이트)
}
const Modal: React.FC<ModalProps> = ({ children, onClose }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={`modal ${isOpen ? 'open' : ''}`}
aria-hidden={!isOpen}
aria-modal="true"
role="dialog"
>
<div className="modal-content">
{children}
<button onClick={() => setIsOpen(true)}>닫기</button>
</div>
</div>
);
};
export default Modal;
주석이 코드와 일치하지 않는다면? 차라리 없는 게 낫습니다. 잘못된 정보는 오류를 유발하고 디버깅 시간을 늘리는 주범입니다. 주석을 업데이트하거나 삭제하여 항상 코드와 일치하도록 유지해야 합니다.
1.4 장황한 주석: 간결함이 최고의 미덕 ✨
// components/ImageGallery.tsx
import React from 'react';
import './ImageGallery.scss';
interface ImageGalleryProps {
images: string[]; // 이미지 URL 배열
}
// 이 컴포넌트는 이미지 갤러리를 렌더링합니다.
// images prop으로 이미지 URL 배열을 전달받아 각 이미지를 표시합니다.
const ImageGallery: React.FC<ImageGalleryProps> = ({ images }) => (
<ul className="image-gallery" role="list">
{images.map((image, index) => (
<li key={index}>
<img src={image} alt={`갤러리 이미지 ${index + 1}`} />
</li>
))}
</ul>
);
export default ImageGallery;
장황한 주석은 코드의 가독성을 떨어뜨리고, 개발자의 집중력을 저하시킵니다. 코드만큼이나 주석도 간결하고 명확하게 작성하는 것이 중요합니다. 위 코드에서 컴포넌트 선언 부분 주석은 삭제해도 충분히 의미가 전달됩니다.
2. 좋은 주석: 가끔은 한 줄기 빛 ✨ ... 정말 가끔은!
모든 주석이 나쁜 것은 아닙니다. 때로는 복잡한 로직이나 의도를 명확히 설명하기 위해 주석의 도움이 필요한 경우도 있습니다. 하지만, 정말 필요한 경우에만, 최소한으로 사용해야 합니다.
2.1 주석이 필요한 경우
법적인 주석: 저작권, 라이선스 정보는 명확하게 명시해야 합니다.
// components/App.tsx
// Copyright 2023. John Doe. All rights reserved.
정보 제공: 복잡한 정규식이나 알고리즘을 설명할 때 유용합니다. 다만, 코드 리팩토링을 통해 주석 없이도 이해될 수 있다면 더욱 좋습니다.
// components/Form.tsx
const validateEmail = (email: string): boolean => {
// RFC 5322 기반 이메일 유효성 검사 정규식
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
return emailRegex.test(email);
};
경고: 특정 코드 수정 시 주의 사항을 명시합니다.
// components/DataSynchronizer.tsx
// 주의: startSync() 함수 호출 이전에 반드시 설정 파일을 로드해야 합니다.
// 설정 파일이 로드되지 않으면 동기화 오류가 발생할 수 있습니다.
const startSync = () => {
// ... 데이터 동기화 로직
};
TODO: 아직 구현하지 못했지만, 향후 추가해야 할 작업을 남겨놓을 때 유용합니다.
// components/UserList.tsx
const UserList: React.FC = () => {
// TODO: 사용자 목록 무한 스크롤 기능 구현
const users = [];
return (
<ul role="list">
{users.map((user) => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
);
};
export default UserList;
3. 주석 없이 코드 가독성 높이기: 코드로 말해요, 우리! 🗣️
주석을 최소화하고 코드 자체로 의미를 명확히 전달하는 것이 클린 코드의 핵심입니다.
3.1 의미 있는 이름: 변수, 함수, 클래스 등에 의미 있는 이름을 사용하면 코드를 읽는 것만으로도 목적을 파악할 수 있습니다.
// components/User.tsx
interface UserProps {
// 나쁜 예:
n: string;
s: 'online' | 'offline';
// 좋은 예:
name: string;
status: 'online' | 'offline';
}
3.2 함수/메서드 분리: 긴 함수를 여러 개의 작은 함수로 분리하고, 각 함수에 명확한 이름을 부여하면 자연스럽게 코드가 설명됩니다.
// components/Form.tsx
// 나쁜 예:
const handleSubmit = () => {
// ... 입력값 검증
// ... API 요청
// ... 성공/실패 처리
};
// 좋은 예:
const validateInput = () => {
// ... 입력값 검증 로직
};
const sendApiRequest = () => {
// ... API 요청 로직
};
const handleResponse = () => {
// ... 성공/실패 처리 로직
};
const handleSubmit = () => {
if (validateInput()) {
sendApiRequest()
.then(handleResponse)
.catch(handleError);
}
};
3.3 적절한 추상화: 클래스, 인터페이스 등을 활용하여 코드를 모듈화 하면 불필요한 주석을 줄일 수 있습니다.
// components/Button.tsx
// 나쁜 예:
interface ButtonProps {
// ... 버튼 공통 속성
// ... primary 버튼에만 필요한 속성
// ... secondary 버튼에만 필요한 속성
}
// 좋은 예:
interface BaseButtonProps {
// ... 버튼 공통 속성
}
interface PrimaryButtonProps extends BaseButtonProps {
// ... primary 버튼에만 필요한 속성
}
interface SecondaryButtonProps extends BaseButtonProps {
// ... secondary 버튼에만 필요한 속성
}
4. 주석과의 동행: 더 나은 개발을 향해 🚶♀️🚶♂️
코드만으로 모든 걸 설명할 수 있다면 좋겠지만, 현실은 그렇지 않죠. 😅
'클린 코드'에서 로버트 C. 마틴은 바로 이 지점을 강조합니다.
주석은 코드를 장황하게 풀어쓰는 게 아니라, 코드로는 드러나지 않는 숨은 의도나 중요한 정보를 명확하게 전달하는 도구라는 거죠.
다시 말해, 주석을 추가하기 전에 코드 자체를 명확하게 만드는 데 집중해야 합니다. 🤔
진정한 클린 코드는 주석 없이도 스스로 이야기하는 코드니 까요! ✨

여러분은 어떤 유형의 주석을 주로 사용하시나요?
혹시 불필요한 주석을 남기는 습관 때문에 고민하고 있지는 않으신가요? 🤔
함께 고민하고 토론하며, 주석을 현명하게 활용하는 개발 문화를 만들어나가면 좋겠습니다! 😊
[참고]
클린 코드 (Clean Code) - 로버트 C. 마틴