코드 작성 가이드 리뷰: 실무에 바로 적용 가능한 개발자의 필독서

2024. 12. 22. 15:25책 리뷰

1. 책 소개

코드 작성 가이드는 이시가와 무네토시가 발표한 Code Readability 강연 내용을 기반으로 만들어진 책입니다.
사실 이 책을 읽기 전에 그의 강연 PDF를 훑어본 적이 있었는데, 대략 700+ 장의 pdf 라서 읽을 엄두를 못 냈습니다 ㅠ
하지만 저자는 서문에서 "이 책을 읽으면 강연을 듣지 않아도 된다"고 할 만큼 자신감을 드러낸만큼, 책을 읽는게 상당히 이득입니다!

책은 단순히 코드를 짜는 기술을 넘어서,
팀 전체의 생산성을 높이는 "가독성 좋은 코드"를 작성하는 법을 알려줍니다.

시중에 나온 다른 클린 코드에 대한 책들을 읽으면, 방법적인 부분에 있어서는 다들 훌륭하다고 생각합니다.
이 책은 마인드셋, 변수의 네이밍부터, 함수, 클래스 등등 모든 프로그래밍 전반에 있어서 작성 가이드를 제공해줍니다.
가이드와 함께 실무에서 실수를 쉽게 할만한 예시를 함께 제공해줘 실무에 도입을 하기 편했습니다.

그리고 저는 다음과 같은 원칙들을 다시 한번 배우면서 도입을 하게 되었습니다.

  • Boy Scout Rule: 코드를 수정할 때마다 조금씩 개선하라.
  • KISS 원칙: 코드는 간단하고 직관적이어야 한다.
  • 단일 책임 원칙: 하나의 함수, 하나의 책임!

2. 책에서 배운 점과 실무 활용 사례

2.1 Boy Scout Rule (보이스카우트 규칙)

책에서 배운 점
"건드린 코드는 들어왔을 때보다 나아져야 한다." 책에서는 이 원칙을 강조하며,
코드 수정 시 작은 변화라도 꾸준히 시도하면 코드 품질이 점진적으로 향상될 수 있다고 설명합니다.

기존의 코드를 그대로 두지 말고 반복적인 로직을 리팩터링하거나,
유지보수를 어렵게 만드는 구조를 개선하라는 메시지가 크게 와 닿았어요.

실무 적용 사례
저희 회사는 출장 관련 예약 플랫폼을 B2B 형태로 제공하고 있어요.
회사마다 알림톡을 보내는 방식이 달라서 이를 처리하기 위해 여러 회사별 분기가 필요했는데,
기존에는 if-else 문으로 처리하고 있었습니다.
새로운 고객사가 추가될 때마다 if 문을 추가해야 해서 코드가 점점 복잡해졌죠.

책을 읽고 나서 전략 패턴을 도입해 이런 문제를 개선했습니다.
(그냥 if 문 분기하고 도망가고 싶던 양심을 눌러앉고)

Before

public void sendNotification(String companyCode, NotificationData data) {
    if ("COMPANY_A".equals(companyCode)) {
        notificationServiceA.send(data);
    } else if ("COMPANY_B".equals(companyCode)) {
        notificationServiceB.send(data);
    } else if ("COMPANY_C".equals(companyCode)) {
        notificationServiceC.send(data);
    } else {
        throw new IllegalArgumentException("Unsupported company code");
    }
}

이 코드의 문제점은 새로운 회사가 추가될 때마다 기존 코드를 수정해야 하고,
분기 처리가 많아질수록 읽기 어려워진다는 미친 단점이 존재했습니다.

After

@Component
public class NotificationSender {
    private final Map<String, NotificationStrategy> strategyMap;

    public NotificationSender(List<NotificationStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(NotificationStrategy::getCompanyCode, Function.identity()));
    }

    public void sendNotification(String companyCode, NotificationData data) {
        NotificationStrategy strategy = strategyMap.get(companyCode);
        if (strategy == null) {
            throw new IllegalArgumentException("Unsupported company code");
        }
        strategy.send(data);
    }
}

public interface NotificationStrategy {
    String getCompanyCode();
    void send(NotificationData data);
}

@Component
public class CompanyANotificationStrategy implements NotificationStrategy {
    @Override
    public String getCompanyCode() {
        return "COMPANY_A";
    }

    @Override
    public void send(NotificationData data) {
        // Company A-specific notification logic
    }
}

@Component
public class CompanyBNotificationStrategy implements NotificationStrategy {
    @Override
    public String getCompanyCode() {
        return "COMPANY_B";
    }

    @Override
    public void send(NotificationData data) {
        // Company B-specific notification logic
    }
}

이렇게 전략 패턴을 도입한 후 새로운 회사가 추가되더라도 기존 코드를 수정하지 않고
새로운 NotificationStrategy 구현체를 추가하기만 하면 돼요.
수정에는 닫혀 있고, 확장에는 열려있는 SOLID 원칙중 OCP 를 잘 지켰다고도 볼 수 있겠네요 !

2.2 KISS (Keep It Simple, Stupid) 원칙

책에서 배운 점
KISS 원칙은 "코드는 단순하고 직관적으로 유지되어야 한다"는 점을 강조합니다.
책을 읽으면서 특히 인상 깊었던 부분은

복잡한 코드는 결국 가독성을 해친다

는 점이었어요.

단순히 코드를 짧게 만드는 게 아니라, 로직이 명확하고 흐름이 자연스러워야 유지보수가 쉬워진다는 걸 깨달았습니다.
(사실 혼자 리팩토링 하는데, 하면 할수록 꼬여가길래 이상하다고 스스로 느꼈습니다 ㅎㅎㅎ)

실무 적용 사례

알림톡을 전송하기 전에 다양한 조건을 검증해야 했습니다.
예를 들어, 사용자 ID와 예약 상태가 유효한지, 회사 코드가 올바른지,
특정 조건에서 데이터가 누락되지 않았는지를 확인했어요.

기존에는 Early Return 방식을 사용해 빠르게 함수 실행을 종료하려고 했는데, 검증 조건이 많아지면서 로직이 지나치게 복잡해졌습니다.

public void validateAndSendNotification(String companyCode, NotificationData data) {
    if (data == null) {
        throw new IllegalArgumentException("Data cannot be null");
    }

    if (data.getUserId() == null) {
        throw new IllegalArgumentException("User ID is missing");
    }

    if (!isValidReservation(data.getReservationId())) {
        throw new IllegalStateException("Invalid reservation");
    }

    if (!isSupportedCompany(companyCode)) {
        throw new IllegalArgumentException("Unsupported company code");
    }

    if (data.getPhoneNumber() == null || data.getPhoneNumber().isEmpty()) {
        throw new IllegalArgumentException("Phone number is required");
    }

    // 모든 검증이 끝난 후 알림톡 전송
    notificationService.send(data.getPhoneNumber(), buildMessage(companyCode, data));
}

이때 문제점은 다음과 같았습니다.

  • 가독성 저하: Early Return 조건이 너무 많아서 한눈에 로직을 이해하기 어렵습니다.
  • 유지보수 부담: 새로운 검증 조건이 추가되면 로직이 점점 더 복잡해집니다.

After: 개선 후 (KISS 원칙 적용)

1. 검증 로직 분리
검증 로직을 별도의 클래스로 분리해 단순화했습니다.

@Component
public class NotificationValidator {
    public void validate(String companyCode, NotificationData data) {
        if (data == null || data.getUserId() == null) {
            throw new IllegalArgumentException("Invalid notification data");
        }

        if (!isValidReservation(data.getReservationId())) {
            throw new IllegalStateException("Invalid reservation");
        }

        if (!isSupportedCompany(companyCode)) {
            throw new IllegalArgumentException("Unsupported company code");
        }

        if (data.getPhoneNumber() == null || data.getPhoneNumber().isEmpty()) {
            throw new IllegalArgumentException("Phone number is required");
        }
    }
}

2. 서비스 계층 간소화
검증 로직을 분리한 후 서비스 계층은 메시지 생성 및 전송에만 집중하도록 단순화했습니다.

public void validateAndSendNotification(String companyCode, NotificationData data) {
    validator.validate(companyCode, data);
    String message = buildMessage(companyCode, data);
    notificationService.send(data.getPhoneNumber(), message);
}

2.3 단일 책임 원칙

책에서 배운 점
단일 책임 원칙은 "책임을 변경하려는 이유로 정의하고, 어떤 클래스나 모듈은 변경하려는 단 하나 이유만을 가져야 한다 "는 기본 원칙이에요. 책을 읽으며 가장 와 닿았던 부분은 "책임이 많아지면 변경 사항이 다른 코드에 영향을 미칠 가능성이 커진다"는 내용이었어요. 이를 실무에 적용하면서 코드 변경 시 안정성을 확보할 수 있었습니다.

실무 적용 사례

배경


기존 알림톡 전송 서비스 계층에서는 다음과 같은 다양한 책임을 모두 한 클래스에서 처리하고 있었습니다:

  1. 데이터 조회 (예약 정보, 사용자 정보 등).
  2. 검증 (사용자 ID 유효성, 회사 코드 확인 등).
  3. 메시지 생성.
  4. 알림톡 전송.

각각의 책임이 섞여 있어 로직이 길어지고, 변경 사항이 발생하면 여러 부분을 동시에 수정해야 했습니다.

Before: 개선 전 (책임이 혼재된 서비스 계층)

public void sendNotification(String companyCode, Long reservationId) {
    // 데이터 조회
    NotificationData data = reservationRepository.findDetailsById(reservationId);

    // 검증
    if (data == null || data.getUserId() == null) {
        throw new IllegalArgumentException("Invalid data");
    }

    if (!isSupportedCompany(companyCode)) {
        throw new IllegalArgumentException("Unsupported company code");
    }

    // 메시지 생성
    String message = String.format("예약 번호: %s, 고객 이름: %s", data.getReservationId(), data.getCustomerName());

    // 알림톡 전송
    notificationService.send(data.getPhoneNumber(), message);
}

문제점

  • 책임 과다: 데이터 조회, 검증, 메시지 생성, 전송이 혼재되어 있음.
  • 테스트 어려움: 각 책임을 독립적으로 테스트할 수 없음.
  • 유지보수 부담: 새로운 로직 추가 시 모든 로직에 영향을 미침.

After: 개선 후 (단일 책임 분리)

1. 데이터 조회 분리

데이터 조회 로직을 별도의 NotificationDataProvider로 분리.

 

@Component
public class NotificationDataProvider {
    public NotificationData getNotificationData(Long reservationId) {
        return reservationRepository.findDetailsById(reservationId);
    }
}

2. 검증 분리

검증 로직을 NotificationValidator로 분리 (위 KISS 사례와 동일).

@Component
public class NotificationValidator {
    public void validate(String companyCode, NotificationData data) {
        if (data == null || data.getUserId() == null) {
            throw new IllegalArgumentException("Invalid data");
        }
        if (!isSupportedCompany(companyCode)) {
            throw new IllegalArgumentException("Unsupported company code");
        }
    }
}

3. 메시지 생성 분리

메시지 생성 로직을 MessageGenerator로 분리.

@Component
public class MessageGenerator {
    public String generateMessage(NotificationData data) {
        return String.format("예약 번호: %s, 고객 이름: %s", data.getReservationId(), data.getCustomerName());
    }
}
 

4. 서비스 계층 재구성

서비스 계층은 데이터 조회, 검증, 메시지 생성, 전송을 조합만 하도록 간소화.

public void sendNotification(String companyCode, Long reservationId) {
    NotificationData data = dataProvider.getNotificationData(reservationId);
    validator.validate(companyCode, data);
    String message = messageGenerator.generateMessage(data);
    notificationService.send(data.getPhoneNumber(), message);
}

검증 분리
검증 로직을 NotificationValidator로 분리 (위 KISS 사례와 동일).

3. 총평

솔직히 처음에는 "코드 가이드? 또 원론적인 얘기겠지" 하고 큰 기대를 안 했습니다.
하지만 이 책을 읽고 나니, 생각이 완전히 바뀌었어요.

다른 코드 관련 책들과는 달리, 이 책은 풍부하고 실제로 실무에서 접할 수 있는 예시를 제공해줍니다.
이 덕분에 책에서 배운 내용을 실제 업무에 바로 적용할 수 있었고, 가독성과 유지보수성을 높이는 데 매우 유용했습니다.

특히, 예시들이 매우 실용적이라서 책을 읽고 나서 어떻게 적용할지 바로 알 수 있었습니다.
예를 들어, 알림톡 전송 로직을 개선하면서 겪었던 상황들이나, 전략 패턴, Early Return 최적화 등은
그 자체로 실무에서 흔히 마주할 수 있는 문제들입니다.

이 책 덕분에 코드 품질을 높이는 데 필요한 실용적인 도구들을 얻게 되었고,
팀원들과의 코드 리뷰에서도 긍정적인 피드백을 많이 받았습니다.

코드 작성 가이드는 신입 개발자뿐만 아니라, 자기 코드의 품질을 고민하는 모든 개발자들에게 꼭 추천하고 싶은 책입니다.
이 책은 단순한 이론에 그치지 않고, 실무에서 바로 적용 가능한 가이드를 제공해줘서,
실제 업무에서 더 나은 코드를 작성하고 싶은 개발자라면 반드시 읽어야 할 필독서입니다.

728x90