-
Notifications
You must be signed in to change notification settings - Fork 415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[라빈] 체스 웹/DB 미션 제출합니다 #133
Conversation
refactor: final 키워드 추가
- 게임을 진행하는 ChessRunner 클래스 구현
…/ 각 체스 말을 초기화 할때의 정보를 enum 으로 구현
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요, 라빈!
구현 잘해주셨어요. :)
몇가지 피드백 추가했는데 참고 부탁드릴게요.
궁금하신 점 있으시면 DM 주세요!
Map<String, Object> model = new HashMap<>(); | ||
|
||
webChessController.start(); | ||
List<TileDto> tileDtos = webChessController.getTiles(); | ||
TeamDto teamDto = webChessController.getCurrentTeam(); | ||
|
||
model.put("tiles", tileDtos); | ||
model.put("currentTeam", teamDto); | ||
|
||
return render(model, "game.html"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Controller는 model에 대한 매핑과 어떤 뷰를 노출할지에 대한 책임까지 가지고있습니다.
이러한 로직들도 컨트롤러에 넘기면 어떨까요? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
컨트롤러는 뷰를 선택하는 역할을 하도록 하고 내부 로직은 서비스 레이어를 만들어서 처리하도록 수정했습니다
ChessBoardDAO chessBoardDAO = new ChessBoardDAO(); | ||
CurrentTeamDAO currentTeamDAO = new CurrentTeamDAO(); | ||
PieceDAO pieceDAO = new PieceDAO(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DAO는 매번 재생성 될 필요가 없는 객체인데요.
생성해서 재사용하도록 수정해보면 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
서비스 레이어에서 한번만 DAO 를 생성해서 재사용 하도록 수정했습니다.
private ChessRunner chessRunner; | ||
private ChessBoard chessBoard; | ||
private CurrentTeam currentTeam; | ||
private PieceOnBoards originalPieces; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
웹 컨트롤러 상에서는 상태를 가지는 객체를 들고있지 않도록 하는게 좋아요.
여러 쓰레드에서 접근하기 때문에 동기화 문제가 발생할 여지가 있습니다. :)
DAO를 통해 데이터를 꺼내가며 생성하도록 구현해보는게 좋을 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
컨트롤러가 상태를 갖는 객체를 갖지 않도록 서비스 레이어로 이동했습니다.
PreparedStatement pstmt = con.prepareStatement(query); | ||
ResultSet rs = pstmt.executeQuery(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
두 객체에 대해서도 꼭 close 해주세요. :)
try-with-resources를 참고해봐도 좋을 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
알려주신 내용을 참고해서 트랜잭션 처리 로직을 변경했습니다. 감사합니다 😀
import java.util.List; | ||
|
||
public class PieceDAO { | ||
public void addPiece(int chessBoardId, List<TileDto> tileDtos) throws SQLException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DAO에 Dto를 사용하는건 프레젠테이션 레이어까지 의존이 생길 여지가 있네요.
가능하면 여기서 사용하는 모델을 통해 저장하도록 해주는게 좋을 것 같아요. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DAO 에서는 데이터베이스에 저장된 데이터와 매핑되는 모델만을 이용하도록 변경했습니다.
pieceDAO.deletePiece(deletedPiece); | ||
} | ||
Optional<PieceOnBoard> sourcePiece = this.originalPieces.find(source); | ||
pieceDAO.updatePiece(sourcePiece.get(), target); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional.get은 왠만하면 지양하는 편이 좋아요. :)
get보다는 그외 제공하는 메서드를 통해 null에 대해서도 처리해주세요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional 에서 get() 을 호출하기 전 isPresent() 를 먼저 호출해서 null 처리를 해주었습니다.
@@ -39,4 +39,26 @@ | |||
public Map<Position, Piece> initialize() { | |||
return InitialBishop.initializeBishops(); | |||
} | |||
|
|||
@Override | |||
public Map<Position, Piece> webInitialize(Map<String, String> pieceOnBoards) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
webInitialize등 도메인에서 web에 대한 의존을 굳이 표현할 필요는 없을 것 같아요.
@Override | ||
public Map<Position, Piece> webInitialize(Map<String, String> pieceOnBoards) { | ||
Map<String, String> bishops = pieceOnBoards.entrySet().stream() | ||
.filter(entry -> entry.getValue().substring(0, 1).toLowerCase().equals("b")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"b"등 하드코딩은 지양하는게 좋을 것 같네요. :)
Map<String, String> knights = pieceOnBoards.entrySet().stream() | ||
.filter(entry -> entry.getValue().substring(0, 1).toLowerCase().equals("n")) | ||
.collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pieceOnBoard에 대해서 pieceUrl을 파싱하는게 아닌,
피스에 대한 정보 없이 이미지 이름 가지고 도메인을 풀어가는것보다는
pieceOnBoard자체에 어떤 피스인지에 대한 정보를 가지고있는게 맞지 않을까요?
도메인을 조금 더 드러나게 모델링해보아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
데이터베이스에 저장되는 piece -> pieceOnBoard 로 변경하고 이미지가 아닌 pieceType, team 정보를 저장하도록 변경하였습니다.
.filter(td -> td.getPosition().equals(tile.position())) | ||
.findFirst() | ||
.orElseThrow(IllegalArgumentException::new); | ||
tileDto.setPieceImageUrl(tile.pieceImageUrl()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setPieceImageUrl
세터는 되도록 지양하도록 구현해보아요. :)
다른 피드백에 드렸다시피 Tile 자체가 어떤 말인지에 대한 정보를 가지고있다면 이러한 주소는 굳이 가지고 있지 않아도 될 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
도메인이 드러나도록 모델링을 변경하니 Tile 클래스가 사라지게 되었습니다. 감사합니다!
Web chess
화투 안녕하세요!
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요! 라빈.
개발 잘해주셨어요.
몇가지 피드백 더 추가했는데 참고 부탁드릴게요. :)
PreparedStatement pstmt = con.prepareStatement(query)) { | ||
pstmt.executeUpdate(); | ||
} catch (SQLException e) { | ||
e.printStackTrace(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이펙티브 자바 아이템 77. 예외를 무시하지 말라
해당 케이스는 복구 불가능한 예외로 보이는데요, 예외를 무시하지 말고 커스텀 예외를 더 던져주는게 좋아보여요 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
답변 주신 내용을 바탕으로 CustomSQLException 을 만들어서 예외 출력 페이지로 이동시켰습니다!
return chessBoard; | ||
} catch (SQLException e) { | ||
e.printStackTrace(); | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
데이터가 없는 경우에 대해서는 null 혹은 Optional을 사용할 수 있지만
해당 케이스에서는 예외를 새로 던져보아요. :)
System.out.println("정상적으로 연결되었습니다."); | ||
} catch (SQLException e) { | ||
System.err.println("연결 오류:" + e.getMessage()); | ||
e.printStackTrace(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
연결 실패시에도 null을 돌려주어 null 위험성을 만드는 것 보다는 예외를 추가로 던져주는게 좋을 것 같아요. :)
private String pieceType; | ||
private String team; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pieceType, team에 대해서도 enum으로 관리해도 될 것 같네요.
position도 하나의 문자열로 처리하기보다는 기존 enum을 사용해서 자료구조를 명확하게 드러내주는게 좋을 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
답변 주신 내용을 바탕으로 도메인을 드러내도록 표현하게 변경하였습니다 :)
for (PieceOnBoard pieceOnBoard : pieceOnBoards) { | ||
String query = "INSERT INTO pieceOnBoard (position, pieceType, team, chessBoardId) " | ||
+ "VALUES(?, ?, ?, ?)"; | ||
PreparedStatement pstmt = con.prepareStatement(query); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PreparedStatement에 대해서도 꼭 close 해주세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try-with-resource 구문의 안쪽에 선언된 PreparedStatement, ResultSet 를 닫는 메서드를 추가하였습니다 :)
try (Connection con = ConnectionManager.getConnection(); | ||
PreparedStatement pstmt = con.prepareStatement(query);) { | ||
pstmt.setInt(1, chessBoard.getChessBoardId()); | ||
ResultSet rs = pstmt.executeQuery(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
result set도 close 해주세요. :)
public Optional<PieceOnBoard> find(String position) { | ||
Optional<PieceOnBoard> piece = Optional.ofNullable(this.pieceOnBoards.stream() | ||
.filter(p -> p.getPosition().equals(position)) | ||
.findFirst() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional을 굳이 추가로 래핑하지 않아도 findFirst 자체가 옵셔널을 리턴해요. :)
.filter(td -> td.getPosition().equals(entry.getKey().toString())) | ||
.findFirst() | ||
.orElseThrow(IllegalArgumentException::new) | ||
.setPieceImageUrl(entry.getValue().toSymbol() + entry.getValue().teamName().toLowerCase()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set을 사용하지 않을수 있지 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DTO 에서 사용하는 모든 setter 메서드를 제거하였습니다.
BoardScoreDTO currentTeam = new BoardScoreDTO(calculateScore()); | ||
currentTeam.setTeam(this.currentTeam.name()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
굳이 setter를 사용하지 않아도 되지 않을까요? 그냥 생성자를 통해 넘겨줘도 될 것 같네요. :)
private ChessBoard chessBoard; | ||
private CurrentTeam currentTeam; | ||
private PieceOnBoards originalPieces; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상태를 가지고 있는 해당 객체들은 싱글 인스턴스 내 필드로 관리하지 말고 메서드 단위로 생명주기를 짧게 가져가도록 해보면 어떨까요?
해당 서비스는 웹 요청 시 여러 쓰레드를 통해 요청이 들어오기 때문에 동기화 문제가 있을 수 있어요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chessBoardId 를 이용하여 해당 객체들의 생명 주기를 메서드 단위로 변경하였습니다.
화투 안녕하세요! 피드백 주신 내용 반영했습니다. 잘못 이해해서 반영한 부분이 있다면 지적 부탁드립니다! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요, 라빈.
피드백 반영 잘해주셨어요!
이번 단계 머지할게요!
간단한 피드백 추가했는데 참고만 한번 부탁드려요!
} catch (CustomSQLException e) { | ||
model.put("errMessage", e.getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그냥 모든 예외를 잡아 처리하도록 하여
http://sparkjava.com/documentation#error-handling
해당 기능을 활용했어도 좋았을 것 같네요. :)
do { | ||
command = Command.of(inputView.askGameCommand()); | ||
} while (isEmptyCommand(command)); | ||
return command.get(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional.get은 직접적으로 사용되지 않는게 좋아요.
} | ||
|
||
private void updateChessBoard(int chessBoardId, String source, String target) { | ||
PieceOnBoard piece = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자료 선언구와 초기화 구는 최대한 같이 해주시는고 참조 변경은 최대한 없애는게 좋을 것 같아요. :)
두개를 선언하게 되더라도요. :)
화투 안녕하세요! 라빈입니다.
체스 미션 1단계 머지 해주시면서 남겨주셨던 피드백 먼저 반영하고 웹과 DB 구현했습니다.
이번에 처음으로 DAO 라는 개념에 대해 알게 되었고 적용해서 프로그램을 구현해 봤습니다. 잘못 사용한 부분이 있다면 조언 부탁드리겠습니다!
감사합니다!