기억이란 지나고 나면 휘발되기 때문에 과정을 기록하는 것은 매우 중요하다.
기록은 다양한 기회가, 무대가 나를 찾아오게 한다.

 

이번주에 진행했던 개발자 퍼스널브랜딩 특강 중 기억에 남는 문장이다.

 

모르고 있던 내용이 아니다. 

기록이 중요성은 직, 간접적으로 배웠지만 다양한 이유(게으름, 귀찮음, 시간 없음, 능력 없음)를 핑계로 거의 하지 않았다.

중요성은 알았지만 필요성을 느끼지 못했던 것이 아닐까 싶다.

 

최근 개발자 퍼스널브랜딩 교육을 5주째 듣고 있다. 

단언컨데 본인이 개발자로서 다음 스텝이 막막하다면 매우 추천한다.
이에 대한 내용은 곧 써볼 예정이다. 혹시 너무 궁금하다면 https://brunch.co.kr/@moon-sky/27 구경해보는걸 추천!
벌써 한 회 차 밖에 남지 않았다는 것이 매우 아쉽다.

 

 

개발자를 위한 퍼스널 브랜딩 워크숍

나만의 강점을 찾고, 이를 기반으로 퍼스널 브랜딩을 시작하는 방법 | 개발자들 위해 디자인된 교육 플랫폼 NextStep에서 개발자들의 퍼스널 브랜딩을 위한 강의를 오픈하였습니다. 개발자를 위한

brunch.co.kr

이 과정에서 ‘기록’은 중요하다고 느껴졌던 몇 가지 포인트가 있다.

첫 번째로는 글은 본인을 나타낼 수 있는 가장 쉬운 방법 중 하나인 점이다. 특히, 말로 표현하기를 어려워하는 사람들에게 글은 충분한 시간을 가지고 작성할 수 있으니 스스로를 표현할 수 있는 최선의 방식이라 느껴졌다.

두 번째로는 언제 어디로 갈지 모르는 개발자의 숙명(?)에 도움이 된다는 점이다. 진행했던 업무들의 히스토리를 기억하는 것은 필수이지 않나 싶다. 과거 면접에서 분명히 내가 했던 일이었지만 기억이 나지 않아 얼레벌레 이야기하면서 화끈거렸던 적이 있다. 기록이 있었다면 기억을 떠올리는데 훨씬 도움이 되었을 것이다.

그래서 휘발되는 기억에 의존하기보단 기록으로 남겨둬야겠다고 생각했다.

 

과거에도 다양한 방식으로 글쓰기를 시도했던 적이 있다. 하지만 꾸준함으로 이어지진 못했다. 이유가 뭘까 곰곰이 생각해 봤다.

기록을 어렵게 생각했던 점이 가장 큰 원인이 아니었을까 싶다.

잘못된 것을 쓰면 안 된다는 강박은 글을 쓰는데 허들이 되었고

잘 읽히는, 재밌는 글을 써야 한다는 생각은 고민만 키우고 기록으로 이어지지 않았다.

 

최근에 느낀 것은 이런저런 걱정들은 차치하고 ‘JUST DO IT’ 이 중요하다는 것이다.

고민만 하다가 시도 조차 하지 못한 것보다 가볍게 생각해서 일단 시도하는 것이 더 가치가 있다.

시도를 해야지 잘못된 것을 고칠 수도 있고, 피드백을 통해 나도 발전할 수 있다.

지금 이 글도 기록에 대해 어려워할 때마다 나의 마음을 다시 상기시킬 수 있도록 작성 중이다.

 

마음가짐을 바꾸고 나니 쓰고 싶은 글감이 많아졌다.

나를 브랜딩 할 수 있는 여러 가지 방법 중 올해 처음으로 시도하는 행동으로 기록을 선택했다. 

당장의 넘치는 열정을 금방 소진시키지 않기 위해 일주일에 1회 작성을 목표로 가져가려고 한다. 

 

벌써 다음글을 쓸 생각에 두근두근 거리는 중이다. 

'MyStory > 에세이' 카테고리의 다른 글

2023년! 아직 반이나 남았다.  (0) 2023.07.24
2023년 나의 목표와 계획에 대한 고찰  (0) 2023.03.11

5주 차 강의 내용 정리

  • 단위 테스트
  • 통합 테스트

단위 테스트

 - 특정 단위(테스트 대상)가 의도한 대로 작동하는지 검증

 

단위란? 

 - 사람마다 정의가 다를 수 있음. 작은부분이라는 점에 초점을 맞추면 됨.

 - 단일 메서드에서 전체 클래스까지 될 수 있음.

 

통합과 고립(Sociable and Solitary)

 - 단위 테스트 작성 시 관계를 맺고 있는 대상(협력 객체)이 있는 경우를 고려해야 함.

 - 협력 객체를 실제 객체로 사용할지, Mock 객체로 사용할지에 따라 테스트 구현이 달라짐.

 

단위의 정의를 논하기 앞서 테스트하는 단위가 통합(Sociable)되어야 하는지 고립(Solitary)되어야 하는지 고려해야 .


Test Double

 - 테스트 목적으로 실제 객체 대신 사용되는 모든 종류의 객체를 표현하는 일반 용어.

 - 즉 실제(클래스, 모듈, 매서드)를 가짜 버전으로 대체한다는 의미.

Stubbing

 - 테스트 동안 호출이 되면 미리 지정된 답변으로 응답.

 - 미리 지정된 것 외의 것에 대해서는 응답 하지 않음.

 

Mockito, MockitoExtension, Spring 등을 활용한 Stubbing 방법이 있다.

 

MockitoExtension을 활용한 예시

@ExtendWith(MockitoExtension.class)
public class AuthServiceTest {
    public static final String EMAIL = "email@email.com";
    public static final String PASSWORD = "password";
    public static final int AGE = 10;

    private AuthService authService;

    @Mock
    private MemberRepository memberRepository;
    @Mock
    private JwtTokenProvider jwtTokenProvider;

    @BeforeEach
    void setUp() {
        authService = new AuthService(memberRepository, jwtTokenProvider);
    }

    @Test
    void login() {
        when(memberRepository.findByEmail(anyString())).thenReturn(Optional.of(new Member(EMAIL, PASSWORD, AGE)));
        when(jwtTokenProvider.createToken(anyString())).thenReturn("TOKEN");

        TokenResponse token = authService.login(new TokenRequest(EMAIL, PASSWORD));

        assertThat(token.getAccessToken()).isNotBlank();
    }
}

 

Test Double은 언제 쓰는가?

 - 테스트 대상이 협력 객체를 가질 때 사용한다.

 

실제 객체를 사용하면 협력 객체의 행위를 협력 객체 스스로가 정의

가짜 객체를 사용하면 협력 객체의 행위를 테스트가 정의


가짜 객체 특징

  • 가짜 객체를 사용 할 경우 테스트 대상을 검증할 때 외부 요인(협력 객체)으로 부터 철저히 격리
  • 하지만 테스트가 협력 객체의 상세 구현을 알아야 함

실제 객체 특징

  • 실제 객체를 사용 할 경우 협력 객체의 상세 구현에 대해서 알 필요가 없음
  • 하지만 협력 객체의 정상 동작 여부에 영향을 받음

테스트 코드를 작성 할 때

  • 가짜 객체를 활용하면 실제 객체를 사용할 때 보다 조금 더 편하게 테스트를 작성할 수 있음
  • 하지만 상세 구현에 의존하는 테스트가 될 수 있음

 


추천하는 방법

  • 우선 TDD를 연습할 때 가급적이면 실제 객체를 활용하는 것을 우선으로 진행
  • 테스트 작성이 어렵거나 흐름이 이어지지 않는다면 테스트 더블을 활용하는 방법으로 접근하시는 것을 추천

통합 테스트

 - 우리가 만드는 대부분의 애플리케이션은 데이터베이스, 파일 시스템, 외부 라이브러리 등과 같이 다른 애플리케이션과 통합되어 개발하는 경우가 많음

 - 적은 비용과 빠른 테스트를 위해 단위 테스트를 주로 사용하지만 실제로 상호 작용하는 내용에 대한 검증도 반드시 필요함

 - 예를 들면 데이터베이스와의 통합을 테스트하는 경우 테스트를 실행할 데이터베이스를 실행해야함

 

외부 라이브러리

테스트의 필요성

 - 라이브러리 기능에 대한 검증은 필요 없음.

 - 단, 그 부분을 사용하는 부분에 대한 검증이 필요.

 

변경할 수 없는 코드 검증 시 실제 객체 사용

 - 작동 원리를 깊이 이해하기 어려움

 - Mock 객체와 실제 객체의 행위를 일치시키기 어려움

@Test
void findPath() {
    // graphService에서 활용하는 Jgrpah라이브러리의 객체를 실제 객체로 사용한다.
    List<Long> stationIds = graphService.findPath(
            Lists.list(line1, line2), 
            station3.getId(), 
            station5.getId(), 
            PathType.DISTANCE
    );

    assertThat(stationIds).hasSize(5);
    assertThat(stationIds.get(0)).isEqualTo(3L);
    assertThat(stationIds.get(1)).isEqualTo(2L);
    assertThat(stationIds.get(2)).isEqualTo(1L);
    assertThat(stationIds.get(3)).isEqualTo(4L);
    assertThat(stationIds.get(4)).isEqualTo(5L);
}

데이터 베이스

 - 테스트를 쉽게 하기 위해 메모리 내 H2 데이터베이스에 연결해서 한다.

@DataJdbcTest
public class StationRepositoryTest {
    @Autowired
    private StationRepository stationRepository;

    @Test
    void saveStation() {
        String stationName = "강남역";
        stationRepository.save(new Station(stationName));

        assertThrows(DbActionExecutionException.class, () -> 
            stationRepository.save(new Station(stationName))
        );
    }
}

 

통합 테스트 vs 단위 테스트

  • 통합 테스트는 통합하고 있는 부분을 실제와 가까운 환경에서 검증하여 기능이 정상적으로 여부를 검증
  • 단위 테스트는 통합하고 있는 부분이 정상적으로 동작한다고 가정하고 단일 기능에 대해서만 검증

참고자료

4주 차 강의 내용 정리

  • ATDD
  • 인수테스트란
  • 4주 차 미션(Lotto) 대한 피드백 

ATDD(인수테스트 주도 개발)

ATDD 프로젝트를 구성하는 인원들과 원할한 협업을 하기위한 소프트웨어 개발 방법론 하나. 기획 단계부터 공통의 목표와 이해를 도모해서 프로젝트를 진행하기 위해 사용됨.

신규 기능 개발시 발생가능한 문제( 도메인에 대한 이해도 부족으로 단위테스트 작성 어려움, 레거시 리팩터링의 어려움 ) 인해 TDD 사이클로 개발이 어려울 경우 인수테스트를 먼저 구현한 이후 단위 테스트를 통해 기능을 완성해 가는 과정 대안이 있다.

 

ATDD 사이클

ATDD 개발 프로세스 

 

  • 예시 1(신규 기능 개발)

요구사항 분석

 - 인수 테스트가 충족해야하는 조건

 - 분석 및 표현에 다양한 방식이 있다.

Ex)시나리오 기반으로 표현하는 방식 (Given/When/Then)

https://www.altexsoft.com/blog/business/acceptance-criteria-purposes-formats-and-best-practices/

 

인수 테스트

 - 분석한 요구사항을 검증하는 단계

 - 실제 요청/응답하는 환경과 유사하게 테스트 환경을 구성

 

문서화

 - Spring Rest Docs를 활용한 API 문서화

 - 문서화를 위해서는 Mock 서버 & DTO 정의가 필요.

  1. 프론트엔드, 다른 백엔드 개발자와 병렬로 작업하는데 유리함
  2. 인수 테스트와는 별개로 API 테스트를 수행
  3. 다른 개발자들과 협업 시 커뮤니케이션에 큰 장점

기능 구현 with TDD

 - 인수테스트 기반으로 하나씩 기능개발을 진행

 - Outside In / Inside Out 방향으로 개발

 

이후 반복적인 리펙터링

 

  • 예시 2(레거시 코드 리펙터링)

리팩터링 대상을 확인

 

리팩터링 방법 선택

 - 먼저 인수 테스트를 작성하여 기존에 구현된 기능을 보호하기

 - 파악이 가능한 부분 부터 단위 테스트를 만들어 기능 검증하기

 

인수 테스트의 보호 속에서 리팩터링 하자!

 

새로운 인수테스트를 작성

 

메서드 분리

 - 인수 테스트 작성 후 메서드 분리 시 사이드 이펙트에 대한 피드백을 바로 받을 수 있음

 - 기능을 구현하다 꼬이거나 잘못 되어도 인수 테스트가 성공한 시점으로 리셋할 수 있음

 - 안심하고 작은 단위로 메서드를 분리

 

단위 테스트 작성

 - 단위가 작아지니 역할이 줄어들고 검증할 대상이 명확해 짐

 - 단위 테스트 하기 쉬운 코드가


인수테스트란

실제 사용자 환경에서 사용자의 입장으로 테스트를 수행하는 것을 말한다. 인수 조건은 개발 용어가 사용되지 않고 일반 사용자들이 이해할 있는 단어를 사용

 

 

인수 테스트 특징

 

전구간 테스트

  • 요청과 응답 기준으로 전 구간을 검증

Black Box 테스트

  • 세부 구현에 영향을 받지 않게 구현하기

 

테스트 만드는 과정

 

테스트 환경 구축

인수 테스트는 시스템 내부 코드를 가능한 직접 호출하지 말고 시스템 구간을 테스트를 하도록 안내하고 있기 때문에 시스템 외부에서 요청하는 방식으로 검증함.

 

인수 테스트 클래스

  • 실제 서버가 아닌 테스트를 위한 서버를 로드하도록 설정
  • 실제 request가 아닌 인수 테스트의 request를 받기 위함
  • @SpringBootTest을 클래스에 붙여주면 테스트를 위한 웹 서버를 사용
  • webEnvironment 설정을 통해 서버의 환경을 지정

 

인수 테스트 객체 설정

  • 테스트를 위한 서버에 요청을 보내기 위한 클라이언트 객체 설정
    • ex. MockMvc, RestAssured, WebTestClient
    • 테스트의 성격에 맞는 클라이언트를 선택해야
MockMvc vs WebTestClient vs RestAssured=> 비교는 다음 포스팅 예정

테스트 서버에 요청 / 응답 결과를 검증

ex)

@DisplayName("지하철역을 생성한다.")
@Test
void createStation() {

    ...
       
    ExtractableResponse<Response> response = RestAssured.given().log().all()
            .body(params)
            .contentType(MediaType.APPLICATION_JSON_VALUE)
            .when()
            .post("/stations")
            .then().log().all()
            .extract();

    // then
    assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
    assertThat(response.header("Location")).isNotBlank();
}

@SpringBootTest

  • 테스트에 사용할 ApplicationContext를 쉽게 지정하게 도와줌
  • 기존 @ContextConfiguration의 발전된 기능
  • SpringApplication에서 사용하는 ApplicationContext를 생성해서 작동

 

webEnvironment

@SpringBootTest의 webEnvironment 속성을 사용하여 테스트 서버의 실행 방법을 설정 링크.

  • MOCK: Mocking된 웹 환경을 제공, MockMvc를 사용한 테스트를 진행할 수 있음
  • RANDOM_PORT: 실제 웹 환경을 구성
  • DEFINED_PORT: 실제 웹 환경을 구성, 지정한 포트를 listen
  • NONE: 아무런 환경을 구성하지 않음

 

❗️인수 테스트 작성 팁

인수 테스트 클래스

  • Feature 기준으로 인수 테스트 클래스를 나눌 수 있음
  • Scenario 기준으로 인수 테스트 메서드를 작성할 수 있음
  • 하나의 Feature 내부에 있는 Scenario는 같은 테스트 픽스쳐를 공유하는 것을 추천

간단한 성공 케이스 우선 작성

  • 동작 가능한 가장 간단한 성공 케이스로 시작
  • 테스트가 동작하면 실제 구조에 관해 더 좋은 생각이 떠오를 수 있음
  • 그 과정에서 발생 가능한 실패를 처리하는것과 성공 케이스 사이에서 우선순위를 가늠

실패하는 테스트 지켜보기

  • 코드를 작성하기 전 테스트가 실패하는 것을 지켜본 후 진단 메시지를 확인
  • 테스트가 예상과 다른 식으로 실패하면 뭔가를 잘못 이해했거나 코드가 완성되지 않았다는 뜻

Given / When / Then

  • When -> Then -> Given 순서로 작성하는 것이 자연스러움

🧑‍💻 인수 테스트 리팩토링

 

테스트의 의도를 명확히 드러내기

가독성에 신경쓰기!

  • 가독성이 좋지 않으면 방치되는 테스트가 될 가능성이 높다
  • 변경 사항에 대해서 수정하기 어렵다. -> 방치될 가능성 높다
    • feat. @Ignore or @Disabled
  • 가독성이 좋으면 해당 기능의 스펙을 나타낼 수 있다.

프로덕션 코드의 가독성이 중요한 만큼 테스트 코드의 가독성도 중요함

 

테스트 코드 중복 제거

  • 기능 개발 간 테스트 코드도 중복이 많이 발생함
  • 테스트 가독성을 저해하는 구조가 나올 수 있어 중복제거가 중요함
  • 가독성이 좋지 않은 테스트 코드는 관리가 되지 않는 가능성이 높음

중복 제거 방법

  • 메서드 분리
  • CRUD 추상화
  • Cucumber JBehave 같은 BDD 도구 사용

메서드 분리

  • 반복되는 코드는 메서드로 분리
  • ex) StationAcceptanceTest

다른 인수 테스트에서 재사용

  • 스텝 메서드들을 static 선언
  • 다른 인수 테스트에서 재사용 가능

4주 차 미션 (JPA)

  • JPA 실습 진행. 관련 문법 학습

소스코드: github.com/louisJu/jwp-jpa-ex

느낀 점

  • 김영한님의 JPA 참고서적을 학습하면서 해당 미션을 진행하니 수월하게 느껴졌다.
  • ENTITY들의 관계성을 잘 고려하면서 설계하는것은 어렵구나 다시한번 느낌.

회고 

 : 생각보다 어려운 피드백없이 진행했던 과제. 하지만 JPA는 너무나도 방대하다는 것을 앎.

 


참고자료

우아한테크캠프 4주차 강의.

ATDD 요구사항 분석 방법:

https://www.altexsoft.com/blog/business/acceptance-criteria-purposes-formats-and-best-practices/

 

ATDD방법론 설명:

https://mysoftwarequality.wordpress.com/2013/11/12/when-something-works-share-it/

3주 차 강의 내용 정리

  • JPA
  • 3주 차 미션(Lotto) 대한 피드백 

JPA

SQL을 직접 다룰 때 발생하는 문제점

  1. 반복 작업
    테이블 구조가 변경(추가, 삭제)되면 관련된 SQL을 전부 수정해줘야 한다.
  2. 신뢰성
    Human error가 발생할 가능성이 높아짐 (ex. 데이터 fetch 로직에서 테이블 간 매핑이나 쿼리 작성이 빠져있는 경우)

=> JPA를 사용하면 위 issue에서 자유로워질 수 있다.

 

더보기

JDBC, SQLMAPPER, ORM

공통점: persistence (데이터를 영속적으로 저장하기 위해 사용하는 기술) 

 

JDBC: java의 JDBC API

          DriverManager -> Connection -> Statement -> ResultSet 으로 이루어져 있는 구조

          커넥션, 쿼리를 직접 작성하고 관리해야 한다.

 

SQL MAPPER: MyBatis, Spring JDBC

                       SQL을 자바 코드로부터 분리한 것

 

ORM: JPA, Hibernate, Spring data JPA, Sping data JDBC

          사람이 쿼리를 관리하지 않는 것! (Lazy Loading, Dirty Checking, Caching 등의 특징이 있음)

 

 

JPA 특징

  1. 데이터베이스 스키마를 자동으로 생성하는 기능을 지원

spring.jpa.hibernate.ddl-auto=create
더보기

설정값 종류

create: 기존 테이블 삭제 후 다시 생성 (DROP + CREATE)

create-drop: create와 같으나 종료 시점에 테이블 DROP

update: 변경된 부분만 반영 (운영 DB에 사용하면 안됨)

validate: entity와 table이 정상 매핑되었는지만 확인

none: 사용하지 않음

 

※ 크리티컬 이슈를 발생시킬 수 있기 때문에 실무에서는 보통 validate or none으로만 사용한다. 

 

    2. @(Annotation)으로 많은 기능을 활용할 수 있다.

@Entity // (1)
@Table(name = "station") // (2)
public class Station {
    @Id // (3)
    @GeneratedValue(strategy = GenerationType.IDENTITY) // (4)
    private Long id;

    @Column(name = "name", nullable = false) // (5)
    private String name;
    
    protected Station() { // (6)
    }
}
  1. @Entity : pk 꼭 가져야함.  엔티티 클래스임을 지정하며 테이블과 매핑된다.
  2. @Table: 굳이 선언할 필요 없음. Name property 이용해서 컨벤션에 맞는 테이블이름을 정할 있다. 생략할 경우 엔티티클래스이름과 동일한 테이블로 매핑
  3. @Id : pk
  4. @GeneratedVAlue : pk 생성 규칙을
  5. @column
    • 컬럼의 이름을 이용하여 지정된 필드나 속성을 테이블의 컬럼에 매핑한다.
    • 굳이 선언하지 않아도 된다.
  6. 매개 변수가 없는 생성자
    • The entity class must have a no-arg constructor. The entity class may have other constructors as well. - JSR 338

   3. 영속성 컨텍스트

  • 엔티티를 영구 저장하는 환경
  • 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다
  • 영속성 컨텍스트의 기능
    - 1차 캐시
    - 동일성 보장
    - 트랜잭션을 지원하는 쓰기 지연
    - 변경 감지
    - 지연 로딩

  4. 엔티티의 생명주기

비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
영속(managed): 영속성 컨텍스트에 저장된 상태
준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
삭제(removed): 삭제된 상태

 

JPA의 연관관계 

엔티티들은 대부분 다른 엔티티와 연관 관계를 맺는다.

객체는 참조를 사용, 테이블은 외래 키를 사용해서 관계를 맺는다.

  • 방향: 단방향, 양방향이 있다. 방향은 객체 관계에서만 존재하고 테이블 관계는 항상 양방향이다.
  • 다중성: 일대일, 다대다, 일대다, 다대일 
  • 연관관계의 주인
    • 객체 사이에 관계가 형성되면 관계의 주인을 정해야 한다.
    • 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 등록, 수정, 삭제할 수 있다.
    • 주인이 아닌 쪽은 읽기만 가능하다.
  • 연관관계의 주인이 아닌 곳에서 입력된 값은 외래 키에 영향을 주지 않는다.
  • 양방향 관계에서는 양쪽 다 데이터 일관성에 대해 신경을 써야 한다.
  • 양방향 관계에서 두 코드는 하나인 것처럼 사용하는 것이 안전하다.
  • 한 번에 양방향 관계를 설정하는 메서드를 연관 관계 편의 메서드라 한다.
public void setLine(Line line) {
    this.line = line;
    line.getStations().add(this);
}

public void addStation(Station station) {
    stations.add(station);
    station.setLine(this);
}
매핑 시 무한루프에 빠지지 않게 조심해야 한다.

 

TIPS

  • AUDITING
    애플리케이션의엔티티의 생성 시간과 마지막 수정시간을 관리할 필요가 있다면 수동으로 매번 추가하는 대신 Auditing 기능을 이용해서 자동으로 추가해 있다.

  1. @Configuration 클래스에 @EnableJpaAuditing 추가한다.
@EnableJpaAuditing
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

    2. 엔티티에 콜백 리스너를 추가한다.

@EntityListeners(AuditingEntityListener.class)
@Entity
public class Line {

    3. 생성 날짜와 마지막 수정 날짜 프로퍼티에 @CreatedDate @LastModifiedDate 추가한다.

@EntityListeners(AuditingEntityListener.class)
@Entity
public class Line {
    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;

    4. @MappedSuperclass 를 사용해서 abstract class 생성해서 사용한다면 중복코드를 분리할 있다.

  • 비교
    엔티티 비교시에는 식별자있어도 충분하다.

좀 더 디테일한 JPA 관련 내용은 추후 해당 블로그에 기재하도록 하겠다.

 

3주 차 미션에 대한 피드백

  • . 매직 넘버는 명명된 상수로 치환해서 사용하기
// AS-IS
public class Calculator {

    public int splitAndSum(String text) {
        String[] numArr = Splitter.split(!isEmptyOrNull(text) ? text : "0");

//TO-BE
public class Calculator {
	private final static String ZERO = "0";

    public int splitAndSum(String text) {
        String[] numArr = Splitter.split(!isEmptyOrNull(text) ? text : ZERO);
        

 

  • 유틸리티 클래스의 접근성 제한하기
    • 유틸 클래스의 경우 불필요한 객체 생성을 방지하기 위해서 Private 생성자를 사용하기
  • 직관적인 코드 작성
//AS-IS
if(inputValue == null || inputValue.isEmpty()) {
	return true;
}
	return false;
        
//TO-BE       
 return inputValue == null || inputValue.isEmpty();

 

  • 스트림을 사용해서 간략하게 코드 작성
//AS-IS
int resultNum = Integer.parseInt(numArr[0]);
for(int i = 1; i < numArr.length; i++) {
	resultNum = add(resultNum, Integer.parseInt(numArr[i]));
}

	return resultNum;

//TO-BE
return Arrays.stream(numArr)
.mapToInt(Integer::parseInt)
.sum();

 

  • RetainAll 함수를 사용하는 방법
//AS-IS
return lotto.nums.stream().
	filter(n -> this.nums.contains(n)).count();
    

 

  • 로또 2,3등 구하는 로직 개선
//AS-IS

    public static Rank of(int matchNums, boolean matchBonus) {
        if (matchNums == SECOND.matchNums && matchBonus) {
            return SECOND;
        }
        if (matchNums == THIRD.matchNums) {
            return THIRD;
        }
        return Arrays.stream(Rank.values())
                .filter(rank -> rank.matchNums == matchNums)
                .findAny()
                .orElse(MISS);
    }
    
//TO-BE

    return Arrays.stream(values())
        .filter(rank -> rank. matchNums == matchNums)
        .filter(rank -> !rank.equals(SECOND) || matchBonus)
        .findFirst()
        .orElse(MISS);

3주 차 미션(Lotto) 리뷰

소스코드: github.com/louisJu/java-lotto-ex

느낀 점

  • 상수처리에 대해서 조금 더 신경 써야 할 것 같음.
  • ENUM 클래스 활용에 아직 많이 미숙하구나. 
  • 스트림 활용법에 대한 학습 필요.

회고 

 : 아직까진 예전에 실습해본 주제로 과제를 진행하다 보니 접근하기 나름 편했다. 역시 반복학습의 효과는 짱...

2주 차 강의 내용 정리

  • TDD, 클린코드
  • 1주차미션(racingCar) 대한 피드백 & 라이브 코딩

Clean Code 가이드

의미 있는 이름

  • 의도를 분명히 한 naming 규칙
    • 협업할 때 새롭게 코드를 볼 사람을 생각해서 더 나은 이름으로 작성하기
    • 주석을 사용하지 않아도 될 정도로 분명하게 naming 하기
// 잘못된 변수 사용
int d; // 경과시간(day)
  • 의미 있게 구분하기
    • 기준을 세우고 네이밍하는 것이 좋다. (ex. 카멜케이스, 스네이크케이스 등)
  • 인터페이스 클래스와 구현 클래스
    • 인터페이스는 공통적인 개념으로 naming하고, 구현 클래스는 의도가 드러날 수 있는 구체적인 naming을 사용하라
  • 클래스 이름
    • 명사, 명사구가 적합
    • Manager, Processer, Data, Info 등과 같은 단어는 피하고, 동사는 사용하지 않는다.
  • 메소드 이름
    • 동사, 동사구가 적합
    • 접근자, 변경자, 조건자에는 값 앞에 get, set, is를 붙인다.
    • 생성자를 중복해서 정의할 땐 정적팩토리 메소드를 사용. 메소드명은 인수를 설명하는 이름으로 사용한다.

경계

wrapper 클래스를 사용

Map, List 같은 collection 등 외부에 노출하는 경우 많은 인터페이스가 노출하게 되기 때문에 wrapper 클래스를 사용해서 숨기는 것이 좋다.

  • 사용자에게 모든 인터페이스를 노출하지 않아도 된다.
  • 내부적인 변경이 발생하더라도 외부 변경은 발생하지 않는다.
Map<Integer, Sensor> sensors = new HashMap<>();
Sensor s = sensors.get(sensorId);
public class Sensors {
    Map<Integer, Sensor> sensors = new HashMap<>();

    pubilc Sensor getById(String id) {
        return sensors.get(id);
    }
}

=> Map을 사용자에게 직접 노출하지 않고 Sensors 클래스르 이용해서 처리 가능하다.

1주차 미션에 대한 피드백

  • 로직이 동일한 테스트일 경우 @ParameterizedTest 를 활용해서 하나로 합칠 수 있다. 
    @ParameterizedTest
    @CsvSource({"20 + 10,30","20 - 10,10","20 * 10,200","20 / 10,2"})
    @DisplayName("사칙연산 test")
    void calculationTest(String inputData, Long result) {
        expression = new Expression(inputData);
        calculator = new Calculator(expression);
        Long testResult = calculator.calculate();
        assertThat(testResult).isEqualTo(result);
    }

 

 

  • 함수형 인터페이스 및 람다 사용.
//AS-IS
private static List<Car> mapCars(int carNums) {
        List<Car> cars = new ArrayList<>();
        for (int i = 0; i < carNums; i++) {
            cars.add(new Car());
        }
        return cars;
    }
//TO-BE
this.cars = Arrays.stream(splitCarNames(carNames)).map(name -> new Car(name)).collect(Collectors.toList());

 

  • 전략패턴 사용
    => 자동차가 움직이는 규칙을 독립시킬 수 있다. 규칙이 독립되면 테스트도 용의해진다. 
public interface MoveStrategy {
    boolean isMove(int value);
}

=> 이동 규칙 관련 인터페이스를 생성

 

public class RandomMoveStrategy implements MoveStrategy {
    private static final int START_CONDITION = 4;
    private static final int END_CONDITION = 9;

    @Override
    public boolean isMove(int value) {
        if(value >= START_CONDITION && value <= END_CONDITION) {
            return true;
        }
        return false;
    }
}

=> 인터페이스를 구현한 실제 적용할 규칙 클래스를 생성

 

public class Racing {
    private List<Car> cars;
    private MoveStrategy moveStrategy;

    public Racing(String carNames) {
        this.cars = Arrays.stream(splitCarNames(carNames)).map(name -> new Car(name)).collect(Collectors.toList());
        this.moveStrategy = new RandomMoveStrategy();
    }

    public void race() {
        for (Car car : cars) {
            car.racing(RacingCarUtils.randomValueGenerator(), moveStrategy);
        }
    }
}

=> 로직에 적용

 

2주차미션(racing-car) 리뷰

소스코드: github.com/louisJu/java-racingcar-ex

 

느낀 점

  • 변수명 결정하는데 시간이 오래 걸렸음. 위에서 언급한 사이트들을 이용한다면 조금 빠르게 naming 을 할 수 있지 않을까?
  • 전략패턴을 학습 및 적용해보면서 코드가 좀 더 이뻐지는 느낌을 받음
  • 테스트 코드 작성에도 신경을 많이 써야할 것 같음
  • 스트림, 람다를 의식적으로 많이 쓰도록 하자!

회고 

 : 코딩을 하다보면 나도 모르게 익숙하고 편한 방법(레거시를 만들어내는) 하게 된다. 의식적인 코딩을 통해서 좀더 나은 코딩을 하도록 노력하자. 
: 이유없는 코드라인은 만들지 않도록 하자.

1주 차 강의 내용 정리

  • 객체 지향 설계 & TDD
  • Pre mission(숫자야구게임)에 대한 피드백 & 라이브 코딩

TDD 접근 방법

  • UI, DB, 외부 라이브러리에 의존하지 않는 도메인 영역을 집중 설계
  • 분리 설계된 도메인에 대한 로직을 테스트하는 것에 집중

단위 테스트 구조

TDD 사이클

TDD의 기본적인 Cycle

나의 연습 방법

  1. 가능한 상세하게 도메인 설계를 진행 (할 수 있는 만큼)
  2. 도메인 별 구현할 기능들 리스트 업
  3. 리스트를 바탕으로 테스트코드 작성
  4. 테스트 코드 compile 에러 발생하지 않도록 처리
  5. 기능 구현
  6. 테스트 코드 확인
  7. 리팩터링 & 3~6 반복하면서 개발 진행

* 정량적인 리팩터링 방법 *
=> 객체지향 생활체조 원칙 적용

  • 규칙 1: 한 메서드에 오직 한 단계의 들여 쓰기(indent)만 한다.
  • 규칙 2: else 예약어를 쓰지 않는다.
  • 규칙 3: 모든 원시 값과 문자열을 포장한다.
  • 규칙 4: 한 줄에 점을 하나만 찍는다.
  • 규칙 5: 줄여 쓰지 않는다(축약 금지).
  • 규칙 6: 모든 엔티티를 작게 유지한다.
  • 규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  • 규칙 8: 일급 컬렉션을 쓴다.
  • 규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.

 

Pre-mission(숫자야구게임) 리뷰

소스코드는 github.com/louisJu/java-baseball-ex에서 확인할 수 있음.

 

설명 

: 패키지 구조 - domain과 view로 구분

: 일급 컬렉션을 사용 (ball count, strike count, baseball number를 객체로 래핑함)

: else, indent를 많이 신경 씀.

 

느낀 점

  • 시작 전 설계에 대한 깊은 고민 없이 개발하다 보니 자주 수정이 발생.
     => (테스트 코드도 수정이 자주 발생, 커밋할 시점 잡기 어려움, 복잡해짐)
  • 개발 전 꼼꼼한 설계에 대한 중요성 다시 느낌.
  • 기능의 역할 정의에 어려움
     => 입력한 야구 번호 검증하는 로직을 어디서(baseball.class or resultChecker.class) 처리하는 게 맞는지 고민이 많았음 
  • 도메인별 명확한 역할 할당, 다른 도메인에서 사용할 땐 메시지를 전달하여 결과만 받아오도록 처리(캡슐화)
    => 참조 depths(dot)를 한 단계로만 처리하도록 함. 
     ex) result.getBall().getBallCount (X) -> result.getBall()

회고 

 : 첫 시간이었고 현재 서비스에서 Node.js를 이용해서 개발하다가 오랜만에 java를 이용해서 개발하니 친정에 돌아온 듯 재미있었다. 잊고 있었던 원칙들이 새록새록 생각나서 즐겁게 고민하면서 코딩했다.

 

참고자료

객체지향 생활체조 원칙

+ Recent posts