설모의 기록

[우아한테크캠프] 5일차 본문

일상/우아한테크캠프

[우아한테크캠프] 5일차

HA_Kwon 2018. 7. 7. 17:47


TDD 마무리, Spring Boot 의 시작


 우아한테크캠프의 첫 주의 마무리로 일주일동안 배운 TDD 방식객체지향 프로그래밍에 대한 내용을 정리하고 앞으로 사용해 볼 Spring Boot 의 기초적인 사용법을 살펴보는 시간을 가졌습니다. 

TDD 방식은 앞서 정리한 포스팅과 같이 테스트 코드를 먼저 작성한 이후에 그 테스트 코드가 제대로 동작하도록 코드를 구현하고 리팩토링하는 방식입니다. 

우아한테크캠프에 참여하기 이전에는 테스트 코드를 짜본 적도 없었고, 코드를 빠르게 구현해나가는 것에 초점이 맞춰져 있었습니다. 그래서 제 코딩습관을 고치는 것이 어색하고 힘들었습니다. 그런데 오늘 강의해주신 pobi의 의견이 와닿았습니다.

TDD 방식의 연습은 실제 프로젝트가 아니라 자신의 장난감 프로젝트에서 시작하라.

제가 테스트 코드에 적응할 수 있었던 것도, 그동안의 제 습관이나 테스트 코드를 고려해보지 않았던 저를 반성하게 된 것도 약 1주일 동안 장난감 프로젝트에서 연습을 해봤기 때문이라고 생각합니다. 이 글을 보고 '테스트 코드를 연습해봐야지' 라는 생각이 드신 분들은 String 클래스의 메소드들을 테스트 하거나, 로또 게임, 가위바위보 게임과 같이 요구사항이 적지만 명확하고 테스트 해보기 쉬운 프로젝트로 연습해보시는 것을 추천합니다.

아래는 오늘 들은 강의 내용을 정리한 내용입니다.




팩토리 메소드(factory method)

디자인 패턴을 공부하신 분들은 팩토리 패턴을 알고 계실 것입니다. 오늘 배운 팩토리 메소드 또한 유사합니다. 생성자 오버로딩을 통해 객체를 생성하도록 구현하는 것은 어떤 인자를 받는 생성자가 있는지를 알고 있지 않은 이상 생성자 이름만 보고는 알 수가 없습니다. 이런 문제점을 해소하는 것이 팩토리 메소드입니다. 아래의 예제를 통해 더 자세히 설명하겠습니다.

public class NumberArray {
private List<Integer> numbers;

private NumberArray() {
numbers = new ArrayList<>();
}

private NumberArray(int number) {
this();
numbers.add((number));
}

public NumberArray(String text) {
this.numbers = Ints.asList(                 Arrays.asList(text.split(","))
.stream()
.mapToInt(Integer::parseInt)
.toArray());
}

public NumberArray(Integer... numbers) {
this.numbers = Arrays.asList(numbers);
}

public NumberArray(List<Integer> numbers) {
this.numbers = numbers;
}
}

위의 NumberArray 는 숫자 배열인 numbers 를 멤버변수로 가지는 클래스입니다. 이 클래스는 생성자를 여러 형태의 인자를 받아 numbers에 저장해주는데요. 위의 코드와 같이 생성자 이름만 보고 객체를 생성해야 하는 개발자의 입장으로서는 이 클래스의 코드를 보지 않는 이상 어떤 인자를 받는 생성자가 있는지를 알기가 쉽지 않습니다. 이 문제점을 해소하기 위해 아래와 같이 팩토리 메소드를 적용해보겠습니다.


public class NumberArray {
private List<Integer> numbers;

private NumberArray() {
numbers = new ArrayList<>();
}

private NumberArray(int number) {
this();
numbers.add((number));
}

private NumberArray(String text) {
this.numbers = Ints.asList(
Arrays.asList(text.split(","))
.stream()
.mapToInt(Integer::parseInt)
.toArray());
}

private NumberArray(Integer... numbers) {
this.numbers = Arrays.asList(numbers);
}

private NumberArray(List<Integer> numberList) {
this.numbers = numberList;
}

public static NumberArray ofBlank() {
return new NumberArray();
}

public static NumberArray ofOneNumber(int number) {
return new NumberArray(number);
}

public static NumberArray ofComma(String text) {
return new NumberArray(text);
}

public static NumberArray ofNumbers(Integer... numbers) {
return new NumberArray(numbers);
}

public static NumberArray ofList(List<Integer> numberList) {
return new NumberArray(numberList);
}
}

위의 코드에서 ofBlank(), ofOneNumber(), ... 와 같은 메소드가 바로 팩토리 메소드입니다. 생성자들은 private 으로 감춰두고, 팩토리 메소드를 통해 어떤 인자를 받을 것인지를 명확하게 한 뒤에 팩토리 메소드 내부에서 새로운 객체를 반환하는 형식입니다. 이렇게 한다면 NumberArray 클래스를 사용할 개발자는 어떤 인자를 넣어야하는지를 명확하게 알고 이용할 수 있습니다. 




객체지향 프로그래밍에서의 중복 제거 방법

객체지향 프로그래밍에서 중복 코드를 제거하는 방법은 다음과 같이 두가지가 있습니다.

  • 상속(is-a 관계)
    - 부모 클래스의 변경하면 자식 클래스 또한 영향을 받는다.
    - 만약 인터페이스를 implements 하는 경우에도 필요하지 않은 인터페이스 내의 메소드까지 자식에서 구현해야 합니다.
  • 조합(has-a 관계)
    - 상속보다는 객체간의 의존관계가 옅어집니다. 
    - 상속보다는 부모 클래스의 변경을 유연하게 대응할 수 있습니다.

위와 같은 이유로 pobi는 두 방법 중 조합  방법을 추천했습니다. 상속을 꼭 해야 하는 경우가 아니라면 조합을 고민해보는 것이 좋습니다. 조합의 예제는 아래와 같습니다.

public class LottoList {
private List<Lotto> lottoList = new ArrayList<>();

public LottoList(Lotto... lotto) {
// 수행할 내용
}
}

LottoList가 Lotto 클래스를 상속하는 것이 아니라 Lotto 객체를 받아서 그 객체를 사용하는 방법이 조합 방법입니다. 이런 방식을 이용하면 필요하지 않은 Lotto 클래스의 메소드와 변수를 가질 필요없이 lotto 객체들을 사용할 수 있습니다.

클래스 인스턴스의 예외처리 코드 또한 중복을 피해야 합니다. 클래스 인스턴스에 대한 예외처리가 클래스 밖에 있다면 그 코드는 잘못된 코드입니다. 클래스 인스턴스에 대한 예외처리는 클래스의 생성자에서 모두 처리해 잘못된 객체는 생성될 수 없도록 exception을 던져야 합니다. 이런식으로 코드를 구현해야 잘못된 인스턴스가 프로그램에서 돌아다니는 경우가 없습니다. 이 예는 아래와 같습니다.

public class LottoNumber {
private int number;
public LottoNumber(int number) {
if (number > 45 || number < 1)
throw new IllegalArgumentException();
this.number = number;
}

public int getNumber() {
return number;
}
}

위의 코드와 같이 인자로 받은 number가 해당 범위의 숫자가 아니라면 exception을 던져 객체가 생성될 수 없도록 구현해야 합니다. 이렇게 생성자에 예외처리를 모아둔다면 보다 안정된 프로그램을 구현하실 수 있습니다.




Value Object라면 Map을 고려하라

이번 우아한테크캠프를 통해 value object란 개념을 알게 되었는데요. value object는

말 그대로 값을 위해 쓰는 객체로, 값을 변경할 수 없는 객체

를 말합니다. 따라서 값을 변경하기 위해서는 새로운 객체를 생성해야 하는데요. 위의 LottoNumber 클래스를 보면 number 멤버변수의 값을 수정하는 코드가 존재하지 않습니다. 이러한 클래스를 VO 로 사용할 수 있습니다. 만약 프로그램 내에서 로또가 1000개 생성되고 하나의 로또에 6개의 LottoNumber가 필요하다면 LottoNumber는 총 6000개가 생성되어야 합니다. 그러나 LottoNumber가 VO 이기 때문에 Map 을 사용해 45개의 LottoNumber를 생성해둔 후에 요청에 맞는 LottoNumber를 반환해준다면 LottoNumber객체는 45개만이 생성됩니다. 




Spring boot 의 기초

오늘은 Spring boot 의 기초 폴더 구조와 사용법을 배웠습니다. 

우선 Spirng boot 의 프로젝트 구조는 다음과 같습니다.

프로젝트를 생성하면 위의 이미지와 같은 프로젝트 구조를 보실 수 있습니다. 

  • src/java/ : 이 곳에 자바 소스코드를 관리하시면 됩니다.
  • src/resources/ : html, css 와 같은 소스를 관리하시면 됩니다.
    - src/resources/static/ : css, fonts 와 같이 정적 자원들을 관리하시면 됩니다.
    - src/resources/templates/ : 동적 코드를 포함한 자원들을 관리하는 디렉토리입니다. 예를 들어, html 파일은 원래 정적 데이터만 보여주는 자원입니다. 그러나 동적으로 생성된 리스트와 같이 동적 데이터를 보여주는 html 을 이용하고 싶다면 템플릿 엔진을 사용해야 합니다. 그리고 동적 html 과 같은 자원들은 src/resources/templates/ 경로에서 관리해야 합니다.


이번엔 간단한 api를 구현하는 방법에 대해 알아보겠습니다. 우선 src/java/ 에 UserController 클래스를 생성해보겠습니다.

@Controller
public class UserController {
private List<User> users = new ArrayList<>();

@PostMapping("/users")
public String create(User user) {
users.add(user);
return "redirect:/users";
}

@GetMapping("/users")
public String list(Model model) {
model.addAttribute("users", users);
return "/user/list";
}
}

@Controller 어노테이션은 이 클래스가 Controller임을 알리기 위해 추가했습니다. UserController는 users 라는 User 객체 리스트 멤버 변수를 가지고 있습니다. 

http 통신에서 단순 조회 api는 get 방식을, 회원가입 또는 정보 수정과 같이 생성이나 수정과 같은 api 는 post 방식을 이용합니다.

위의 예제는 회원가입 api인 post 방식의 /users 와 회원가입된 유저의 리스트를 보여주는 api인 get 방식의 /users 입니다. 어노테이션을 통해 post 와 get 을 구분시켜준 후 ("경로") 에 경로를 작성해주시면 됩니다. 

@PostMapping("/users")
public String create(User user) {
users.add(user);
return "redirect:/users";
}

이번엔 인자를 설명 드리겠습니다. 클라이언트가 post 방식으로 /users 경로에 요청을 보내면 위의 함수가 실행됩니다. 이 때 인자를 User user 로 받으면 클라이언트가 보낸 내용대로 User 객체가 생성됩니다. 이 때, 주의하셔야 할 점은 User 클래스에 기본 생성자가 필요하다는 것입니다. 클라이언트가 보낸 인자를 비교해 User의 생성자를 이용하는 방식이 아니라 기본 User 객체를 만들고 setter 메소드를 통해 객체 내용을 채워나가기 때문입니다. 따라서 기본 생성자와 setter 메소드가 필요합니다.

"redirect:/users" 를 반환하게 되면 get 방식의 /users api를 호출하는 것과 같습니다. 따라서 위의 create 메소드는 users 리스트에 User 객체 하나를 추가한 후에 바로 get 요청을 하는 것과 같습니다.


@GetMapping("/users")
public String list(Model model) {
model.addAttribute("users", users);
return "/user/list";
}

위의 list 메소드는 html 파일을 반환하는 작업을 수행합니다. 위와 같이 "/user/list" 를 반환하면 내부적으로 /user/list 를 찾아 반환시켜주기 때문에 list.html 파일이 반환됩니다. 또한 Model 객체를 생성해 addAttribute() 메소드를 통해 속성을 추가하면 list.html 파일에서 동적으로 model 객체의 속성을 이용할 수 있습니다. 예를 들어 동적 리스트를 생성하고 싶다면 users 속성을 이용해 users 리스트로 구성된 회원 리스트를 보여줄 수 있습니다.




우아한테크캠프 첫 째주

처음 우아한형제들의 "가평같은 방"에 들어가 어색해하며 팀원들과 인사하던게 몇 시간 전인 것만 같은데 벌써 일주일이 흘렀습니다..

우아한테크캠프에 참여하기 직전에는 동아리, 인턴 등을 통해 다양한 프로젝트를 해왔던 저는 실무에 잘 적응할 수 있을거라 생각했습니다. 하지만 이런 모든 생각을 반성하게 해준 고마운 첫째 주였습니다. '내가 코딩을 잘하는게 아니였구나', '내 코딩습관이 좋은 방법은 아니였구나' 를 끊임없이 반성하는 계기가 되었고, 팀원들과 같이 생각하고 배우며 더 발전해야겠다는 다짐을 했습니다.

'모르는 것을 부끄러워할 시간에, 공부하고 배워 아는 것으로 만들자' 를 목표로 발전해왔던 만큼, 앞으로도 잘 알지 못하는 스프링을 제 것으로 만들고 좋은 코딩 습관을 기르기 위해 노력할 것입니다.

'일상 > 우아한테크캠프' 카테고리의 다른 글

[우아한테크캠프] 7일차  (0) 2018.07.11
[우아한테크캠프] 6일차  (0) 2018.07.09
[우아한테크캠프] 4일차  (0) 2018.07.06
[우아한테크캠프] 3일차  (0) 2018.07.04
[우아한테크캠프] 2일차  (0) 2018.07.03
Comments