반응형

1. 과제

 어플리케이션을 문서화하고 Docker를 사용하여 빌드하라.

 

2. 배운점

2.1. 도커 개념

 도커, 도커 파일, 도커 이미지, 컨테이너와 같은 개념을 정리하였다.

 

2.2. 문서화

 JavaDoc를 사용해 주석들을 문서화하고,  asciidoctor를 사용해 API도 문서화하는 방법을 알아보았다.

 

3. 느낀점

 사실 asciidoctor를 통해 API 문서화 방법을 실습하면서 'Swagger를 통해 API 문서를 생성하는 방법을 알려주는게 더 좋지 않았을까' 라는 생각이 많이 들었다. javaDoc과 같은 경우는 API 명세 뿐 아니라 비지니스 로직에 대한 명세도 상세히 기입할 수 있어 이점이 있으나, asciidoctor과 같은 경우는 개발자가 작성한 테스트 코드를 통해 API 명세를 제공하여 누락될 여지가 있고, 실제 API 테스트도 Swagger가 훨씬 간편하기 때문이다.

 도커의 개념, 도커 파일, 이미지 생성, 컨테이너 빌드에 대한 내용이 한 시간 남짓한 강의로 제공되었는데, 강의의 코드를 보고 따라치는 느낌이라 도커에 대한 자세한 내용은 얻어갈 순 없었다. 도커에 대한 일반적인 프로세스 및 개념정도의 이해를 목적으로 한 것 같았다.

 이로써 마지막 8주차 강의까지 모두 끝이났다. 8주간 받았던 피드백들 하나하나 정말 소중했고, 특히 테스트 주도 개발을 위해 어떻게 시작해야 할지에 대한 방향이 잡힌 것 같다. 8주의 기간동안 받았던 피드백들을 다시한번 복기하며 정리하여 추후 있을 토이 프로젝트 배운 내용을 적용할 수 있도록 할 것이다.

 

반응형
반응형

1. 과제

 Spring Security를 사용하여 비밀번호 암호화 및 인증, 인가를 적용하라.

 

2. 배운점

2.1. JWT RefreshToken과 AccessToken

 인증 시 JWT RefreshToken와 AccessToken을 발급하는 방식을 적용해보았다. RefreshToken은 DB에 저장하였고, AccessToken 만료 시 RefreshToken을 통해 AccessToken을 재발급해주는 기능을 구현하였다.  현재는 AccessToken 만료 시 클라이언트에게 401에러와 Expired 에러를 알려주는데, 이렇게 할 경우 클라가 RefreshToken을 통해 AccessToken을 재발급받는 API를 타야하는 번거로움이 있다. RefreshToken을 세션에 넣어두고 AccessToken이 만료됐을 때 세션에서 RefreshToken을 가져와 AccessToken을 재발급해주는 Filter를 생성한다면 이러한 번거로움을 없앨 수 있을 것 같다.

 

2.2. 어노테이션 기반 인가처리

 이전에는 SecurityConfig 설정 파일 antMatcher.hasRole과 같은 메서드를 사용하여 인가처리를 하였는데,  Controller 메서드에 @PreAuthorize 를 사용하여 인가처리를 하는 방법을 알게되었다. 설정파일에 할 경우 antMatcher.hasRole 메서드의 순서에 따라 본인이 원하는 인가 처리가 되지 않을 수 있는데 반에 이 방식은 보다 명시적인 느낌을 받았다.

 

2.3. JWT + SpringSecurity

  SpringSecurity에  JWT 인증 방식을 적용해보았다. AccessToken와 RefreshToken을 사용한 JWT 인증의 프로세스를 이해하니 폼 인증 방식보다 훨씬 유연하다는 생각이 들었다.

 

3. 느낀점

 RefreshToken, AccessToken을 사용한 JWT 인증, 인가 프로세스, SpringSecurity 설정 파일에 입력했던 CSRF, Session 관련 메서드들의 의미와 사용 이유를 확실히 이해하게 되었다.

 TDD를 위해 테스트 코드를 선 작성하고 로직을 구현하는 것도 훨 자연스러워졌다. 이 기능은 어떤 클래스가 책임져야 할지, 전역객체에 너무 의존하는 로직은 아닌지, 테스트가 어렵진 않을지 생각하며 코드를 작성하는 습관을 갖는게 중요하다는 것도 확실히 느끼게 된 주 차 였다.

 

반응형
반응형

1. 과제

 JWT 토큰을 사용하여 인증, 인가를 구현하라

 

2. 배운점

2.1. JWT

 JWT 토큰을 적용하여 인증 처리를 하였다. 토큰 적용 후 사용목적이나 한계점, 효과적인 토큰 사용 방법들에 대해 공부하며 JWT 토큰에 대한 개념을 확립할 수 있었다. 정리한 개념은 따로 포스팅하였다.

https://tlatmsrud.tistory.com/87

 

2.2. Interceptor에 대한 테스트 코드

 요청에 대한 권한 여부를 체크하는 방식으로 커스텀 어노테이션 방식을 채택했다.

클라이언트로부터 요청이 들어왔을 때 실행되는 컨트롤러 메서드에 커스텀 어노테이션(@CheckJwtToken)이 있을 경우 JWT 토큰을 검증하도록 하였으며 이를 위해 앞단에 Interceptor를 구현하였다.

 Interceptor의 테스트 코드 작성 시 preHandler메서드에 대한 테스트 작성이 어려웠다. HttpServletRequest, HttpServletResponse, Handler에 대한 파라미터를 테스트코드에서 어떻게 넘겨야할지 감이 안잡혔기 때문이다. 우여곡절 끝에 MockHttpServletRequest, MockHttpServletResponse, HandlerMethod로 구현하게 되었다.(멘토님 감사합니다.)

HandlerMethod로 구현한 이유
 디스패처 서블릿은 애플리케이션이 실행될 때 모든 컨트롤러의 메소드를 추출한 뒤 HandlerMethod 형태로 저장해두고, 실제 요청이 들어오면 요청 조건에 맞는 HandlerMethod를 참조하여 해당 메서드를 실행시킨다.
 실제 API에 대한 요청이 들어올 경우 Interceptor의 preHandler 메서드 파라미터인 handler에 HandlerMethod가 들어오기 때문에 HandlerMethod로 구현하였다.

 

HandlerMethod 생성자 파라미터는 beanObject, methodName, parameter로 구성되는데, 테스트 클래스에 MockHandler 클래스를 하나 만들어 아래과 같이 구현하고, beanObject는 테스트 클래스를, methodName에는 @CheckJwtToken가 포함된 메서드 명을 기재하였다. 이로써 Interceptor에 대한 테스트코드를 작성할 수 있었다. 아래는 MockHandler 클래스와 preHandler 메서드에 대한 테스트 코드이다.

class MockHandler{
	@CheckJwtToken
    public void handleRequest(){

    }
}

 

@Test
	void withInvalidTokenReturnFalse() throws Exception {
		MockHttpServletRequest request = new MockHttpServletRequest();
 		MockHttpServletResponse response = new MockHttpServletResponse();

		HandlerMethod handler = new HandlerMethod(new MockHandler(), "handleRequest");

		request.setMethod("GET");
		request.addHeader("Authorization", "Bearer "+INVALID_TOKEN);

		// 유효하지 않은 토큰일 경우 PreHandle 메서드에서 예외가 발생하며 최종적으로 false를 리턴함.
		boolean result = loginCheckInterceptor.preHandle(request, response, handler);

		assertThat(result).isFalse();
 }

 

2.3. ParameterizedTest

 테스트 메서드의 로직은 동일하나 특정 변수 값만 다른 테스트 케이스가 있다. 예를들어 유효하지 않은 토큰 검증에 대한 테스트 시 토큰의 요청 값으로 null, 빈 값, 쓰레기값이 들어갈 수 있는데, 이를 각각 테스트하기 위해 다음과 같이 3개 메서드로 구현할 수 있다.

void parseTokenWithBlankToken(){
	assertThatThrownBy(() -> authenticationService.parseToken(" "))
  		.isInstanceOf(InvalidTokenException.class);
}

void parseTokenWithNullToken(){
	assertThatThrownBy(() -> authenticationService.parseToken(null))
  		.isInstanceOf(InvalidTokenException.class);
}

void parseTokenWithInvalidToken(){
    // INVALID_TOKEN 은 실제 유효하지 않은 토큰을 담은 static 변수임.
	assertThatThrownBy(() -> authenticationService.parseToken(INVALID_TOKEN))
  		.isInstanceOf(InvalidTokenException.class);
}

위의 경우 테스트 로직은 동일하나 토큰 값이 달라서 각각의 메서드를 만들어 처리하였는데, 이처럼 특정 변수 값만 다르고 로직이 동일할 경우 ParameterizedTest를 통해 하나의 메서드로 테스트 가능하다.

@ParameterizedTest
    @ValueSource(strings = { INVALID_TOKEN, "", null})
    void parseTokenWithInvalidTokens(String token){
        assertThatThrownBy(() -> authenticationService.parseToken(token))
                .isInstanceOf(InvalidTokenException.class);
    }

 

3. 느낀점

 실무에서 JWT 토큰 적용을 검토했던 적이 있다. 적용을 목적으로 했던거라 개념적인 내용을 많이 건너뛰었던 것 같았는데 이번 기회에 JWT 토큰에 대한 기본 개념, 사용 이유, 보안상 이점, 한계점과 같은 것들을 공부하게 되어 좋았다. 

 테스트 코드는 Controller, Service, Repository 정도로만 구현을 하다가 이번에 처음으로 Interceptor를 구현하게 되었는데 처음 해보는거라 감이 아예 잡히지 않았었다. prehandle 메서드 검증을 위해 파라미터를 채우는 방법을 몰랐기 때문이었는데, 멘토님께서 도와주시어 해결할 수 있었다.

 이제 2주 남았다. 남은 2주도 미루지 않고 잘 해보자!

반응형
반응형

1. 과제

 TDD 기반의 회원 관리 REST API를 구현하라.

 

2. 배운점

2.1. Validation

 하나의 DTO에 대해 상황에 맞는 Validation을 체크해야하는 부분이 있었다. 예를들어 유저 최초 생성시에는 모든 필드에 대한 NotBlank를 적용하고, 수정시에는 몇몇 필드에 대한 NotBlank를 적용하는 것과 같은 부분이다.

 사내 프로젝트에 적용할때는 DTO Class를 inner Class로 관리하여 Create, Update Class를 생성하고 각각에 대해 Validation을 적용했었는데 찾아보니 Validation Group을 지정하는 방법이 있어 이를 적용해보았다.

 적용해본 결과 그 방법이 간단하지 않고, 결과적으로 하나의 DTO에 여러 기능들이 응집되어 있는 형태가 되었다. 만약 Validation이 더 추가된다면 거대한 DTO가 될 우려가 있다는 멘토님의 조언을 받을 수 있었다. Validation은 InnerClass가 좋은 방법인 것 같다.

 

2.2. 테스트 커버리지의 목적

 과제가 끝나면 테스트 커버리지를 체크한다. 커버리지를 100%로 맞추려하다보니 비지니스 로직이 없는 DTO도 테스트 클래스를 별도로 만들게 되었는데 이렇게 하다보면 결국 '기존 코드와 테스트코드를 1:1비율로 만들게 되겠는데?' 라는 우려가 들기 시작했다. 테스트 커버리지가 100%가 아니라는 건 뭔가 헛점이 있는 어플리케이션이라는 생각이 들었기 때문이다. 이에 대해 멘토님께서 좋은 참고자료를 주셨는데, 글을 읽어보니 필자처럼 100% 커버리지에 너무 신경쓰는 사람들에 대한 따끔한 조언의 포럼이었다.

 테스트 커버리지의 목적은 '테스트 되지 않는 부분이 있는지 확인'하기 위함이다. DTO의 경우 테스트가 필요 없는 부분이므로 굳이 이에대한 테스트코드를 작성할 필요가 없었다.

 

2.3. Fixture 관리

 테스트 코드 작성 중 중복되는 고정 데이터를 Fixture라고 한다. 예를들어 Update를 위한 객체가 이에 해당하는데 다음과 같이 Builder나 new 생성자를 통해 생성하게 된다.

UserData userData = UserData.builder()
                .name("앙김홍집")
                .email("hongzip@naver.com")
                .password("123123")
                .build();

 BDD 테스트의 경우 사용자의 여러 행동을 예측하여 테스트 코드를 작성하게 되는데 이럴 경우 하드코딩이 들어간 이 로직이 중복되게 되는데 이 경우 Enum을 통해 깔끔하게 관리할 수 있다.

 

public enum UserFixture {

    UPDATE_USER(1L, "앙김홍집","hongzip@naver.com","123123"),
    CREATE_USER(2L, "앙생성집","sangzip@naver.com","12345");

    private final Long id;
    private final String name;
    private final String email;
    private final String password;

    private UserFixture(Long id, String name, String email, String password){
        this.id = id;
        this.name = name;
        this.email = email;
        this.password = password;
    }

    public UserData getUserData(){
        return new UserData(id, name, email, password);
    }

}

 

위와같이 Enum을 만들어 놓고 중복되는 부분에서 호출만해주면 된다. 이를 통해 고정 데이터를 관리할 수 있고, 하드코딩으로 인한 문제도 막을 수 있다.

@Test
@DisplayName("UserNotFoundException 예외를 던진다")
void throwsUserNotFoundException() {
	UserData userData = UserFixture.UPDATE_USER.getUserData();

	assertThatThrownBy(() -> userService.updateUser(INVALID_ID, userData))
		.isInstanceOf(UserNotFoundException.class);
}

 

3. 느낀점

 코드를 짜면서 들었던 찝찝함, 궁금증들을 모두 풀수있어 좋았다. Validation의 경우도 실무에서 적용해보았으나 그 방식에 대해 '이렇게 하는게 과연 맞나?' 라는 찝찝함도 이번 기회를 통해 해소할 수 있었다. 또한 멘토분께서 던져주시는 용어들에 대해 알아가는 과정이 너무 좋았다.

  사실 테스트 코드를 작성하는 건 아직 익숙하지 않다. 먼저 테스트코드를 작성하고 리팩토링하고 로직을 적용하는 방식이 너무 생소하지만 계속 하다보면 이것도 몸에 배겠지? 예전 자바지기님의 TDD 강의를 잠깐 들은 적이 있는데 그분께서 하신 말씀이 생각난다. TDD는 운동처럼 계속 해야한다고, 하다보면 자연스럽게 하게된다고.

반응형
반응형

1. 과제

 지금까지 배운 내용을 토대로 '고양이 장난감 가게' 어플리케이션을 개발하라.

 

2. 배운점

2.1. 계층별 단위테스트 방법

 - 계층별 단위테스트 방법을 알게되었다. 일반적으로 각 계층은 하위 계층에 의존적이다. 클린 아키텍쳐로 구성한 고양이 장난감 가게는 Web,  Controllers, Use Cases, Entites 계층으로 구성되었으며, 계층별 단위테스트 방법을 익혔다.

출처 : http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

1) Web

 mockMvc 와 Mock Service(Use Cases)로 테스트하였다. mockMvc는 서블릿 컨테이너의 구동 없이 HTTP 서블릿 요청과 응답을 제공하는 유틸리티 클래스이고, Mock Service는 Controller가 의존하는 Service를 Mocking 한 객체이다. Web 어플리케이션의 REST API 호출 테스트가 목적이기에 실제 Service 로직을 확인할 필요가 없으므로 Mocking한다.

 

2) Controllers

 Controller와 Mock Service(Use Cases)로 테스트하였다. 위와 마찬가지로 Controller 테스트가 목적이기에 Service를 Mocking 하였다. Controller가 의존하는 Service에 대해서는 메서드 호출 여부만 확인하면 된다. 확인이 필요한 메서드는 verify 메서드를 통해 확인하였다.

 초기 테스트 코드에는 Controller가 호출하는 모든 Service 메서드에 대해 verify 검증을 하였으나, Mock Service의 메서드에 Stubbing을 하여 응답 지정하고, 테스트를 통해 이 값을 받는 행위 자체가 Service의 메서드를 호출한 것이므로 메서드 검증을 할 필요가 없었다. 해서 응답 값이 없거나, 확인이 어려운 메서드에 대해서만 verify로 검증하였다.

 

3) Use Cases(Service or Application)

 Service와 Mock Repository로 테스트하였다. Service 테스트가 목적이기에 의존하는 Repository를 Mocking하였다. 마찬가지로 응답값이 없거나, 확인이 어려운 메서드에 대해서만 verify로 검증하였다.

 

4) Entities

 Repository와 서버 방식의 h2 DB로 테스트하였다. JPA를 사용하는 경우 JPA에서 지원하는 CrudRepository 같은 클래스를 상속받아 가져다 쓰므로 사실 검증할 필요가 없다. 단, 직접 쿼리를 작성할 경우에는 검증이 필요하다.

 

3. 느낀점

 비록 매우 단순하지만 클린 아키텍쳐가 적용된 프로그램의 단위 테스트코드를 작성해봄으로써 테스트코드에 대한 메커니즘을 이해할 수 있었다.

 필자는 이번 과제 중 Repository에 대한 테스트가 계속 실패하는 이슈가 있었다. 도저히 이해가 되지 않아 필자가 모르는 JPA의 영속성 컨텍스트 이슈로 판단하였고, 멘토님께 도움을 요청하였다.

 원인은 단순 테스트 데이터를 Insert하는 부분이었으며, 인메모리가 아닌 실제 DB 서버 방식으로 변경 후 디버깅하며 DB 데이터를 확인한 결과 필자가 예상하지 못한 데이터가 들어있었다. 데이터가 이렇게 들어간 원인도 정말 너무 단순했다.

 멘토님의 피드백 중 Repository 테스트 방식에 대해 언급해주신 내용이 있다. DB를 인메모리 방식의 h2를 많이 사용하나 개발자가 생각한 동작과 다를 수 있다는 점에서 실제 DB 서버와 연결하여 사용하는 것을 권장한다는 것. 이 내용이 사실 와닿지 않았지만, 내가 한 짓을 통해 단박에 이해하게 되었다! 5주차도 열심히!

반응형
반응형

1. 과제

 테스트 코드를 작성하라.

2. 배운점

2.1. 테스트 코드

토이프로젝트에 TDD를 적용해볼까 하고 테스트코드에 대해 알아본 적이 있었다. 무작정 예제코드를 통해 공부를 시작했는데 개념과 방법론을 이해하지 못하니 어렵고 복잡하게만 느껴져 건너 뛴적이 있다.

 이번 주자에 테스트 코드를 작성하면서 개념과 방법론에 대해 다시 궁금해졌다. 사용하는 개념들을 정리하고 멘토님의 조언을 통해 TDD의 필요성을 조금은 이해하게 된것같다. 그중에서도 특히 이해가지 않던 Mock Object. 가짜객체에 대한 개념과 사용 이유를 확실히 이해하게 되었다. 이 내용은 현재 정리중이며 어느정도 정립이 되었을 때 블로그에 올리도록 하겠다.

 

2.1. given.willthrow에 대한 문제

 예외를 설정하는 stubbing 메서드인 given.willthrow를 호출했을 때 실제로 해당 예외가 발생하는 이슈가 있었다.

 원인은 동일한 상황에 대한 stubbing 케이스에 대해 given.willthrow 구문이 두번 실행될 경우 발생했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    ...
    
        @Nested
        @DisplayName("Task 상세조회 테스트")
        class TaskDetailTest{
            
            @BeforeEach
            void setUp(){
                ...
                
                   given(taskService.getTask(INVALID_ID)).willThrow(new TaskNotFoundException(INVALID_ID));
          }
 
            @Test
            @DisplayName("유효 ID 상세조회")
            void detailWithValidId() throws Exception {
                ...
            }
 
            @Test
            @DisplayName("유효하지 않는 ID 상세조회")
            void detailWithInvalidId() throws Exception {
 
                mockMvc.perform(get("/tasks/"+INVALID_ID))
                        .andExpect(status().isNotFound())
                        .andExpect(content().string("{\"message\":\"Task not found\"}"));
            }
        }
cs

TaskDetailTest에 대한 Junit 테스트를 실행할 경우 detailWithValidId, detailWithInvalidId 메서드에 대한 테스트를 각각 진행하게 되는데 @BeforEach 어노테이션으로 인해 한 메서드의 테스트가 시작할때마다 setUp() 메서드를 호출하게 됐고, 두번째 테스트 시 호출되는 setUp() 의 given절에서 TaskNotFoundException 이 발생하게 됐다.

다양한 테스트를 통해 실제 예외가 발생한 구간을 알아내었다.

 

1. setUp() 메서드 호출

2. given() 메서드를 통해 taskService.getTask에 대한 stubbing 객체 생성

3. willThrow를 통해 stubbing에 대한 가짜 예외 생성

4. detailWithValidId 테스트 코드 실행

5. setUp() 메서드 호출

6. given() 메서드를 통해 taskService.getTask에 대한 stubbing 객체 시 TaskNotFoundException  발생!!

 

즉, taskService.getTask에 대한 stubbing이 정해진 상태에서 다시 given() 을 통해 해당 taskService.getTask()에 대한 stubbing을 생성하려 하나 이미 TaskNotFoundException을 갖고 있기에 해당 값이 반환되고 있었다.

reset 메서드를 이용하여 taskService를 리셋시키거나, given.willthrow 구문이 한번만 실행될 수 있도록 setUp에서 detailWithInvalidId 로 이동시켜주면 된다.

 

3. 느낀점

테스트 코드를 처음 작성하며 느낀건,

첫번째, 테스트 코드는 객체 지향적이고 클린한 코드를 만들수 있도록 노력하게 해준다(?)

 단위 테스트는 객체, 메서드 단위이다. 결합도가 높거나 코드가 복잡할 경우 테스트의 범위도 커지기 때문에 다양하고 정밀한 테스트가 어려워진다. 때문에 테스트를 위해 객체지향적이고 클린한 코드를 지향하게 되는 느낌을 받았다.

 

두번째, 현업에서 TDD를 지양하는 이유

 작성한 모든 로직에 대한 테스트 코드를 작성하니 실제 로직을 작성하는 시간과 테스트 코드를 작성하는 시간이 비슷하게 들었다. 실제 서비스를 개발하는 현업에서 TDD가 포함된다면 그 공수는 배로 들지않을까.. 라는 생각과 함께 TDD를 지양하는 이유에 대해 확실히 느끼게 되었다.

 

세번째, 테스트를 코드로

 현업에서 어떠한 테스트를 한다라는 건 실제로 API를 호출해 보는 것이었다. 테스트는 로직이 개발되거나 수정됐을 때 진행했기에 코드가 수정이 될 때마다 실제 API를 호출했다. 하지만 이를 코드로 구현하니 테스트에 걸리는 시간을 많이 줄일 수 있고, 현업에서 TDD를 지양하는 이유인 '시간'이 이 상쇄될 수 있지 않을까라는 생각이 들었다. 테스트를 코드로 한다는 건 딱 들었을땐 당연한 소리같지만 실제 현업에서는 당연하지만은 않았기 때문이다. 테스트를 코드로 구현한다는 것에 다시금 많은 생각이 들었던 주차였다.

 

반응형
반응형

1. 과제

 객체지향적인 REST API를 스프링 부트로 구현하라.

 

2. 배운점

2.1. HTTP Status 를 개념있게 사용하자

 서버 오류가 발생했을 때는 500, 리소스가 없을때는 404, 성공했을 때는 200, 요청 값이 비정상적일때는 400. 개발을 하면서 내가 접했던, 그리고 아는 HTTP Status는 딱 이정도였다. 이번 과제를 진행하며 처음으로 201, 204 등의 코드를 접해보았으나 의미와 개념을 파악하지 않고 '메서드에 따라서 상태코드가 달라질 수 있구나' 라고만 생각하고 넘겼다.

알고보니 201은 요청성공 및 리소스 생성일때, 즉 Create한 요청을 보냈을 때의 상태코드이고, 204는 요청 성공했으나 응답값이 없을 때, Delete한 요청을 보냈을 때 사용 가능한 상태코드이다. 404도 클라이언트 자원(html, css, img 등)이 없을 때의 상태코드로 알고있었으나 클라이언트 자원 뿐 아니라 서버 리소스가 없을 때도 404 상태코드로 표현이 가능했다.

이를테면 id에 매핑된 정보를 수정요청할 때 서버에서 유효한 id 값인지 확인하게되는데 이 값이 없을 경우 상태코드를 404로 리턴해도 된다.

 100번 대는 정보응답, 200번 대는 성공, 300번 대는 리다이렉트 400번 대는 클라이언트 에러, 500번 대는 서버 에러로 백의자리 숫자에 따라 상태코드가 구분된다는 것도 처음 알게 되었다.

아래는 HTTP 상태코드에 대한 공식문서이다.

https://developer.mozilla.org/ko/docs/Web/HTTP/Status

 

2.2. 스프링 부트야 고맙다.

 1주차에서는 스프링 없이 Java로만 코드를 짜다보니 코드가 길어지고 시간도 많이들었다. 이번 주차에 스프링 프레임워크를 사용하니 훨씬 쉽고 깔끔한 코드를 구현할 수 있었다. 나쁘게만 보였던 스프링 부트가 조금은 착해보인다.

 

2.3. 공식문서 보는 습관을 들이자.

 람다식을 사용하니 멘토님께서 공식문서 주소를 주시며 한번 봐보라고 하셨다. 하지만 필자는 공식문서의 영어가 보이는 순간 자신감을 잃는다. 한글로 번역을 때리면 코드들도 번역이 되기에 블로그를 기웃기웃 거렸다.

 하지만 생각과 달리 공식문서에서 제공되는 예제 코드들은 정말 간단하고 이해하기 쉽게 되어있었다. 설명들은 번역기로 돌려가며 이해했고, 코드들은 쭉 코딩해보니 맥락이 이해되면서 예제코드도 이해되기 시작했다. 그리고 무엇보다 간지가 좀 나는것 같다. 공식문서를 읽는 개발자... 아무쪼록 공식문서를 보는 습관을 들여보겠다!

3. 느낀점

이번 과제는 1주차보다 빨리 끝났다. 이유는 스프링 문법을 사용했기 때문이다. 생각없이 사용했던 어노테이션과 스프링 문법들에 대해 생각의 여지를 갖게 해준 시간이었던 것 같다.

 HTTP Status 코드를 새롭게 이해하게 됐고, 공식문서를 보는 게 얼마나 중요한 것인지도 알게되었다. 뭔가 다음주차부터는 큰 고통받을 것 같아 두려운 마음이 있지만 멘토님이 있으니 걱정안한다. -ㅅ-

반응형
반응형

1. 과제

 객체지향적인 REST API를 구현하라

 

2. 배운점

2.1. '객체에게 묻기보단 시켜야 한다.'

 이번 주차에 가장 나에게 와닿았던 피드백이다. 객체가 갖고있는 정보에 대해서 해야할 일이 있을 때 단순히 그 정보를 받아온 후에 다른 객체에서 뭔가를 처리하는게 아니라 그 처리마저도 객체에게 시켜야한다는 의미였다.

처음엔 이 의미가 이해가 가지 않아 현재 내 코드를 '후임에게 업무를 맡기는 상황'에 적용해보았다.

 

 일반적으로 후임에게 업무를 맡길때  '~에 대한 A, B, C 업무를 모두 처리해주시고, 저한테 보고해주세요' 라고 시킨다. 그런데 내 코드는 '~에 대한 업무를 처리해주시되, C 업무는 저한테 주시면 제가 알아서 처리할게요' 였다. (ㅜㅠ)

객체의 멤버필드에 대한 비지니스 로직이 필요한 부분이 있었는데, 나는 해당 멤버필드를 get메서드로 가져온 후 다른 객체에서 로직을 처리하고 있었기 때문이다.

 

 만약 이 상황에서 업무 내용이 변경되거나 차질이 생긴다면 어떨까?

 전자의 경우 업무에 대한 모든 책임을 후임이 맡게된다. 후임은 작업을 하고 나는 보고받으면 된다.

 후자의 경우 A, B 업무에 대한 책임은 후임이, C 업무에 대한 책임은 내가 맡게 된다. 맡은 업무에 대해서 작업은 책임을 맡은 사람이 하며, A, B, C 모두 관련성 있는 업무이기 때문에 나와 후임 모두 사이드 이팩트를 확인하는 일을 추가적으로 해야한다. 결국 업무에 대한 응집도는 분산되어 낮아지고, 결합도는 나와 후임으로 증가하는 느낌이 들었다.

 

 '객체에게 묻기보단 시켜야 한다.' 라는 피드백이 객체지향적인 설계의 가장 기본을 지키세요 라는 의미가 아니었을까 라는 생각과 함께 자바 개발자로써의 부끄러움와 지금이라도 알게되어 다행이라는 안도감이 몰려왔다.

 

2.2. 관습을 지키자.

 코드를 짜면서 사실 언어의 관습에 대해 생각해본적이 없다. 언어별 코드 정렬 방식, 변수 네이밍 및 케이스 등.

그런데 관습을 지키지 않는다면 관습을 지키는 개발자들에게 혼란을 야기할 수 있다는 걸 알았다.

 

2.3. 변수명에 대한 고민

 변수명은 개발자가 이해하기 쉬운 이름으로 설정하자. 불필요한 축약이나 본인만 알 수 있는 네이밍을 피해야 하고, 상황에 따라 실제 사용하는 기술 명칭을 넣는 것도 방법이다.

 

배운점을 3개정도 썼지만 사실 이것말고도 아주 많다. 1번은 너무 강렬하게 다가와 꼭 정리를 하고 싶어 길게 썼다.

 

3. 느낀점

 처음 과제를 접했을 때 너무 간단하게 느껴져 하루 이틀이면 끝날것이라 생각했다.

 하지만 코드를 fork, commit, pr하는 과정부터 버벅거렸고, 코드 리뷰를 받다보니 어느새 일주일이 지나있었다. 멘토님께서 개선이 필요한 부분을 찾아주셨으나, 계속적으로 피드백이 온걸 보면 거의 지뢰찾기 게임을 하시지 않았나 싶다. 너무 감사했지만 한편으로는 조금 죄송스러웠다.

 이제 1주차인데 나의 잘못된 코딩 습관들을 고쳐야 하고, 언어에 대해 배워야할 것들이 아주 많다는 것을 느꼈다. 언어의 특성에 대해 너무 생각없이 개발해 온 지난 세월에 대해 안타까움도 느껴졌다. 이번 피드백을 머릿속에 새기고 내일부터 있을 2주차에 적용할 수 있도록 고민하며 개발할 것이다. 2주차도 열심히!

 

 

 

 

반응형

+ Recent posts