공부방/프리코스

우테코 프리코스 1주차 후기

EVO. 2023. 10. 25. 16:32
이 글을 쓰는 목적은
1. 앞으로 과제를 진행하는 데 1주 차에 지켰던 것을 2주 차에서도 까먹지 않고 다시 지키기 위해서.
2. 우테코에 떨어져도 4주간 몰입해서 공부했던 경험을 기록으로 남기고 싶어서다.

1주차의 프리코스가 끝이 났다. 이번 주차는 요구사항이 까다롭지 않았는데, '프리코스는 이런 거니 한번 경험해 봐'라는 체험의 의미로 여겼다. 항상 프로젝트를 진행하면서 구현에만 급급했던 난 이번 기회에 하고 싶은 것들을 다 해보겠다는 마음가짐으로 임하였다. 그리고 가장 중요한 요구사항만큼은 꼭 지키기로 마음먹고 일주일 동안 수십 번은 읽어본 것 같다.(그동안 요구사항 제대로 안 읽어서 점수 깎인 적이 많았기 때문에..)

 

이번주차의 문제는 숫자 야구 게임이었다. (문제 및 코드는 아래에)

 

목표

  • 자바 컨밴션 지키기
  • 모듈화 해보기
  • 테스트 코드 작성해보기
  • 디버깅 툴로 오류 잡아내기

 

자바 컨밴션 지키기

프로그래밍 요구사항을 보면은 자바 코드 컨밴션을 준수하며 프로그래밍한다 라고 적혀있다. 지킬게 많은 것 같았지만 구현이 끝나고 클래스를 모듈화 해보려고 노력하니 컨밴션 내용의 웬만한 것은 지켜진 것 같다.

 

자바 컨밴션 내용을 간략하게 정리하면 아래와 같다.

더보기
  • 한 함수에 depth indent는 최대 2까지만 허용 (예를들어 while 안에 if문 있으면 2)
  • else 예약어를 쓰지 않았는가 (else if는 허용. 단, else는 모든 예외를 else로 보내버리기 때문에 위험)
  • 컬렉션에 일급 컬렉션을 적용했는가? (아래에 정리했다)
  • 3개 이상의 인스턴스 변수를 가진 클래스를 구현하지 않았는가(클래스를 분리하다보면 지킬 수 있는 컨밴션 인것 같다)
  • getter/setter 없이 구현했는가 (메세지를 전달하여 책임에 맞는 클래스가 역할을 맡아야 한다는 것으로 이해했다)
  • 핵심 로직을 구현하는 도메인 객체에 getter/setter를 쓰지 않고 구현했는가. 단 dto는 허용 (위에 것이랑 뭐가 다른지 아직 잘모르겠다)
  • 코드 한 줄에 점(.)을 하나만 허용했는가 (다른 클래스의 메서드의 또 다른 객체를 호출하는 것과 같이 깊이 파고들며 결합도를 높이는 행위를 지양하라는 뜻으로 이해했다 = 디미터의 법칙: 친구하고만 대화하라)
  •  메서드의 인자 수를 제한하라. 4개의 인자는 허용하지 않는다. 3개도 지양하라 (객체를 넘기는 식으로 하는 게 좋을 것 같다)
  • 메서드가 한 가지 일만 담당하도록 구현해라 (메서드 분리)
  • 클래스를 작게 유지하기 위해 노력해라. 메서드 당 line을 10까지만 허용. 길이가 길어지면 메서드로 분리
  • 매직 리터럴 / 매직 넘버 사용 자제하고 상수 사용(static final 활용)

추가 : 메서드, 변수 네이밍 컨밴션 지키기

메서드와 변수도 네이밍을 지켜야지 구현 내역을 뜯어서 보지 않아도 된다. 이름만 보고도 어떤 동작을 하는 지 예측할 수 있기 때문에 유지보수가 편하다고 배웠다. 

 

다음 두개의 글을 참고했다. 메서드 참고글 , 변수명 짓기 참고글

메서드 네이밍을 잘 짓기 위해서는 다음 방식으로 먼저 생각이 거쳐야 한다.

  • 왜 존재해야 하는가
  • 무슨 작업을 하는가
  • 어떻게 사용하는가

하나의 예시를 들어보겠다. 1주차 요구사항을 보면 컴퓨터는 세 자리의 숫자를 무작위로 선정해야 한다.

이를 위 세 가지 원칙에 근거하여 분리하면

  • 왜 존재해야 하는가 (유저가 정한 숫자를 비교해 결과를 출력하기 위해서)
  • 무슨 작업을 하는가 (컴퓨터는 세자리 숫자를 무작위로 정한다)
  • 어떻게 사용하는가 (1~9까지 숫자 3개를 랜덤으로 가져와 배열에 저장한다)

3개 숫자를 랜덤으로 가져와서 저장한다는 것은 초기화 느낌이 들어가 있다고 생각했다. 그래서 메서드 명을 initComputerNumber 라고 지었다. 컴퓨터가 숫자를 초기화 한다 라는 의미가 들어가게 만들었는 데 나중에 클래스를 분리하면서 해당 로직에 대한 책임을 서비스클래스에게 주면서 createNonDuplicatedNumber라고 메서드명을 바꿨다.

 

다음은 변수명 짓기를 했다. 의미를 정확하게 전달하는 게 중요하기 때문에 긴 변수명도 괜찮다는 것을 알게 됐다. 그래서 최대한 어떤 역할을 하는 지 정확하게 전달하기 위해서 길게 적었다. 예를들면 컴퓨터는 랜덤으로 정한 세 자리 숫자를 컬렉션에 저장해야 할텐데 이 컬렉션의 참조명을 정해야 한다. 이때 분리, 랜덤, 숫자, 컬랙션 이 네가지가 모두 들어갈 수 있도록 다음과 같이 변수명을 지었다.

 

모듈화 해보기

이 작업은 오랜 시간 동안 공부한 내용을 적용하는 기회였다. 과제를 받자마자 처음엔 모든 로직을 한 클래스에 넣었다. 그 결과물은 대략 100줄 정도로, 클래스를 분리할 필요성을 딱히 느끼지 못했다. 하지만, 1주차 과제를 통해 더 많은 것을 배우고 싶었다. 그래서 코드를 모듈화시켜 보기로 결정했다. 객체지향에 대한 유명한 책인 '객체지향의 사실과 오해'를 읽어면서 역할, 책임, 협력 관점이라는 새로운 관점에서 코드 분리를 어떻게 해야 할지 고민해 보았다. 모듈화는 생각보다 너무 어려웠고, 스프링부트에서 차용하고 있는 MVC패턴에 대해 공부를 해보고 적용시켜보았다.

 

MVC패턴에 대한 공부는 다음을 참고했다. MVC 각 컴포넌트 역할 , 10분 테코톡 MVC
아래는 배운 내용을 정리하고 지켜보려고 노력했다.

더보기

## Model ?

어플리케이션이 `무엇`을 할 것인지 정의한다. `내부 비즈니스 로직을 처리하기 위한 역할`을 한다. 즉, 데이터 저장소(ex. DB)와 연동하여 사용자가 입력한 데이터나 사용자에게 출력할 데이터를 다룬다. 

## View ?

최종 사용자에게 무엇을 `화면(UI)`로 보여준다. 화면에 `무엇을 보여주기 위한 역할`을 한다. 즉, 모델이 처리한 데이터나 그 작업 결과를 가지고 사용자에게 출력할 화면을 만든다. 

## Controller ?

Model과 View 사이에 있는 컴포넌트이다. Model이 데이터를 `어떻게 처리할지 알려주는 역할`을 한다. 클라이언트의 요청을 받으면 해당 요청에 대한 실제 업무를 수행하는 Model을 호출한다. 클라이언트가 보낸 데이터가 있다면, 모델을 호출할 때 전달하기 쉽게 적절히 가공한다. Model이 업무 수행을 완료하면 그 결과를 가지고 화면을 생성하도록 View에 전달한다. 즉, 클라이언트의 요청에 대해 Model과 View를 결정하여 전달하는 일종의 조정자로서의 일을 한다.  
Controller는 다른 컴포넌트들에 대해 알고 있다. 자기 자신 외에 Model과 View가 무엇을 수행하는지 알고 있다.  


---
1. Model은 Controller와 View에 의존하지 않아야 한다 = 모델 내부에 컨트롤러와 뷰에 관련된 코드가 있어서는 안된다.
2. View는 Model에게만 의존해야하고 Controller에는 의존하면 안된다. = View 내부에 Model의 코드만 있을 수 있고, Controller의 코드가 있으면 안된다.
3. view가 Model로부터 데이터를 받을 때는, 사용자마다 다르게 보여주어야 하는 데이터에 대해서만 받아야 한다.
4. Controller는 Model과 View에 의존해도 된다. (Controller 내부에는 Model과 View의 코드가 있을 수 있다.)
5. view가 Model로 부터 데이터를 받을 때 , 반드시 Controller에서 받아야 한다.

최대한 관심이 같은 것들끼리는 모으고, 관심이 다른 것들끼리는 분리를 시켰다.

물론 공책에 그렸다.

MVC를 배울 때 Model, View, Controller 밖에 없길래 서비스 클래스는 처음에는 안만들었지만, 자바 컨밴션에 일급컬렉션에 대해 공부를 하면서 서비스 클래스의 필요성이 느껴져 만들게 되었다.

 

그외에도 사용자가 입력한 정해진 문자 ("1"//재시작 , "2"//종료)는 상수로 만들면 괜찮겠다 싶어서 몇 주전에 공부한 Enum도 적용해봤다.

 

예외 처리

요구사항을 보면 프로그램 종료 시 System.exit()를 호출하지 않는다라고 적혀있다. 함수를 리턴해서 종료시키는 것도 괜찮은 방법인 것 같았지만 뭔가 우아하게(?) 종료시키고 싶었다. 그래서 굳이 예외로 던져서 종료시키는 방식을 택했다.

 

이렇게 RuntimeException을 상속한 예외를 던져주고

main에서 받아서 필요한 일들을 처리했다.

 

디버깅 툴로 오류 잡아내기

 

부끄럽게도 이전까지는 디버깅을 쓸 줄 몰라 사용하지 않았다. 그래서 구현을 할 때도 추상화를 피했는데, 그 이유는 오류가 발생하면 로직이 복잡해져서 코드를 이해하는 데도 시간이 걸렸기 때문이다.. 이번에는 이런 나쁜 습관을 고치고자 디버깅 툴을 배웠다. 다음 글을 참조했다. https://jojoldu.tistory.com/149

 

IntelliJ 디버깅 해보기

안녕하세요? 이번 시간엔 intellij의 debugging 을 간단하게 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와 세

jojoldu.tistory.com

 

결론 부터 말하면 개발 속도가 30프로는 상향된 느낌이다. 오류를 찾는 일이 수월해졌고, API를 직접 살펴볼 필요 없이 디버깅 툴로 쉽게 확인할 수 있다는 사실이 신세계였다.

 

다음 예시는 테스트코드를 짜면서 오류가 났었는 데 이때 디버깅 툴을 쓰면서 알게된 사실이다.

그냥 애플리케이션 콘솔 창에 엔터키만 치면 '\n' 형태로 Scanner 에게 전달이 되는 데, 테스트에서 직접 ""을 만들고 Scanner에게 전달하면 null이 전달된다.

그래서 테스트 처럼 진행하면 Scanner 내부에서 NoSuchElementException을 발생시킨다. 하지만 요구사항에 의하면 사용자 입력에 관한 예외처리는 모두 IllegalArgumentException으로 처리해야 하기에 예외처리를 해주었다.

 

테스트 코드 작성해보기

테스트 코드의 중요성은 수없이 들었다. 그래서 예전에도 테스트코드가 뭔지 공부를 해봤고, 프로젝트에도 적용을 잠깐 해봤다. 하지만, 일반적인 애플리케이션에서는 한 기능을 처리하기 위해 다른 객체들과 메세지를 주고 받는데, 이를 어떻게 테스트해야 할 지 몰라서 그냥 Application을 실행시키고 데이터베이스와 HTTP를 모두 연결해 테스트 하는 방식을 사용했다.

 

하지만 이번에는 시간도 여유롭고 해서 다음 글을 참조하며 진지하게 공부를 해봤다. Unit 테스트의 작성Stub 활용

테스트는 단위 테스트와 통합 테스트로 나뉘어있다. 단위테스트의 장점은 개발한 기능들을 빠르게 검증할 수 있으며, 수시로 실행해 검증하는 방식이라는 것을 알게 되었다. 그동안 내가 해왔던 방식은 통합 테스트 였고 시간이 굉장히 걸리는 방식이었다. 단위테스트를 진행하면서, 하나의 기능에 여러 객체들이 연결되어 있을 때 이를 해결하는 방법은 테스트 더블을 이용하는 것이다. 테스트 더블은 영화 촬영에서 위험한 장면을 스턴트가 대신하는 것처럼, 테스트 더블 역시 실제 객체를 대신하는 역할을 한다.그중에서 스텁(stub)에 대해 알게 되었고, Mockito 라이브러리를 사용하는 방식을 알게되었다. 하지만 Mockito 방식은 배울 것이 많아 보여, 직접 구현하는 방식인 테스트 코드 내에 인터페이스와 구현체를 작성하여 가짜 객체를 만들었다.

 

예를들어 아래를 보면 빨간 박스로 친 세 곳이 다른 객체를 연결해놓은 곳인데 이를 해결해야 했다.

ComputerModel과 UserModel을 테스트 코드내에서 만들어주었다. 그리고 이들을 주입하기 위해서 테스트할 서비스 메서드를 흉내내서 가짜 클래스를 테스트 클래스 내에 만들었다. 그다음 이 클래스를 이용하면 끝.

 

 

쓰다보니 너무 후기가 길어졌다. 다음 주차부터는 요구사항이 복잡해지니 글보다 구현에 집중해야겠다. 
더도말고 덜도말고 지금정도만큼.