코드의 포매팅을 읽는 방법
프로그래머는 대부분의 시간을 다른 사람의 코드를 읽는 데 씁니다. 기능을 추가하거나 버그를 고칠 때도, 먼저 기존 코드의 의도를 파악해야 합니다. 이때 들여쓰기, 줄 길이, 공백 같은 포매팅은 단순한 외형이 아니라, 코드의 구조와 복잡도를 전달하는 핵심 신호입니다.
들여쓰기: 계층 구조를 시각적으로 읽기
들여쓰기는 코드의 범위 관계를 명시합니다.
def calculate_total(items):
total = 0
for item in items:
if item.price > 0:
total += item.price
return total
이 코드에서 total = 0은 함수 전체의 범위에서 유효하고, for 루프는 그 안에 중첩되며, if 문은 루프 내에서만 평가됩니다. 들여쓰기가 이 관계를 명확히 보여줍니다.
들여쓰기가 규칙을 무시하면 어떻게 될까요? Python에서는 문법 오류로 처리하지만, 중괄호를 사용하는 Java나 C 같은 언어는 다릅니다.
// 중괄호 언어에서 들여쓰기 오류
if (condition) {
int x = 0; // 실제로는 if 블록 밖에 있음
doSomething(); // 이 줄도 if 블록 밖
}
컴파일러는 중괄호를 따르지만, 실제 로직과 눈에 보이는 범위가 다릅니다. 이는 단순한 가독성 문제가 아니라 의도와 실제 동작의 불일치로 이어집니다.
깊은 중첩은 조건이나 루프가 많다는 신호입니다. 이를 개선하는 한 가지 방법은 조기 반환(early return / guard clause)입니다.
// 개선 전: 깊은 중첩
function processUser(user) {
if (user) {
if (user.isActive) {
if (user.balance > 0) {
sendNotification(user.email);
updateBalance(user.id);
}
}
}
}
// 개선 후: 가드 클로즈
function processUser(user) {
if (!user) return;
if (!user.isActive) return;
if (user.balance <= 0) return;
sendNotification(user.email);
updateBalance(user.id);
}
조기 반환은 불필요한 중첩을 제거하고 핵심 로직을 앞으로 드러냅니다.

줄 길이와 배치: 복잡도 신호
짧은 줄에 단계별로 코드를 배치하면 작업의 흐름이 명확합니다.
List<String> names = users.stream()
.filter(user -> user.isActive())
.map(User::getName)
.sorted()
.collect(Collectors.toList());
각 메서드가 한 줄씩 있으므로, 순차적 변환 파이프라인이라는 의도가 드러납니다. 한 줄로 몰아쓰면?
List<String> names = users.stream().filter(user -> user.isActive()).map(User::getName).sorted().collect(Collectors.toList());
같은 기능이지만 읽기가 어렵고, 수정할 때도 한 메서드를 찾기 힘듭니다.
함수의 매개변수도 마찬가지입니다. 매개변수가 많다는 것이 항상 함수가 책임을 많이 진다는 뜻은 아닙니다. 설정 객체(configuration object)나 렌더링 함수처럼 단일 책임이면서도 인자가 많은 경우가 있기 때문입니다. 하지만 매개변수를 명확히 배치하면 함수의 복잡도를 한눈에 파악할 수 있습니다.
# 한 줄로: 복잡도가 숨겨짐
def create_report(name, date_start, date_end, include_summary, include_charts, format_type): pass
# 명확히 배치: 복잡도가 드러남
def create_report(
name,
date_start,
date_end,
include_summary,
include_charts,
format_type
):
pass
공백과 빈 줄: 논리적 경계
빈 줄은 논리적으로 관련된 코드 그룹을 분리합니다.
function authenticate(username, password) {
const user = database.findUser(username);
if (!user) return null;
const hashedPassword = hash(password);
if (user.passwordHash !== hashedPassword) return null;
const token = generateToken(user.id);
return token;
}
빈 줄이 세 개의 논리적 단계(“사용자 조회” → “비밀번호 검증” → “토큰 생성”)를 구분합니다. 빈 줄이 없으면 각 단계가 독립적인지 연관되는지 불명확해집니다.
연산자 양쪽의 공백도 식의 의도를 드러냅니다.
# 명확함
result = (a + b) * c - d
# 불명확함
result = (a+b)*c-d
자동 포매터와 스타일 가이드
현대 개발에서 포매팅은 대부분 자동으로 처리됩니다. Python의 Black, JavaScript의 Prettier, Go의 gofmt 같은 자동 포매터를 사용하면 일관된 스타일을 유지할 필요가 없습니다. 대신 팀은 PEP 8이나 Google Style Guide 같은 스타일 가이드를 정하고, 포매터를 그에 맞게 설정합니다.
포매터를 도입하면 두 가지 이득을 얻습니다. 첫째, 포매팅 논쟁이 사라지고, 둘째, git diff가 깔끔해져 코드 리뷰가 효율적입니다. 불필요한 공백 변경이 없으므로 실제 로직 변화만 명확히 드러나기 때문입니다.
결론
포매팅은 코드를 작성한 사람의 의도를 드러내는 가장 빠른 신호입니다. 들여쓰기의 깊이, 줄의 길이, 공백의 배치는 코드가 얼마나 복잡한지, 어떤 문제를 해결하려는지를 보여줍니다. 자동 포매터로 일관성을 유지하고, 스타일 가이드에 따르면 코드는 읽기 쉬워지고 협업과 버전 관리가 수월해집니다. 다음 번에 코드를 읽을 때, 먼저 포매팅의 패턴을 관찰해보세요. 그것만으로 코드의 복잡도와 의도가 상당 부분 드러날 것입니다.
'코드를 읽는 방법' 카테고리의 다른 글
| [Git] "왜 이렇게 만들었을까"의 답이 여기 있다 (0) | 2026.05.29 |
|---|---|
| [Formatting] Decode What Your Code Is Saying (0) | 2026.05.28 |
| [Code Patterns] How Names Reveal What Code Really Does (0) | 2026.05.27 |
| [주석 해석] 코드 한 줄의 진짜 의도를 읽는 법 (0) | 2026.05.27 |
| [네이밍] 이름만 봐도 코드가 읽힌다 (0) | 2026.05.27 |