드디어 4주차까지 마무리를 했다. 시작할 때 4주가 길게 느껴졌지만, 막상 지나고 보니 정말 짧게 느껴졌다. 정말 어떻게 시간이 흘러갔는지 모르겠다.
이번 주차에는 조금 엄격한(?) 요구사항이 추가되었다. 메소드도 10라인 이하로 제한하고 우테코에서 지정한 클래스를 활용하여 개발을 진행했어야 했었다. 개인적으로 메소드의 라인 제한보다 우테코에서 지정한 클래스를 활용해서 개발하는 부분이 더 힘들었던 것 같다.
먼저 의존성 주입에 대해서 이야기 하고 싶다. 간단하게 개발하려면 static을 활용해서 모든 로직을 실행할 수 있겠지만, 이번에 주어진 클래스들을 보면 수정제한이 걸린 부분에 static을 걸 수가 없는 부분이 있다. 3주차 숫자야구 미션에서도 DI를 활용해서 의존성을 주입해주었지만, 이번 주차 때의 우테코에서 제공한 클래스를 보며 DI를 적용하면 되겠구나 라는 생각이 들었다.
public class BridgeGameContainer {
private static BridgeGameContainer bridgeGameContainer;
private BridgeGameContainer() {
}
public static BridgeGameContainer getInstance() {
if (bridgeGameContainer == null) {
bridgeGameContainer = new BridgeGameContainer();
}
return bridgeGameContainer;
}
public BridgeGameSystem bridgeGameSystem() {
return new BridgeGameSystem(bridgeMaker(), inputView(), outputView());
}
private BridgeMaker bridgeMaker() {
return new BridgeMaker(bridgeNumberGenerator());
}
private BridgeNumberGenerator bridgeNumberGenerator() {
return new BridgeRandomNumberGenerator();
}
private InputView inputView() {
return new InputView();
}
private OutputView outputView() {
return new OutputView();
}
}
의존성 주입을 위해 먼저 Container
객체를 만들었다. Container
객체는 싱글톤으로 동작하며 Application.java
에서 BridgeGameSystem
의 의존성을 주입하여 실행한다.
public class Application {
public static void main(String[] args) {
BridgeGameContainer bridgeGameContainer = BridgeGameContainer.getInstance();
try {
BridgeGameSystem bridgeGameSystem = bridgeGameContainer.bridgeGameSystem();
bridgeGameSystem.play();
} catch (IllegalArgumentException e) {
System.out.println(ExceptionMessage.PREFIX + e.getMessage());
}
}
}
위와 같이 bridgeGameContainer.bridgeGameSystem()
메소드를 통해서 의존성을 주입받는다. 이를 활용해서 static 없이 주어진 클래스의 요구사항을 만족할 수 있었다!
메소드 10줄 제한 때문에 길이 길이 긴 메소드를 작성할 수 없었다. 다리 건너기 게임의 흐름을 담당하는 BridgeGameSystem
클래스는 게임의 로직을 순차적으로 수행하기 위해서 하드코딩 한다면 10줄 기본으로 넘어갈 거 같아 작게 메소드 단위로 구분했다.
public void play() {
Moving moving;
GameStatus gameStatus;
do {
moving = move();
gameStatus = getGameStatus(moving);
} while (gameStatus.equals(GameStatus.NEXT) || gameStatus.equals(GameStatus.RETRY));
outputView.printResult(bridgeGame);
}
private Moving move() {
Moving moving = bridgeGame.move(inputView.readMoving());
outputView.printMap(moving);
return moving;
}
private GameStatus getGameStatus(Moving moving) {
if (bridgeGame.getResult() == Result.SUCCESS) {
return GameStatus.SUCCESS;
}
if (moving.getResult() == Result.FAIL) {
GameCommand gameCommand = inputView.readGameCommand();
return bridgeGame.getGameStatusByGameCommand(gameCommand);
}
return GameStatus.NEXT;
}
가장 큰 틀은 play()
메소드에서 진행이 되고, move()
는 다리는 건너는 행위 그리고 getGameStatus()
는 다리는 건너는 행위에 의한 결과를 처리하는 메소드이다. 만약 getGameStatus()
가 SUCCESS
(다리 한칸을 건너는 것을 성공), RETRY
(실패했지만 재시도)를 반환한다면 반복은 종료되고 결과를 반환하게 된다. 이렇게 3가지 메소드로 분리해서 메인 로직을 작성했다. 뭔가 쉽게 설명하자면 다음과 같이 분리를 한 것과 마찬가지이다.
플레이 -> [ 다리 건너기, 결과에 따른 로직 수행하기, 결과 출력 ]
이런식으로 잘게 쪼개서 해결하면 모든 메소드를 10줄 라인 아래로 작성하며 코드를 작성할 수 있다.
이번 미션을 진행하면서 조금 부족했다고 느꼈던 부분들이 있다. 가장 먼저 고민이 들었던 것은 어느정도의 퀄리티가 완성되어야 커밋을 해야하나 라는 점이었다. 클린코드고 뭐고 일단 기능이 굴러가기만 하면 그대로 커밋을 진행해도 되는건지 아니면 어느정도 리팩토링을 하고 커밋을 진행해야 하는지 경험이 부족해서 감이 잘 잡히지 않았다. 커밋 내역에 깔끔한 코드만 남기고 싶은 마음은 아직 초보이기에 그런 것일까 고민에 휩싸였다. 하지만 우테코에서 의도한 바는 처음부터 깔끔한 코드를 작성하는 것이 아닌 것 같았다. 이번 주차의 주요 내용은 클래스의 분리
와 리팩토링
이었다. 그런 점을 미루어 보아 처음부터 깔끔한 코드를 작성하는 것은 힘드니까 점차 코드를 수정해가며 좋은 코드로 바꾸어보아라 같은 의미를 내포하는 것 같다. 하지만 갈피를 잡지 못해 결국 점차 리팩토링 되었던 나의 코드는 그저 한 개의 커밋으로 퉁 쳐버렸다. 그런 뒤에 커밋 내역을 보니 내가 리팩토링을 하며 개선해나갔던 부분, 고민했던 부분들이 보이지 않았다. 우테코에서는 그런 부분을 커밋 내역에 한번 포함시켜보라는 의미였을지도 모르는데 말이다. 잘 진행해왔던 1~3주차 였다고 생각하지만 가장 마음에 들지 않게 4주차를 마무리했다.
더 나아가 클래스 및 메소드에 책임과 역할에 관한 내용이다. 많은 글들과 클린코드를 보면 메소드와 클래스는 한 가지의 일만 해야한다는 이야기들이 많이 있다. 이는 굉장히 추상적인 내용이지만 핵심은 많은 일을 하면 안된다는 것이다.
하지만 코드를 작성하다보면 자연스레 하나의 메소드와 클래스에 많은 책임과 역할이 담기게 된다. 매우 자연스럽게 말이다. 그래서 헷갈리는 부분이 있었다. 사용자의 값을 입력받고, 이를 검증하고 변환하는 부분을 만들려고 할 때, 입력을 받는 로직을 그대로 작성하고 검증과 변환의 로직을 메소드로 분리해서 입력을 받는 메소드 안에 해당 메소드를 호출해도
입력을 받는 메소드는 많은 책임을 지고 있는 건지, 아니면 분리 했기 때문에 책임이 덜 해지는 건지에 대해 의문이 생겼다. 이에 대해서는 Discussion에 질문을 남겨볼 생각이다.
이번 시간에는 클래스 분리에 대해 집중적으로 생각하며 개발했습니다. 또한 메소드 제한이 10줄이기 때문에 정말 많이 분리하려고 노력했습니다. indent에 관한 제한은 2 이지만 자체적으로 1이라는 제한을 걸고 개발에 임했습니다.
게임 로직 자체는 많이 어렵지 않았지만, 늘어난 요구사항을 모두 충족하며 개발하려다보니 처음엔 힘든 부분이 있었습니다.
하지만 메일로 받은 내용처럼 처음엔 조금 길에 늘여뜨려 작성하다가 점점 리팩토링을 통해 개발하다보니, 꽤나 간단하게 분리할 수 있는 부분도 있었습니다.
매주 미션을 하며 느끼는 부분이지만, 정말 좋은 클린코드를 작성하려면 무엇보다도 코드의 구조가 중요하다고 생각했습니다. 아무리 좋은 코드를 짜려고 해도 구조가 보수적이고 서로 많이 얽혀있다면 그 코드의 깨끗함은 한계가 존재한다고 생각이 들었습니다. 4주 동안 애플리케이션의 로직보다 눈에 보이는 코드의 구조를 신경 쓰는데 많이 집중하고 주목하게 된 시간이었습니다. 언뜻보면 정말 짧은 시간이었지만, 매주 매주 미션을 수행한 뒤 친구들과 함께했던 프로젝트의 코드나 토이 프로젝트로 진행한 코드를 보면 참 많이 부족했구나 라는 생각이 들었습니다.
늘 개발을 하면서 깔끔하게 작성하고 싶다. 한 눈에 들어오게 잘 정리된 코드를 작성하고 싶다는 생각을 하며 살았는데, 이를 연습하기는 쉽지 않았습니다. 하지만 우아한 테크코스 프리코스를 참여하면서 코치님이 내주신 문제에 요구사항을 잘 생각하고 실제 프로그램 개발에 적용하면서 그 중요성과 저만의 작은 노하우를 터득하게 되었습니다. 아직 한참 부족할지는 모르겠지만 앞으로 클린코드를 작성하는데에 기초 체력 정도는 얻었다고는 생각하고 있습니다.
Tecoble 및 우테코 유튜브도 정말 많은 도움이 되었습니다. 제가 고민하고 있던 부분들을 선배 개발자 분들께서 잘 풀어주시고 이해가 잘 되도록 예제와 함께 설명을 해주시니 정말 감사할 따름이었습니다. 아직은 제가 작성한 프로그램이 남들이 보기에도 잘 짜여지고 깨끗하고 깔끔한지는 모르겠습니다. 하지만 4주전과 지금의 저를 비교해봤을 때 정말 많이 발전한 느낌을 받습니다. 이를 계기로 공부 방향에 대해서도 많은 고민을 하게 되었고, 개발자로서의 덩치만 키우는 것이 아닌 실력을 빈틈을 계속해서 메워나가 탄탄한 개발자가 될 수 있는 초석을 마련한 것 같습니다.
4주 동안 많은 인원의 학생들을 관리하시느라 정말 고생 많으셨고, 결과에 상관없이 정말 좋은 값진 시간이었습니다. 앞으로도 우테코를 통해 많은 학생들이 저와 같은 동기부여와 노하우를 터득하는 시간을 가졌으면 좋겠습니다. 😊
잘보고갑니다!