자바 코드의 품질을 높이는 100가지 방법 – 책 소개

🗓️

한번은 마주쳐본 문제들에 대한 자바 언어 스팩의 심도있는 분석

한빛미디어 서평단 <나는리뷰어다> 활동을 위해서 책을 협찬 받아 작성된 서평입니다.

  • 원제 : Grokking Algorithms, Second Edition
  • 저자 : Aditya Y Bhargava
  • 출판 : 한빛미디어, 2025 / Manning Publication, 2024

Java는 명실상부 OOP를 대표하는 프로그래밍 언어다. 특히 이 언어를 사용하여 만들어진 Spring framework 는 대규모 백엔드 어플리케이션을 이야기 할 때 빠짐없이 등장한다. 타이트한 규칙의 프레임워크의 도움을 받아 프레임워크를 알고있으면 모두가 이해할수있는 아키텍처를 구성한다. 그런데 문제는 구현이다.

비즈니스 로직은 여전히 Java 를 사용하여 구현해야 한다. 문자열, 컬렉션, 숫자, 비트와 같은 자료구조와 더불어 이제는 빼놓을 수 없는 ‘대용량 처리’ 유행에 동시성 프로그래밍까지. Java 를 사용한다면 날이 갈수록 Java 언어에 대해 더 깊은 이해를 요구한다.

21세기 2025년. 이제는 notepad 나 vi 에디터를 열고 프로젝트 단위의 개발을 하지는 않는다. 우리는 IDE 를 사용하여 개발한다. 이에 따라오는 정적 분석기를 통해 컴파일 오류를 컴파일 하지 않고도 알아낸다. 나아가 자동완성, 타입추론, 참조검사 등 더이상 정적 분석기의 도움 없이는 지금과 같은 속도로 개발할 수 없는 시대가 되었다.

책의 저자는 Java IDE로 유명한 JetBrains의 IntelliJ 정적 분석기를 개발했다. 우리는 정적 분석기의 도움을 여전히 받고 있지만, 정적 분석기가 만능은 아니다. 그래서 정적 분석기가 잡아내지 못하지만 발생하면 적지 않은 파급효과를 일으키는 코드 실수에 대해 분석-원리-해결 순으로 상세하게 알려준다.

개인적으로 인상깊었던 부분들 몇가지를 정리했다.

묵시적 타입 변환에 대해서 표현식이 달라진다는 이야기를 시작하면서 독립형 표현식과 다형성 표현식을 소개한다. 독립형 타입은 주변의 영향을 받지 않고 정해진 타입으로 표현식을 판단할 수 있는 a + b와 같은 표현식을 의미한다. 다형성 타입은 Collections.emptyList()같이 제네릭 같은 컨텍스트에 의해 결과에 영향을 미치는 표현식을 의미한다.

그런데 박스 타입이라 일컫는 원시 래퍼 타입은 독립형 표현식이고 원시 타입과 비교를 할때는 원시 타입이 우선시 된다고 한다. 책에서는 다음 코드 조각으로 예시를 보여준다

Double valueOrZero(boolean condition, Double value) {
    return condition ? value : 0.0;
}

이는 0.0의 원시 타입에 우선에 따라 박스 타입인 Double이 아니라 원시 타입인 double이 반환되고, 이를 다시 박싱한다고 한다. 따라서 기대에 따라 작동하려면 valueOf()를 사용하여 감싸면 된다고 한다. 그래서 원시타입에 대해서는 박스 타입이 있다면 가능한 원시타입으로 변환하는 것이 좋다고 한다. 이는 nullable을 방지하는 효과도 있다.

NPE를 다룰때 조심해야 하는 부분의 실제에 대해 알려준다. Nullable 하다면 가능한 Optional 을 사용하고, 그마저 어렵다면 @Nullable 어노테이션이라도 활용하라고 한다. Java 10부터 제공되는 Collection.copyOf() 을 사용해 immutable 한 복사본을 사용하면 nullable 을 허용하지 않는다는 것도 알려준다. 그래도 Null을 꼭 다뤄야 한다면 차라리 열거형을 사용하라고 한다. 특히 박스타입인 Boolean 의 경우 null 을 허용하기 때문에 차라리 참, 거짓, null 을 표현하는 열거형을 만들어서 사용하라고 한다.

Stream API 는 중간 연산을 가지고 있다. 그러나 Stream 은 최종 연산이 호출되기 전에는 아무일도 일어나지 않는다. 만약 중간 연산이 인자를 동반하는 함수의 입력이 발생한다면 부수효과를 일으키게 된다. 이 상황은 parallelStream() 을 사용할때도 예외가 발생할 수 있기 때문에 부수효과를 발생시켜야 한다면partitioningBy()와 같은 컬렉션 함수로 분리하여 멱등성을 유지할 필요가 있다고 한다.

여러 스레드 사이에서 공유 데이터를 다룰때에 대한 팁도 알려준다. Queue에서 작업을 꺼내 수행하기 위해 isEmpty()로 검사하고 remove()를 호출한다면 두번의 호출이 이뤄지기 때문에 원자적인 상호작용이 아니라고 한다. 이같은 작업에서 원자성을 유지하기 위해서 poll()로 대상을 꺼내오면 된다고 한다. 만약 큐가 비었다면 poll()은 null을 반환 하기 때문에 스레드에서 영향을 받지 않는 로컬 변수로 null 검사가 가능해지는 것이다. 요는 동시성 컬렉션 메서드를 연쇄 호출했을때 원자적 처리에 대해서 보장하지 않기 때문에 고민을 하고 연산 선택을 하라는 것이다. Atomic 레퍼런스 역시 set()같은 메서드는 스레드 세이프 하지 않기 때문에 updateAndGet() 메서드를 사용해 구현하라고 알려준다.

이 외에도 유닛 테스트, 객체간 비교, 문자열, 비트 연산 등에서 실수 할 수 있는 문제들에 대해 앞서 소개한 내용처럼 심도있는 고찰을 밀도있게 전달하고 있다. 아마도 이펙티브 자바와 많이 비교가 될 것 같은데, 그 책은 OOP나 리팩토링에 중점을 둔 내용이라면 이번에 소개한 ‘자바 코드의 품질을 높이는 100가지 방법’은 실무에서 자바를 사용했을때 발생할 수 있는 오류의 파훼와 언어 스팩에 포함되어있는 내부 구현에 대해서 많은 인사이트를 얻을 수 있었다.

한빛미디어 서평단 <나는리뷰어다> 활동을 위해서 책을 협찬 받아 작성된 서평입니다.