1. 클린 코드란 무엇인가
1.1 정의
클린 코드는 단순히 잘 동작하는 코드가 아니라, 가독성, 유지보수성, 확장성이 뛰어난 코드를 의미합니다.
이는 협업과 장기적인 코드 품질을 유지하기 위한 필수적인 개발 철학 입니다.
1.2 클린 코드를 왜 해야 할까?
- "이걸 내가 짰다고?"
- 클린 코드는 미래의 나 혹은 동료에게 친절한 코드
- 클린 코드는 미래의 나 혹은 동료에게 친절한 코드
- Dirty Code는 폭탄
- A를 수정을 하니 B에서 터지네?
- 협업의 필수 조건
- 의도가 명확한 코드는 팀워크를 원활하게 만듭니다.
- 기술 부채는 무섭다
- Dirty Code가 쌓이면 나중에 리팩토링이 아니라 재개발이 더 좋을수도….
- Dirty Code가 쌓이면 나중에 리팩토링이 아니라 재개발이 더 좋을수도….
1.3 리팩터링은 언제 해야 할까?
- "이 코드, 이해가 안 돼!"
- 코드가 읽기 어려운 순간
- "여기 고치면 저기 터지네?"
- 수정할 때마다 오류가 발생한다면
- "이거 너무 반복되는 것 같은데?"
- 같은 코드가 여기저기 복붙되어 있다면?
- "새로운 기능 추가가 너무 힘들어!"
- 확장하려는데 코드 구조가 방해된다면?
- "테스트가 너무 어려워!"
- 단위 테스트를 작성하기 힘들다면?
- 단위 테스트를 작성하기 힘들다면?
2. 클린 코드의 기본 원칙
2.1 의미 있는 이름 짓기
// 나쁜 예
public void processData(List<String> data) {
for (String item : data) {
if (item.length() > 5) {
System.out.println(item);
}
}
}
// 좋은 예
public void printLongUserNames(List<String> userNames) {
final int MIN_NAME_LENGTH = 5;
for (String userName : userNames) {
if (userName.length() > MIN_NAME_LENGTH) {
System.out.println(userName);
}
}
}
- 정리
- 구체적이고 의도를 담은 이름을 사용
- 매직 넘버를 피하라
- 데이터의 의미를 이름에 반영
2.2 함수 분리 하기
// 나쁜 예
public void sendEmail(String recipient, String subject, String body) {
if (recipient == null || recipient.isEmpty()) {
throw new IllegalArgumentException("Recipient cannot be null or empty");
}
System.out.println("Connecting to SMTP server...");
System.out.println("Authenticating...");
System.out.println("Sending email to: " + recipient);
System.out.println("Subject: " + subject);
System.out.println("Body: " + body);
System.out.println("Email sent successfully.");
}
// 좋은 예
public void sendEmail(String recipient, String subject, String body) {
validateRecipient(recipient);
connectToSmtpServer();
authenticate();
deliverEmail(recipient, subject, body);
}
private void validateRecipient(String recipient) {
if (recipient == null || recipient.isEmpty()) {
throw new IllegalArgumentException("Recipient cannot be null or empty");
}
}
private void connectToSmtpServer() {
System.out.println("Connecting to SMTP server...");
}
private void authenticate() {
System.out.println("Authenticating...");
}
private void deliverEmail(String recipient, String subject, String body) {
System.out.println("Sending email to: " + recipient);
System.out.println("Subject: " + subject);
System.out.println("Body: " + body);
System.out.println("Email sent successfully.");
}
- 정리
- 하나의 함수는 하나의 역할만 수행 (
sendEmail
은 이메일 발송의 흐름만 관리) - 복잡한 작업은 작은 함수로 분리
- 함수 이름은 동작과 목적을 명확히 표현
- 하나의 함수는 하나의 역할만 수행 (
2.3 불필요한 주석 제거
// 나쁜 예
public void processTransaction(Account fromAccount, Account toAccount, double amount) {
// 송금 금액이 0보다 커야 합니다.
if (amount <= 0) {
throw new IllegalArgumentException("송금 금액은 0보다 커야 합니다.");
}
// 잔액 확인
if (fromAccount.getBalance() < amount) {
throw new IllegalStateException("계좌 잔액이 부족합니다.");
}
// 같은 계좌인지 확인
if (fromAccount.equals(toAccount)) {
throw new IllegalArgumentException("같은 계좌로 송금할 수 없습니다.");
}
// 송금 실행
fromAccount.withdraw(amount); // 돈을 출금합니다.
toAccount.deposit(amount); // 돈을 입금합니다.
// 송금 로그
System.out.println("송금 성공: " + amount + "원 전송됨.");
}
// 좋은 예
public void processTransaction(Account fromAccount, Account toAccount, double amount) {
// 비즈니스 규칙: 송금 금액은 0보다 커야 함
if (amount <= 0) {
throw new IllegalArgumentException("송금 금액은 0보다 커야 합니다.");
}
// 비즈니스 규칙: 송금 계좌 잔액이 부족하면 송금 불가
if (fromAccount.getBalance() < amount) {
throw new IllegalStateException("계좌 잔액이 부족합니다.");
}
// 비즈니스 규칙: 동일 계좌 간 송금 금지 (실수 방지 목적)
if (fromAccount.equals(toAccount)) {
throw new IllegalArgumentException("같은 계좌로 송금할 수 없습니다.");
}
// 송금 실행
fromAccount.withdraw(amount);
toAccount.deposit(amount);
// 로그 기록: 성공적인 송금을 기록 (보안 및 추적 목적)
System.out.println("송금 성공: " + amount + "원 전송됨.");
}
- 정리
- 주석은 코드가 아닌 의도를 설명
- “어떻게”가 아닌 “왜”를 설명
- 주석 대신 명확한 변수와 함수 이름으로 의도를 드러냄
- 불필요한 주석은 제거하고, 코드는 가능한 자체적으로 읽히게 작성
- 주석은 코드가 아닌 의도를 설명
2.4 코드 중복 제거
// 나쁜 예
public void printUserName(String name) {
System.out.println("User: " + name);
}
public void printAdminName(String name) {
System.out.println("Admin: " + name);
}
// 좋은 예
public void printName(String role, String name) {
System.out.println(role + ": " + name);
}
- 정리
- DRY (Don’t Repeat Yourself) 원칙을 준수
2.5 복잡한 코드를 단순화하기
// 나쁜 예
if (user != null && user.getAge() > 18 && user.isActive()) {
// do something
}
// 좋은 예
if (isActiveAdultUser(user)) {
// do something
}
private boolean isActiveAdultUser(User user) {
return user != null && user.getAge() > 18 && user.isActive();
}
- 정리
- 조건문이 복잡하거나 여러 논리를 포함한다면 메서드로 분리
2.6 부정 표현을 긍정 표현으로 바꾸기
// 나쁜 예
if (!user.isInActive()) {
return "Inactive User";
}
return "Active User";
// 좋은 예
if (user.isActive()) {
return "Active User";
}
return "Inactive User";
- 정리
- 긍정적 변수명 사용
- 긍정적 조건문 작성
- 이중 부정 지양
2.7 else 문 사용 지양하기
// 나쁜 예
public void login(User user) {
if (user != null) {
if (user.isActive()) {
if (user.isVerified()) {
System.out.println("Login successful");
} else {
System.out.println("User is not verified");
}
} else {
System.out.println("User is inactive");
}
} else {
System.out.println("Invalid user");
}
}
// 좋은 예
public void login(User user) {
if (user == null) {
System.out.println("Invalid user");
return;
}
if (!user.isActive()) {
System.out.println("User is inactive");
return;
}
if (!user.isVerified()) {
System.out.println("User is not verified");
return;
}
// 모든 조건을 통과한 경우
System.out.println("Login successful");
}
// 더 좋은 예
public void login(User user) {
String validationResult = validateUser(user);
if (!validationResult.equals("Valid")) {
System.out.println(validationResult);
return;
}
// 모든 조건을 통과한 경우
System.out.println("Login successful");
}
private String validateUser(User user) {
if (user == null) {
return "Invalid user";
}
if (!user.isActive()) {
return "User is inactive";
}
if (!user.isVerified()) {
return "User is not verified";
}
return "Valid";
}
- 정리
- else 문을 피하고 기본 동작을 명시
- 전처리와 핵심 로직을 분리
- 각 조건은 독립적으로 처리
참고
- Chat GPT
- https://velog.io/@teo/cleancode
'Study > etc...' 카테고리의 다른 글
소프트웨어 아키텍처 (0) | 2025.01.03 |
---|---|
TDD(Test-Driven Development) (1) | 2024.12.18 |