Java Refactoring -10, 원래 기능과 다른 Null 예외 처리 개선

박태건·2021년 7월 21일
0

리팩토링-자바

목록 보기
10/13
post-thumbnail

레거시 코드를 클린 코드로 누구나 쉽게, 리팩토링

위 책을 보면서 정리한 글입니다.

원래 기능과 다른 Null 예외 처리 개선

Null에 대한 반복 처리는 코드의 독립성과 신뢰성을 떨어뜨린다.

  • 프로그램 수행 과정 중, 전달 받은 객체가 Null이면 분기문으로 객체를 확인하고 예외 로직을 수행한다.
  • 무의식적으로 객체를 확인하는 분기문과 예외 처리 로직이 코드 곳곳에서 반복된다.
    • 대부분 객체를 전달하는 곳에서 객체가 Null인 상황을 알 수 있다.

  • 전달하는 곳에서 객체가 Null인 상황을 미리 알고 이에 대한 예외 처리를 대신 수행하는 객체로 전달하면
  • 이를 전달받은 곳에서는 예외 처리가 적어도 한 개는 줄어든다.
    • Null에 대한 예외 처리를 하는 객체를 Null 객체라고 부른다.

  • 객체가 Null일 때, 예외 처리를 특정 객체에 위임하면 로직 내에서는 객체를 통한 로직 수행에만 집중 가능하고
  • 코드의 간결성과 가독성을 높일 수 있다.

개선방향

객체를 전달하는 클래스에서 예외 처리를 처리하도록 하자.

질문답

단순 Null 확인에도 Null 객체를 별도로 정의해서 사용해야 할까

  • 일회성이라면 만들 필요가 없다. 다만, Null 확인이 반복적으로 나타나거나 예외 처리를 위해 별도의 로직이 추가될 때
    • 이 때는 Null 객체를 따로 정의하면 본래의 기능에 집중 가능해 코드의 가독성이 높아진다.
    • 또한, 의도치 않게 Null 확인이 빠진 부분에서 이를 보완할 수 있는 코드가 되기도 한다.

전달받은 객체가 Null인지를 확인하고 예외 처리를 직접 하는 것이 문제가 될까

  • Null 처리의 또 다른 문제는, 인터페이스를 사용하는 객체들이 Null에 대한 처리를 해야 한다는 것.
    • 이는 의도하지 않은 기능이 포함되어 단일 책임의 원칙에 위배 된다.
    • Null 객체의 핵심은 Null 상황에 대한 처리를 직접 하지 않고 해당 인터페이스를 정의한 객체로 위임한다는 것에 있다.

레거시 코드

SnsWriter 클래스

public class SnsWriter {
    private List<String> accounts;

    public SnsWriter(List<Account> accounts) {
        this.accounts = accounts;
    }

    public void write(String message, String link, String imagePath) {
        for(Account account : accounts) {
            Writer writer = account.getWriter();

            // Writer가 Null인 SNS에 대해서는 재인증 요청
            if(writer != null) {
                writer.write(message, link, imagePath);
            } else {
                // 사용자에게 인증 요청
                new Author().requestAuth();
            }
        }
    }
}

Account 인터페이스

public interface Account {
    // 해당 SNS에 필요한 게시물 등록 객체 가져오기
    public Writer getWriter();
}


FaceAccount 클래스

public class FaceAccount implements Account {
    private String authKey;

    public FaceAccount(String authKey) {
        this.authKey = authKey;
    }

    @Override
    public Writer getWriter() {
        if(authKey != null) {
            // 인증 정보가 있으면 게시물 등록 객체 전달
            return new FaceWriter(authKey);
        } else {
            // 인증 정보가 없으면 Null 전달
            return null;
        }
    }
}

Writer 인터페이스

public interface Writer {
    public void write(String message, String link, String imagePath);
}

FaceWriter 클래스

public class FaceWriter implements Writer {
    private final String authKey;

    public FaceWriter(string authKey) {
        this.authKey = authKey;
    }

    @Override
    public void write(String message, String link, String imagePath) {
        // 게시물 등록
    }
}

위 코드의 문제점
1. SnsWriter 객체에는 Account가 전달하는 Writer가 Null인지를 확인하고 이에 대한 예외 처리를 하는 로직이 포함되어 있다.

레거시 코드 개선 과정

Null 객체 정의

  • SnsWriter의 Null 처리를 NoAuthWriter로 이동하여 NoAuthWriter.wirte()에서 사용자에게 인증을 요청하는 로직을 정의

NoAuthWriter 클래스

public class NoAuthWriter implements Writer {
    @Override
    public void write(String message, String link, String imagePath) {
        // 사용자에게 인증 요청
        new Author().requestAuth();
    }
}

Account 구현체의 getWriter() 수정

public class FaceAccount implements Account {
    private String authKey;

    public FaceAccount(String authKey) {
        this.authKey = authKey;
    }

    @Override
    public Writer getWriter() {
        if(authKey != null) {
            // 인증 정보가 있으면 게시물 등록 객체 전달
            return new FaceWriter(authKey);
        } else {
            // Null - NoAuthWriter
            return new NoAuthWriter();
        }
    }
}

테스트 코드 생성

public class AccountTest {
    @Test
    public void testGetWriter() throws Exception {
        // Given
        // 인증 정보가 없는 계정을 설정
        FaceAccount faceAccount = new FaceAccount(nul);

        // When
        Writer writer = faceAccount.getWriter();

        // Then
        // Null Object로 반환되므로 Null이 아니다
        Assert.assertNotNull(writer);

        // 의도한 Writer와 동일한지 확인
        Assert.assertEquals(NoAuthWriter.class, writer.getClass());
    }
}

SnsWriter 수정

public class SnsWriter {
    private List<String> accounts;

    public SnsWriter(List<Account> accounts) {
        this.accounts = accounts;
    }

    public void write(String message, String link, String imagePath) {
        for(Account account : accounts) {
            Writer writer = account.getWriter();
             writer.write(message, link, imagePath); 
        }
    }
}

개선된 레거시 코드

NoAuthWriter 클래스

  • Writer가 Null인 경우 처리할 예외 로직들이 추출된 클래스로, 개선 전 SnsWriter에 포함되어 있던 Null 처리 로직이 구현

Account 클래스

  • 인증 정보가 없으면 NoAuthWriter를 반환.
  • 개선 전에는 Null을 반환하였으나 Null을 대신할 객체를 반환하도록 변경.

SnsWriter 클래스

  • Account 객체가 반환하던 Writer 객체가 Null인지 확인할 필요 없이 Writer.writer()를 수행.
  • Account 객체에서는 더는 null을 반환하지 않으므로 null 확인이 불필요하여 예외 로직또한 반환 객체로 수행 가능.

요약 및 정리

반환된 객체가 Null일 때 추가로 처리되는 부분을 객체로 처리 가능하도록 개선

  • Null 객체는 예외 처리만을 위한 객체
  • 한 기능을 수행하기 위해 별도의 객체를 정의하는 것이 불편할 수 있지만 Null 객체의 가장 큰 장점은 코드 내에서 Null을 예측하고 이를 위한 예외 로직이 다른 곳으로 이동하는 것.
  • 이는 코드가 원래의 한 가지 기능을 수행할 수 있도록 개선한다.

Null 객체 패턴을 이용하여 개선하기 위한 생각의 흐름

  1. 전달받은 객체가 Null일 때 처리 로직이 있는지 확인
  2. 객체를 전달하는 곳에서도 Null을 전달하는 코드가 있는지를 확인
  3. 공통으로 Null을 처리 할 수 있는 객체를 만든다.
  4. Null인 객체를 전달하는 경우에는 예외 처리를 수행하는 Null 객체를 전달.
profile
노드 리액트 스프링 자바 등 웹개발에 관심이 많은 초보 개발자 입니다

0개의 댓글