-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from GDSC-Hongik/cmj7271
3주차 미션코스 업로드
- Loading branch information
Showing
7 changed files
with
275 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 에 표현한다. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` 로 호출된 메세지는 모두 동일한 메세지를 갖고, 변경에도 용이해요. | ||
또한, 어떤 에러들이 있는지도 한 곳에서 쉽게 확인할 수 있다는 장점이 있어요. | ||
|
||
이 외에도 에러에 따른 상태코드, 기본값 등 다양한 설정에 활용할 수 있어요. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` 를 통해 줄일 수 있을 거 같다고 생각했어요. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 에서 함수에 마우스를 갖다대면 아래와 같은 이미지가 나와요. | ||
 | ||
어떤 에러를 던지는지 미리 알 수 있어요. | ||
|
||
이 에러는 잘못된 형식을 입력할 시, 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` 함수에서 처리하도록 할 수 있어요. | ||
이런 식으로 에러를 한 곳에서 처리하도록, 에러 처리의 책임을 상위 메소드에게 부여하는 역할을 해줘요. | ||
|
||
이렇게 적절한 에러 표시와 처리를 통해 예외상황을 잘 처리할 수 있답니다. |