서점에 들렀다가 자바 코딩의 기술이라는 책의 목차를 보고 멘토링 할 때 받았던 코드 리뷰 내용들을 비교해 보고 싶어서 구매했다.
또 얇고 작기 때문에 부담 없이 읽어볼 수 있을 것 같았다.
책의 부제 '현장에서 뽑은 70가지 예제로 배우는 코드 잘 짜는 법' 으로, 초보 개발자인 나의 구미를 당기게 했다.
아래는 책에서 다루고 있는 내용 중 일부이다.
참조 누수 피하기
class Inventory {
private final List<Supply> supplies;
Inventory(List<Supply> supplies) {
this.supplies = supplies;
}
List<Supply> getSupplies() {
return supplies;
}
}
명백하지 않은 객체에는 외부에서 접근할 수 있는 내부 상태가 거의 항상 있습니다.
이러한 상태를 어떤 방식으로 조작할지 신중히 결정해야 합니다. 그렇지 않으면 심각한 버그를 초래할 수 있습니다. 위 코드에서 예로 든 Inventory는 자료 구조를 포함하는 매우 일반적인 클래스 입니다. 자료 구조는 외부에서 먼저 초기화 된 후 Inventory의 생성자에 삽입됩니다.
클래스 자체로는 문제가 없지만 사용 예를 한 번 살펴봅시다.
class Usage {
static void main(String[] args) {
List<Supply> externalSupplies = new ArrayList<>();
Inventory inventory = new Inventory(externalSupplies);
inventory.getSupplies().size(); // == 0
externalSupplies.add(new Supply("Apple"));
inventory.getSupplies().size(); // == 1
inventory.getSupplies().add(new Supply("Banana"));
inventory.getSupplies().size(); // == 2
}
}
우선 빈 externalSupplies
를 새 Inventory에 전달하고 이어서 getSupplies()
가 빈 리스트를 반환합니다. 하지만 inventory는 내부의 제품 리스트를 전혀 보호하지 않습니다. externalSupplies
리스트에 제품을 추가하거나 getSupplies()
가 반환한 리스트에 변경 연산을 수행하면 재고 상태가 바뀝니다.
supplies 필드에 final 키워드를 붙여도 이러한 동작을 막지 못합니다. 그 뿐만 아니라 나중에 쉽게 예외를 발생시킬 수 있는 null 또한 생성자로 전달될 수 있습니다.
class Inventory {
private final List<Supply> supplies;
Inventory(List<Supply> supplies) {
this.supplies = new ArrayList<>(supplies);
}
List<Supply> getSupplies() {
return Collections.unmodifiableList(supplies);
}
}
위 코드의 Inventory는 내부 구조를 훨씬 더 잘 보호합니다. 전달한 리스트의 참조가 아니라 리스트 내 Supply 객체로 내부 ArrayList를 채웁니다. 그리고 null이 들어오면 바로 예외를 발생시킵니다.
또한 내부 리스트를 get 하는 과정에서 unmodifiableList()
로 래핑한 후 노출합니다. 이로써 읽기 접근만 가능하죠. 리스트에 원소를 추가하려면 이러한 기능을 하는 명시적인 메서드르 작성해야 합니다.
class Usage {
static void main(String[] args) {
List<Supply> externalSupplies = new ArrayList<>();
Inventory inventory = new Inventory(externalSupplies);
inventory.getSupplies().size(); // == 0
externalSupplies.add(new Supply("Apple"));
inventory.getSupplies().size(); // == 0
// UnsupportedOperationException
inventory.getSupplies().add(new Supply("Banana"));
}
}
빈 catch 블록 설명하기
가끔은 예외를 넘기고 아무것도 하지 말아야 할 때도 있습니다. 그래도 빈 catch 블록을 마주하면 항상 이상한 기분이 들죠. ( "이거.. 잘못 짠 코드인가?, 혹시 버그인가 ? ") 아무리 생각해 보아도 쉽게 답을 찾을 수 없습니다. 이는 결국 소중한 프로그래밍 시간을 다시 확인하는데 낭비하게 됩니다.
class Logbook {
static final Path LOG_FOLDER = Paths.get("/var/log");
static final String FILE_FILTER = "*.log";
List<Path> getLogs() throws IOException {
List<Path> result = new ArrayList<>();
try (DirectoryStream<Path> directoryStream =
Files.newDirectoryStream(LOG_FOLDER, FILE_FILTER)) {
for (Path logFile : directoryStream) {
result.add(logFile);
}
} catch (NotDirectoryException e) {
}
return result;
}
}
→ 빈 catch 문에 주석으로 표시해둔다.
→ Exception 변수명을 ignored
로 선언한다.
} catch (NotDirectoryException ignored) {
// No directory -> no logs!
}
이런 내용들을 주로 다루고 있다. 이펙티브 자바나 클린코드 책에서 코드 리뷰 부분만 떼서 다루는 책 이라고 생각하면 될 것 같다.
이미 알고 있던 내용들도 있었지만 내가 알고있던 지식들이 틀리지 않았다는 것을 확인할 수 있었고 아주 사소한 코딩 습관부터 무심코 지나칠 수 있는 치명적인 코딩 습관까지 다루고 있기 때문에 한 번쯤 읽어보는 것도 괜찮을 것 같다.
'개발 서적 정리' 카테고리의 다른 글
[Modern Java in Action] null 대신 Optional을 사용하기 (0) | 2020.08.02 |
---|---|
[Modern Java in Action] Stream 2편 (0) | 2020.07.30 |
[Modern Java in Action] 함수형 인터페이스 (0) | 2020.07.25 |
[Java8] .map() vs .flatMap() 차이점 알아보기 (0) | 2020.07.24 |
[Modern Java in Action] Stream (0) | 2020.07.13 |