전체 보기

[내 코드가 그렇게 이상한가요?] 1. 잘못된 구조의 문제 깨닫기

작성일자
2023/12/05
태그
SUB PAGE
프로젝트
내 코드가 그렇게 이상한가요?
책 종류
1 more property
설계를 소홀히 하면 코드를 읽고 이해하는 데 시간이 오래 걸리고, 버그가 계속해서 발생하며, 나쁜 구조로 인해서 더 나쁜 구조가 만들어진다.
설계를 소홀히 해 만들어지는 나쁜 구조로 인해 발생할 수 있는 폐해의 예시를 살펴보자.

1. 의미를 알 수 없는 이름

자료형, 메모리 제어와 같은 프로그래밍이나 컴퓨터 용어를 기반으로 이름 붙이는 기술 중심 명명
번호를 붙여서 이름 짓는 일련번호 명명은 코드의 의미를 알 수 없게 하고, 이해하기 어렵다.
충분히 이해하지 못한 채로 코드를 변경하면 버그가 발생할 것이다.
의도와 목적을 드러내는 이름을 사용하면 구조가 간단하고 명확해져서 좋다.

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

if 조건문의 중첩이 많을수록 어디서부터 어디까지가 if 조건문 처리 블록인지 확인하기 힘들어져 코드의 가독성이 나삐진다.
이해하기 힘들어서 디버깅과 기능 변경에 오랜 시간이 걸릴뿐더러, 분기 로직을 정확하게 이해하지 못하고 기능을 변경하면 버그가 벌생할 수도 있다.
(6장에서 이어서 설명)

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

데이터 클래스는 아래와 같이 데이터를 갖고 있기만 하는 클래스다.
public class ContractAmount { // 계약 금액 public int amountIncludingTax; // 세금 포함 금액 (ex. 110원 = 원래 금액 100원+소비세 10원) public BigDecimal salesTaxRate; // 소비세율 (ex. 10%) }
Plain Text
복사
데이터 클래스에는 데이터뿐만 아니라, 세금이 포함된 금액을 계산하는 로직도 필요한데, 이러한 계산 로직을 클래스가 아닌 다른 클래스에 구현하는 일이 벌어질 수 있다.
public class ContractManager { // 계약 관리 public ContractAmount contractAmount; // 세금 포함 금액 계산 public int calculateAmountIncludingTax(int amountExcludingTax, BigDecimal salesTaxRate) { // ex. 100원, 0.1(10%) BigDecimal multiplier = salesTaxRate.add(new BigDecimal("1.0")); // ex. 1.1 (110%) BigDecimal amountIncludingTax = multiplier.multiply(new BigDecimal(amountExcludingTax)); // ex.110% * 100원 = 110원 return amountIncludingTax; } // 계약 체결 public void conclude() { // 생략 contractAmount = new ContractAmount(); contractAmount.amountIncludingTax = calculateAmountIncludingTax(amountExcludingTax, salesTaxRate); contractAmount.salesTaxRate = salesTaxRate; // 생략 } }
Plain Text
복사
이 코드의 문제점이 뭘지 살펴보자.
(1) 낮은 응집도로 사양 변경에 불리 (코드 중복, 수정 누락, 가독성 저하)
데이터를 담고 있는 클래스와 데이터를 사용하는 계산 로직이 멀리 떨어져 있는 바람에, 누군가는 계산 로직의 존재 자체를 몰라 계속 구현하게 될 수 있다.
데이터와 로직 등이 분산되어 있는 것을 응집도가 낮은 구조라고 하는데, 이 구조는 특히 사양이 변경됐을 때 불리하다.
예를 들어, 소비세율 관련한 사양이 변경되었을 때 세금 포함 금액을 계산하는 로직이 여러 곳에 중복되게 흩어져 있다면 일괄적으로 수정해주기 어렵기 때문이다.
즉, 의도하지 않게 코드 중복이 발생할 수 있고, 이로 인해 사양이 변경될 때 중복된 코드를 모두 고치다 일부 코드를 놓쳐 버그를 낳을 수 있다.
또한 코드가 분산되어 있으면, 중복된 코드를 다 찾는 것만으로도 시간이 오래 걸려 가독성이 떨어진다.
참고로 가독성이란 코드의 의도나 처리 흐름을 얼마나 빠르고 정확하게 읽고 이해할 수 있는지를 나타내는 지표다.
(2) 초기화되지 않은 상태(쓰레기 객체)
초기화하지 않으면 쓸모 없는 클래스, 초기화히지 않은 상태가 발생할 수 있는 클래스를 안티 패턴인 쓰레기 객체라고 부른다.
왜 안티패턴이냐면, 따로 초기화히지 않고 사용했을 때 NullPointerException이 발생할 수 있기 때문이다.
예를 들어, 아래와 같은 코드는 NullPointerException이 발생한다.
ContractAmount amount = new ContractAmount(); System.out.println(amount.salesTaxRate.toString()); // NPE 발생
Plain Text
복사
(3) 잘못된 값 할당
데이터 클래스는 잘못된 값이 쉽게 들어갈 수 있는 구조다. 그리고 값이 잘못되었다는 것은 요구사항에 맞지 않음을 의미한다.
잘못된 값이 들어가지 않게, 데이터 클래스를 사용하는 쪽의 로직을 변경해 유효성 검사를 하더라도 이는 결국 응집도 낮은 구조라 또 다른 문제가 발생한다.
그렇다면 데이터 클래스는 절대 실무에서 사용하면 안될 악의 존재일까? 그건 아니다.
일부러 데이터 클래스로 설계하는 경우도 있다. 이는 앞서 언급한 데이터 클래스의 리스크를 충분히 해소할 수 있는 경우에 한정되는데 5장의 DTO 부분에서 이어서 설명한다.