『리더블 코드』 4장과 5장을 읽고, AI 시대에 힘을 쏟아야 할 곳은 코드의 모양인가 주석인가
요약
이 글은 코드 자체의 기능적 정확성보다 일관된 포맷(format)과 가독성이 중요함을 강조합니다. 함수나 변수 선언, 코드 블록 등을 통일된 규칙으로 작성하면 독자가 구조를 빠르게 파악할 수 있어 유지보수와 협업에 큰 도움이 됩니다.
핵심 포인트
- 코드의 가독성은 '보기 좋음'이 아닌 '읽는 부담 감소'가 목적입니다.
- 변수/필드 순서나 들여쓰기 등은 규칙을 정하고 일관되게 지키는 것이 중요합니다.
- AI 시대에도 사람이 작성한 코드는 결국 리뷰 과정을 거치므로, 가독성을 높이는 포맷팅이 여전히 중요합니다.
지금까지의 독서 감상은 다음과 같습니다.
이 장의 핵심은 일관된 포맷(format)이 '절대적으로 올바른 작성 방식'을 따르는 것보다 더 중요하다는 점입니다. 가독성을 높이는 것은 보기 좋게 하기 위함이 아니라, 읽는 부담을 줄여서 한눈에 코드의 구조를 파악할 수 있도록 하기 위함입니다.
만약 여러 개의 코드 블록이 같은 종류의 처리를 한다면, 형태와 줄 바꿈 방식을 통일해야 합니다. 먼저 나쁜 예시를 보겠습니다.
void AddUser(int id, string name);
void RemoveUser(int id);
void UpdateUserInfo(int id, string name, int age, string address);
세 함수 모두 사용자 관련 작업이지만, 함수 이름의 길이가 제각각이고 인자(argument)의 시작 위치도 통일되어 있지 않습니다. 빠르게 훑어봐도 규칙성을 파악하기 어렵습니다. 이렇게 정리하면 다음과 같습니다.
void AddUser (int id, string name);
void RemoveUser (int id);
void UpdateUserInfo(int id, string name, int age, string address);
인간의 뇌는 문자를 읽는 것보다 형태를 인식하는 것이 훨씬 빠릅니다. 윤곽만 갖추어도 빠르게 훑어보는 속도가 크게 올라갑니다.
동일한 정보를 세로로 정렬하면 눈에 난간이 생긴 듯한 효과가 있습니다.
string name = "田中太郎";
int age = 28;
double salary = 15000.0;
...
변수 선언을 모아서 보면 오타나 타입의 차이점을 바로 알 수 있습니다. 반면에, 변수 이름 하나만 바꿔도 다른 줄의 공백까지 수정해야 하고, 커밋(commit)의 diff도 지저분해집니다. 책의 결론은, 사용해도 좋지만 억지로 맞추려 노력하거나 유지보수 부담이 크다면 포기하는 것이라는 것입니다.
변수, 필드, 인자의 순서는 동작에 영향을 주지 않지만, 가독성에는 영향을 줍니다. 책의 나쁜 예시에서는 헤더 파일의 필드가 name, id, address 순서인데, 구현 파일의 Save 함수에서는 address, id, name 순서로 배열되어 있습니다. 중요도 순, 알파벳 순, 기능 그룹 순, 포맷 순 등 하나의 규칙을 정하고 끝까지 지켜야 합니다. 독자는 한 번 순서를 기억하면 다른 곳에서도 예측할 수 있기 때문입니다.
문장에 단락이 있는 것처럼, 코드도 덩어리로 몰아넣으면 읽기 힘듭니다. 절차마다 빈 줄로 구분하고 개요 주석을 1줄 추가하면, 함수가 몇 단계에 걸쳐 작동하는지 한눈에 알 수 있습니다.
def generate_report(user_id):
# 데이터를 로드합니다
user = db.get_user(user_id)
...
중괄호를 줄 바꿈할지, 들여쓰기에 공백과 탭 중 어느 것을 사용할지, 함수 이름을 PascalCase로 할지 SnakeCase로 할지 등 이런 문제에 유일한 정답은 없습니다. 프로젝트의 규약이 잘못되었다고 생각해도 기존 규약을 따르는 것이 중요합니다. 최악인 것은 어떤 스타일을 선택했느냐가 아니라, 같은 코드 안에 여러 스타일이 섞여 있는 경우입니다.
읽으면서 처음에 생각한 것은, 이 장의 가치가 대규모 언어 모델(LLM)의 발전으로 떨어지지 않을까 하는 점이었습니다. 이미 AI에게 오래된 코드를 리팩토링하게 하는 회사가 나오고 있습니다. 오래된 코드에는 필연적으로 스파게티 코드(일명 똥코드)가 섞여 있는데, AI가 그것을 이해하고 더 정돈되고 빠른 방식으로 대체하는 작업은 앞으로 점점 쉬워질 것입니다. 이 흐름이라면, 사람이 한 줄씩 손으로 모양을 다듬는 중요성은 낮아진다고 생각합니다.
하지만 그렇다고 해서 모양이 중요하지 않다는 이야기는 아닙니다. AI가 작성한 코드도 결국 사람은 리뷰하게 됩니다. 정돈된 코드가 리뷰하는 사람의 부담은 명확히 적습니다. 따라서 제 생각은, 사람이 손으로 아름다운 코드를 쓸 필요성은 낮아지는 반면, AI의 출력물에 포맷이나 코딩 규약의 제약을 가할 필요성은 변하지 않는다는 것입니다. 그 규약은 나중에 코드를 읽는 사람을 편하게 하기 위함입니다.
이 장의 핵심은 주석(comment)의 목적은 작성자의 의도를 독자에게 전달하는 것이지, 코드를 읽으면 알 수 있는 내용을 반복하는 것이 아니라는 점입니다.
코드 문법을 한국어로 번역한 것 같은 주석은 화면 공간만 낭비할 뿐입니다. 예를 들어 class Account에 'Account 클래스의 정의'라고 적어도, 누가 봐도 이해할 수 있습니다.
경계선상에 있는 경우도 있습니다. 새로운 정보는 없어 보이지만, 생각할 필요가 없는 주석 말입니다.
2つ目の「*」以降をすべて削除する
name = ''.join(line.split('')[:2])
코드를 읽으면 알 수 있지만, 이 주석은 높은 수준의 의도를 그대로 말로 표현해 준다. 문자열 슬라이스를 직접 해독할 필요가 없으므로 가치 있는 주석이라고 할 수 있다.
함수는 여기저기서 호출되며, 호출하는 쪽에서는 함수 위에 있는 주석을 볼 수 없다. 예를 들어 DeleteRegistry라는 함수에 '핸들을 해제하기만 하고 실제 레지스트리는 삭제하지 않는다'는 주석이 붙어 있다고 가정해 보자. Delete라는 단어는 파괴적인 의미를 가지므로, 이름만 보면 데이터를 지운다고 오해하기 쉽고, 그 모호함을 주석으로 보완하게 된다. ReleaseRegistryHandle로 이름을 변경하면 의미가 이름 안에 들어간다. 책의 말을 빌리자면, 훌륭한 코드는 형편없는 코드에 아무리 좋은 주석을 붙이는 것보다 우수하다.
코드를 작성할 때 머릿속에 있던 중요한 정보를 남겨둔다. 왜 그렇게 했는지, 무엇에 막혔었는지, 실제 측정값은 어땠는지 등을 말이다. 책의 예시는 '이 데이터셋에서는 이진 트리가 해시 테이블보다 40% 빠르다. 해시 계산 비용이 노드 비교보다 높기 때문에'라는 것이다. 코드를 본 것만으로는 절대 알 수 없는 정보이며, 나중에 유지보수하는 사람이 빠르다고 착각한 작성 방식에 '최적화'해 버리는 무의미함을 막아준다.
코드를 개선해 가는 도중에 불완전한 부분이 나오는 것은 피할 수 없다. 부끄러워하지 않고 적어두면 된다. 책에서는 자주 사용되는 마커 몇 가지를 소개하고 있다.
| 마커 | 의미 |
|---|---|
TODO: | 나중에 처리하거나 개선할 부분 |
FIXME: | 알려진 결함이 있음 |
HACK: | 임시방편적인 방법이라 보기 좋지 않음 |
XXX: | 경고, 여기에 중대한 문제가 있음 |
'이 클래스는 비대해져서 혼란스럽다. ResourceNode 서브클래스를 분리해서 리팩토링하는 것이 좋다'와 같이 현재 상황과 개선 방향을 그대로 적어두어도 좋다. 문제를 제시하고 생각 방식까지 덧붙여 두면, 나중에 온 사람이 형편없는 코드를 앞에 두고 손대지 못하게 하는 일을 막아줄 수 있다.
상수를 정의할 때 중요한 것은 그 상수가 무엇이라는 이름이 아니라, 왜 그 값으로 했는지이다. 예를 들어 NUM_THREADS = 8 뒤에 'CPU 코어 수의 2배로 설정. 더 늘리면 컨텍스트 스위치 때문에 오히려 느려진다'라고 덧붙이면, 왜 1도 아니고 50도 아닌 8인지 알 수 있다. SECONDS_PER_DAY = 86400처럼 자명한 상수에 군더더기 주석을 달 필요는 없다.
프로젝트에 익숙하지 않은 신입이 '어? 왜 이렇게 작성했지?'라고 생각할 만한 곳에 주석을 단다. 첫 번째는 잘 알려지지 않은 테크닉이다.
// vector의 내부 메모리를 해제한다 (STL의 swap 테크닉)
// clear()는 요소를 지울 뿐, 메모리는 반환하지 않는다
vector<float>().swap(data);
많은 C++ 개발자는 이 세부 사양을 모르기 때문에, 주석이 없으면 '왜 clear()를 사용하지 않는가'라는 의문을 가질 수 있다. 두 번째는 숨겨진 리스크이다. 예를 들어 SendEmail이라는 함수는 이름만 봐도 외부 메일 서비스를 호출하여 최대 1분 동안 블록할 수 있다는 것을 알 수 없다. 주석이 없으면 HTTP 요청 내에서 직접 호출해서 서비스를 마비시키는 사람이 나올 수도 있다.
주석은 한 줄의 코드 위뿐만 아니라, 더 큰 구조에 대한 설명에도 사용될 수 있다. 파일이나 클래스 레벨의 주석에서는 전체 역할을 작성한다. 예를 들어 '이 파일은 파일 시스템 관련 보조 인터페이스를 제공하며, 권한이나 경로 결합 등을 다룬다'라고 적어두면, 신입은 파일을 열자마자 어떤 파일인지 알 수 있다. 코드 블록의 요약 주석은 긴 처리의 목적을 한 단어로 요약한다.
# 고객 본인이 구매한 상품으로 좁힌다
for customer_id in all_customers:
for sale in all_sales[customer_id].sales:
...
루프를 한 줄씩 읽지 않아도, 이 로직이 무엇을 위해 존재하는지 미리 알 수 있으므로, 세부 사항에서 길을 잃지 않을 수 있다.
처음부터 완벽한 주석을 쓰려고 하다가 결국 아무것도 안 쓰는 사람이 많다. 책이 권하는 것은 3단계이다. 먼저, 머릿속의 말을 그대로 적는다. 구어체여도, 거칠어도 좋다. 다음으로, 다시 읽으면서 모호하거나 설명이 부족한 부분을 찾아본다. 마지막으로, 더 정확한 표현으로 다듬는다. 코드를 쓰면서 주석도 쓰고, 나중에 한 번에 정리하려고 하지 않는 것이 중요하다. 빨리 쓸수록 비용은 낮고 정보도 정확해진다.
이 장은 AI 시대에는 오히려 중요도가 높아진다고 생각한다.
AI가 코드 파일을 읽을 때, 주석도 함께 읽어 들인다. 4장에서 적었던 흐름이 이어져, AI가 코드를 직접 이해하는 힘이 점점 올라간다면, '이 부분이 무엇을 의미하는지', '이 함수는 무엇을 나타내는지'와 같은 주석은 AI에게 새로운 정보가 거의 없다. 모델이 똑똑해질수록, 그런 주석의 가치는 떨어진다.
그렇다면 무엇을 써야 할까. 책의 답은, 생각한 과정을 쓰는 것이다. 왜 그렇게 선택했는지, 무엇에 막혔었는지, 실측값은 어땠는지, 그리고 코드에 이미 존재하는 알려진 결함 같은 것들이다. 예를 들어 '이 데이터셋에서는 이진 트리가 해시 테이블보다 40% 빠르다'라는 정보는 코드를 봐도 절대로 나올 수 없다. 읽는 사람의 입장에서 생각하면, 왜 그렇게 작성했는지라는 이유야말로 남겨야 할 것이다.
이는 AI에도 해당한다. AI가 이러한 주석을 읽으면, 이 작성 방식이 의도적인 것임을 알기 때문에, 굳이 해시 테이블로 '최적화'해 버리지는 않을 것이다. 그 부분에 대해 더 적절한 리팩토링(refactoring) 제안을 해주거나, 우리의 원래 생각을 한 단계 더 발전시켜 주기도 할 것이다.
두 장을 나란히 놓고 보면, 코드의 '어떻게 하는가(how)'는 사람에게나 AI에게나 점점 읽기 쉬워진다. 읽을 수 없는 것은 '왜 그렇게 했는지(why)'이다. 이것이 AI 시대에 이 두 장의 무게감이 달라지는 이유라고 생각한다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기