Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
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
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20240923 클린 코드(Clean Code)작성 원칙 본문

개발Article

[TIL]20240923 클린 코드(Clean Code)작성 원칙

최밤빵 2024. 9. 23. 22:51

클린 코드(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(), "결제 확인!");
    }
}

→ 이 코드는 클린 코드 원칙을 따른 결제 처리 클래스이다. 각 함수가 단일 책임을 가지고, 함수명만으로도 명확한 기능을 알 수 있다. 의존성을 주입받아 테스트 가능성을 높이고, 주석 없이도 이해할 수 있는 코드가 되었다.

 

▶결론

클린 코드는 단순히 컴파일러가 이해하는 코드가 아닌, 사람이 이해하기 쉬운 코드를 지향한다. 이를 위해 의미 있는 이름을 사용하고, 함수는 한 가지 역할만 하도록 분리하며, 중복을 피하고, 조건문은 단순화해야 한다. 또한, 테스트 가능한 코드를 작성하는 것도 매우 중요하다. 클린 코드는 단순히 현재의 프로젝트뿐만 아니라, 미래의 코드 유지보수와 확장성에도 큰 도움을 준다.  지금 구현했던 결제 기능과 관련 된 코드를 리팩토링 해보면서 클린 코드 원칙을 적용해봤는데, 아직은 많이 어색한 느낌이라, 가독성이 좋아진 것 같으면서도 평소 작성해왔던 코드 느낌과 달라서 익숙해지려면 꽤 많은 시간이 걸릴 것 같다😢