Skip to content

Commit

Permalink
Merge pull request #19 from GDSC-Hongik/cmj7271
Browse files Browse the repository at this point in the history
3주차 미션코스 업로드
  • Loading branch information
cmj7271 authored Dec 1, 2024
2 parents e201467 + 8dc6b3a commit ed1269a
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 4 deletions.
6 changes: 3 additions & 3 deletions 2nd-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
### 진행 요구 사항
* 기능 목록에 따라 커밋 혹은 브렌치를 나눈다.
* 한번에 `git add .` 을 통해 커밋하는 것은 금지한다.
* 이번 과제부터는 `spring` 을 사용한다.
* `build.gradle` 을 수정하여 `spring` 의존성을 설치하고 시작한다.
* 이번 과제부터는 `spring boot` 을 사용한다.
* `build.gradle` 을 수정하여 `spring boot` 관련 의존성을 설치하고 시작한다.
* 1주차 미션코스의 코드를 재활용하여 진행한다.(코드 수정은 허용한다.)
* 데이터베이스를 사용하지 않고, 메모리에서만 구동시킨다.

Expand All @@ -28,7 +28,7 @@
* 아래 기능을 수행하는 API 를 설계하고, 구현한다.(모두 json 형식으로 진행한다.)
* 지정한 캐릭터의 현재 상태를 알려준다.
* 지정한 캐릭터가 현재 가능한 행동들을 알려준다.
* 이때, 스킬은 기준에 따라 이름순, 혹은 남은 쿨타임 순으로 정렬한다.(query string 사용하)
* 이때, 스킬은 기준에 따라 이름순, 혹은 남은 쿨타임 순으로 정렬한다.(query string 사용한다)
* 몇턴 남았는지 또한 표시해준다.
* 1주차에 구현한 공격과 스킬들을 실행하고, 그에 따른 데미지 혹은 감소시킨 데미지(방어)의 수치를 반환한다.
* 아래는 1주차의 공격과 스킬 기능이다.
Expand Down
54 changes: 54 additions & 0 deletions 3rd-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 3주차 미션코스

## 학습목표
* DB 와 연결해보기
* 리팩토링 해보기 -> [1주차 공통 피드백 참고하기](./articles/feedback.md)

## 진행방식
* [기존에 포크한 레포지토리](https://github.com/GDSC-Hongik/2024-2-mission-course-java)를 pull 을 통해 업데이트하여 진행한다.
* 포크한 레포지토리에서 자신의 깃허브 이름으로 된 브렌치를 만들어서 진행한다.
* 아래 주어진 명세에 맞게 구현한다.
* 주어지지 않은 내용은 스스로 판단하여 구현한다.

## 제출 방법
* 자신의 이름으로 된 브렌치를 포크한 레포지토리의 main 브렌치에 PR을 날린다.

## 턴제 게임
### 진행 요구 사항
* 기능 목록에 따라 커밋 혹은 브렌치를 나눈다.
* 한번에 `git add .` 을 통해 커밋하는 것은 금지한다.
* 이번 과제부터는 `JPA` 을 사용한다.
* `build.gradle` 을 수정하여 `JPA` 의존성을 설치하고 시작한다.
* 2주차 미션코스의 코드를 재활용하여 진행한다.(코드 수정은 허용한다.)
* 게임 진행 중 서비스가 꺼지더라도, 재부팅시 이어서 진행가능해야 한다.
* 즉, DB 에 필요한 정보들을 저장해두고, 게임이 종료시에만, 데이터가 리셋된다.

### 기능 요구 사항
* 기존 2주차 까지의 명세를 따르나, 아래의 변경 사항이 추가된다.
* 캐릭터의 정보에 `job` 이 추가된다.
* `job` 에 따라 사용가능한 스킬들이 고정된다.
* `job` 에 따라, 기본 체력, 기본 마나가 주어진다.
* 사용자로부터 `레벨`을 입력받아 다음과 같은 수식으로 체력과 마나가 결정된다.
* 체력 = 기본체력 + 레벨 * 10
* 마나 = 기본 마나 + 마나 * 5
* 사용자가 지정된 형식을 지키지 않은 입력을 했을 경우, `400 Bad Request` 에러를 반환한다.
* 존재하지 않는 직업을 골랐을 경우, `404 Not Found` 에러를 반환한다.

### 입력
설계와 구현에 따라 엔드포인트, 경로 매개변수, request body, query string 등 다양하게 활용한다.
단, 캐릭터의 정보를 입력 받을 때는, `이름`, `직업`, `레벨` 을 입력받아야한다.
> 예시 (1주차 과제의 형식을 이용할 경우)
> `알파카,기사,10`
>
> 예시(json 형식일 경우)
> ```json
> {
> "name": "알파카",
> "job": "기사",
> "level": 10
> }
> ```
### 출력
구현에 따라 response body 에 표현한다.
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,44 @@ TODO: VO, DTO, DAO 공부해보기

공부하느라 수고하셨습니다!
그럼 2주차 미션도 진행해보아요.
[2주차 미션코스 명세서](./2nd-spec.md)
[2주차 미션코스 명세서](./2nd-spec.md)

---

# 미션 코스 3주차
벌써 마지막 미션 코스이네요. 여기서 공부한 내용들이 앞으로도 도움이 되었으면 좋겠네요!
마지막 미션으로는 기존 코드를 활용하여 DB 와 연동해보고, 에러 처리를 해보려고 해요.
그전에 제가 어떤 의도로 3주차 미션을 만들었는지 한번 짧게 돌아보려고 해요.

## 3주간의 과정 돌아보기
저희는 가장 먼저 비지니스 로직을 작성해보았어요.
저희만의 서비스를 위한 로직들로 기본적인 턴제 게임을 구현했어요.
아직은 API, DB 가 없기 때문에 간단한 콘솔 입출력을 활용했고요.
여기서 저희는 java 와 좀 더 친해지고, 객체지향에 대해 공부했어요.

그 다음으로는 해당 로직을 가지고, 콘솔 입출력에서 API 로 업그레이드 해봤어요.
프론트가 갖춰진다면 어엿한 하나의 게임을 만들었다고도 할 수 있죠.

하지만, 사실 이것만으로는 서비스가 꺼진다면 모든 데이터가 날라가요.
일부로 DB 사용을 금하고 메모리로 구현을 했기 때문이죠.
마지막으로 여러분이 DB 에 데이터를 영구적으로 저장한다면,
서버가 꺼지더라도 데이터는 남아있기 때문에 이어할 수 있을거에요.

저는 백엔드 개발을 DB, java, 통신(API) 이렇게 3가지로 나뉜다고 생각하고 미션코스 짜보았어요.
첫 주에는 java 와 친해지고, 비지니스 로직을 짜보는 연습을,
둘째 주는 외부와의 통신을 위한 API 설계를 해보았어요.
마지막 주에는 통신한 데이터를 저장할 수 있는 방법을 연습을 해볼거에요.
그리고 추가적으로 요구사항 변경과 에러 처리에 대응하는 연습도 해블께요.

## 개념 정리
이번 개념 정리로는 에러 처리 위주로 다뤄보려고 해요.
Java 는 `execption` 을 통해서 에러를 처리하는데요.
발생하는 위치에서도 일단 호출하는 함수(caller) 에서도 처리가 가능한 등 고민할 여지가 많아요.
기본적인 내용을 같이 공부하고 실제로 적용해봅시다.

### [try-catch](./articles/try_catch.md)
### [enum](./articles/enum.md)

마지막 개념 공부까지 수고하셨어요!
정말 마지막 미션까지 힘내보아요.
[3주차 미션코스](./3rd-spec.md)
34 changes: 34 additions & 0 deletions articles/enum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# enum
java 의 enum 기능은 강력해요.
생각보다 다양한 기능을 할 수 있고, 그만큼 복잡해요.
한번 알아봅시다.

기본적인 문법은 다음 자료를 확인해주세요!
> [enum 기초와 응용](https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%97%B4%EA%B1%B0%ED%98%95Enum-%ED%83%80%EC%9E%85-%EB%AC%B8%EB%B2%95-%ED%99%9C%EC%9A%A9-%EC%A0%95%EB%A6%AC#enum_%EC%84%A0%EC%96%B8)
enum 의 예시로 에러 메시지를 한 곳에서 관리할 수 있어요.
아래의 예시를 한번 볼께요.
```java
public enum Error {
FILE_NOT_FOUND("file 경로를 찾을 수 없습니다."),
FILE_FORMAT_NOT_MATCH("file 의 형식이 올바르지 않습니다."),
INPUT_WRONG_INPUT("잘못된 입력입니다. 다시 입력해 주세요."),
PRODUCT_EXCEEDED("재고 수량을 초과하여 구매할 수 없습니다. 다시 입력해 주세요.")
;

private final String message;

Error(String message) {
String prefix = Config.ERROR_PREFIX; // == [ERROR]
this.message = prefix + " " + message;
}
public String message() {
return message;
}
}
```
위와 같이 작성함으로서, 에러 메세지를 일괄적으로 관리할 수 있었어요.
`Error.FILE_NOT_FOUND` 로 호출된 메세지는 모두 동일한 메세지를 갖고, 변경에도 용이해요.
또한, 어떤 에러들이 있는지도 한 곳에서 쉽게 확인할 수 있다는 장점이 있어요.

이 외에도 에러에 따른 상태코드, 기본값 등 다양한 설정에 활용할 수 있어요.
90 changes: 90 additions & 0 deletions articles/feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# 공통 피드백
여러분들의 코드를 한번 쭈욱 살펴봤어요.
공통적으로 한번 고민해보면 좋을 거 같은 키워드를 같이 알아보아요.
(추가로 3주차까지 계속 사용할 코드이기 때문에 리팩토링 해보는 것도 괜찮을거 같네요!)

## 불변성(NOT DONE)
불변성은 코드의 버그를 줄일 뿐만 아니라, 개발자가 코드를 편하게 짜게 도와줘요.

예시와 함께 알아볼께요.
```java
public class Character {
// ...
private List<Skill> skills;
}

public class Skill {
private int min;
private int max;
private String name;
// ...
}
```


## interface
인터페이스를 객체의 협력 관계를 나타낼 수 있는 좋은 수단이라고 소개했어요.
간단하게 캐릭터와 스킬 객체 간의 관계로 생각해볼께요.
간단한 협력관계를 떠올려보면, `캐릭터가 스킬을 사용하여 데미지를 입힌다` 가 있을거에요.
즉, 스킬은 캐릭터에게 `데미지를 알려줘야하는 책임` 이 부여돼요.
반대로 캐릭터는 `데미지를 입는다는 책임` 이 있을 거에요.
객체가 다른 객체로부터 책임을 요구받아 구현을 해야하는 의무가 발생했어요.

이런 식으로 다른 객체로부터 요청받은 책임들을 나열하는 곳을 `인터페이스`라고 생각하면 좋을 거 같아요.
반대로, 객체 내부에서의 작업, 예컨데 검증 로직이라든가, 객체의 util 함수의 경우는 적을 필요가 없을거 같아요.

## getter, setter
getter, setter 는 최대한 지양해야 해요.
만약 둘을 모두 사용한다면, 그 멤버 변수는 `public` 과 무슨 차이점이 있나요?

객체지향의 중요한 특성 중 `캡슐화` 라는 용어를 들어봤을 거에요.
캡슐화를 통해 객체의 경계를 분명히 하여, 객체 내부의 구현의 자율성을 보장해요.
다시 말하면, 객체의 내부 구현은 신경쓰지 않겠다는 의미에요.
대신 원하는 데이터를 주기만 하면 된다는 의미이죠.

만약 객체(A)에서 다른 객체(B)의 `getter` 를 사용한다고 해볼께요.
근데, B 에서 A의 getter 를 왜 사용하나요?
A 의 책임을 B 에서 하고 있진 않나요?

예를 들어, `main` 에서 `Character` 객체에서 사용가능한 `Skill` 들만 필요하다고 해볼께요.
이 때, 이 로직을 `main``getSkills` 를 호출해서 일일이 검증해야 할까요?
그보다는 `Character` 내부에서 `availableSkills` 라는 메소드를 만드는게 더 좋아보여요.
이를 통해 불필요한 멤버변수 노출을 막고, 좀 더 직관적인 메소드명이 나오게 되었어요.

`setter` 의 경우도 비슷합니다. 책임을 전가하고 있진 않은지 주의해야해요.
또한 정말 필요하다면, 그 목적이 분명하도록 메서드명을 지어야해요.

그래서 제가 추천하는 방법은 일단 최대한 `getter`, `setter` 를 쓰지 않으려고 노력하는 것입니다.
정말 고민해도 필요하다면, 사용할 순 있습니다. 뭐든지 100% 네버는 없으니까요.
하지만, 고민하다보면 대부분의 경우는 필요하지 않을 거에요.

## abstract class vs interface
사실 저도 최근에 알게 된 문법인데요.
`abstract class``interface` 를 대체할 수 있지 않을까? 하는 질문을 어떤 분께 받았어요.
이에 대해 찾아본 자료와 의견을 공유해보면 좋을거 같아서 글로 남겨보아요.
간단히 문법적으로 설명하면, `abstract class` 는 공통 멤버 변수를 정의할 수 있어요.
반면에 `interface` 에서 변수를 정의하면 그것은 상수로서 작동해요.

간단한 예시를 들어볼께요.
```java
abstract class Animal {
private int leg;
// ...
}

public class lion extends Animal {
// leg 멤버 변수가 정의되어 있음
// ...
}
public class tiger extends Animal {
// leg 멤버 변수가 정의되어 있음
// ...
}
```
하지만 `interface` 는 저러한 기능을 지원하진 않아요.
일일이 `private int leg` 를 선언해줘야 합니다.
자세한 이야기는 [abstract vs interface](https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-vs-%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0#%EC%B6%94%EC%83%81_%ED%81%B4%EB%9E%98%EC%8A%A4_%EC%A0%95%EB%A6%AC)
를 참고하면 좋을거 같아요.

제 개인적인 감상은 `abstract` 문법은 추후 리팩토링에서, 혹은 구현을 하면서 고려해야 할 사항인 것 같아요.
전체적인 객체 설계는 `interface` 를 통해서, 구현하면서 중복되는 코드는 `abstract` 를 통해 줄일 수 있을 거 같다고 생각했어요.
Binary file added articles/resources/parseInt_explanation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 53 additions & 0 deletions articles/try_catch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# try-catch 문법
자바는 기본적으로 예외 처리를 `exception` 을 통해 다뤄요.
자세한 내용은 다음 글을 참고하면 좋을 거 같아요.

> [자바의 에러](https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%97%90%EB%9F%ACError-%EC%99%80-%EC%98%88%EC%99%B8-%ED%81%B4%EB%9E%98%EC%8A%A4Exception-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC)
> [에러 처리: try-catch](https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%ACException-%EB%AC%B8%EB%B2%95-%EC%9D%91%EC%9A%A9-%EC%A0%95%EB%A6%AC)
저러한 에러들은 `try-catch` 라는 문법을 이용하여, 개발자가 예외 처리를 해요.
이 때, 그 자리에서 바로 처리할지, 아니면 호출하는 함수(caller) 에서 처리할지 결정해줘야해요.
간단한 예시를 볼께요.
```java
public int parseIntWithNoError(String str) {
try {
return Integer.parseInt(str);
} catch(NumberFormatException e) {
return 0;
}
}
```
원래 `parseInt``NumberFormatException` 예외를 던지는 함수예요.
intellij 에서 함수에 마우스를 갖다대면 아래와 같은 이미지가 나와요.
![parseInt 설명 이미지](./resources/parseInt_explanation.png)
어떤 에러를 던지는지 미리 알 수 있어요.

이 에러는 잘못된 형식을 입력할 시, 0 이라는 기본값을 반환하도록 되어 있어요.
이처럼 기본값 등으로 에러를 정정할 수도, 에러를 발생시켜 시스템을 정지시킬 수도 있어요.

하지만, 한편으로는 에러가 발생하는 곳에서 산발적으로 처리하면 불편하다고 여길 수도 있어요.
한 곳에 모아서 처리하고 싶을 때는 `throws` 를 사용하면 됩니다.
위의 예시를 조금 변형해볼께요.
```java
public int parseIntThrowError(String str) throws NumberFormatException{
return Integer.parseInt(str);
}
```
(사실 `Integer.parseInt` 메소드랑 큰 차이는 없지만 예시로서 봐주세요!)
이제 이 함수에서의 에러처리에 대한 책임은 호출자 메소드(caller)로 넘어갑니다.

예컨데 다음과 같아요.
```java
public static void main(String[] args) {
String str = "100";
try {
parseIntThrowError(str);
} catch(NumberFormatException e) {
System.out.println(0);
}
}
```
`parseIntThrowError` 에서 처리하지 않고, 메소드를 부른 `main` 함수에서 처리하도록 할 수 있어요.
이런 식으로 에러를 한 곳에서 처리하도록, 에러 처리의 책임을 상위 메소드에게 부여하는 역할을 해줘요.

이렇게 적절한 에러 표시와 처리를 통해 예외상황을 잘 처리할 수 있답니다.

0 comments on commit ed1269a

Please sign in to comment.