[F-Lab 챌린지 29일차 TIL] 재귀함수, 프로젝트

성수데브리·2023년 7월 26일
0

f-lab_java

목록 보기
23/73

알고리즘

재귀 함수

  • 자기 자신을 호출하는 함수
  • 점화식이 존재하는 문제는 재귀 함수로 접근하는 방법이 더 쉽다.
  • 재귀 함수를 사용하면 상태 변화시키는 데 변수를 사용할 일이 줄어 사이드 이펙트를 줄일 수 있다.
  • 단점은 재귀의 최대 범위와 한계점
    • 파이썬은 1000개
  • 꼬리 재귀(tail recursion)
    • 스택 초과 단점을 개선하기 위해 만들어진 개념

    • 재귀 호출을 함수의 마지막 return 문으로만 사용한다.

    • 호출 스택을 추가로 사용할 필요 없이 스택의 현재 영역을 재사용할 수 있다.

      → by Tail Call Optimization. 컴파일러가 자동 최적화 해주는 것. 그러나 파이썬은 안 된다.

      current : 현재 계산 중인 피보나치 수

      next : 다음 피보나치 수

      재귀 호출이 계속 진행되면서 currentnext

      nextcurrent + next 로 업데이트 된다.

      # 꼬리 재귀함수로 구현한 피보나치 수
      def fib_tail(n, current=0, next=1):
      	if n <= 1:
      			return next 
      	return fib_tail(n-1, next, current+next)


프로젝트

테스트의 구성 요소 : 상황, 실행, 결과 확인

  1. LoginAuthenticationProvider 테스트는 Member 조회 부분이 외부 상태(DB)에 따라 테스트 결과가 달라진다.
    • 테스트 대상의 상황과 결과에 외부 요인이 관여할 경우 대역을 사용하면 테스트 작성이 쉬워진다.
    • 대역이란 테스트 대상이 의존하는 대상의 실제 구현을 대신하는 구현이다.



Request Body

문제의 코드

body = request.getReader()
              .lines()
              .collect(Collectors.joining());

return objectMapper.readValue(body, LoginRequestDto.class);

필터에서 로그인 정보를 추출해 VO 객체로 역직렬화하는 코드를 작성했다.

이 코드에 달린 리뷰는 Request body 데이터는 한 번 읽으면 이후 소실될 가능성이 있으니 주의가 필요하다는 것이다.

왜 소실될까?

A ServletRequest object provides data including parameter name and values, attributes, and an input stream. Interfaces that extend ServletRequest can provide additional protocol-specific data (for example, HTTP data is provided by jakarta.servlet.http.HttpServletRequest.

ServletRequest 는 서블릿 컨네이너가 요청이 들어오면 요청에 포함된 정보를 맵핑하는 객체다.

input stream 스트림도 담고 있다.

ServletRequest 에는 input stream 을 처리하는 메서드가 2개가 존재한다.

  1. getReader()BufferedReader 를 사용해 스트림을 문자열로 처리해준다.
/**
 * Retrieves the body of the request **as character data** using a <code>**BufferedReader**</code>. The reader translates
 * the character data according to the character encoding used on the body. Either this method or
 * {@link #getInputStream} may be called to read the body, not both.
 *
 * @return a <code>BufferedReader</code> containing the body of the request
 *
 * @exception java.io.UnsupportedEncodingException if the character set encoding used is not supported and the text
 *                                                     cannot be decoded
 * @exception IllegalStateException                if {@link #getInputStream} method has been called on this request
 * @exception IOException                          if an input or output exception occurred
 *
 * @see #getInputStream
 */
BufferedReader getReader() throws IOException;
  1. getInputStream()ServletInputStream ****을 사용해 스트림을 바이너리 데이터로 처리한다.
/**
 * Retrieves the body of the request **as binary data** using a {@link **ServletInputStream**}. Either this method or
 * {@link #getReader} may be called to read the body, not both.
 *
 * @return a {@link ServletInputStream} object containing the body of the request
 *
 * @exception IllegalStateException if the {@link #getReader} method has already been called for this request
 * @exception IOException           if an input or output exception occurred
 */
ServletInputStream getInputStream() throws IOException;

주석에 친절하게 둘 다 사용할 순 없다고 명시해놨다….

IO 를 처리하는 클래스를 열어보면 왜 두 메서드를 모두 사용하면 안되는지 답이 있다.

/**
     * Tests if this input stream supports the {@code mark} and
     * {@code reset} methods. Whether or not {@code mark} and
     * {@code reset} are supported is an invariant property of a
     * particular input stream instance. The {@code markSupported} method
     * of {@code InputStream} returns {@code false}.
     *
     * @return  {@code true} if this stream instance supports the mark
     *          and reset methods; {@code false} otherwise.
     * @see     java.io.InputStream#mark(int)
     * @see     java.io.InputStream#reset()
   */
  public boolean markSupported() {
      return false;
  }

BufferedReader 의 슈퍼클래스 ReaderServletInputStream 의 슈퍼 클래스인 InputStream 모두 mark()reset() 을 지원하지 않는다.

즉 한번 소비가 끝난 스트림은 처음 위치로 돌아가 다시 읽을 수 없다.

그래서 stream 을 한 번 소비하면 다시 읽을 방법이 없다.

해결 방법은 Request body 의 Stream 을 캐싱해야된다.


Java IllegalStateException: “getInputStream() has already been called for this request” | Baeldung

Reading HttpServletRequest Multiple Times in Spring

JSON 데이터를 자바로 파싱하는 가장 빠른 방법

java json 라이브러리 별 parser 속도 비교. :: 서비의 다락방

0개의 댓글