Skip to content

[4, 5단계 - 체스] 이프(송인봉) 미션 제출합니다.#399

Merged
hongbin-dev merged 44 commits intowoowacourse:sinb57from
sinb57:step2
Apr 13, 2022
Merged

[4, 5단계 - 체스] 이프(송인봉) 미션 제출합니다.#399
hongbin-dev merged 44 commits intowoowacourse:sinb57from
sinb57:step2

Conversation

@sinb57
Copy link
Copy Markdown

@sinb57 sinb57 commented Apr 6, 2022

안녕하세요! 이프입니다. 예상과 달리 4,5단계 체스 미션이 오래 걸렸네요ㅠ
다음은 이전 PR 코드로부터의 변경 사항과, 아직 미흡한 부분을 정리한 내용입니다!!


변경 사항

  1. ChessGame 상태 패턴 수정
    chess.domain.game.ChessGame에서 사용되던 command 패키지를 제거했고
    state 패키지의 구성은 RunningState와 FinishedState 두가지로만 이뤄지도록 수정했습니다.

  2. CommandExecutor을 통해 처리하던 분기문 수정
    이전 PR에서 피드백 주신대로, application.console 패키지의 CommandExecutor를 통해
    처리되던 ChessApplication의 분기문 로직을 수정했습니다.
    현재 ChessApplication#play에서 각 명령어에 따른 조건문을 통해 로직을 수행하고 있습니다.


미흡한 부분(진행 예정)

  1. Dao의 jdbc 코드에서 반복되어지는 코드를 메서드 분리할 예정입니다.

  2. chess.dao.mysql 패키지의 jdbc 코드를 인터페이스로 추상화할 예정입니다.

  3. chess.domain.game.repository.GameRepository가 현재 PlayerRepository를 인스턴스로 지니고 있습니다.
    현재는 ChessGame과 Player의 의존관계 때문에 GameRepository가 PlayerRepository를 소유하도록 했지만,
    GameRepository로부터 PlayerRepository를 분리하여 상위 계층에서 두 Repository를 사용하도록 할 예정입니다.

sinb57 added 30 commits April 4, 2022 19:23
Copy link
Copy Markdown

@hongbin-dev hongbin-dev left a comment

Choose a reason for hiding this comment

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

안녕하세요 이프! 리뷰어 앨런입니다.
4, 5단계 미션 구현하시느라 고생많으셨습니다.
리뷰는 웹, DB 위주로 진행했어요. 코멘트 확인부탁드려요~

Comment on lines +23 to +24
final ChessGame chessGame = ChessGame.initializeChessGame();
chessGame.start();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

체스게임의 초기화하는 부분을 나누신 이유가 있을까요? 객체의 흐름이 순서에 의존적인 것 같아요.
만약 ChessGame코드를 사용하는 다른 클라이언트 입장에서는 '사용이 어려울 수 있겠다'라는 생각이 드네요

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.

기존의 도메인을 최소한의 수정으로 재사용하던 과정에서 이런 로직이 나왔네요. 수정하도록 하겠습니다~!


get("/", gameController.index());

path("/room", () -> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

자원이 game인데요. games라는 prefix는 어떨까요?

또 아래 다른 path의 식별자들이 gameId인데요. url을 보고( /games/:id) 어떤 식별자를 줄 수 있을지 이해하기 좋을 것 같습니다.

Comment on lines +34 to +39
get("/start", gameController.createNewGame());
get("/:gameId", gameController.loadGame());
post("/:gameId/move", gameController.movePiece());
post("/:gameId/promotion", gameController.promotion());
get("/:gameId/status", gameController.calculatePlayerScores(), gson::toJson);
get("/:gameId/end", gameController.endGame());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

라우터와 컨트롤러를 각각 관리하는게 좋네요 👍

Comment on lines +21 to +23
public GameDao(final MysqlConnector mysqlConnector) {
this.mysqlConnector = mysqlConnector;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dao 입장에서는 어떤 커넥터로 생성하든 상관없지 않을까요?

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.

넵! 전략패턴으로 변경하겠습니다

public GameDto findById(final Long id) {
final Connection connection = mysqlConnector.getConnection();

GameDto gameDto = null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

null 할당을 꼭 피했으면 좋겠어요!

Comment on lines +76 to +79
} catch (SQLException e) {
e.printStackTrace();
}
return gameDto;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

쿼리 중 예외가 생기면 catch부분으로 빠질 것 같은데요.
그럼 스택트레이스 출력하고 별다른 액션이 없네요. 그럼 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.

예외처리 로직이 미흡했네요ㅜ 수정하겠습니다!

Comment on lines +100 to +102
}
return null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이 부분도 null반환을 피했으면 좋겠습니다!
catch 부분에서 예외를 내거나, 대체 값을 반환하면 return문을 제거할 수 있을 것 같아요.

Comment on lines +31 to +33
} catch (final SQLException e) {
throw new IllegalStateException(e.getMessage());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍

Comment on lines +46 to +52
try {
resultSet.close();
preparedStatement.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

지금 이 부분에 문제가 발생할 수 있어요. 만약 resultSet.close();를 진행 중 문제가 생기면, ps와 connection은 안 닫히게됩니다. 각각의 실패 했을 때 액션처리가 안된 것 같아요.

try-with-resources를 사용해서 자원 닫는것을 위임해볼 수 있을 것 같아요.
https://tecoble.techcourse.co.kr/post/2021-04-26-try-with-resource/ 테코블의 참고하면 좋을 것 같아요~

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.

아! close 과정에서 문제가 발생하는 상황을 고려하지 못했네요. 수정하겠습니다!!

import chess.dao.dto.GameDto;
import chess.dao.dto.GameUpdateDto;

class GameDaoTest {
Copy link
Copy Markdown

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.

테스트용 데이터베이스를 만들어 분리하도록 하겠습니다!!

@sinb57
Copy link
Copy Markdown
Author

sinb57 commented Apr 10, 2022

DB와 관련하여 지적해주셨던 Null 사용과 예외처리 미흡 등을 해결하기 위해
chess.dao.connet 패키지에 JdbcTemplate 클래스를 생성했습니다. try-with-resources를 적극 활용했고
이를 통해 GameDao와 PlayerDao에서 반복 수행되던 try-catch 문을 제거했습니다.


GameRepository가 PlayerRepository를 인스턴스로 지니면서 Player에 대한 DB 로직을 수행하는 것이 과연 옳은가 생각해봤어요.
GameRepository에게 과한 역할이 주어진 것은 아닐까 생각해봤지만, ChessGame에 Player가 속해있으니 이런 구조는 어쩔 수 없지 않을까 하는 생각도 드네요... 분리하고자 하니 어떻게 분리해야할지도 모르겠고요.. 어렵네요ㅠㅜ
현 상황의 문제점이 무엇이라 생각하는지 앨런의 생각이 궁금합니다.

@hongbin-dev
Copy link
Copy Markdown

GameRepository가 PlayerRepository를 인스턴스로 지니면서 Player에 대한 DB 로직을 수행하는 것이 과연 옳은가 생각해봤어요. GameRepository에게 과한 역할이 주어진 것은 아닐까 생각해봤지만, ChessGame에 Player가 속해있으니 이런 구조는 어쩔 수 없지 않을까 하는 생각도 드네요... 분리하고자 하니 어떻게 분리해야할지도 모르겠고요.. 어렵네요ㅠㅜ 현 상황의 문제점이 무엇이라 생각하는지 앨런의 생각이 궁금합니다.

일단 계층구조가 controller - service - repository - dao - data인 것 같은데요.
repository와 dao를 따로 나눈 이유가 궁금합니다. 각각의 책임과 역할이 무엇인가요?

또 repository같은 경우 도메인 패키지 내에 있는데, 정말 도메인이 맞는가? 도 고민되네요.

@sinb57
Copy link
Copy Markdown
Author

sinb57 commented Apr 12, 2022

DAO는 DB 테이블 중심의 객체이므로 도메인 객체와의 의존성이 없어야 한다고 생각했습니다.
이를 위해 DAO와의 메시징이 DTO를 통해 이뤄지도록 설계했으며,
도메인<->DAO 로직을 Repository가 행하도록 했습니다.
(이 과정을 Service에서 행할 수 있지만, Service의 메인로직과 DAO관련로직을 분리하고자 했습니다.)

Repository와 DAO에 대한 각각의 책임과 역할을 말씀드리자면,
Repository는 외부로부터 도메인(ChessGame, Player)를 요청받는 일종의 컬렉션이고
DAO는 온전히 DB와의 연동에 책임을 다하는 객체입니다.

Repository를 도메인 패키지에 위치한 이유는 Repository가 도메인을 다루는 컬렉션이기 때문입니다.
이 부분에 대해 Repository가 DAO에 의존하고 있는데 도메인에 위치하는 것이 과연 옳은가 생각하실 수 있습니다.
지금은 DAO를 통해 DB에 객체를 저장하는 방식을 취하지만,
경우에 따라 자체적으로 List 혹은 Map 컬렉션을 통해 휘발성 메모리 방식을 취할 수도 있습니다.
상황에 따라 객체를 저장하는 방식이 달라질테지만, 근본적으로
객체를 저장하는 컬렉션이라는 점은 변치않습니다.
따라서 Repository는 도메인 패키지에 위치하는 것이 맞다고 판단했습니다.


제 생각에 잘못된 부분이 있으면 과감히 지적해주시기 바랍니다.

@hongbin-dev
Copy link
Copy Markdown

Repository와 DAO에 대한 각각의 책임과 역할을 말씀드리자면,Repository는 외부로부터 도메인(ChessGame, Player)를 요청받는 일종의 컬렉션이고DAO는 온전히 DB와의 연동에 책임을 다하는 객체입니다.

설명 감사합니다! DAO는 도메인의 컬렉션이 될 수 없는건가요? 비슷한 역할을 하는 것 같은데, 계층이 더 추가되어서 코드파악하기가 더 어려운 부분이 있는 것 같습니다.

Repository를 도메인 패키지에 위치한 이유는 Repository가 도메인을 다루는 컬렉션이기 때문입니다.
이 부분에 대해 Repository가 DAO에 의존하고 있는데 도메인에 위치하는 것이 과연 옳은가 생각하실 수 있습니다.
지금은 DAO를 통해 DB에 객체를 저장하는 방식을 취하지만,
경우에 따라 자체적으로 List 혹은 Map 컬렉션을 통해 휘발성 메모리 방식을 취할 수도 있습니다.
상황에 따라 객체를 저장하는 방식이 달라질테지만, 근본적으로
객체를 저장하는 컬렉션이라는 점은 변치않습니다.

DAO가 도메인인가? 는 아니라고 말씀드리고싶네요. 도메인객체가 데이터베이스에 저장되고, 파일시스템에 저장되는건 세부사항이라고 생각되네요

Copy link
Copy Markdown

@hongbin-dev hongbin-dev left a comment

Choose a reason for hiding this comment

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

안녕하세요 이프!
피드백 반영해주신부분 확인했고, 쿼리사이드의 중복코드를 잡아보셨군요 💯
고민해보면 좋을포인트 몇개 남겨봤어요. 레벨1 고생많으셨어요

Comment on lines +26 to +31
private Connection createConnection() {
try {
return DriverManager.getConnection(URL, USERNAME, PASSWORD);
} catch (final SQLException e) {
throw new IllegalStateException(e.getMessage());
}
Copy link
Copy Markdown

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.

커넥션 풀에 대해 조사해보겠습니다!ㅎ

Comment on lines +9 to +11
private static final String URL = "jdbc:mysql://localhost:13307/chess";
private static final String USERNAME = "user";
private static final String PASSWORD = "password";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

임베디드 db를 사용하면, 별도의 db준비없이도 테스트할 수 있을 것 같아요.
이건 반영하지 않으셔도 됩니다!

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.

오 찾아보겠습니다!

Comment on lines +40 to +44
public static ChessGame initializeAndStartChessGame() {
final ChessGame chessGame = initializeChessGame();
chessGame.start();
return chessGame;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

생성자와 start()메서드는 어떤 차이가 있을까요?

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.

콘솔 어플리케이션의 로직 때문에 ChessGame의 생성자와 start()가 분리되어 있습니다.
콘솔 어플리케이션에서는 프로그램 시작과 동시에 ChessGame의 생성자를 호출하여 게임 준비 상태에 접어들고
"start" 명령어 입력 시에 ChessGame#start 메서드를 호출하여 게임 진행 상태에 접합니다.
그에 반해, 웹 어플리케이션에서는 별도의 게임 준비 상태 없이,
"새로 시작하기" 버튼을 통해 ChessGame 생성과 동시에 게임 진행 상태로 빠져듭니다.

두 어플리케이션 간의 차이로 인해 현재, initializeAndStartChessGame과 같은 정적 팩토리 메서드가 탄생했습니다..

문제를 이와 같이 해결한 부분에 대해서는 달리 드릴 말씀이 없습니다.
해당 메서드가 과연 옳은가 하는 질문에는 그렇지 않다고 답변드리고 싶습니다.
허나 어떻게 보완하는 것이 옳은가하는 질문에는 제대로 답변드리기가 어렵습니다.
계속해서 방법을 생각 중이며, 필요에 따라 ChessGame 도메인의 변경에 따른 각 Contoller의 변경도 고려하고 있습니다.

Copy link
Copy Markdown

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.

맞습니다. 제너럴한 형태로 수정되도록 고려해보겠습니다!

@hongbin-dev hongbin-dev merged commit dd5ba11 into woowacourse:sinb57 Apr 13, 2022
@hongbin-dev
Copy link
Copy Markdown

GameRepository가 PlayerRepository를 인스턴스로 지니면서 Player에 대한 DB 로직을 수행하는 것이 과연 옳은가 생각해봤어요.
GameRepository에게 과한 역할이 주어진 것은 아닐까 생각해봤지만, ChessGame에 Player가 속해있으니 이런 구조는 어쩔 수 없지 않을까 하는 생각도 드네요...

이 부분 답변을 못드렸네요 😅 GameRepository에서 playerRepository를 의존하고 있군요. 가볍게 든 생각은 해당 비즈니스가 레포지토리역할이 아닌, 서비스의 역할이 아닌가 의심이 되네요. 해당 책임을 상위로 올려보는 시도를 해볼 수 있을 것 같아요.

@sinb57
Copy link
Copy Markdown
Author

sinb57 commented Apr 14, 2022

DAO는 도메인의 컬렉션이 될 수 없는건가요? 비슷한 역할을 하는 것 같은데, 계층이 더 추가되어서 코드파악하기가 더 어려운 부분이 있는 것 같습니다.

DAO(Data AccessObject)는 이름 그대로 DB와 관련된 책임을 수행해야 한다고 생각했기 때문에, SRP에 따라 DB<->도메인 변환 로직 수행은 DAO의 책임이 아니라고 판단했습니다. 또한 DAO가 도메인을 반환하는 형태를 지니게 된다면, DB<->도메인 변환 로직으로 인해 DAO가 비대해질 것이라 판단했습니다.


DAO가 도메인인가? 는 아니라고 말씀드리고싶네요. 도메인객체가 데이터베이스에 저장되고, 파일시스템에 저장되는건 세부사항이라고 생각되네요

DAO가 도메인인가? 부분이 이해가 되지 않아 Repository가 도메인인가?로 받아들이려고 합니다. (잘못되었다면 지적 부탁드립니다.) DAO와 레파지토리에 관해 조사를 이어가던 중, DAO와 REPOSITORY 논쟁를 통해 마틴 파울러의 Separated Interface를 알게 되었습니다. 관련된 내용을 좀더 조사해보고 이를 적용해볼까 합니다.


GameRepository에서 playerRepository를 의존하고 있군요. 가볍게 든 생각은 해당 비즈니스가 레포지토리역할이 아닌, 서비스의 역할이 아닌가 의심이 되네요. 해당 책임을 상위로 올려보는 시도를 해볼 수 있을 것 같아요.

서비스에서 행할까 생각했지만, ChessGame과 Player의 repository 로직을 억지로 분리하려 하니 도메인 영역까지 영향을 미치게 됩니다. 그래서 이와 같은 구조가 별 수 없다고 생각했었습니다. 하지만 이번에 DAO와 레파지토리를 조사하던 과정에서 알게 된 DAO와 REPOSITORY 논쟁에 나오는 객체의 집합인 AGGREGATE 별로 하나의 REPOSITORY를 사용한다. 예를 들어 Order와 OrderLineItem이 하나의 불변식을 공유하는 단위이고 OrderLineItem의 생명 주기가 Order에 종속된다면 OrderRepository를 사용해서 모든 Order와 OrderLineItem 컬렉션을 관리한다.를 통해 불연듯 깨달았습니다.

현재 저의 코드는 레파지토리가 다른 레파지토리를 소유하면서 문제가 발생했습니다. 하지만 좀 더 근본적으로 바라보면, 왜 레파지토리를 분리해야 하는가가 문제였습니다. 예전 질문에서도 말씀드렸듯 ChessGame과 Player는 끊을래야 끊을 수 없는 관계입니다. 이를 굳이 두 레파지토리로 분리했기 때문에 현 상황이 도래한 것입니다.

제가 도달한 해결책은 PlayerRepository를 제거하고 GameRepository에서 GameDao와 PlayerDao를 이용하는 구조를 취하는 것입니다.


이번 방학 기간 동안 오브젝트 책을 읽고 있습니다. 책을 읽으면서 트레이드 오프에 관해 여러 생각을 가질 수 있었습니다.
관심사의 분리 / 역할과 책임의 분리 / 의존성과 결합성 / 코드의 복잡성 등등 고려해야 할 사항은 많지만,
모든 것은 만족시킬 수 있는 상황은 쉽게 오지 않더라고요. 경우에 따라서는 포기해야 하는 부분이 있습니다.
지금은 여러가지를 시도해가며 하나씩 체험해가고 있습니다. 이러한 경험이 쌓이면 추후에
무엇을 포기해야 하는지 쉽게 판단할 수 있지 않을까 기대하고 있습니다.

@hongbin-dev
Copy link
Copy Markdown

hongbin-dev commented Apr 15, 2022

이야기하다보니 DDD 이야기까지 간 것 같은데요. 이 부분을 잘 몰라서, 제 의견 요점만 정리해놓겠습니다.

  1. 관습상 DAO와 Repository는 영속계층을 도메인모델로 가져오는객체로 알고 있습니다. 그래서 두 계층을 혼용하는사례는 본적이없는데요. 개인적으로 파악하기 어려웠습니다.
    1. 또 Repository가 DAO를 의존하면서 한 계층이 더 늘어났는데요. 위 코멘트처럼 계층파악하기가 어렵습니다. 새로운 개발자가 참여했는데, Repository와 DAO를 함께 접하면 이름에서 헷갈릴 수 있고, 계층파악이 어려울 수 있습니다.
  2. 이프가 사용하는 Repository는 도메인이 아닙니다. 현재 구조상 인프라스트럭쳐에요. Repository가 DAO를 의존하는데 도메인이라고 볼 수 있을까요?

말씀드리고 싶은부분은 DAO나 Repository한 쪽의 네이밍만 사용하면 좋겠습니다.

현재 Repository가 DDD에서 말하는 Repository인지는 모르겠으나, Repository를 인터페이스로 분리하고, 현재의 DAO를 JdbcRepository로 변경하면 의존성의 흐름이 자연스럽긴 하겠네요.

이번 미션에서는 Web와 DB연동이 목적이라서 이 이야기는 여기서 마치겠습니다. 더 이상은 웹/DB미션과 벗어나는 것 같아요~ 고생하셨어요

@sinb57
Copy link
Copy Markdown
Author

sinb57 commented Apr 16, 2022

현재 구조상 인프라스트럭쳐에요. Repository가 DAO를 의존하는데 도메인이라고 볼 수 있을까요?

해당 부분에 대해 찾아본 바로는, 여러 자료에서 Separated Interface를 적용해 domain에 repository 인터페이스를 두고 infrastructure에 repository의 구현체를 두는 형태를 취하곤 하네요. (결국 DDD네요...)


두 계층을 혼용하는 사례는 본적이없는데요. 개인적으로 파악하기 어려웠습니다.

DAO를 처음 접하면서 기존에 스프링에서 사용했던 Repository와 차이점을 조사하고 제 나름대로의 정의를 내리다보니
그 정의가 일반적으로 알려져있는 통념과는 달라, 코드를 바라보시기 어려우셨으리라 생각합니다. 죄송하다는 말씀 드립니다.


이번 미션을 진행하면서 그동안의 코드 방식과 달리, 여러 가지 생각을 갖고서 각종 시도를 해봤습니다.

레벨1 초기에는 클래스/메서드 명명을 잘하고 내부구현로직을 깔끔하게 작성해서
다른 개발자들이 읽기 좋게 만드는 것이 최고라고 생각했습니다.
하지만 레벨1이 진행될수록 미션의 규모는 커져갔고, 제가 관리할 수 있는 구조적인 허용치를 점차 벗어났습니다.
제 코드를 스스로 관리하고 리팩토링하는 것이 버거워졌습니다.

이런 상황에서 자연스레 코드를 바라보는 관점이 유지보수로 바뀌었습니다.
그래서 지난 PR에서 말씀드린대로 객체지향 설계에 대한 고민을 시작했고, 그 고민들을 코드에 녹여냈습니다.
객체지향 설계.. 많이 어렵습니다. 알고 있는 것, 조사한 것을 어느 하나 포기하지 않고 모두 적용해보려 하다보니
코드만 길어지고 복잡해졌습니다. 트레이드 오프를 어떻게 관리해야할지 감이 오지 않더군요.

지금 굉장히 방황하고 있습니다. 레벨1이 마무리되었고 이제 레벨2가 시작하는데
갈피를 못잡고 이리저리 치이고 있는 이 상황이 몹시 우습네요.

적다보니 사설이 많아졌네요. 많은 부분에 있어서 코드가 마음에 들지 않으셨을텐데도
2주 동안 꼼꼼히 코드를 봐주시면서 상세한 피드백 남겨주셔서 감사합니다.

@hongbin-dev
Copy link
Copy Markdown

이프 긴 기간 고생많으셨습니다.

그 정의가 일반적으로 알려져있는 통념과는 달라, 코드를 바라보시기 어려우셨으리라 생각합니다. 죄송하다는 말씀 드립니다.

아닙니다! 죄송할게 있나요?? 제가 말씀드린 건 '코드보기 어려웠다 -> 이 부분의 계층을 없애면 이해하기 좋을 것 같다'의 뉘앙스였습니다. 제가 불편한 건 아니였어요 😅
또 코드 작성하실 때와 PR커멘트를 보면서 많이 고민하시고 계시구나라는 생각이 들었지만, 한편으로 모두 답변을 못드린 것 같아서 죄송하기도 합니다.

많은시도와 이프의 생각이 있는 코드 잘 보았고, 불편하지 않았습니다! 고생 많으셨고 방학기간 잘 보내세요~ 🙂

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