[리뷰] 읽기 좋은 코드가 좋은 코드다.
회사에 비치되어 있어 읽게 된 책.
오래된 책이지만 나에게 필요한 내용은 모두 들어있었다. 책은 1~4부로 나눠져 있으며 인덱스는 다음과 같다.
- 1부_표면적인 수준에서의 개선
- 2부_루프와 논리를 단순화하기
- 3부_코드를 재작성하기
- 4부_선택된 주제들
이 리뷰에서는 책을 읽으며 이건 기억하고 싶다! 라는 생각이 들었던 내용만 기록하여 두고두고 곱씹을 예정.
1. 표면적인 수준에서의 개선
표면적 수준이란 좋은 이름을 짓고, 좋은 설명을 달고, 코드를 보기 좋게 정렬하는 따위를 의미한다. 1장에서는 이러한 표면적 수준의 개선 방향을 제시하고 있다. 다만 이러한 즉각적인 변경은 코드베이스 전체에 영향을 줄 수 있으므로 신중하게 처리를 해야할 것이다.
📌 미학
✅ 의미 있는 순서를 선택하고 일관성 있게 사용하라.
코드의 순서는 코드의 정확성과 아무런 상관이 없는 경우가 많다. 그러나 코드를 읽는 것은 사람이므로 코드 전반에 걸쳐서 일관된 방식으로 나타내야 한다.
✅ 정리
- 변수의 순서를 HTML 폼에 있는 <input> 필드의 순서대로 나열하라.
- '가장 중요한 것'에서 시작해서 '가장 덜 중요한 것'까지 순서대로 나열하라.
- 알파벳 순서대로 나열하라.
- 일관성 있는 스타일은 '올바른'스타일보다 더 중요하다.
📌 주석에 담아야 하는 대상
✅ 주석에 담지마!
- 코드 자체에서 재빨리 도출될 수 있는 사실. - 주석을 읽는 것보다 코드를 보는 게 더 빠를 때.
- 나쁜 함수명과 같이 나쁘게 작성된 코드를 보정하려고 '애쓰는 주석'. 그렇게 하는 대신 코드를 수정하라.
✅ 주석에 담아!
- 코드가 특정한 방식으로 작성된 이유를 설명해주는 내용(감독의 설명)
- 코드에 담긴 결함. TODO : 혹은 XXX: 와 같은 표시를 사용하라.
- 어떤 상수가 특정한 값을 갖게 된 '사연'.
✅ 주석을 작성할 때...
- 코드를 읽는 사람이 자기가 작성한 코드의 어느 부분을 보고 '뭐라고?'라는 생각을 할지 예측해보고 그 부분에 주석을 추가하라.
- 평범한 사람이 예상하지 못할 특이한 동작을 기록하라.
- 파일이나 클래스 수준 주석에서 '큰 그림'을 설명하고 각 조각이 어떻게 맞춰지는지 설명하라.
- 코드에 블록별로 주석을 달아 세부 코드를 읽다가 나무만 보고 숲은 못 보는 실수를 저지르지 마라.
📌 명확하고 간결한 주석 달기
✅ 구체적인 용법을 설명해주는 입/출력 예를 사용하라.
주석으로 이러쿵 저러쿵 설명을 자세하게 달아봤자 적절한 예시가 코드를 이해하는 데 더 효과적이다.
// 입력된 'src'의 'chars'라는 접두사와 접미사를 제거한다.
String Strip(String src, String chars) {...}
위 내용의 주석이 틀린 것은 아니지만 다음의 질문에 대한 답을 포함하고 있지 않다.
- chars가 제거되어야 하는 정확한 부분 문자열을 의미하는가 아니면 특정한 순서가 정해지지 않은 문자의 집합을 의미하는가?
- src의 끝에 chars가 여러 번 있으면 어떻게 되는가?
// 예 : Strip("aabba/a/ba", "ab")은 "/a/"를 반환한다.
// 예 : String("ab", "a") 은 "b"를 반환한다. → 이건 2번째 질문에 대한 답이 되지 않으므로 부적절한 예시다.
String Strip(String src, String chars) {...}
반면 이 주석은 올바른 예시 하나로 모든 설명을 끝내버리고 있다.
✅ 정리
- it이나 'this' 같은 대명사가 여러 가지를 가리킬 수 있다면 사용하지 않는 것이 좋다.
- 함수의 동작을 실제로 할 수 있는 한도 내에서 최대한 명확하게 설명하라.
- 코드가 가진 의도를 너무 자세한 내용이 아니라 높은 수준에서 개괄적으로 설명하라.
- 많은 의미를 함축하는 단어로 주석을 간단하게 만들라.
2. 루프와 논리를 단순화하기
2부에서는 루프와 논리 구조를 단순화하는 방법에 대한 내용을 다룬다. 복잡한 루프, 거대한 표현, 많은 변수를 깔끔하게 바꿔보자.
📌 읽기 쉽게 흐름제어 만들기
✅ 함수 중간에서 반환하여 중첩을 제거하라.
중첩이 여러번 되어있는 함수는 이해하기 매우 복잡하다. 중첩이 꼭 필요하다면 함수 중간에 특정 조건을 만나면 값을 중간에서 반환하여 중첩을 최대한 제거한다.
function processUser(user) {
if (user) {
if (user.isActive) {
if (user.age >= 18) {
console.log("User is valid and active.");
return true;
} else {
console.log("User is not old enough.");
return false;
}
} else {
console.log("User is not active.");
return false;
}
} else {
console.log("No user provided.");
return false;
}
}
같은 동작을 하는 함수일 때 위 함수는 상단의 if조건을 계속 갖고 있는 상태로 동작을 따라가야 한다.
function processUser(user) {
if (!user) {
console.log("No user provided.");
return false;
}
if (!user.isActive) {
console.log("User is not active.");
return false;
}
if (user.age < 18) {
console.log("User is not old enough.");
return false;
}
console.log("User is valid and active.");
return true;
}
이 경우는 중첩 할 필요없이 조건만으로 빠르게 판단이 가능하다.
✅ 정리
- 삼항 연산자는 매우 간단할 때만 사용해야 한다.
- 줄 수를 최소화하는 일보다 다른 사람이 코드를 읽고 이해하는 데 걸리는 시간을 최소화하는 일이 더 중요하다.
- (while (bytes_expected > bytes_received))와 같은 비교 구문을 작성할 때는 변화하는 값을 왼쪽에 놓고 안정적인 값을 오른쪽에 놓는 편이 좋다. ( 대부분의 인간이 그렇게 읽는 게 편하다고 느끼도록 프로그래밍 되어있음)
- if/else 문의 블록 순서를 바꿀 수도 있다. 일반적으로 긍정적이고, 쉽고, 흥미로운 경우를 앞에 놓도록 하라.
- 중첩된 코드 블록은 더 높은 집중력을 요구한다. 지나친 중첩을 피하려면 '선형적인'코드를 추구하라.
- 함수 중간에 반환하면 중첩을 피하고 코드를 더 깔끔하게 작성할 수 있다.
3. 코드 재작성하기
3부에서는 코드를 아래 내용을 바탕으로 전체적으로 함수 수준에서 변경하는 방법에 대해 제시한다.
- 프로그램의 주된 목적과 부합하지 않는 '상관없는 하위문제'를 추출하라.
- 코드를 재배열하여 한 번에 한 가지 일만 수행하게 하라.
- 코드를 우선 단어로 묘사하고, 이 묘사를 이용하여 깔끔한 해결책을 발견하도록 하라.
📌 생각을 코드로 만들기
✅ 논리를 명확하게 설명하기
더 나은 코드 작성을 위해 코드의 논리를 쉬운 말로 묘사한 뒤 코드를 재작성하는 것을 말한다.
이건 나도 자주 사용하는 방법인데 복잡하거나 놓치면 안되는 부분이 있을 때 순서를 미리 글로 써두면 확실히 더 정확하고 빠르며,더 나은 코드를 작성할 수 있다.
function calculateDiscount($userType, $totalAmount) {
if ($userType == 'premium') {
if ($totalAmount > 100) {
return $totalAmount * 0.8; // 20% 할인
} else {
return $totalAmount * 0.9; // 10% 할인
}
} else {
if ($totalAmount > 100) {
return $totalAmount * 0.95; // 5% 할인
} else {
return $totalAmount; // 할인 없음
}
}
}
유저 타입에 따라 할인을 다르게 적용하는 함수인데, 여기에 논리를 미리 명확하게 해두고 코드를 작성하면 좀 더 매끄러운 코드를 작성할 수 있다.
// ㅁ조건
// 유저 타입이 "premium"인 경우:
// 주문 금액이 100을 초과하면 20% 할인.
// 그렇지 않으면 10% 할인.
// 유저 타입이 "premium"이 아닌 경우:
// 주문 금액이 100을 초과하면 5% 할인.
// 그렇지 않으면 할인 없음.
function calculateDiscount($userType, $totalAmount) {
// Premium 회원인 경우
if ($userType == 'premium') {
return $totalAmount > 100 ? $totalAmount * 0.8 : $totalAmount * 0.9;
}
// 일반 회원인 경우
return $totalAmount > 100 ? $totalAmount * 0.95 : $totalAmount;
}
✅ 정리
'무언가를 쉬운 말로 설명하기'라는 방법은 코드를 작성하는 이상의 적용범위를 갖는다. 예를 들어 어떤 대학의 컴퓨터 연구실은 프로그램을 디버깅할 때 누군가에게 도움을 요청하기에 앞서 그 문제를 방 한 켠에 놓아둔 곰 인형에게 말로 설명하라는 정책을 가지고 있다. 놀랍게도 이렇게 문제를 큰 소리로 말하는 행위가 학생 스스로 해결책을 찾게 도움을 주는 것으로 드러났다.
이렇게 볼 수도 있다. 자신의 문제를 쉬운 말로 설명할 수 없으면, 해당 문제는 무언가 빠져 있거나 아니면 제대로 정의되지 않은 것이다.
어떤 프로그램을 혹은 어떤 생각이라도 말로 설명하는 행위는 문제의 틀을 제대로 잡는 데 도움을 준다.
📌 코드 분량 줄이기
✅ 코드베이스를 작게 유지하기
프로젝트가 커지면, 디렉터리는 더 많은 소스파일로 가득 찬다. 얼마 지나지 않아서 소스파일을 정리하는 데 더 많은 디렉터리가 필요하다. 어느 함수가 어느 함수를 호출하는지 기억하기 더 어려워지고, 버그를 잡는 데도 더 많은 노력이 필요하게 된다.
✅ 코드베이스를 작고 가볍게 유지하기 위해 해야 할 일
- 일반적인 '유틸리티'를 많이 생성하여 중복된 코드를 제거하라.
- 사용하지 않는 코드 혹은 필요 없는 기능을 제거하라.
- 프로젝트가 서로 분절된 하위프로젝트로 구성되게 하라.
- 코드베이스의 '무게'를 항상 의식하여 가볍고 날렵하게 유지시켜라.
정원사는 식물의 곁가지를 잘라주어 식물이 생기를 띠고 잘 자라게 한다. 이와 같이 거추장스럽기만 하고 실제로 사용하지 않는 코드를 제거하는 일은 바람직하다.
코드가 일단 작성되면, 프로그래머는 종종 이를 없애기 꺼려한다. 현실적으로 코드양이 업무량을 대변하기 때문이다. 많은 코드를 제거하는 것은 그 코드를 작성하는 데 투자한 시간이 사실은 쓸모 없는 시간이었음을 인정하는 셈이라고 여기는 것이다. 이런 생각에서 벗어나야 한다!
프로그래밍은 창의력을 요구하는 분야다. 사진사, 작가, 영화감독 같은 사람은 모든 작업 결과를 보존하지 않는다.
그동안 시간에 쫓겨 기능을 구현하는 데에만 집중했었는데, 이번에 이 책을 읽으며 스스로 내 코드를 리뷰해보는 시간을 가질 수 있었다.
이 책을 읽었다고 해서 당장 읽기 쉬운 코드를 뚝딱 작성할 수 있는 것은 아니지만,,,그렇게 되기 위해 노력하는 것이 개발자의 숙명이겠지요..