본문으로 바로가기

[도서] 자바 코딩의 기술 후기

category 개발 서적 정리 2021. 10. 7. 15:28

서점에 들렀다가 자바 코딩의 기술이라는 책의 목차를 보고 멘토링 할 때 받았던 코드 리뷰 내용들을 비교해 보고 싶어서 구매했다.

또 얇고 작기 때문에 부담 없이 읽어볼 수 있을 것 같았다.

책의 부제 '현장에서 뽑은 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!
      }

 


이런 내용들을 주로 다루고 있다. 이펙티브 자바나 클린코드 책에서 코드 리뷰 부분만 떼서 다루는 책 이라고 생각하면 될 것 같다.

이미 알고 있던 내용들도 있었지만 내가 알고있던 지식들이 틀리지 않았다는 것을 확인할 수 있었고 아주 사소한 코딩 습관부터 무심코 지나칠 수 있는 치명적인 코딩 습관까지 다루고 있기 때문에 한 번쯤 읽어보는 것도 괜찮을 것 같다.

 

 

자바 코딩의 기술

내 코드, 정말 괜찮을까?전문가의 코드와 비교하면서 배운다코딩 스킬을 개선하는 가장 좋은 방법은 전문가의 코드를 읽는 것이다. 오픈 소스 코드를 읽으면서 이해하면 좋지만, 너무 방대하고

book.naver.com