본문 바로가기
내 코드가 그렇게 이상한가요?

1장 잘못된 구조의 문제 깨닫기

by lleesla 2024. 3. 11.

1장 잘못된 구조의 문제 깨닫기

  • 나쁜 구조의 폐해란
    • 코드를 읽고 이해하는 데 시간이 오래 걸림
    • 버그가 계속해서 발생함
    • 나쁜 구조로 인해서 더 나쁜 구조가 만들어짐

1.1 의미를 알 수 없는 이름

  • 좋지 않은 이름이 일으키는 악영향을 소개한다.
    • 기술 중심 명명 : 기술 기반으로 이름을 붙이는 것
    • class MemoryStateManager { void changeIntValue01(int changeValue) { ... } }
    • 일련번호 명명 : 클래스와 메서드에 번호를 붙여서 이름 짓는 것
    • class Class001 { void method001(); void method002(); void method003(); }
  • 위와 같이 이름을 지으면 코드에서 어떠한 의도도 읽어 낼 수 없다.
  • 읽고 이해하는 데 시간이 오래걸린다.
  • 충분히 이해하지 못한 상태로 코드를 변경하면 버그가 발생한다.
  • 의도와 목적을 드러내는 이름을 사용하기만 해도 구조가 간단하고 명확해진다.

1.2 이해하기 어렵게 만드는 조건 분기 중첩

  • 중첩문이 많을 수로 가독성이 나빠진다. 어디서부터 어디까지가 if 조건문 처리 블록인지 확인하기 힘들기 때문이다.
  • 조건이 복잡해질수록 코드를 읽고 이해하기 힘들다.
  • 이해가 힘들면 디버깅과 기능 변경에 더 오랜 시간이 걸린다.
  • 게다가 로직을 정확하게 이해하지 못하고 기능을 변경하면 버그가 발생할 수도 있다.

1.3 수많은 악마를 만들어 내는 데이터 클래스

  • 데이터 클래스의 어떤 점이 나쁜지 살펴보자.
    • 업무 계약을 다루는 서비스에서 계약 금액을 처리하는 요구사항을 클래스로 구현하는 상황
    // 계약 금액
    public class ContractAmount {
    	public int amountIncludingTax; // 세금 포함 금액
    	public BigDecimal salesTaxRate; //소비세율
    }
    
    • public 인스턴스 변수를 갖고 있으므로 클래스 밖에서도 데이터를 자유롭게 변경할 수 있는 구조
    • 이처럼 데이터를 갖고 있기만 하는 클래스를 데이터 클래스라고 한다.
    • 요구사항에는 세금이 포함된 금액을 계산하는 로직도 필요한데, 계산 로직을 데이터 클래스가 아닌 다른 클래스에 구현하는 일이 벌어지곤 한다. 설계를 따로 고려하지 않아 생기는 일이다.
    // 계약을 관리하는 클래스
    public class ContractManager {
    	public ContractAmount contractAmount;
    	
    	// 세금 포함 금액 계산
    	public int calculateAmountIncludingTax() {
    		...
    	}
    	
    	// 계약 체결
    	public void conclud() {
    		...
    	}
    }
    
    • 위 코드의 문제점을 알아보자

1.3.1 사양을 변경할 때 송곳니를 드러내는 악마

  • 업무 계약 서비스에서 소비세와 관련된 사양이 변경되어, 소비세율과 관련된 로직을 변경했다.
  • 그런데 며칠이 지나 소비세율이 변경되지 않았다는 장애 보고가 올라왔다. 원인을 조사해보니, 다른 곳에도 세금 포함 금액을 계산하는 로직이 존재하고 있었다.
  • 담당자는 소비세와 관련된 부분을 소스 코드 전체에서 찾기 시작했는데, 놀랍게도 세금 포함 금액을 계산하는 로직이 수십 곳에 있었다.
  • 왜 이런 일이 생긴 것일까? 세금 포함 금액은 여러 상황에서 필요하므로 여러 곳에 구현되기 쉽다.
  • 계산 로직을 어느 한 곳에 만들어두면, 사람들이 모두 그것만 사용하고 따로 구현하지 않겠지라고 생각할 수도 있다. 하지만 설계에 관심이 없다면, 유지보수하는 사람이 많을수록 이미 구현되어 있다는 사실을 모르고 따로 구현해버릴 수도 있다.
  • 이런 상황은 데이터를 담고 있는 클래스와 데이터를 사용하는 계산 로직이 멀리 떨어져 있을 때 자주 일어난다.
  • 이처럼 데이터와 로직이 분산되어 있는 것을 응집도가 낮은 구조라고 한다.

1.3.2 응집도가 낮아 생길 수 있는 여러 가지 문제를 살펴 보자.

  • 코드 중복
  • 수정 누락
  • 가독성 저하
    • 코드가 분산되어 있으면 중복된 코드를 포함해서 관련된 정보를 다 찾는 것만으로도 시간이 오래 걸린다.
  • 초기화되지 않은 상태(쓰레기 객체)
    • 코드를 실행하면 NPE(Null Point Exception)가 발생한다. 소비세율 salesTaxRate 은 BigDecimal 타입이므로 따로 초기화하지 않으면 null이 들어간다.
    • 이처럼 초기화하지 않으면 쓸모없는 클래스 또는 초기화하지 않은 상태가 발생할 수 있는 클래스를 안티 패턴 쓰레기 객체 라고 부른다.
  • ContractAmount amount = new ContractAmount(); System.out.println(amount.salesTaxRate.toString());
  • 잘못된 값 할당
    • 현재 데이터 클래스는 소비세율을 음수로 대입해도 값이 들어간다. 따라서 잘못된 값이 쉽게 들어갈 수 있는 구조이다.
    ContractAmount amount = new ContractAmount();
    amount.salesTaxRate = new BigDecimal("-0,1");
    
    • 잘못된 값이 들어가지 않게 데이터 클래스를 사용하는 로직을 살짝 변경해서 유효성 검사하게 만들수 있지만, 사용하는 곳마다 검사 로직을 추가해야 하니 여러 곳에 코드가 중복될 수 있다.
    • 그렇게 되면 요구사항이 변경될 때마다 수정 누락과 가독성 저하같은 문제가 계속 발생한다.