| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- 디자인강의
- 국비지원교육
- API
- 환급챌린지
- mysql
- UXUIPrimary
- Java
- 디자인교육
- Be
- UXUI챌린지
- 패스트캠퍼스
- KDT
- 디자인챌린지
- 시스템설계
- 국비지원
- 백엔드개발자
- 국비지원취업
- 오픈패스
- 백엔드 부트캠프
- Spring
- UXUI기초정복
- OPENPATH
- 부트캠프
- 오블완
- baekjoon
- 백준
- 티스토리챌린지
- 내일배움카드
- JPA
- 오픈챌린지
- Today
- Total
군만두의 IT 개발 일지
[스터디14] 01. 클린 코드의 원칙 본문
목차
1장. 클린 코드의 원칙
클린 코드란 동료들이 쉽게 이해하고, 수정과 확장이 용이한 '좋은' 코드를 의미한다.
1-1 클린 코드를 지켜야 하는 이유
개발자는 일정 압박, 잦은 변경 요청, 개발자 간의 다양한 코딩 스타일 때문에 일관된 코드 스타일을 유지하기 어렵다. 그럼에도 클린 코드를 지키는 이유는 다음과 같다.
- 다른 개발자나 미래의 자신이 코드를 이해하고 수정하는 데 드는 시간과 비용을 크게 줄인다.
- 유지 보수 비용 절감 및 개발 생산성 향상에 직접적으로 기여한다.
- 장기적으로 프로젝트의 성공을 보장하는 필수 요소이다.
1-2 의미 있는 이름 짓기
네이밍 규칙 4가지
표기 방식의 일관성을 유지하기 위해 미리 정한 네이밍 규칙이 있다.
| 규칙 | 방식 | 예시 (user name) |
| 카멜 케이스 | 뒤에 오는 단어의 첫 글자를 대문자로 표기 | userName |
| 파스칼 케이스 | 두 단어의 첫 글자를 모두 대문자로 표기 | UserName |
| 케밥 케이스 | 단어마다 하이픈(-)으로 연결 | user-name |
| 스네이크 케이스 | 단어마다 언더바(_)로 연결 | user_name |
자바에서의 네이밍 규칙
- 변수: 소문자로 구성하거나 카멜 케이스 적용
- 함수: 카멜 케이스 적용
- 클래스: 파스칼 케이스 적용
- 상수: 대문자로 구성
케밥 케이스는 yml 형태의 파일명에, 스네이크 케이스는 데이터베이스의 컬럼명(열 이름)에 주로 사용된다.
가독성 고려하기
가독성 좋은 코드란 한마디로 잘 읽히고 작성한 의도를 예측할 수 있는 코드다. 변수, 함수, 클래스 이름은 작성한 코드의 기능과 의도가 잘 드러나도록 지어야 한다.
기능에 맞는 이름 짓기
- 클래스: 객체로 사용하므로, 명사 또는 명사구로 이름 짓기
- 함수: 동작을 수행하므로, 동사 또는 동사구로 이름 짓기
- 구성 요소의 위치와 맥락에 맞게 이름을 지어야 한다. (예: User 클래스 내부 멤버 변수에 user를 붙이는 것은 부적절하다)
- 매직 넘버는 의미를 알 수 있도록 상수로 선언하면 가독성과 유지 보수성이 모두 향상된다.
1-3 주석 제대로 사용하기
가독성이 뛰어나고 작성 의도가 분명하게 파악되는 코드보다 더 좋은 주석은 없다. 주석은 오직 보조 수단이며, 제대로 사용할 경우에만 유효하다.
좋은 주석 vs 나쁜 주석
| 좋은 주석 | 나쁜 주석 |
| 작성 의도를 설명하는 주석 | 데드 코드(미사용 코드) |
| 정보나 의미를 알려주는 주석 | 이력 관리용 주석 (Git이 대체함) |
| 강조나 경고 주석 | 코드 작성 시점을 기념하는 주석 |
| TODO 주석 | 거짓 정보가 담긴 주석 |
| 소유권, 저작권을 나타내는 주석 |
데드 코드와 이력 관리용 주석
'언젠가는 쓰지 않을까?' 하는 마음에 코드를 주석 처리해 남겨두는 경우가 많지만, 데드 코드는 코드 복잡도를 높이기만 할 뿐이다. 깃허브 같은 형상 관리 도구로 언제든 이전 버전을 복원할 수 있으므로 과감히 삭제하는 편이 좋다.
거짓 정보가 담긴 주석
반드시 제거해야 하는 주석은 잘못된 정보가 담긴 주석이다. 코드가 업데이트되는 것처럼 주석도 꾸준히 갱신해야 한다.
1-4 복잡한 조건식은 함수로 변경하기
복잡한 조건식을 그대로 두면 가독성이 떨어지고 의도를 파악하기 어렵다. 조건식을 별도 함수로 추출하면 함수 이름만으로 조건의 의미를 직관적으로 이해할 수 있다. 매직 넘버나 의미가 모호한 값은 상수로 선언하여 활용하면 다음과 같은 이점이 있다.
- 매개변수의 의미를 파악하기 쉽다.
- 조건문의 의도를 직관적으로 파악할 수 있다.
- 변수명을 통해 값의 의미를 유추할 수 있다.
// 상수 선언으로 의도를 명확하게 표현
static final int MOVE_DOWN = 1;
static final int STONE_EXIST = 1;
static final int MOVE_RIGHT = 0;
// 함수명, 매개변수, 조건식이 모두 명확해짐
static void moveUser(User user, int direction) {
if (direction == MOVE_RIGHT) {
// ...
}
if (map[next_y][next_x] == STONE_EXIST) {
// ...
}
}
1-5 함수는 하나의 기능만 수행하기
원칙 1: 간결하고 명확한 함수 설계하기
- 작은 함수 만들기: 의도적으로 여러 기능을 한 함수에 담지 않고, 중첩된 조건문이나 반복문은 가능한 한 사용하지 않는다. 로직 대신 함수를 호출해 처리하는 방식이 가독성 면에서 바람직하다.
- 기능이 드러나는 이름 짓기: 다른 개발자가 함수 이름을 보고 어떤 기능을 수행하는지 쉽게 짐작할 수 있어야 한다. 다소 길더라도 명확하다면 좋은 이름이다.
- 매개변수 최소화하기: 매개변수가 많아질수록 함수를 이해하기 어려워진다. 일반적으로 3개 이상 늘어나지 않도록 주의하고, 클래스의 멤버 변수로 대체할 수 있는지 고민해 본다.
원칙 2: 추상화 레벨을 고려한 내려 읽기 형태 만들기
추상화 레벨이란 함수가 수행하는 기능이 얼마나 구체적인지 혹은 추상적인지를 가리키는 개념이다. 함수 안에서 호출되는 함수들이 동일한 추상화 수준을 유지하도록 설계하면, 위에서 아래로 책을 읽듯 이해하기 쉬운 형태가 된다.
// 같은 추상화 레벨의 함수들을 호출
public static void createShoppingMall() {
createUserMgmtModule(); // 고객 관리
createProductMgmtModule(); // 상품 관리
createDeliveryMgmtModule(); // 배송 관리
}
private static void createUserMgmtModule() {
CreateRegisterUserFunction(); // 회원 가입
CreateModifyUserFunction(); // 정보 수정
CreateDeleteUserFunction(); // 탈퇴
}
원칙 3: 함수 이름과 다른 기능 수행하지 않기
함수명을 짓고 나면, 그 이름에서 예측되는 기능을 정확히 수행하고 있는지 반드시 확인해야 한다. 예를 들어 isAnswerCorrect 함수가 정답 여부 확인 외에 점수 증가 로직까지 포함하면, 함수명만으로는 추가 동작을 예측할 수 없어 협업 시 혼란을 준다.
원칙 4: 조회와 명령 분리하기
변숫값을 업데이트하는 명령 함수 내부에 조회 로직을 함께 구현하면 함수의 기능이 모호해진다. 조회와 명령을 각각 다른 함수로 분리해 역할을 명확히 구분하는 것이 좋다.
// 조회 함수와 명령 함수 분리
if (studentNameExists(students, updateStudent.getName())) {
updateScore(students, updateStudent);
}
// 조회: 학생 존재 여부 확인
private static boolean studentNameExists(ArrayList<Student> students, String name) {
for (int i = 0; i < students.size(); i++) {
if (students.get(i).getName().equals(name))
return true;
}
return false;
}
// 명령: 점수 업데이트
private static void updateScore(ArrayList<Student> students, Student updateStudent) {
// 업데이트 로직
}
1-6 생성자 가독성 높이기
생성자에 매개변수가 많을수록 클라이언트 코드에서 각 매개변수가 무엇을 의미하는지 파악하기 어렵다. 이때 빌더 패턴(Builder Pattern)을 적용하면 가독성을 크게 높일 수 있다.
빌더 패턴 적용
- 원본 클래스 내부에 정적 클래스인 Builder를 선언한다.
- Builder 클래스는 원본 클래스의 멤버 변수를 모두 가지고 있다.
- 각 변숫값을 저장하는 함수를 체이닝 가능하도록 구현한다. (return this)
- build() 함수에서 현재 변숫값을 이용해 원본 객체를 생성하고 반환한다.
// 빌더 패턴을 사용해 원하는 속성만 자유롭게 선택해 객체 생성
Person builderPerson = new Person.Builder("쫑이")
.age(20)
.gender("M")
.height(180)
.build();
Person builderPerson2 = new Person.Builder("하루")
.age(5)
.build();
단, 빌더 패턴이 항상 정답은 아니다. 클래스가 단순하거나 생성자를 자주 사용하지 않는 경우, 빌더 패턴을 적용하면 오히려 코드가 더 복잡해질 수 있다.
1-7 오류 코드보다 예외 사용하기
함수의 실패 케이스를 처리할 때 오류 코드로 처리하는 방식보다 예외(Exception)를 활용하는 방식이 더 클린 코드에 가깝다.
오류 코드 vs 예외 처리
| 오류 코드 방식 | 예외 처리 방식 |
| 함수 내부 분기문으로 오류 처리 | try~catch 문으로 예외 처리 |
| '함수는 하나의 기능만 수행하자' 원칙 위반 | 오류 처리와 핵심 로직이 분리됨 |
| 추상화 레벨이 어긋남 | 추상화 레벨이 대등함 |
| 조건문 분기가 많아져 흐름 파악이 어려움 | 흐름이 명확해 이해하기 쉬움 |
예외 처리의 장점
- 실패 케이스 발생 시 예외를 생성해 상위 함수로 전달하거나, try~catch 문으로 처리하는 방식이 깔끔하다.
throws Exception을 사용하면 호출한 함수에서 발생한 예외를 그대로 자신을 호출한 함수로 넘겨줄 수 있다.- 오류 처리 역시 하나의 기능이므로, 핵심 로직과 분리해야 함수의 단일 책임 원칙이 지켜진다.
// try~catch 문으로 예외 처리
public class Main {
private static final Logger Logger =
java.util.logging.Logger.getLogger(Main.class.getName());
public static void main(String[] args) {
VPC vpc = new VPC();
// ...
try {
vpc.deleteVPCResourceUsingException();
vpc = null;
} catch (Exception e) {
Logger.log(Level.WARNING, e.getMessage());
}
}
}
이 글은 『Do it! 클린 프로그래밍』 책의 내용을 바탕으로 작성되었습니다.
'학습일지 > Java' 카테고리의 다른 글
| [스터디14] 03. 클린 코드 관점의 테스트 코드 (0) | 2026.05.23 |
|---|---|
| [스터디14] 02. 코드 스멜과 리팩터링 (0) | 2026.05.13 |
| [스터디10] 15. 트랜잭션과 락, 2차 캐시 (0) | 2025.12.08 |
| [스터디10] 14. 고급 주제와 성능 최적화 (0) | 2025.11.28 |
| [스터디10] 13. 컬렉션과 부가 기능 (0) | 2025.11.01 |

