밤빵's 개발일지
[TIL]20240923 클린 코드(Clean Code)작성 원칙 본문
클린 코드(Clean Code)는 개발자들이 읽기 쉽고, 유지보수 가능한 코드를 작성하는 데 도움을 주는 일련의 원칙과 기법을 의미한다. 이를 통해 코드를 처음 접하는 다른 개발자도 쉽게 이해하고 확장할 수 있고, 코드 수정 시 오류 발생 가능성을 최소화할 수 있다. 클린 코드는 읽기 쉽고, 이해하기 쉬운 코드를 의미하며, 나 자신뿐만 아니라 다른 개발자들도 쉽게 이해하고 유지보수할 수 있는 코드를 작성하는 것을 목표로 한다.
▶ 클린 코드란?
클린 코드(Clean Code)란, 이해하기 쉽고, 명확하며, 중복을 최소화한 코드를 말한다. 클린 코드는 단순히 컴파일러가 에러 없이 실행할 수 있는 코드가 아닌, 사람이 읽기 쉬운 코드를 지향한다. 코드의 가독성을 높이고 유지보수를 쉽게 하며, 더 적은 버그를 발생시키는 것이 클린 코드 작성의 목표이다. 이러한 클린 코드를 작성하기 위해서는 몇 가지 원칙을 따라야 한다.
▶ 클린 코드 작성 원칙
▷ 의미 있는 이름을 사용한다.
클린 코드 작성에서 가장 기본적인 원칙은 의미 있는 이름을 사용하는 것이다. 변수, 함수, 클래스의 이름이 그 목적과 역할을 명확히 나타내도록 한다. 이름만 보고도 그 기능을 추측할 수 있게끔 작성한다.
// 나쁜 예시
int p; // "p"는 무엇을 의미하는지 알기 어렵다
// 좋은 예시
int paymentAmount; // 결제 금액임을 명확히 나타냄
→ 의미 있는 이름을 사용하는 것이 클린 코드의 첫 걸음이고, 코드의 가독성을 크게 향상시킨다.
▷ 함수는 한 가지 작업만 하도록 작성한다.
함수는 한 가지 작업만 수행하도록 작성해야 한다. 여러 가지 기능을 하나의 함수에 몰아넣으면 코드를 이해하기 어렵고, 유지보수가 어려워진다. 함수의 길이가 길어지면 그만큼 많은 일을 하고 있다는 신호일 수 있다. 각 함수는 단일 책임만 가지도록 명확하게 나눈다.
// 나쁜 예시: 결제 처리와 유효성 검사, 이메일 발송 모두 하나의 함수에 포함됨
public void processPayment(PaymentRequestDto request) {
validatePayment(request);
processIamportPayment(request);
sendConfirmationEmail(request);
}
// 좋은 예시: 각 기능을 분리하여 단일 책임 원칙을 준수함
public void processPayment(PaymentRequestDto request) {
validatePayment(request);
executeIamportPayment(request);
sendConfirmationEmail(request);
}
private void validatePayment(PaymentRequestDto request) {
// 결제 요청 유효성 검사 로직
}
private void executeIamportPayment(PaymentRequestDto request) {
// 결제 처리 로직
}
private void sendConfirmationEmail(PaymentRequestDto request) {
// 결제 완료 이메일 발송 로직
}
→함수를 작고 명확하게 나누면, 각각의 함수가 단일한 책임을 가지고 동작하게 되어 코드의 가독성과 유지보수성이 좋아진다.
▷주석을 남발하지 않는다.
주석은 코드를 설명하기 위해 사용되지만, 과도한 주석은 오히려 가독성을 떨어뜨릴 수 있다. 클린 코드에서는 주석보다 명확한 코드를 작성하는 것이 중요하다. 코드 자체가 충분히 설명이 가능해야 하며, 주석이 필요 없는 코드가 좋은 코드이다. 주석은 필수적인 경우에만 간결하게 사용한다.
// 나쁜 예시: 불필요한 주석
int amount = 10000; // 결제 금액을 10000으로 설정
// 좋은 예시: 코드 자체가 명확
int paymentAmount = 10000;
→ 코드가 명확하다면 주석은 최소화하는 것이 좋다는 점을 알수있다.
▷ 중복을 피한다
중복된 코드는 유지보수를 어렵게 하고, 버그 발생 확률을 높인다. DRY 원칙(Don't Repeat Yourself)에 따라, 동일한 기능을 반복하지 않고, 재사용 가능한 함수나 클래스로 추출한다.
// 나쁜 예시: 중복된 코드
public void prepareAndProcessPayment(PaymentRequestDto request) {
// Iamport Prepare 호출
IamportResponse<Prepare> response = iamportClient.preparePayment(request);
if (response == null || response.getResponse() == null) {
throw new RuntimeException("결제 준비 오류");
}
// 결제 처리
processPayment(response);
}
// 좋은 예시: 공통 메서드로 추출
public void prepareAndProcessPayment(PaymentRequestDto request) {
IamportResponse<Prepare> response = prepareIamportPayment(request);
processPayment(response);
}
private IamportResponse<Prepare> prepareIamportPayment(PaymentRequestDto request) {
IamportResponse<Prepare> response = iamportClient.preparePayment(request);
if (response == null || response.getResponse() == null) {
throw new RuntimeException("결제 준비 오류");
}
return response;
}
→ 중복된 코드를 상수나 공통 메서드로 추출하니, 코드의 유지보수가 쉬워지고, 오류 발생 가능성도 줄어들었다.
▷ DRY 원칙(Don't Repeat Yourself)?
→ 소프트웨어 개발에서 매우 중요한 원칙 중 하나로, 코드의 중복을 최소화하라는 의미를 담고 있다. 동일한 로직이나 데이터를 여러 곳에서 반복하여 사용하지 말고, 한 번만 작성하고 이를 재사용하라는 개념이다. DRY 원칙을 적용하면, 코드의 유지보수성이 크게 향상된다. 중복된 코드가 여러 곳에 존재하면, 수정이 필요할 때 모든 곳을 일일이 수정해야 하므로 버그 발생 가능성이 커지고, 코드의 일관성을 유지하기 어렵다. 하지만 동일한 로직을 한 곳에 모아두고 필요할 때마다 호출하면, 수정이 필요할 때도 한 곳만 수정하면 되므로, 코드의 일관성과 유지보수성이 크게 향상된다. 예를 들어, 결제 로직에서 중복된 검증 코드가 여러 곳에 존재할 경우, DRY 원칙을 적용해 검증을 하나의 함수로 분리해두고 필요할 때마다 호출하는 방식으로 중복을 제거할 수 있다.
▷ 조건문을 단순하게 작성했다
복잡한 조건문은 코드를 읽기 어렵게 만든다. 가능한 한 조건문을 단순화하고, 복잡한 조건이 필요한 경우 이를 함수로 분리해 코드의 명확성을 높인다.
// 나쁜 예시: 복잡한 조건문
if (user.isActive() && !user.isSuspended() && user.hasValidSubscription()) {
// 결제 처리 로직
}
// 좋은 예시: 조건을 함수로 분리
if (canProcessPayment(user)) {
// 결제 처리 로직
}
private boolean canProcessPayment(User user) {
return user.isActive() && !user.isSuspended() && user.hasValidSubscription();
}
→ 조건문을 함수로 분리하니 코드의 의미가 명확해지고, 재사용성도 높아졌다.
▶테스트 가능한 코드 작성
클린 코드를 작성하는 또 하나의 중요한 원칙은 테스트 가능한 코드를 작성하는 것이다. 테스트 가능한 코드는 버그를 미리 방지할 수 있고, 코드 수정 후에도 기능이 정상적으로 동작하는지 확인할 수 있어야 한다. 이를 위해 함수는 가능한 한 독립적으로 동작하고, 외부 의존성이 낮아야 한다.
// 나쁜 예시: 외부 시스템에 의존적
public void sendEmail(String message) {
EmailService emailService = new EmailService();
emailService.send(message);
}
// 좋은 예시: 의존성을 주입하여 테스트 가능
public class EmailSender {
private final EmailService emailService;
public EmailSender(EmailService emailService) {
this.emailService = emailService;
}
public void sendEmail(String message) {
emailService.send(message);
}
}
→ 의존성을 주입받는 방식으로 코드를 작성하면, 테스트 시에 가짜 객체(Mock Object)를 사용해 테스트할 수 있어 테스트의 유연성이 높아진다.
▶ 클린 코드를 적용한 결제 처리 예시
클린 코드 원칙을 적용해 포트원 결제 처리 코드를 리팩토링해 보았다.
public class PaymentService {
private final DiscountCalculator discountCalculator;
private final PaymentRepository paymentRepository;
private final EmailService emailService;
public PaymentService(DiscountCalculator discountCalculator, PaymentRepository paymentRepository, EmailService emailService) {
this.discountCalculator = discountCalculator;
this.paymentRepository = paymentRepository;
this.emailService = emailService;
}
public void processPayment(PaymentRequestDto request) {
if (isValidRequest(request)) {
applyDiscount(request);
savePayment(request);
sendConfirmationEmail(request);
}
}
private boolean isValidRequest(PaymentRequestDto request) {
return request != null && request.getAmount() > 0;
}
private void applyDiscount(PaymentRequestDto request) {
discountCalculator.calculate(request);
}
private void savePayment(PaymentRequestDto request) {
paymentRepository.save(request);
}
private void sendConfirmationEmail(PaymentRequestDto request) {
emailService.send(request.getEmail(), "결제 확인!");
}
}
→ 이 코드는 클린 코드 원칙을 따른 결제 처리 클래스이다. 각 함수가 단일 책임을 가지고, 함수명만으로도 명확한 기능을 알 수 있다. 의존성을 주입받아 테스트 가능성을 높이고, 주석 없이도 이해할 수 있는 코드가 되었다.
▶결론
클린 코드는 단순히 컴파일러가 이해하는 코드가 아닌, 사람이 이해하기 쉬운 코드를 지향한다. 이를 위해 의미 있는 이름을 사용하고, 함수는 한 가지 역할만 하도록 분리하며, 중복을 피하고, 조건문은 단순화해야 한다. 또한, 테스트 가능한 코드를 작성하는 것도 매우 중요하다. 클린 코드는 단순히 현재의 프로젝트뿐만 아니라, 미래의 코드 유지보수와 확장성에도 큰 도움을 준다. 지금 구현했던 결제 기능과 관련 된 코드를 리팩토링 해보면서 클린 코드 원칙을 적용해봤는데, 아직은 많이 어색한 느낌이라, 가독성이 좋아진 것 같으면서도 평소 작성해왔던 코드 느낌과 달라서 익숙해지려면 꽤 많은 시간이 걸릴 것 같다😢
'개발Article' 카테고리의 다른 글
[TIL]20240925 서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR) 비교 (0) | 2024.09.25 |
---|---|
[TIL]20240924 H2 데이터베이스 사용법 (2) | 2024.09.24 |
[WIL]20240922 API Rate Limiting (3) | 2024.09.23 |
[TIL]20240921 SQL&NoSQL 데이터베이스 (0) | 2024.09.21 |
[TIL]20240920 리플렉션(Reflection)의 활용 (2) | 2024.09.20 |