development-logo
코드를 작성할 때 종종 “이 로직을 어떻게 단순하고 읽기 쉽게 표현할 수 있을까?” 고민하게 됩니다. 제가 수강 중인 “Readable Code: 읽기 좋은 코드를 작성하는 사고법” 강의 [논리, 사고의 흐름] 섹션에서는, 인지적 부담을 줄이고 가독성을 높이기 위해 고려해야 할 여러 포인트를 다루는데, 개인적으로 불필요한 else 제거 (Early Return), 중첩 분기/반복 최소화, 변수 선언 위치 등이 특히 인상 깊었습니다.
이 내용들을 학습하고 나니, 작은 코드 한 줄도 사람의 사고 방식을 기준으로 재배치하거나 단순화할 수 있다는 걸 체감했습니다. 오늘은 강의를 보고 개인적으로 느낀 점 그리고 함께 진행중인 인프런 워밍업 클럽 스터디 3기 미션까지 공유해 보겠습니다.
아래 코드는 사용자가 생성한 “주문(Order)”이 유효한지 검증하는 메서드입니다.
[섹션 3. 논리, 사고의 흐름]에서 이야기한 내용을 적용해 읽기 좋은 코드로 리팩토링해 봅시다.
원본 코드
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
} else {
if (order.getTotalPrice() > 0) {
if (!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
} else {
return true;
}
} else if (!(order.getTotalPrice() > 0)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
}
return true;
}
Javapublic boolean validateOrder(Order order) {
// 주문 항목 유무
if (order.getItems() == null || order.getItems().isEmpty()) {
log.info("주문 항목이 없습니다.");
return false;
}
// 총 가격 검증
if (order.getTotalPrice() <= 0) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
// 고객 정보 검증
if (!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
// 여기까지 왔다면 유효한 주문
return true;
}
Javapublic boolean validateOrder(Order order) {
// Order에게 스스로 검사하도록 메시지(메서드)를 보낼 수 있음
// 내부 로직에서 "항목, 총가격, 사용자 정보"를 판단
return order.isValid();
}
Java// 예: Order 내부
public boolean isValid() {
if (this.items == null || this.items.isEmpty()) {
log.info("주문 항목이 없습니다.");
return false;
}
if (this.totalPrice <= 0) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
if (!this.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
Java어떠한 방법을 택하든, 중첩된 조건을 가능한 단순화하고, 각 조건의 의도를 빠르게 파악할 수 있도록 작성하는 것이 핵심이라고 생각합니다.
SOLID에 대하여 자기만의 언어로 정리해 봅시다.
미션에서 강조된 포인트 중 하나는, ‘자신의 언어로 표현할 수 있는가’ 였습니다.
아래는 제가 개인적으로 이해한 SOLID 각 원칙입니다.
정의
하나의 클래스(혹은 모듈)는 “오직 하나의 책임(변경 이유)”만 가져야 한다.
// SRP를 어긴 예시 (하나의 클래스가 여러 책임 수행)
public class OrderService {
// 주문 처리 로직
public void processOrder(Order order) {
// 비즈니스 로직...
}
// DB 접근 로직 (DAO 역할)
public void saveOrderToDatabase(Order order) {
// DB connection...
// SQL INSERT...
}
// 메일 발송 로직
public void sendOrderConfirmationMail(Order order) {
// SMTP 로직...
}
}
Java// SRP 준수 예시 (책임을 구분하여 클래스 분리)
public class OrderService {
private final OrderRepository orderRepository;
private final MailService mailService;
public OrderService(OrderRepository orderRepository, MailService mailService) {
this.orderRepository = orderRepository;
this.mailService = mailService;
}
// 주문 처리 (비즈니스 로직)
public void processOrder(Order order) {
// 1) 주문 관련 비즈니스 로직...
// 2) 주문 DB 저장은 OrderRepository에게 맡김
orderRepository.save(order);
// 3) 메일 발송은 MailService에게 맡김
mailService.sendConfirmation(order);
}
}
// DB 접근 전담
public class OrderRepository {
public void save(Order order) {
// DB 커넥션 및 SQL 로직...
}
}
// 메일 발송 전담
public class MailService {
public void sendConfirmation(Order order) {
// SMTP, 메일 전송 로직...
}
}
Java정의
확장에는 열려 있고, 수정에는 닫혀 있어야 한다.
// OCP를 어긴 예시: 할인 정책 추가할 때마다 if문 수정
public class DiscountService {
// type으로 어떤 할인인지 구분
public int getDiscountPrice(int price, String discountType) {
if ("rate".equals(discountType)) {
return price - (int)(price * 0.1); // 10% 할인
} else if ("fixed".equals(discountType)) {
return price - 2000; // 2000원 할인
} else if ("vip".equals(discountType)) {
return price - 3000; // 추가로 VIP 할인
}
return price;
}
}
Java// OCP 준수 예시: 인터페이스로 추상화 -> 새로운 할인 정책을 추가할 때 확장
public interface DiscountPolicy {
int applyDiscount(int price);
}
public class RateDiscountPolicy implements DiscountPolicy {
@Override
public int applyDiscount(int price) {
return price - (int)(price * 0.1);
}
}
public class FixedDiscountPolicy implements DiscountPolicy {
@Override
public int applyDiscount(int price) {
return price - 2000;
}
}
// 사용처
public class DiscountService {
private final DiscountPolicy discountPolicy;
public DiscountService(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
public int getDiscountPrice(int price) {
return discountPolicy.applyDiscount(price);
}
}
Java정의
부모 클래스(인터페이스) 자리에 자식 클래스를 대입해도, 정상적으로 동작해야 한다.
// LSP를 어긴 예시
public class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) { this.width = width; }
public void setHeight(int height) { this.height = height; }
public int area() {
return width * height;
}
}
public class Square extends Rectangle {
// 정사각형은 width, height가 같아야 하므로
// 오버라이딩 시, LSP 위반 가능성
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // 정사각형이므로 height도 width에 맞춤
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
// LSP를 위반하는 케이스
// rectangle 객체로 치환했을 때 문제가 생김
public static void testRectangle(Rectangle r) {
r.setWidth(10);
r.setHeight(5);
// 우리가 기대하는 area는 10*5=50
// But Square로 치환되어 있으면 area()=25(정사각형이 돼버림)
}
Java해결
정의
클라이언트는 자신이 사용하지 않는 메서드에 의존하면 안 된다.
// ISP를 어긴 예시: 인터페이스가 너무 비대함
public interface MultiFunction {
void print(String text);
void scan(String filePath);
void fax(String number);
}
// 단순 프린터는 fax 기능 불필요
public class SimplePrinter implements MultiFunction {
@Override
public void print(String text) {
// print logic
}
@Override
public void scan(String filePath) {
// scan 필요없어도 구현 강제
}
@Override
public void fax(String number) {
// fax 필요없어도 구현 강제
}
}
Java// ISP 준수 예시: 인터페이스 분리
public interface Printer {
void print(String text);
}
public interface Scanner {
void scan(String filePath);
}
public interface Fax {
void fax(String number);
}
// 필요한 기능만 구현
public class SimplePrinter implements Printer {
@Override
public void print(String text) {
// print logic
}
}
Java정의
고수준 모듈(주요 로직)이 저수준 모듈(세부 구현)에 의존하지 않도록, 둘 다 추상화된 인터페이스에 의존하라.
// DIP 위반 예시: 고수준 모듈이 직접 저수준 구현을 생성하고 의존
public class OrderService {
private MySqlOrderRepository repository = new MySqlOrderRepository();
public void processOrder(Order order) {
repository.save(order);
}
}
Java// DIP 준수 예시: 추상화(인터페이스)에 의존
public class OrderService {
private final OrderRepository repository; // 인터페이스
public OrderService(OrderRepository repository) {
this.repository = repository; // 외부에서 구현체 주입
}
public void processOrder(Order order) {
repository.save(order);
}
}
public interface OrderRepository {
void save(Order order);
}
public class MySqlOrderRepository implements OrderRepository {
@Override
public void save(Order order) {
// MySQL 저장 로직...
}
}
public class MongoOrderRepository implements OrderRepository {
@Override
public void save(Order order) {
// MongoDB 저장 로직...
}
}
Java이상 SOLID 각 원칙을 제 관점에서 풀어보고, 간단한 예시 코드를 곁들여 보았습니다.
이 5가지 원칙을 모두 지키면 객체지향 설계의 큰 틀이 잡히며, 유지보수성과 확장성이 좋아집니다.
논리, 사고의 흐름에서 강조된 내용들을 실습해보며, 중첩 if-else나 긴 메서드를 단순화하는 것만으로도 코드를 읽는 사람의 인지적 부담이 크게 줄어든다는 점을 체감했습니다.
또한 SOLID 원칙은 객체지향 설계를 할 때 왜 이렇게 나누고, 왜 이렇게 의존관계를 맺는가?에 대한 큰 방향을 잡아줍니다. 처음부터 완벽하게 지키기는 어렵지만, 코드를 리팩토링할 때마다 조금씩 적용해보면 확실히 확장성이나 가독성이 올라가는 걸 느낄 수 있습니다.
이상으로, 강의 내용을 바탕으로 읽기 좋은 코드에 대해 다시 생각해본 후기와 미션 과제 정리를 마칩니다.
감사합니다.
HTTP 상태코드란 무엇인가? HTTP 상태코드(HTTP Status Code)는 서버가 클라이언트의 요청을 처리한 결과를 수치화된 코드로 나타내는…
HTTP란 무엇인가? HTTP(Hypertext Transfer Protocol)는 웹에서 데이터를 주고받기 위해 사용하는 응용 계층 프로토콜입니다. 우리가 브라우저에서…
HTTP란 무엇인가? HTTP(Hypertext Transfer Protocol)는 인터넷에서 웹 브라우저와 웹 서버가 서로 통신하기 위해 사용하는 프로토콜입니다.…
들어가며 우리가 인터넷에서 웹사이트에 접속할 때 가장 먼저 하는 일은 브라우저 주소창에 어떤 문자열을 입력하는…
인터넷 네트워크란? "인터넷(Internet)"이라는 단어는 "인터네트워크(Internetwork)"의 줄임말입니다. 즉, 여러 개의 네트워크가 상호 연결되어 전 세계적으로 하나의…