Skip to content

[๐Ÿš€ ์‚ฌ์ดํด2 - ๋ฏธ์…˜ (๊ธฐ๋ฌผ ํ™•์žฅ + DB ์ ์šฉ)] ๋ฃจ๋“œ๋น„์ฝ” ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#325

Open
HyoYoonNam wants to merge 73 commits intowoowacourse:hyoyoonnamfrom
HyoYoonNam:cycle2

Conversation

@HyoYoonNam
Copy link
Copy Markdown

@HyoYoonNam HyoYoonNam commented Apr 8, 2026

์•ˆ๋…•ํ•˜์„ธ์š”, ์ฃผ๋…ธ!
์ด๋ฒˆ์—๋„ ๋ฏธ๋ฆฌ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค~~!

์ฒดํฌ ๋ฆฌ์ŠคํŠธ

  • ๋ฏธ์…˜์˜ ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ชจ๋‘ ๊ตฌํ˜„ํ–ˆ๋‚˜์š”?
    • Repository ๊ณ„์ธต์—์„œ Connection๊ณผ PreparedStatement๋Š” ํ•˜๋‚˜์˜ try-with-resources ๋ธ”๋ก์œผ๋กœ ์ž์›์„ ๋ฐ˜๋‚ฉํ•  ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ ํ•ด๋‹น ๋ธ”๋ก ๋‚ด์—์„œ ์ƒ์„ฑํ•ด์•ผ ๋˜๋Š” ResultSet์„ ๋‹ซ์„ ๋ฐฉ๋ฒ•์ด ์ถ”๊ฐ€๋กœ ํ•„์š”ํ–ˆ๊ณ , ์ด๋ฅผ ์œ„ํ•ด ๋‚ด๋ถ€์—์„œ try-with-resources๋ฅผ ์ค‘์ฒฉ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. (์•„๋ž˜์— ํ•ด๋‹น ์ฝ”๋“œ)

public long save(JanggiGame janggiGame) {
String sql = "INSERT INTO game (current_turn) VALUES (?)";
try (
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)
) {
pstmt.setString(1, janggiGame.currentTurn().name());
pstmt.executeUpdate();
// TODO: ResultSet ์ž์›๋„ ๋‹ซ์•„์•ผ ๋จ์„ ์ธ์ง€ํ•ด์„œ ์ผ๋‹จ ์ค‘์ฒฉ try-with-resources๋กœ ๋Œ€์‘ํ–ˆ์ง€๋งŒ, ์ค‘์ฒฉ์—†์ด ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•œ ์ง€ ๊ณ ๋ฏผ ํ•„์š”.
try (ResultSet rs = pstmt.getGeneratedKeys()) {
if (rs.next()) {
long gameId = rs.getLong(1);
insertPieces(conn, gameId, janggiGame);
return gameId;
}
}
throw new SQLException("๊ฒŒ์ž„ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}

  • Gradle test๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ํ†ต๊ณผํ–ˆ๋‚˜์š”?
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜๋‚˜์š”?

์–ด๋–ค ๋ถ€๋ถ„์— ์ง‘์ค‘ํ•˜์—ฌ ๋ฆฌ๋ทฐํ•ด์•ผ ํ• ๊นŒ์š”?

cycle1์˜ merge ์‹œ์ ์— ๋‚จ๊ฒจ์ฃผ์‹  ๋นˆ ๊ธฐ๋ฌผ์€ ๋งค๋ฒˆ ์ƒ์„ฑ๋˜์–ด์•ผ ํ• ๊นŒ?์— ๋Œ€ํ•ด ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ฐ˜์˜ํ–ˆ์Šต๋‹ˆ๋‹ค!

public Piece placedAt(Intersection intersection) {
return alivePieces.getOrDefault(intersection, Piece.EMPTY);
}

public final class Piece {
public static final Piece EMPTY = new Piece(PieceType.EMPTY, Side.NONE);

๊ธฐ์กด์—๋Š” Piece์˜ movablePaths() ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ PieceType์˜
movablePaths()์™€ movableDestinations๋ฅผ ์ ˆ์ฐจ์ ์œผ๋กœ ํ˜ธ์ถœํ–ˆ๋‹ค.

์ด๋ฅผ PieceType์ด ์ž์œจ์ ์œผ๋กœ ํ–‰๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก movableDestinations๋งŒ
์™ธ๋ถ€๋กœ ๊ณต๊ฐœํ•œ๋‹ค.

๋˜ํ•œ Piece์˜ movablePaths()๊ฐ€ List<Intersection>์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ๊ณผ ๋‹ฌ๋ฆฌ,
๋ฉ”์„œ๋“œ๋ช…์ด 'Paths'์—ฌ์„œ ์ด๋ฅผ 'Destinations'๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
๊ธฐ์กด์—๋Š” Piece.EMPTY ์ƒ์ˆ˜๋ฅผ public์œผ๋กœ ์ œ๊ณตํ•˜๊ธด ํ–ˆ์ง€๋งŒ,
new Piece(PieceType.EMPTY, Side.NONE)์ฒ˜๋Ÿผ ์ƒ์„ฑ์ž๋กœ ๊ฐ•์ œ ์ƒ์„ฑํ•˜๋ฉด ์ƒˆ๋กœ์šด
๋นˆ ๊ธฐ๋ฌผ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฆฌํ„ด๋˜๋Š” ๊ฒƒ์„ ๋ง‰์„ ์ˆ˜ ์—†์—ˆ๋‹ค.

Piece์˜ ์ƒ์„ฑ์ž๋ฅผ private์œผ๋กœ ๊ฐ์ถ”๊ณ , ์ •์  ํŒฉํ„ฐ๋ฆฌ ๋ฉ”์„œ๋“œ of()๋งŒ ์™ธ๋ถ€๋กœ
์—ด์–ด ๋งŒ์•ฝ ๋นˆ ๊ธฐ๋ฌผ์„ ์ƒ์„ฑํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๋‚ด๋ถ€์—์„œ ์ƒ์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๋Š” EMPTY๋ฅผ
๋ฆฌํ„ดํ•˜๋„๋ก ํ•œ๋‹ค.
๊ธฐ์กด์—๋Š” 'Point'๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ƒˆ๋กœ์šด ์š”๊ตฌ์‚ฌํ•ญ์—์„œ์˜ '์ ์ˆ˜'๊ฐ€ ์žฅ๊ธฐ ๋„๋ฉ”์ธ์—์„œ 'Point'๋กœ ์‚ฌ์šฉ๋˜๊ธฐ
๋•Œ๋ฌธ์— ํ˜ผ๋ž€์„ ๋ฐฉ์ง€ํ•˜๊ณ ์ž 'Position'์œผ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
๊ธฐ์กด ๋ฌธ์ œ 1: ์™•์ด ์žกํ˜”๋Š”๋ฐ๋„ ์ ์ˆ˜ ๊ธฐ๋ฐ˜์œผ๋กœ ์ŠนํŒจ๋ฅผ ํŒ์ •
ํ•ด๊ฒฐ 1: ์™•์ด ์žกํ˜”๋‹ค๋ฉด, ๋ฌด์กฐ๊ฑด ์žกํžˆ์ง€ ์•Š์€ ์ชฝ ์Šน๋ฆฌ.

๊ธฐ์กด ๋ฌธ์ œ 2: ์™•์ด ์žกํ˜”์„ ๋•Œ๋งŒ ๊ฒŒ์ž„์ด ์ข…๋ฃŒ
ํ•ด๊ฒฐ 2: ์™•์ด ์žกํžˆ์ง€ ์•Š์•˜๋”๋ผ๋„ ์–‘์ธก ์ง„์˜ ์ ์ˆ˜๊ฐ€ ๋ชจ๋‘ 30์  ๋ฏธ๋งŒ์ด๋ฉด ์ข…๋ฃŒ

๊ธฐํƒ€ ๊ฐœ์„  1: TestFixture ๋„์ž…ํ•˜์—ฌ ์ดˆ๊ธฐ ์ƒํ™ฉ ์…‹ํŒ…์„ ๋‹จ์ˆœํ™”

๊ธฐํƒ€ ๊ฐœ์„  2: ์ŠนํŒจ ํŒ์ •์˜ ๊ทผ๊ฑฐ(์™• ์žกํž˜ or ์ ์ˆ˜ ๋†’์Œ)๋ฅผ ํ•จ๊ป˜ ์ถœ๋ ฅ

๊ธฐํƒ€ ๊ฐœ์„  3: GameResult record ๋„์ž…
ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋Š” ๊ฐ™์€ interface์˜ default method์ธ
palaceDiagonalPaths์—์„œ๋งŒ ํ˜ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆํ•„์š”ํ•œ ์™ธ๋ถ€ ๋…ธ์ถœ์„ ์—†์• ๊ณ ์ž
private์œผ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
Copy link
Copy Markdown
Member

@Choi-JJunho Choi-JJunho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๋ฃจ๋“œ๋น„์ฝ” step2๋„ ๊ณ ์ƒํ•˜์…จ์Šต๋‹ˆ๋‹ค~
์งˆ๋ฌธ์ฃผ์‹ ๋‚ด์šฉ๊ณผ ์ฝ”๋“œ ๊ด€๋ จํ•ด์„œ ๋ช‡๊ฐ€์ง€ ์ฝ”๋ฉ˜ํŠธ ๋‚จ๊ฒผ์œผ๋‹ˆ ํ™•์ธ ๋ถ€ํƒ๋“œ๋ฆด๊ฒŒ์š”

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CREATE TABLE IF NOT EXISTS piece (
    game_id         INTEGER     NOT NULL,
    position_row    INTEGER,
    position_file   INTEGER,
    side            VARCHAR(10) NOT NULL,
    piece_type      VARCHAR(10) NOT NULL,
    FOREIGN KEY (game_id) REFERENCES game(game_id),
    PRIMARY KEY (game_id, position_row, position_file)
);

varchar 10์œผ๋กœ ์„ค์ •ํ•ด์ค€ ์ด์œ ๊ฐ€ ์žˆ์„๊นŒ์š”?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์ข‹์€ ์‹œ๋„์˜€๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค~! ๋‹ค๋งŒ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๋กœ Map์„ ๋‘๋ฉด์„œ ํ‘œํ˜„ํ•˜๋ ค๋‹ค๋ณด๋‹ˆ ๊ณผ๋„ํ•œ ์ถ”์ƒํ™”์ฒ˜๋Ÿผ ๋‹ค๊ฐ€์˜ค๊ธฐ๋„ ํ•˜๋„ค์š”

YANGI : You Ainโ€™t Gonna Need It ๋ผ๋Š” ๋ง์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ์ž‘์—…๋งŒ์„ ํ•˜๋ผ๋Š” ์˜๋ฏธ์ธ๋ฐ์š”. ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜๋‹ค๋ณด๋ฉด ์ง€๊ธˆ์ฒ˜๋Ÿผ ํ˜„์žฌ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ, ํ™•์žฅ์„ฑ์„ ๊ณ ๋ คํ•ด ๋ฏธ๋ฆฌ ์ž‘์—…ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋“ฑ์žฅํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ์–ด์ง„ ์š”๊ตฌ์‚ฌํ•ญ์— ์ง‘์ค‘ํ•˜๊ณ , ์ด๋Ÿฐ ํ–‰์œ„๋ฅผ ์ง€์–‘ํ•˜๋ผ๋Š” ์˜๋ฏธ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋ง์ธ๋ฐ ์ด ์˜๋ฏธ๋ฅผ ๋ฐ˜์˜ํ•ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ‘œํ˜„ํ•ด๋ณผ๊นŒ์š”?

์กฐ๊ฑด์ด 2~3๊ฐœ๋ฐ–์— ์•ˆ๋˜๋Š” ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” ๊ฐ„๋‹จํ•œ ์กฐ๊ฑด ๋ถ„๊ธฐ๊ฐ€ ์˜คํžˆ๋ ค ์ฝ๊ธฐ ์‰ฌ์šธ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค

Comment on lines +125 to +126
public abstract double bonusPoint();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ํ˜„์žฌ Side๊ฐ€ ๊ฐ€์ง„ ์ฑ…์ž„์ด ๋งŽ์•„์ง€๋ฉด์„œ ์ด๋Ÿฐ ๊ณ ๋ฏผ์ด ๋“œ๋Š”๊ฒƒ ๊ฐ™๋„ค์š”
Side์˜ ์—ญํ• ๊ณผ ์ฑ…์ž„์„ ํ•œ๋ฒˆ ๋‹ค์‹œ ์ƒ๊ฐํ•ด๋ณด๋ฉด์„œ ์ ์ ˆํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋Š”์ง€ ๊ฒ€ํ† ํ•ด๋ณด๋ฉด ์ข‹๊ฒ ๋„ค์š”

bonusPoint ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋Š” ์ •๋ง๋กœ Side๊ฐ€ ๊ฐ€์ ธ์•ผํ•˜๋Š” ํ–‰์œ„์ผ๊นŒ์š”?

Comment on lines -11 to +24
public final class StraightLineMovement extends Movement {
public final class StraightLineMovement extends Movement implements PalaceMovement {

private static final MoveAmount MOVE_AMOUNT = new MoveAmount(1);

@Override
protected List<Path> candidatePaths(Intersection from, Side side) {
return allDirectionPaths(from, side);
List<Path> paths = new ArrayList<>();

List<Path> orthogonalPaths = allDirectionPaths(from, side);
List<Path> palaceDiagonalPaths = PalaceMovement.super.palaceDiagonalPathsForStraight(from);
paths.addAll(orthogonalPaths);
paths.addAll(palaceDiagonalPaths);

return List.copyOf(paths);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์ œ ์˜๊ฒฌ์„ ๋จผ์ € ๋ง์”€๋“œ๋ฆฌ๊ธฐ ์ „์— ๋ฃจ๋“œ๋น„์ฝ”์˜ ์ƒ๊ฐ์„ ๋จผ์ € ๋“ฃ๊ณ ์‹ถ๋„ค์š”! (์งˆ๋ฌธ์— ์งˆ๋ฌธ์œผ๋กœ ๋‹ตํ•˜๊ฒŒ๋˜์—ˆ๋„ค์š” ๐Ÿ˜… )
๋ฃจ๋“œ๋น„์ฝ”๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ณธ๋ถ„์€ ๋ฌด์—‡์ธ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค! default ๋ฉ”์„œ๋“œ๋Š” ์™œ ์กด์žฌํ• ๊นŒ์š”?

Comment on lines +61 to +66
if (from.row() == 1 || from.row() == 3) {
return new Intersection(2, 5);
}

return new Intersection(9, 5);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์˜๋ฏธ๊ฐ€ ๋ชจํ˜ธํ•œ ๊ฐ’๋“ค (๋งค์ง๋„˜๋ฒ„)๋“ค์ด ์ž์ฃผ ๋“ฑ์žฅํ•˜๋Š” ๊ฒƒ ๊ฐ™๋„ค์š” ๐Ÿ‘€ ์ƒ์ˆ˜๋กœ ์ถ”์ถœํ•  ๋ฐฉ๋ฒ•์ด ์—†์„๊นŒ์š”?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(PalaceMovement์™€ Intersection์—์„œ ์‚ฌ์šฉ๋˜๋˜ ๋งค์ง๋„˜๋ฒ„ ๊ด€๋ จ)

๊ถ์„ฑ๊ณผ ๊ด€๋ จํ•œ ํŒ๋‹จ ๋ฐ ๋งค์ง๋„˜๋ฒ„๋ฅผ Palace ๋ฉ”์„œ๋“œ ๋ฐ ์ƒ์ˆ˜๋กœ ์ด๋™ํ–ˆ์Šต๋‹ˆ๋‹ค!

public final class Palace {
private static final int MIN_FILE = 4;
private static final int CENTER_FILE = 5;
private static final int MAX_FILE = 6;
private static final int HAN_MIN_ROW = 1;
private static final int HAN_CENTER_ROW = 2;
private static final int HAN_MAX_ROW = 3;
private static final int CHO_MIN_ROW = 8;
private static final int CHO_CENTER_ROW = 9;
private static final int CHO_MAX_ROW = 10;
private Palace() {
}
public static boolean contains(Intersection pos, Side side) {
final int row = pos.row();
final int file = pos.file();
boolean inPalaceFile = file >= MIN_FILE && file <= MAX_FILE;
if (side == Side.HAN) { // ๋งŒ์•ฝ Side enum์˜ ์ด๋ฆ„์ด ๋‹ค๋ฅด๋‹ค๋ฉด ๋งž๊ฒŒ ์ˆ˜์ •ํ•ด์ฃผ์„ธ์š”
return inPalaceFile && row >= HAN_MIN_ROW && row <= HAN_MAX_ROW;
}
return inPalaceFile && row >= CHO_MIN_ROW && row <= CHO_MAX_ROW;
}
public static boolean isCenter(Intersection pos) {
final int row = pos.row();
final int file = pos.file();
return file == CENTER_FILE &&
(row == HAN_CENTER_ROW || row == CHO_CENTER_ROW);
}
public static boolean isCorner(Intersection pos) {
final int row = pos.row();
final int file = pos.file();
boolean isHanCorner = Math.abs(row - HAN_CENTER_ROW) == 1;
boolean isChoCorner = Math.abs(row - CHO_CENTER_ROW) == 1;
boolean isFileCorner = Math.abs(file - CENTER_FILE) == 1;
return (isHanCorner || isChoCorner) && isFileCorner;
}
public static Intersection getCenterOf(Intersection cornerPos) {
if (cornerPos.row() <= HAN_MAX_ROW) {
return new Intersection(HAN_CENTER_ROW, CENTER_FILE);
}
return new Intersection(CHO_CENTER_ROW, CENTER_FILE);
}
public static List<Intersection> getCornersOf(Intersection center) {
if (!isCenter(center)) {
throw new IllegalStateException("์ค‘์•™ ์ขŒํ‘œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.");
}
final int r = center.row();
final int f = center.file();
return List.of(
new Intersection(r - 1, f - 1), new Intersection(r - 1, f + 1),
new Intersection(r + 1, f - 1), new Intersection(r + 1, f + 1)
);
}
public static Intersection getOppositeCornerOf(Intersection corner) {
if (!isCorner(corner)) {
throw new IllegalArgumentException("์ฝ”๋„ˆ ์ขŒํ‘œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.");
}
Intersection center = getCenterOf(corner);
int deltaRow = center.row() - corner.row();
int deltaFile = center.file() - corner.file();
return new Intersection(center.row() + deltaRow, center.file() + deltaFile);
}
}

Comment on lines +61 to +66
public static Side from(String sideName) {
return Arrays.stream(values())
.filter(side -> side.name().equals(sideName))
.findAny()
.orElse(NONE);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ด๋‚˜, DB์— ์ž˜๋ชป๋œ ๊ฐ’์ด ์ €์žฅ๋˜์–ด์žˆ์œผ๋ฉด ์˜ˆ์™ธ์—†์ด NONE์ด ๋ฐœ์ƒํ•˜๊ฒŒ๋˜๋Š”๋ฐ์š” ์ด ๋ถ€๋ถ„์€ ์˜๋„ํ•˜์‹  ๋ถ€๋ถ„์ผ๊นŒ์š”?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์˜๋„ํ•˜์ง€ ์•Š์•˜๊ณ , ์ œ๊ฐ€ ์ œ๋Œ€๋กœ ํ™•์ธํ•˜์ง€ ์•Š์•„ ๋ฐœ์ƒํ•œ ์ƒํ™ฉ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ˜์˜ํ•˜์—ฌ ์ฐพ์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ orElseThrow()๋ฅผ ๋˜์ง€๋„๋ก ๊ฐœ์„ ํ–ˆ๋Š”๋ฐ์š”.

์ด ๊ฒฝ์šฐ ๊ฒฐ๊ตญ Enum์ด ์ž์ฒด์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” valueOf()์™€ ๋กœ์ง์ด ๋™์ผํ•˜๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ Side.from() ์ž์ฒด๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ์ด๋ฅผ ํ˜ธ์ถœํ•˜๋˜ ๊ณณ์—์„œ๋Š” Side.valueOf()๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค.

public static <T extends Enum<T>> T valueOf(Class<T> enumClass,
                                            String name) {
    T result = enumClass.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumClass.getCanonicalName() + "." + name);
}

Comment on lines +7 to +11
public static MoveCommand from(String rawMoveCommand) {
if (rawMoveCommand.equalsIgnoreCase("exit")) {
// TODO ์ด๊ฒƒ๋„ null ๋„ฃ์–ด์„œ ๋ฑ‰๋Š” ๊ฑฐ ํ˜ธ์ถœ๋ถ€์—์„œ๋Š” ๋ฐฉ์–ด์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์• ์ดˆ์— null ๋ฑ‰๋Š” ๋ฐฉ์‹์ด ๋ฌธ์ œ๊ธด ํ•œ ๋“ฏ
return new MoveCommand(Type.EXIT, null);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO๊ฐ€ ๋‚จ์•„์žˆ๋„ค์š” ๐Ÿ‘€ ์ด๋ถ€๋ถ„์€ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null์ด๋‚˜ ๊ฐ’ ๋Œ€์‹  Optional์„ ๋ฑ‰๋„๋ก ํ•˜์—ฌ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๊ฒ€์ฆ ํ›„ ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

private GameWrapper processTurn(MoveCommand moveCommand, GameWrapper gameWrapper) {
JanggiGame janggiGame = gameWrapper.game();
Intersection startPosition = moveCommand.selectedToMove()
.orElseThrow(() -> new IllegalArgumentException("์ด๋™ํ•  ์ขŒํ‘œ(x,y) ๋˜๋Š” ์ข…๋ฃŒ(exit)๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."));

Comment on lines +69 to +77
public boolean isPalaceCenter() {
return (row == 2 || row == 9)
&& file == 5;
}

public boolean isPalaceCorner() {
return (row == 1 || row == 3 || row == 8 || row == 10)
&& (file == 4 || file == 6);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฐ’๋“ค์ด ๋งŽ๋„ค์š” ๐Ÿ’ซ
์ด๋Ÿฐ๊ฒƒ๋“ค์€ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ ํŽธํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„๊นŒ์š”?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(PalaceMovement์™€ Intersection์—์„œ ์‚ฌ์šฉ๋˜๋˜ ๋งค์ง๋„˜๋ฒ„ ๊ด€๋ จ)

๊ถ์„ฑ๊ณผ ๊ด€๋ จํ•œ ํŒ๋‹จ ๋ฐ ๋งค์ง๋„˜๋ฒ„๋ฅผ Palace ๋ฉ”์„œ๋“œ ๋ฐ ์ƒ์ˆ˜๋กœ ์ด๋™ํ–ˆ์Šต๋‹ˆ๋‹ค!

public final class Palace {
private static final int MIN_FILE = 4;
private static final int CENTER_FILE = 5;
private static final int MAX_FILE = 6;
private static final int HAN_MIN_ROW = 1;
private static final int HAN_CENTER_ROW = 2;
private static final int HAN_MAX_ROW = 3;
private static final int CHO_MIN_ROW = 8;
private static final int CHO_CENTER_ROW = 9;
private static final int CHO_MAX_ROW = 10;
private Palace() {
}
public static boolean contains(Intersection pos, Side side) {
final int row = pos.row();
final int file = pos.file();
boolean inPalaceFile = file >= MIN_FILE && file <= MAX_FILE;
if (side == Side.HAN) { // ๋งŒ์•ฝ Side enum์˜ ์ด๋ฆ„์ด ๋‹ค๋ฅด๋‹ค๋ฉด ๋งž๊ฒŒ ์ˆ˜์ •ํ•ด์ฃผ์„ธ์š”
return inPalaceFile && row >= HAN_MIN_ROW && row <= HAN_MAX_ROW;
}
return inPalaceFile && row >= CHO_MIN_ROW && row <= CHO_MAX_ROW;
}
public static boolean isCenter(Intersection pos) {
final int row = pos.row();
final int file = pos.file();
return file == CENTER_FILE &&
(row == HAN_CENTER_ROW || row == CHO_CENTER_ROW);
}
public static boolean isCorner(Intersection pos) {
final int row = pos.row();
final int file = pos.file();
boolean isHanCorner = Math.abs(row - HAN_CENTER_ROW) == 1;
boolean isChoCorner = Math.abs(row - CHO_CENTER_ROW) == 1;
boolean isFileCorner = Math.abs(file - CENTER_FILE) == 1;
return (isHanCorner || isChoCorner) && isFileCorner;
}
public static Intersection getCenterOf(Intersection cornerPos) {
if (cornerPos.row() <= HAN_MAX_ROW) {
return new Intersection(HAN_CENTER_ROW, CENTER_FILE);
}
return new Intersection(CHO_CENTER_ROW, CENTER_FILE);
}
public static List<Intersection> getCornersOf(Intersection center) {
if (!isCenter(center)) {
throw new IllegalStateException("์ค‘์•™ ์ขŒํ‘œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.");
}
final int r = center.row();
final int f = center.file();
return List.of(
new Intersection(r - 1, f - 1), new Intersection(r - 1, f + 1),
new Intersection(r + 1, f - 1), new Intersection(r + 1, f + 1)
);
}
public static Intersection getOppositeCornerOf(Intersection corner) {
if (!isCorner(corner)) {
throw new IllegalArgumentException("์ฝ”๋„ˆ ์ขŒํ‘œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.");
}
Intersection center = getCenterOf(corner);
int deltaRow = center.row() - corner.row();
int deltaFile = center.file() - corner.file();
return new Intersection(center.row() + deltaRow, center.file() + deltaFile);
}
}

Comment on lines +56 to +79
public long save(JanggiGame janggiGame) {
String sql = "INSERT INTO game (current_turn) VALUES (?)";

try (
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)
) {
pstmt.setString(1, janggiGame.currentTurn().name());
pstmt.executeUpdate();
// TODO: ResultSet ์ž์›๋„ ๋‹ซ์•„์•ผ ๋จ์„ ์ธ์ง€ํ•ด์„œ ์ผ๋‹จ ์ค‘์ฒฉ try-with-resources๋กœ ๋Œ€์‘ํ–ˆ์ง€๋งŒ, ์ค‘์ฒฉ์—†์ด ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•œ ์ง€ ๊ณ ๋ฏผ ํ•„์š”.
try (ResultSet rs = pstmt.getGeneratedKeys()) {
if (rs.next()) {
long gameId = rs.getLong(1);
insertPieces(conn, janggiGame, gameId);

return gameId;
}
}

throw new SQLException("๊ฒŒ์ž„ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

game INSERT์™€ piece INSERT๊ฐ€ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์—ฌ์žˆ์ง€ ์•Š๋„ค์š”. insert๊ฐ€ ์™„๋ฃŒ๋˜๊ณ  ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ• ๊นŒ์š”?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๊ฒŒ์ž„์€ ์ €์žฅ๋˜์ง€๋งŒ ๊ทธ์— ๋Œ€ํ•œ ๊ธฐ๋ฌผ ์ •๋ณด๋Š” ์ œ๋Œ€๋กœ ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š” ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๋ฅผ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ์•„๋‹Œ TransactionTemplate๊ฐ€ ๋‹ด๋‹นํ•˜๋„๋ก ๋ณ€๊ฒฝํ•˜๋Š” ๊ณผ์ •์—์„œ ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค!

public <T> T execute(TransactionCallback<T> action) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
T doingResult = action.doInTransaction(conn);
conn.commit();
return doingResult;
} catch (SQLException e) {
rollback(conn);
throw new DataAccessException(e);
} catch (RuntimeException e) {
rollback(conn);
throw e;
} finally {
close(conn);
}
}

public GameWrapper createGame(Board board) {
return transactionTemplate.execute(conn -> {
JanggiGame janggiGame = JanggiGame.create(board);
long generatedKey = repository.save(conn, janggiGame);
return new GameWrapper(generatedKey, janggiGame);
});
}


import java.util.Date;

public record GameSummary(long id, Date startedAt, String currentTurn) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Date์™€ LocalDateTime ์˜ ์ฐจ์ด๋ฅผ ํ•œ๋ฒˆ ํ™•์ธํ•ด๋ณด๊ณ  ์ด์•ผ๊ธฐํ•ด์ฃผ์„ธ์š”!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Date์˜ ๊ฒฝ์šฐ JDK 1.0๋ถ€ํ„ฐ ํฌํ•จ๋œ ๊ฒƒ์œผ๋กœ '๊ฐ€๋ณ€์ '์ด๋ผ๋Š” ํฐ ๋‹จ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ java.util ํŒจํ‚ค์ง€ ํ•˜์œ„์— ์œ„์น˜ํ•ด์žˆ๋Š”๋ฐ, ์˜ค๋ผํด์—์„œ "Contains the collections framework, legacy collection classes, event model, date and time facilities" ๋ผ๊ณ  ์„ค๋ช…ํ•˜๋Š”, ์‹œ๊ฐ„์„ ๋‹ค๋ฃจ๋Š” '๋ ˆ๊ฑฐ์‹œ'ํ•œ ๋ฐฉ๋ฒ•์ž„์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

์ดํ›„ JDK 1.8์—์„œ ๋‚ ์งœ์™€ ์‹œ๊ฐ„์„ ์ „์šฉ์œผ๋กœ ๋‹ค๋ฃจ๋Š” java.time ํŒจํ‚ค์ง€๊ฐ€ ๋“ฑ์žฅํ–ˆ๊ณ , LocalDateTime์ด ์—ฌ๊ธฐ์— ์†ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
๊ฐ€์žฅ ํฐ ๋ณ€ํ™”๋กœ๋Š” '๋ถˆ๋ณ€'์ด๋ผ๋Š” ๊ฒƒ์ด๊ณ , ์ด์™ธ์— ๋‚ ์งœ/์‹œ๊ฐ„ ๊ณ„์‚ฐ์„ ์œ„ํ•œ ํŽธ์˜ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ ๋“ฑ์ด ๋˜์—ˆ์Œ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

The main API for dates, times, instants, and durations.
...
All the classes are immutable and thread-safe.

๊ฒฐ๊ณผ์ ์œผ๋กœ, ๋‹ค์Œ์ฒ˜๋Ÿผ LocalDateTime์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค!

public record GameSummary(long id, LocalDateTime startedAt, String currentTurn) {
}

public List<GameSummary> findAll(Connection conn) {
String sql = ""
+ "SELECT game_id, created_at, current_turn "
+ "FROM game ";
List<GameSummary> gameSummaries = new ArrayList<>();
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
gameSummaries.add(new GameSummary(
rs.getLong("game_id"),
rs.getObject("created_at", LocalDateTime.class),
rs.getString("current_turn")
));
}
}

๊ธฐ์กด์—๋Š” ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๋ฅผ ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ์ˆ˜ํ–‰ํ–ˆ๋‹ค.

๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ํ…œํ”Œ๋ฆฟํ™”ํ•œ๋‹ค. ๋˜ํ•œ ๋•๋ถ„์— ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ๋Š” DB
์ข…์†์ ์ธ ์˜ˆ์™ธ๋ฅผ ๋‹ค๋ฃจ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
๊ธฐ์กด์˜ from()์€ ์ฐพ์ง€ ๋ชปํ•˜๋ฉด NONE์„ ๋ฆฌํ„ดํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š”
๊ณณ์ด ์—†์—ˆ๊ณ , ์˜คํžˆ๋ ค ์กฐ์šฉํ•œ ์‹คํŒจ๋กœ ์ธํ•ด ๋ฌธ์ œ ์›์ธ์„ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ํ–ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ ์ž orElse(NONE)์„ orElseThrow(...)์œผ๋กœ ๋ณ€๊ฒฝํ–ˆ๋”๋‹ˆ, ๊ทธ
๊ตฌ์กฐ๊ฐ€ Enum์ด ์ž์ฒด์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” valueOf()์™€ ๋™์ผํ•จ์„ ํ™•์ธํ–ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ค‘๋ณต์ ์œผ๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ณ , JDK ์ƒ์—์„œ ์ด๋ฏธ ๊ตฌํ˜„๋œ Enum.valueOf()๋ฅผ
์‚ฌ์šฉํ•œ๋‹ค.
์ง„์˜๋ณ„ ๋ณด๋„ˆ์Šค ์ ์ˆ˜, ์ฆ‰ '์ ์ˆ˜'๋ž€ ๊ฒŒ์ž„์„ ์ง„ํ–‰ํ•˜๋Š” ์ƒํ™ฉ์—์„œ ํ•„์š”ํ•œ
๊ฐœ๋…์ด๋‹ค. ๋”ฐ๋ผ์„œ ์ง„์˜ ๊ทธ ์ž์ฒด๋กœ ๋ณด๋„ˆ์Šค ์ ์ˆ˜๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ,
๊ฒŒ์ž„์—์„œ ์ฑ…์ž„์„ ๊ฐ€์ง์ด ์ ์ ˆํ•˜๋‹ค.
๊ธฐ์กด์—๋Š” DB ๊ด€๋ จ ์˜ˆ์™ธ๋งŒ ํ•ธ๋“ค๋งํ•˜์—ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ์—๋Š”
๋กค๋ฐฑ์ด ์ˆ˜ํ–‰๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด DB ์˜ˆ์™ธ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ RuntimeException๋„ ์ถ”๊ฐ€๋กœ ๋‹ค๋ฃฌ๋‹ค.
Copy link
Copy Markdown
Author

@HyoYoonNam HyoYoonNam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์•ˆ๋…•ํ•˜์„ธ์š”, ์ฃผ๋…ธ!

์ด๋ฒˆ์—๋„ ์ž˜ ๋ถ€ํƒ๋“œ๋ฆฌ๊ณ , ์ƒˆ๋กญ๊ฒŒ ์‹œ์ž‘๋˜๋Š” ํ•œ ์ฃผ๋„ ํ™”์ดํŒ…์ž…๋‹ˆ๋‹ค!!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์ปจํŠธ๋กค๋Ÿฌ ๋‚ด๋ถ€์—์„œ if ๋ถ„๊ธฐ๋ฅผ ์ถ”์ƒํ™”ํ•˜์—ฌ ๊ฐ€์ง€๋˜ Map์„ ์ œ๊ฑฐํ•˜๊ณ , ๋ช…์‹œ์ ์œผ๋กœ if ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค!

private void dispatchLoadCommand(LoadCommand loadCommand, List<Long> selectableNumbers) {
long gameNumberToLoad = loadCommand.gameNumberToLoad();
if (!selectableNumbers.contains(gameNumberToLoad)) {
throw new IllegalArgumentException(gameNumberToLoad + "๋Š” ์œ ํšจํ•˜์ง€ ์•Š๋Š” ๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค.");
}
if (loadCommand.isNewGame()) {
newGameFlow();
return;
}
if (loadCommand.isLoadGame()) {
continueGame(gameNumberToLoad);
return;
}

Comment on lines +7 to +11
public static MoveCommand from(String rawMoveCommand) {
if (rawMoveCommand.equalsIgnoreCase("exit")) {
// TODO ์ด๊ฒƒ๋„ null ๋„ฃ์–ด์„œ ๋ฑ‰๋Š” ๊ฑฐ ํ˜ธ์ถœ๋ถ€์—์„œ๋Š” ๋ฐฉ์–ด์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์• ์ดˆ์— null ๋ฑ‰๋Š” ๋ฐฉ์‹์ด ๋ฌธ์ œ๊ธด ํ•œ ๋“ฏ
return new MoveCommand(Type.EXIT, null);
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null์ด๋‚˜ ๊ฐ’ ๋Œ€์‹  Optional์„ ๋ฑ‰๋„๋ก ํ•˜์—ฌ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๊ฒ€์ฆ ํ›„ ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

private GameWrapper processTurn(MoveCommand moveCommand, GameWrapper gameWrapper) {
JanggiGame janggiGame = gameWrapper.game();
Intersection startPosition = moveCommand.selectedToMove()
.orElseThrow(() -> new IllegalArgumentException("์ด๋™ํ•  ์ขŒํ‘œ(x,y) ๋˜๋Š” ์ข…๋ฃŒ(exit)๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."));

Comment on lines +51 to +62
public int toPoint() {
return type.point();
}

public PieceType getType() {
return type;
}

public Side getSide() {
return side;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๊ณผ๊ฑฐ(์šฐํ…Œ์ฝ” ์ž…๊ณผ ์ „)์— ๋ง์”€ํ•˜์‹  toDbRow()๊ฐ™์€ ๋งคํ•‘ ๋ฉ”์„œ๋“œ๋ฅผ ๋„๋ฉ”์ธ ์•ˆ์— ๋‘๋Š” ๋ฐฉ์‹์„ ๋ฐฐ์› ์—ˆ๋Š”๋ฐ, ํฌ๋ฃจ๋“ค๊ณผ ์ด์•ผ๊ธฐํ•˜๋‹ค ๋ณด๋‹ˆ 'ํŽธํ•˜๊ธด ํ•˜์ง€๋งŒ ๋„๋ฉ”์ธ์ด DB๋‚˜ ๋ทฐ์— ์ข…์†๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค'๋กœ ๊ฒฐ๋ก ๋‚ด๋ ค ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

PieceMapper ์ค‘๊ฐ„ ๋‹ค๋ฆฌ๋ฅผ ๋†“๋Š”๋‹ค๋Š” ๋ฐฉํ–ฅ์€ ์ƒ๊ฐํ•˜์ง€ ๋ชปํ–ˆ๊ณ , ๋•๋ถ„์— ์ ์šฉํ•ด๋ดค์Šต๋‹ˆ๋‹ค!

์ ์šฉ ๊ณผ์ •์—์„œ '๋งคํผ๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ๊ฒฐ๊ตญ ๋„๋ฉ”์ธ์˜ getter๋Š” ์—ด๋ ค์•ผ ํ•œ๋‹ค'๋Š” ์ ์„ ํ™•์ธํ–ˆ๊ณ , ๋งคํผ์˜ ์˜์˜๋Š” 'getter๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ณณ์„ ๋งคํผ ํ•œ ๊ณณ์œผ๋กœ ์‘์ง‘'์‹œํ‚ค๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€๋กœ, PieceMapper๋ฅผ ์‚ฌ์šฉํ•  ๊ณณ์ด repository๋กœ ํ•œ์ •๋œ๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ํด๋ž˜์Šค ์ ‘๊ทผ ์ œ์–ด์ž๋ฅผ package-private๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค(ํ•˜์œ„ ๋ฉ”์„œ๋“œ๋“ค์˜ ๊ฒฝ์šฐ ํด๋ž˜์Šค ์ˆ˜์ค€์˜ ์ ‘๊ทผ ์ œ์–ด์ž๋ฅผ ๋”ฐ๋ผ๊ฐ์œผ๋กœ ์•Œ๊ณ  ์žˆ์ง€๋งŒ, ๋ฒ”์œ„๋ฅผ ํ•œ์ •ํ•œ๋‹ค๋Š” ์˜๋ฏธ๋ฅผ ๊ฐ•์กฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ”์„œ๋“œ๋“ค์˜ ์ ‘๊ทผ ์ œ์–ด์ž๋“ค๋„ public์ด ์•„๋‹Œ package-private๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค).

final class PieceMapper {
private PieceMapper() {
}
static PieceEntity toEntity(long gameId, Intersection position, Piece piece) {
return new PieceEntity(
gameId,
position.row(),
position.file(),
piece.getType().name(),
piece.getSide().name()
);
}
static Piece toPiece(String typeName, String sideName) {
return Piece.of(
PieceType.from(typeName),
Side.valueOf(sideName)
);
}
}

for (Entry<Intersection, Piece> entry : map.entrySet()) {
PieceEntity entity = PieceMapper.toEntity(gameId, entry.getKey(), entry.getValue());
pstmt.setInt(2, entity.row());
pstmt.setInt(3, entity.file());
pstmt.setString(4, entity.type());
pstmt.setString(5, entity.side());
pstmt.addBatch();
}


public final class MemoryDBConnectionUtil {

private static final String JDBC_SQLITE_URL = "jdbc:sqlite::memory:?cache=shared";
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'๋ฆฌ๋ทฐ์–ด๊ฐ€ clone ํ›„ ๋ณ„ ๋‹ค๋ฅธ ์„ค์ •์—†์ด ๋ฐ”๋กœ Application์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ'์ด ์ œ๊ฐ€ ์ค‘์š”ํ•˜๊ฒŒ ์ƒ๊ฐํ•œ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

์ด๋•Œ MySQL์€ ๋กœ์ปฌ ์„ค์น˜๋ฅผ ์š”๊ตฌํ•˜๊ฑฐ๋‚˜, Docker๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ๋„์ปค์˜ ์„ค์น˜๊ฐ€ ์š”๊ตฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„ ํƒ์—์„œ ๋ฐฐ์ œํ–ˆ์Šต๋‹ˆ๋‹ค.
(์ œ๊ฐ€ ์ฐพ์€ ๋ฐ”๋กœ๋Š” ๊ทธ๋žฌ๋Š”๋ฐ, ๋ฐฉ๋ฒ•์ด ์žˆ์Œ์—๋„ ์กฐ์‚ฌ๊ฐ€ ๋ฏธ์ˆ™ํ•ด์„œ ๋‚ด๋ ค์ง„ ๊ฒฐ๋ก ์ด๋ผ๋ฉด ์ฃ„์†กํ•˜๋‹ค๋Š” ๋ง์”€๋“œ๋ฆฝ๋‹ˆ๋‹ค!!)

์ดํ›„ H2์™€ SQLite๊ฐ€ ํ›„๋ณด๋กœ ๋‚จ์•˜๋Š”๋ฐ, H2๋Š” ๊ณผ๊ฑฐ์— ๊ฐ„๋‹จํ•˜๊ฒŒ๋‚˜๋งˆ ์‚ฌ์šฉํ•œ ๊ฒฝํ—˜์ด ์žˆ์–ด ์ด๋ฒˆ ๊ธฐํšŒ์— ์ƒˆ๋กœ์šด ์‹œ๋„๋ฅผ ํ•˜๊ณ ์ž SQLite๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.


import java.util.Date;

public record GameSummary(long id, Date startedAt, String currentTurn) {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Date์˜ ๊ฒฝ์šฐ JDK 1.0๋ถ€ํ„ฐ ํฌํ•จ๋œ ๊ฒƒ์œผ๋กœ '๊ฐ€๋ณ€์ '์ด๋ผ๋Š” ํฐ ๋‹จ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ java.util ํŒจํ‚ค์ง€ ํ•˜์œ„์— ์œ„์น˜ํ•ด์žˆ๋Š”๋ฐ, ์˜ค๋ผํด์—์„œ "Contains the collections framework, legacy collection classes, event model, date and time facilities" ๋ผ๊ณ  ์„ค๋ช…ํ•˜๋Š”, ์‹œ๊ฐ„์„ ๋‹ค๋ฃจ๋Š” '๋ ˆ๊ฑฐ์‹œ'ํ•œ ๋ฐฉ๋ฒ•์ž„์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

์ดํ›„ JDK 1.8์—์„œ ๋‚ ์งœ์™€ ์‹œ๊ฐ„์„ ์ „์šฉ์œผ๋กœ ๋‹ค๋ฃจ๋Š” java.time ํŒจํ‚ค์ง€๊ฐ€ ๋“ฑ์žฅํ–ˆ๊ณ , LocalDateTime์ด ์—ฌ๊ธฐ์— ์†ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
๊ฐ€์žฅ ํฐ ๋ณ€ํ™”๋กœ๋Š” '๋ถˆ๋ณ€'์ด๋ผ๋Š” ๊ฒƒ์ด๊ณ , ์ด์™ธ์— ๋‚ ์งœ/์‹œ๊ฐ„ ๊ณ„์‚ฐ์„ ์œ„ํ•œ ํŽธ์˜ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€ ๋“ฑ์ด ๋˜์—ˆ์Œ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

The main API for dates, times, instants, and durations.
...
All the classes are immutable and thread-safe.

๊ฒฐ๊ณผ์ ์œผ๋กœ, ๋‹ค์Œ์ฒ˜๋Ÿผ LocalDateTime์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค!

public record GameSummary(long id, LocalDateTime startedAt, String currentTurn) {
}

public List<GameSummary> findAll(Connection conn) {
String sql = ""
+ "SELECT game_id, created_at, current_turn "
+ "FROM game ";
List<GameSummary> gameSummaries = new ArrayList<>();
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
gameSummaries.add(new GameSummary(
rs.getLong("game_id"),
rs.getObject("created_at", LocalDateTime.class),
rs.getString("current_turn")
));
}
}

Comment on lines +69 to +77
public boolean isPalaceCenter() {
return (row == 2 || row == 9)
&& file == 5;
}

public boolean isPalaceCorner() {
return (row == 1 || row == 3 || row == 8 || row == 10)
&& (file == 4 || file == 6);
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(PalaceMovement์™€ Intersection์—์„œ ์‚ฌ์šฉ๋˜๋˜ ๋งค์ง๋„˜๋ฒ„ ๊ด€๋ จ)

๊ถ์„ฑ๊ณผ ๊ด€๋ จํ•œ ํŒ๋‹จ ๋ฐ ๋งค์ง๋„˜๋ฒ„๋ฅผ Palace ๋ฉ”์„œ๋“œ ๋ฐ ์ƒ์ˆ˜๋กœ ์ด๋™ํ–ˆ์Šต๋‹ˆ๋‹ค!

public final class Palace {
private static final int MIN_FILE = 4;
private static final int CENTER_FILE = 5;
private static final int MAX_FILE = 6;
private static final int HAN_MIN_ROW = 1;
private static final int HAN_CENTER_ROW = 2;
private static final int HAN_MAX_ROW = 3;
private static final int CHO_MIN_ROW = 8;
private static final int CHO_CENTER_ROW = 9;
private static final int CHO_MAX_ROW = 10;
private Palace() {
}
public static boolean contains(Intersection pos, Side side) {
final int row = pos.row();
final int file = pos.file();
boolean inPalaceFile = file >= MIN_FILE && file <= MAX_FILE;
if (side == Side.HAN) { // ๋งŒ์•ฝ Side enum์˜ ์ด๋ฆ„์ด ๋‹ค๋ฅด๋‹ค๋ฉด ๋งž๊ฒŒ ์ˆ˜์ •ํ•ด์ฃผ์„ธ์š”
return inPalaceFile && row >= HAN_MIN_ROW && row <= HAN_MAX_ROW;
}
return inPalaceFile && row >= CHO_MIN_ROW && row <= CHO_MAX_ROW;
}
public static boolean isCenter(Intersection pos) {
final int row = pos.row();
final int file = pos.file();
return file == CENTER_FILE &&
(row == HAN_CENTER_ROW || row == CHO_CENTER_ROW);
}
public static boolean isCorner(Intersection pos) {
final int row = pos.row();
final int file = pos.file();
boolean isHanCorner = Math.abs(row - HAN_CENTER_ROW) == 1;
boolean isChoCorner = Math.abs(row - CHO_CENTER_ROW) == 1;
boolean isFileCorner = Math.abs(file - CENTER_FILE) == 1;
return (isHanCorner || isChoCorner) && isFileCorner;
}
public static Intersection getCenterOf(Intersection cornerPos) {
if (cornerPos.row() <= HAN_MAX_ROW) {
return new Intersection(HAN_CENTER_ROW, CENTER_FILE);
}
return new Intersection(CHO_CENTER_ROW, CENTER_FILE);
}
public static List<Intersection> getCornersOf(Intersection center) {
if (!isCenter(center)) {
throw new IllegalStateException("์ค‘์•™ ์ขŒํ‘œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.");
}
final int r = center.row();
final int f = center.file();
return List.of(
new Intersection(r - 1, f - 1), new Intersection(r - 1, f + 1),
new Intersection(r + 1, f - 1), new Intersection(r + 1, f + 1)
);
}
public static Intersection getOppositeCornerOf(Intersection corner) {
if (!isCorner(corner)) {
throw new IllegalArgumentException("์ฝ”๋„ˆ ์ขŒํ‘œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.");
}
Intersection center = getCenterOf(corner);
int deltaRow = center.row() - corner.row();
int deltaFile = center.file() - corner.file();
return new Intersection(center.row() + deltaRow, center.file() + deltaFile);
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๊ธฐ์กด์—๋Š” ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ณ„์ธต๊ณผ ๊ด€๋ จ๋œ ์˜ˆ์™ธ๋ฅผ ๋ชจ๋‘ IllegalStateException์œผ๋กœ ๊ฐ์‹ธ์„œ ๋˜์กŒ๋Š”๋ฐ, ๋ฆฌํŒฉํ„ฐ๋ง ๊ณผ์ •์—์„œ ๊ตฌ๋ถ„์ด ๊ฐ€๋Šฅํ•œ ์ปค์Šคํ…€ ์˜ˆ์™ธ๋กœ ๋ฐ”๊ฟ”์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

Comment on lines +20 to +64
public <T> T execute(TransactionCallback<T> action) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);

T doingResult = action.doInTransaction(conn);

conn.commit();

return doingResult;
} catch (SQLException e) {
rollback(conn);
throw new DataAccessException(e);
} catch (RuntimeException e) {
rollback(conn);
throw e;
} finally {
close(conn);
}
}

private void rollback(Connection conn) {
if (conn == null) {
return;
}

try {
conn.rollback();
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, LOG_MESSAGE_FORMAT.formatted("๋กค๋ฐฑ"), e);
}
}

private void close(Connection conn) {
if (conn == null) {
return;
}

try {
conn.close();
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, LOG_MESSAGE_FORMAT.formatted("Connection ๋ฐ˜๋‚ฉ"), e);
}
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๊ฒฐ๋ก : ๋กค๋ฐฑ๊ณผ ์ž์› ๋ฐ˜๋‚ฉ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋Š” ์ƒ์œ„ ๊ณ„์ธต์œผ๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ๋กœ๊ทธ๋งŒ ์ฐ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทผ๊ฑฐ ๋ฐ ์ƒํ™ฉ ์„ค๋ช…:

  1. (line 26) action.doInTransaction()์—์„œ CRUD๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ
  2. (line 32) 1๋ฒˆ์˜ ์˜ˆ์™ธ๋ฅผ catchํ•˜๊ณ , rollback()์„ ํ˜ธ์ถœ
  3. (line 48) ๋กค๋ฐฑ ๊ณผ์ •์—์„œ ๋˜ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ
  4. (line 32) 3๋ฒˆ์˜ ์˜ˆ์™ธ๊ฐ€ ๋‹ค์‹œ ๋˜์ ธ์ง€์ง€๋งŒ, ์ด๋ฅผ catchํ•ด์ค„ ๊ณณ์ด ๋” ์ด์ƒ ์—†์Œ
  5. ๋”ฐ๋ผ์„œ line 33์€ ์‹คํ–‰๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— 1๋ฒˆ์˜ ์˜ˆ์™ธ๊ฐ€ ์ƒ์œ„ ๊ณ„์ธต์œผ๋กœ ๋˜์ ธ์ง€์ง€ ์•Š๊ณ , ๋กค๋ฐฑ์œผ๋กœ ์ธํ•œ 3๋ฒˆ ์˜ˆ์™ธ๋งŒ ๋˜์ ธ์ง.

์ด๋•Œ '์ƒ์œ„ ๊ณ„์ธต์œผ๋กœ ์ „๋‹ฌ๋˜์–ด์•ผ ํ•  ์ค‘์š”ํ•œ ์˜ˆ์™ธ'๋Š” ๊ทผ๋ณธ์ ์ธ 1๋ฒˆ ์˜ˆ์™ธ์ธ๋ฐ, ์ด๋ฅผ ์ˆ˜์Šตํ•˜๋˜ ๊ณผ์ •์˜ ๋กค๋ฐฑ ์˜ˆ์™ธ๊ฐ€ 1๋ฒˆ ๋Œ€์‹  ๋˜์ ธ์ง€๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Œ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

๋กค๋ฐฑ์— ๋Œ€ํ•œ ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ ธ๋„ ๊ทผ๋ณธ์ ์ธ CRUD ์˜ˆ์™ธ๊นŒ์ง€ ๊ฐ™์ด ๋‹ด์•„์„œ ๋˜์ง€๋ ค๋ฉด ๋‹ค์Œ์ฒ˜๋Ÿผ ์ค‘๋ณต์ ์ธ catch block์ด ํ•„์š”ํ•œ๋ฐ, ๋„ˆ๋ฌด ๊ณผํ•œ ๋Œ€์‘์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์—ฌ ๋กค๋ฐฑ ์˜ˆ์™ธ๋Š” ๋‹จ์ˆœํžˆ ๋กœ๊ทธ๋งŒ ์ฐ์Œ์œผ๋กœ์จ CRUD ์˜ˆ์™ธ๊ฐ€ ๋ฎ์–ด์ง€์ง€ ์•Š๊ณ  ์ƒ์œ„ ๊ณ„์ธต์œผ๋กœ ๋˜์ ธ์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

public <T> T execute(TransactionCallback<T> action) {
    Connection conn = null;
    try {
        conn = dataSource.getConnection();
        conn.setAutoCommit(false);

        T doingResult = action.doInTransaction(conn);

        conn.commit();

        return doingResult;
    } catch (SQLException e) {
        // ๋กค๋ฐฑ์—์„œ ๋‚˜์˜จ ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ try-catch๋กœ ์žก์•„์„œ ์ฒ˜๋ฆฌ
        try {
            rollback(conn);
        } catch (DataAccessException exception) {
            throw new DataAccessException(e);
        }
        throw new DataAccessException(e);
    } catch (RuntimeException e) {
        rollback(conn);
        throw e;
    } finally {
        close(conn);
    }
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Date์™€ LocalDateTime ์ฐจ์ด๋ฅผ ์•Œ์•„๋ณด๋ฉด์„œ ์‚ฌ์šฉํ•œ ์‹คํ—˜ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/woowacourse/java-janggi/pull/325/changes#r3069609600

์—์„œ ๋‹ค๋ฃฌ '์˜ˆ์™ธ ๊ฐ€๋ ค์ง' ๋ฌธ์ œ๋ฅผ ํ…Œ์ŠคํŠธํ•œ ์‹คํ—˜ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants