Insight/서평

[서평] 자바 코드의 품질을 높이는 100가지 방법

Nithen 2025. 3. 27. 17:50
728x90

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


자바 코드의 품질을 높이는 100가지 방법 / 타기르 발레예프 지음 / 한빛미디어

 

왜 이런 일이 발생했는가? 처음부터 실수를 예방할 수는 없는가? 앞으로도 비슷한 문제가 발생할 수 있는가? 팀원들이 똑같은 실수를 반복하지 않게 하려면 무엇을 해야 하는가? 결국 필자는 모든 실수에 통용되는 만병통치약은 없다는 것을 깨달았다. ⋯ 그럼에도 실수란 모름지기 반복되는 법이다. 따라서 실수는 분류할 수 있으며 각각의 유형마다 일반화된 대응책도 있다. 앞길에 존재하는 함정을 미리 아는 것만으로도 함정을 피할 준비는 이미 갖춰진 셈이다.

 

💧 10년 정수가 담긴 자바 챔피언의 오류 해결 특강 

세상 그 어떤 유능한 개발자도 실수할 수 있다. 그리고 그로 인해 발생한 서비스 오류는 엄청난 손실을 야기한다. 저자 타기르 발레예프는 약 15 여년간 상업용 소프트웨어 개발자로서 근무하며 여느 때처럼 오작동을 바로잡기 위해 일하고 있었다. 그러다 FindBugs(현 SpotBugs)라는 자바 정적 분석 도구를 접하게 되었고, 몇 분만에 수십 개의 실수를 지적해주는 정적 분석의 열렬한 팬이 되어버렸다. 그러나 이 도구를 적극적으로 활용하려 할 수록 오탐지와 심각하지 않은 문제를 마주하게 되었고, 결국 은탄환은 없음을 깨닫게 되었다. (No Silver Bullet)

 

IntelliJ IDEA의 정적 분석팀으로 10년 동안 근무하며, 그는 한계가 있음을 깨닫는 데 그치지 않고 그럼에도 문제를 줄이기 위한 방법을 고민했다. 이 책은 그의 고민이 오롯이 담긴, 살아 숨 쉬는 100종의 정수(精髓; essence)이다. 책에서는 컴파일 시점에는 알 수 없었던 '진짜' 문제만을 대상으로 이를 유형화하고 때로는 실제 프로덕션 코드로 이를 설명한다. 개발자의 의도는 무엇이었고, 문제의 원인은 무엇이고, 어떻게 문제를 해결하는지 또 실수를 예방할 수 있는지, 각 문제의 유형에 최적화된 해결책을 제시한다.

개발자의 의도와 달리, 컴파일러가 실제로 어떻게 동작하는지 설명한다.

🙆‍♂️  런타임 오류 패턴: 기본부터 심화까지

Java 프로그램을 작성할 때, 컴파일 오류를 해결하기 위해 그렇게 많은 시간을 쏟는 개발자는 없을 것이다. Java 컴파일러는 꽤나 엄격한 편이고 빌드 시 발생한 예외와 그 위치, 심지어는 코드 작성 시점에 IDE에 그어진 빨간 줄만 보더라도 '아차, 이걸 놓쳤었네' 하고 뚝딱 해결할 것이다. 그러니 실질적으로 개발자들을 열받게 만드는 것은 런타임 오류라는 데에는 모두 이견이 없으리라 생각한다. 

기본은 자세히

비교적 우리가 익숙하게 인지하고 있는 기본적인 유형은 좀 더 자세히 다루고 있다는 느낌을 받았다. 예를 들어, 표현식을 다루는 2장에서는 연산자에 대한 내용이 많다. 우선순위의 경우 덧셈보다 곱셈이 먼저 수행되는 1 + 1 * 4 != 8 와 같은 자명한 사례 대신,  1 + 1 << 2 == 8 처럼 비트 시프트 등의 다소 이색적인 사례를 소개한다. 논리 연산의 경우는 어떨까? 논리 합 또는 곱에 &&, || 연산자를 사용하는 것은 자명한데, 이러한 단락 연산자(short-circuit operator)는 비단락 연산자 &, |와 어떻게 다르고 그래서 어떻게 문제가 생길 수 있는지, 또 복합 할당 연산으로 활용될 때는 어떤 점을 조심해야 하는지 보다 심도있는 내용을 다룬다. 이 책에서 기본이란 이런 느낌이다. 

실수 2-7 비단락 논리 연산자 사용 

한 가지 예시를 살펴보자. 나는 다음 코드를 보며 고개를 갸우뚱했다.
"음.. 여기에 문제가 있다고?"

2.7.2 - using non-short-circuit operator / before, after

 

일단 개발자의 의도는 세 종류의 검사 모두 통과하는 경우에만 true를 반환하는 것이다. 그리고 이 과정을 복합 할당 연산자로서의 비단락 연산 &=으로 구현했다. 논리곱 &&이 피연산자(operand) 중 false가 발생하는 순간 다음 명령을 수행하는 것과는 달리, 비단락 연산은 피연산자 내 false가 있더라도 해당 명령을 끝까지 수행한다. 따라서 검사 로직이 복잡하다면 불필요하게 프로그램의 복잡도를 높이는 안티 패턴이기에 저자는 이를 지적한 것이다. 다음 두 코드를 통해 실행되는 횟수에 차이가 있음을 확인할 수 있다.

2.7.2 - using non-short-circuit operator / class impl, test count

🤔 심화: 이걸 문제로 보는 게 맞을까?

물론 복잡하게 중첩되어 로직에 오류를 만드는 사례도 소개한다. 해당 사례에서는 정적 분석기에서 이를 어떻게 탐지하는지 소개하고 실수 방지 가이드에 비단락 논리 연산자를 사용하지 않도록 권고한다. 앞서 소개한 사례처럼 분명하게 로직에 오류가 발생하는 경우는 아니지만, 위 코드는 분명 최적화의 관점에서는 불필요하게 복잡도를 증가시키고 있으므로 단락 논리 연산자를 사용해서 해결하는 것이 합리적으로 보인다. 그러나 내가 처음 가졌던 의문은 좀 달랐다.

 '오류(error)를 만드는 것이 아니라면, 어떠한 관점에서든 이를 잘못되었다고(wrong) 할 수 있을까?'
'&를 의도적으로 사용한 로직에 대해서는 false positive 아닌가?' 

 

다름과 틀림을 구분하고자 다소 비판적으로 바라보고 있었는데, 신기하게 저자는 이에 대해서도 의견을 남겨두었다. 책에서는 '양쪽 피연산자 평가'라는 제목으로 절의 마지막에서 이러한 내용을 다루고 있다. check라는 피연산자가 모두 의도적으로 부수 효과(side effect)를 가지고 있다면, 단락 논리 연산자 &&를 사용했을 때 첫 번째의 결과에 의해 나머지가 실행되지 않을 수 있다. 이런 경우에는 & 사용이 합당한데, 심지어 이런 경우에도 &&를 사용하도록 리팩터링하거나 명시적으로 정적 분석 억제 주석을 추가하도록 권고하고 있다. 

 

그러니까 일반화하자면 이 책에서 나오는 '의도하지 않았던 동작의 원리'는 사실 언제든 '의도적으로' 활용될 수 있다. (부수 효과를 의도하고 비단락 논리 연산자를 사용하는 것처럼) 그리고 그 때 비록 결과가 의도한대로 정상적으로 출력되더라도, 동일한 동작을 더 분명하게 표현할 수 있는 방법이 있다면 그렇게 바꾸어 나가야 한다. 그것이 리팩터링이고 이 책에서 소개하는 자바 코드의 '품질을 높이는' 길이다. 이처럼 2장 7절 비단락 연산자 예제는 이 책에서 저자가 코드로부터 개발자의 의도를 파악하기 위해 노력하고 깊이 있게 고민했던 것을 느낄 수 있는 대목 중 하나였다.

📋 다양한 요약 및 정리

책을 읽다보면 군더더기 없이 깔끔하게 잘 정돈되어 있다는 느낌이 든다. 각 절마다 다양한 하위 주제로 유형을 상세히 구분하고 문제 및 대응 방안을 요약 정리한다. 가령, 2장 표현식 마지막에서는 내용을 총 정리한다. 내용은 다음과 같다. 

더보기
  • 수학적 연산자의 우선순위는 마냥 직관적이지 않다. 특히 비트 연산자와 논리 연산자는 더욱 까다롭다. 우선순위가 한 눈에 보이지 않을 때는 항상 괄호를 사용하라.
  • 덧셈 기호는 숫자와 문자열에 적용되는 방식이 다르며, 간혹 두 방식이 한 표현식에서 혼용되기도 한다. 단일 표현식에서는 덧셈과 문자열 연결 기능을 혼합하지 않는 것이 좋다. 단항 덧셈 연산자는 프로젝트 전체적으로 금지하라.
  • 문자열을 연달아 길게 이어 붙일 때, 전체 문자열이 아닌 마지막 조각에 최종 메서드를 호출할 위험이 있다. 긴 문자열을 다룰 때는 최근 자바 버전에 도입된 텍스트 블록 구문을 사용하는 것이 좋다.
  • 조건 표현식에서 각 분기의 표현식 타입이 서로 다를 경우, 복잡하며 비직관적인 규칙에 따라 타입이 변환된다. 결과 타입이 확실치 않을 때는 if 문을 사용하라.
  • Integer, Double, Boolean 등의 박싱된 타입은 가능한 한 지양하라. 묵시적 변환이 발생하며 성능 오버헤드를 일으킬 우려가 있다. 또한 실수로 null을 넣으면 NullPointerException이 발생한다.
  • 가변 인수 메서드는 사용하기 편리하다. 그러나 가변 인수 메서드에 정확히 하나의 인수를 전달하면 모호성이 발생한다. 배열이나 컬렉션을 또 다른 배열로 래핑할 위험이 있다.
  • 자바는 메서드 호출이나 표현식의 결과를 무시할 수 있다. 일부러 그렇게 할 때도 있지만 실수의 원인이 되는 경우도 많다. 불변 클래스는 자신을 고치지 못하므로 새로운 객체를 생성한다는 점을 기억하라. 또한 이들이 만일 예외 클래스라면 객체 생성 시 throw를 추가하라.
  • 메서드 참조 구문을 통해 깔끔하고 간결한 함수 표현식을 만들 수 있다. 그러나 메서드나 생성자 오버로드가 있으면 컴파일러가 바인딩할 메서드를 오해할 수 있으므로 주의하라. 확실치 않을 때는 람다 구문을 사용하라.

🙅‍♂️ Java 표준에만 집중한다 

[서드 파티는 다루지 않는다] 간혹 어노테이션 패키지나 테스트 프레임워크에 대한 내용이 나오긴 하지만, 그 외 대부분 Java 표준 라이브러리에 대한 내용을 중심으로 다룬다. 따라서, Spring 프레임워크의 활용에 관심이 많다면 이 책은 적절하지 않다. 가령, 3장 프로그램 구조에서의 문제 중 일부는 프레임워크 기능을 활용하여 해결할 수도 있을 것이다.

 

[코드 전문이 제공되지 않는다] 직접 실행해보며 내용을 검증하려는 이들이 있다면 유감이다. 이 책에는 잘못된 코드의 패턴만을 간략히 소개하기 때문에, 실행 가능한 형태의 코드 전문을 항상 제공하지는 않는다. 이러한 이유로 나 또한 새롭게 레포를 생성하여 2장의 14개 절에 대해서는 나름대로 실습을 진행해보았다. 필요하다면 다음 레포와 사이트를 참고하자

 

GitHub - ooMia/100_java_mistakes: A practical project for implementing concepts from the book '100 Java Mistakes and How to Avoi

A practical project for implementing concepts from the book '100 Java Mistakes and How to Avoid Them (자바 코드의 품질을 높이는 100가지 방법)' - ooMia/100_java_mistakes

github.com

 

100 Java Mistakes

Practicing the concepts and exercises from the book `100 Java Mistakes and How to Avoid Them`

oomia.github.io


마치며

의심할 여지가 없는 수작이다. Java 프로그래밍에 관심 있는 그 누가 보더라도 도움이 될만한 책이라고 감히 단언할 수 있다

더보기

...라고 생각하지만, 소수의 의견도 항상 존재하는 법 🤣 해외 책 리뷰 사이트에서 4.29/5의 평점이었는데, 그 중 낮은 평점을 제시한 한 의견을 찾아보았고, 이렇게도 생각할 수 있겠구나 흥미로워 가져왔다.

goodreads.com: 그리고 chatGPT 번역
책을 쓰는 것은 대담한 도전이라고 생각한다. 원고를 제출하는 순간, 저자는 낯선 사람들이 몇 시간 동안 읽을 가치가 있는 글을 썼다고 주장하는 셈이니까. 하지만 이 책에서는 내가 기대했던 우아함과 '아하!' 하는 깨달음이 부족하다고 느꼈다. 마치 전형적인 "고전적인 엔지니어링 서적"에서 예상할 수 있는 것처럼, 다소 인공적이고 야심(또는 열정)이 부족한 느낌이었다.

...(중략)

내가 느낀 가장 큰 문제는, 저자가 단순히 소나(Sonar) 이슈 목록을 펼쳐놓고 하나씩 예제를 설명하는 것처럼 보였다는 점이다. 몇 가지 메모를 남기기는 했지만, 350페이지를 읽고 난 후에도 내가 자바나 프로그래밍 기법에 대한 사고가 기대했던 만큼 발전했다고 느끼지는 않았다.

 

아마 이 리뷰어는 로버트 마틴의 [클린 코드], 켄트 벡의 [테스트 주도 개발] 같은 느낌의 책을 기대하지 않았을까 싶다. 내 기억으로는 두 책 모두 일정한 형식을 가지고 잘 정돈된 느낌보다는, 서사에 맞춰 저자가 자연스럽게 자신의 이야기를 하는 느낌이었다. 두 책과 비교하자면, 이 책은 다소 실무 메뉴얼 같은 느낌이다. 나름의 정해진 템플렛이 있고, 주제에 따라 그 내용만 다르다. 개인적으로는 이렇게 잘 정돈된 책이야말로, 나중에 다시 폈을 때도 읽기 쉬워 좋은 책이라 생각하긴 한다. 사고력 증진에 대한 의견은, 아마 리뷰를 작성한 사람이 이 이슈들에 이미 충분히 익숙하기 때문이라 생각한다. 하지만 적어도 저자가 의도했던 이 책의 대상 독자 - 실무 경험이 충분하지 않은 중급 개발자에 해당된다면 큰 도움을 얻을 수 있을 것이라 생각한다.

[링크]
I believe that this book is most useful for middle-level software developers who already know the Java language but may have not enough practical programming experience. It is likely that some bug patterns described in the book may be unknown to senior software developers as well. Less experienced developers or even advanced students might also find this book interesting.

 

728x90