우아한테크코스5기

[우테코] 사다리 미션 2단계 회고: TDD, 원시값 포장, 객체의 책임... 너무 어려웡

깃짱 2023. 2. 27. 12:37
반응형

머지는 아직 안 되었지만, 그냥 써보는 회고

 

💋 코드 저장소

Pull Request 링크

 

[2단계 - 사다리 게임 실행] 깃짱(조은기) 미션 제출합니다. by eunkeeee · Pull Request #188 · woowacourse/j

안녕하세요! 새로운 리뷰어님!!! 제 코드 읽어주셔서 감사합니다! 깃짱 드림.

github.com

깃허브 링크

 

GitHub - eunkeeee/java-ladder: 사다리타기 미션을 위한 저장소

사다리타기 미션을 위한 저장소. Contribute to eunkeeee/java-ladder development by creating an account on GitHub.

github.com

 

💋 리뷰어의 교체...?!!

지난 번 리뷰어 제이미가 진짜 1단계에서 엄청난 정성으로 리뷰를 해주고, 휴가를 떠났다. 

그래서 낙동강 오리가 되어버린 나는 네오에게 4시부터 한시간 동안 풀스윙 방에서 라이브 코드리뷰를 받게 되었다.

전 부담스러워서 흐엉하고 있는데, 생각보다 라이브 코드리뷰... 괜찮았다? 배운게 많아서 이 기억이 날아가기 전에 바로 기록해 보려 한다.

 

 

💋 둘 다 포기하지 않을 수 있는 방법을 찾아보자

 

두 가지 방법이 있을 때, 나는 주로 둘 중 하나를 선택해야 한다는 문제로 인지하고, 장단점을 재보면서 하나를 포기하는 쪽으로 생각해 왔다. 근데 네오랑 이야기하다가 느낀건, 왜 둘 다 포기해야 함?

 

이번에 구현한 사다리 게임 2단계의 실행 예시를 하나 들고 왔다.

 

참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)
pobi,honux,crong,jk

실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)
꽝,5000,꽝,3000

최대 사다리 높이는 몇 개인가요?
5

사다리 결과

pobi  honux crong   jk
    |-----|     |-----|
    |     |-----|     |
    |-----|     |     |
    |     |-----|     |
    |-----|     |-----|
꽝    5000  꽝    3000

결과를 보고 싶은 사람은?
pobi

실행 결과
꽝

결과를 보고 싶은 사람은?
all

실행 결과
pobi : 꽝
honux : 3000
crong : 꽝
jk : 5000

 

상황: 사다리 게임을 시작하면서 참여할 사용자들의 이름을 받고, 그 뒤에 꽝, 당첨, 당첨금액 등등 입력 받는다. 이때 사람을 4명 입력했다면, 당첨 결과도 정확히 4개를 입력해야 한다.

 

나는 사용자의 이름을 Names 객체로, 꽝/당첨에 대한 결과를 Missions 객체에 저장했다.

 

나는 아래의 두 가지 방법밖에 존재하지 않는다고 생각했었다.

 

방법1

 

먼저 사용자에 이름을 받아 Names 객체를 만든다.

 

new Names(inputView.readNames());

 

이후에 names 객체의 size도 파라미터로 받는 Missions 객체를 생성한다.

 

new Missions(inputView.readMissions(), size);

 

하지만, 나중에 생성된 객체라는 이유로 Missions 객체가 size를 받아서 검증하는 것이 굉장히 이상하게 느껴졌다. Missions 객체만 떼어서 봤을 때, 객체의 생성에 왜 size가 들어오는지 이해하기 어려웠다.

 

test 코드를 짤 때는 더욱 어색하게 느껴졌다. 저 뒤에 있는 2는 어떤 것을 의미하는 것이지? 하는 생각이 계속 들었다.

 

class MissionsTest {

    @Test
    void 미션_index로_가져오기() {
        Mission mission1 = new Mission("당첨");
        Mission mission2 = new Mission("꽝");

        Missions missions = new Missions("당첨,꽝", 2);

        Assertions.assertThat(missions.getMissionByIndex(0)).isEqualTo(mission1);
        Assertions.assertThat(missions.getMissionByIndex(1)).isEqualTo(mission2);
    }
}

 

방법2

 

Names 클래스와 Missions 클래스가 함께 있는 MainController에서 검증을 했다.

이 경우, Missions 객체에 size라는 애매한 파라미터가 담기지 않아서 좋았지만, Names와 Missions가 모두 생성된 후에 검증하기 때문에 예외를 더 늦게 발견하게 되고, 설명할 수 없는 이유로 MainController에서 검증하는 것이 어색하게 느껴졌다.

 

더 좋아보이는 방법

 

네오랑 이야기하는데, 네오는 그 두 가지 장점을 다 모으는 방법에 대해 생각해 봤냐고 물어봤다.

않이 당연히 여태 생각하다 왔겠죠...  근데 그냥 이 두 가지를 모두 포장한 객체를 통해서 검증하면 되는거 아닌가?

 

결과적으로, 네오의 피드백을 받고, GameElementsManager라는 클래스를 만들어, 생성자에서 둘의 개수가 같은지 검증하는 과정을 진행했는데, 이렇게 하니깐 Util에 static한 메서드를 통해서 검증하는 것과 크게 다르지 않다고 느껴졌고, 한 클래스의 역할이 오직 저 두개의 개수가 같은지 보는 것 뿐인데 따로 만드는 것은 너무 거창하다고 생각했다. 지토와 이야기하다 보니, 정적 팩터리 메서드의 인자를 통해서 검증하고, 그 중 Missions 클래스 자체적으로는 알빠? 인 내용은 실제 생성자로 넘기지 않으면 된다고 이야기했다. 지토의 입장은, 상태를 초기화할 때 직접적으로 관련이 없는 파라미터의 경우 생성자보다는 정적 팩토리 메소드를 통해 넘기는 것이 더 좋다는 것이었다. 일단 그렇게 수정하긴 했는데, 이것이 진짜일지 지토의 뇌피셜일지는 아직 잘 모르겠으니 앞으로 두고두고 생각해 봐야겠다. 

 

public class Missions {
    private final List<Mission> missions;

    private Missions(List<String> missions) {
        this.missions = formatMissions(missions);
    }

    public static Missions createWithSize(List<String> missions, int size) {
        validateSize(missions, size);
        return new Missions(missions);
    }

    private static void validateSize(List<String> missions, int size) {
        if (missions.size() != size) {
            throw new IllegalArgumentException("미션의 개수를 다시 확인해 주세요!");
        }
    }

    private List<Mission> formatMissions(List<String> missions) {
        ... 
    }

    public List<Mission> getMissions() {
        ...
    }

    public Mission findByPosition(Position position) {
        ...
    }
}

 

아무튼 그래도 이번에 배운 태도는 그냥 막 이분법적으로 생각해서 둘 중 하나를 포기해야 겠다는 마음보다는 항상 더 좋은 방법이 있나 더 많이 고민하고 찾아봐야겠다는 것이다!

 

 

💋 원시값은 얼만큼 포장할까? 이건 과대포장일까?? 

 

원시값을 포장하는 건 얼만큼 하는 것이 좋을까? 나는 사용자로부터 입력받는 Name, Mission, Position 모두 포장했다. 포장을 많이 하다 보니 같은 이름이더라도 14줄에서 생성한 new Name("깃짱")과 18줄에서 생성한 new Name("깃짱")은 각자 객체를 생성하기 때문에 다른 주소값을 가지고 있었고, 따라서 프로그램은 이 둘을 다른 객체로 인지하게 된다. 

 

이를 해결하기 위해서 우리가 인지하는 String인 "깃짱"이 같으면 같은 객체라고 말을 해줘야 하는데 이때 사용하는 것이 equals()와 hashCode 메서드 오버라이딩이다. 따라서 원시값 포장을 하다 보면 이 두 메서드에 대한 오버라이딩은 따라올 수밖에 없다. 

 

그렇다면, 원시값 포장은 그래서 얼만큼 하는 것이 좋을까?

 

장점이라면, 더 안전한 검증을 통해서 안정적이고 명확한 도메인을 만들 수 있다는 것이다. 예를 들어서, 중복되는 닉네임을 사용할 수 없게 하는 우테코 세계에서 우테코 6기에 들어온 사람이 깃짱이라는 이름을 하겠다고 이야기했다고 생각해 보자. 그러면 프론트엔드 사람은 책임지고 이거 중복 이름이에요. 5기에 깃짱이 살았어요라고 말해야 겠지만, 그 나쁜 사람이 만약에 프론트를 뚫고 깃짱이라는 이름을 백엔드로 넘겨버린다면??? 백엔드는 이번엔 우테코 크루 DB에 그년의 닉넴을 올리기 전에 나를 한번 더 돌아보고 거절해야 한다. 이처럼 원시값을 포장하게 되면 domain 영역에서 한 번 더 검증을 할 수 있고, 나는 이 게임의 메인 로직인 이름, 미션, 위치에 대해서는 꼭 원시값을 포장해야 한다고 생각했다. 회사로 치면 돈이니까?

 

단점에 대해서는 네오에게 말했던 것으론, equals 메서드를 재정의 너무 많이 하면 지저분하고 안 예쁘다. 네오는 그게 다냐고 했다. 자리에 와서 이리내에게 물어보니, 포장한 값을 getter로 벗겨서 가져오는게 힘들었다고 한다.

 

+++ 

아직도 나는 저 이외의 단점은 찾지 못했다. 이리내 외에도 지토에게 물어봐도, 벗겨서 가져오는 것 외에는 뚜렷한 단점이 없다.

 

추가적으로 장점에 대해서 다시 작성해 보면,

원시값 포장 과정에서 자연스럽게 포장 클래스 내부에 검증 로직을 만들어서 책임을 분리할 수 있다는 것과,  비즈니스 로직 전반에서 포장값을 사용함으로서 항상 신뢰를 확보할 수 있다는 것이다!

 

원시값 포장에 대한 엄청난 장점은 이 글에서 찾아볼 수 있다.

 

 

💋 도메인은 서로를 파라미터로 공유하면 안된다??

 

이번에 생성한 객체 Player는 사용자의 이름과 현재 위치에 대한 정보를 담고 있었다. 

나는 Player가 사다리를 '능동적으로' 타고 내려가는 것을 표현하고 싶어서 move()라는 메서드를 작성했고, 어쩔 수 없이 그 메서드는 사다리인 Ladder 클래스를 받게 되었다. 

 

이 부분에 대해서 어떤 크루는 Ladder라는 도메인의 큰 부분을 모두 넘겨버리니 MVC의 정신에 어긋난다고 말했다. (하지만 결과적으로 난 이 말은 잘못되었다고 생각한다.)

물론 코드레벨에서는 아는게 많아질 수록 문제가 많이 생긴다. 

 

import domain.ladder.Lines;
import domain.mission.Mission;
import domain.mission.Missions;
import domain.player.Player;
import domain.player.Players;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

 

아는게 많다는건, 이런걸 말하는거다 ㅋㅋㅋㅋ

클래스 열 개에 대한 정보를 알면, 영향 받는 클래스가 많아지고, 많은 클래스를 전달하다 보면 안좋은 일이 생길 여지가 많다. 따라서 판단 기준이 중요하다!

 

내가 '능동적으로' 사용자를 움직이는 것이 굉장히 중요하다고 생각한다면 넘겨도 좋다.

사실 객체 간 협력이라는 것을 하려면 서로에 대해 알아야 할 것이다. 그런데 마구잡이로 서로 넘겨주는 것은 좋지 않다.

최대한 이 객체의 책임이 어디까지인지 생각해 보고, 이 객체의 책임이 아니다 싶은 부분에 대해서만 책임을 지는 객체에게 메세지를 보내듯 해결할 수 있을 것 같다.

 

여우와 이야기해보면서, 좋은 이야기를 들을 수 있었다. 여우는 domain 내 다른 객체들을 관련 있는 것은 묶을 수도 있고, 서로 의존할 수 있다고 생각한다. 관련만 있다면 서로의 파라미터로 들어가던 필드로 존재할 수 있다. 여전히 규칙은 있다. 상위와 하위는 분리가 되어야 한다. 다시 말해서 하위인 LadderStep은 상위인 Line을 가지면 안되고, 상대적으로 하위인 Line은 상위인 Ladder를 가지면 안된다. 묶음을 어떻게 보냐에 따라서 다른데, 여우는 Ladder의 위와 아래에 존재하는 Names와 Missions까지 Ladder와 한 묶음으로 보았다고 한다. 따라서 여우의 관점에서는 Names가 Missions를 받는 것이 전혀 어색하지 않은 것이다!

 

 

💋 Controller는 어떤 일을 하는 걸까...?

 

어떤 사람은 Controller는 상태(필드변수)를 가지면 안된다고 말하고, 어떤 사람은 Controller에서 어떤 검증도 하면 안된다고 말한다. 근데, 둘 다 왜 그럴까? 필드를 가지면 안된다는 내용은, 필드로 도메인 객체들을 담다보면 Controller가 domain에 의존하게 되어서 안된다는 말... 와닿지는 않지만 인정할 수는 있다. 

 

제리는 Controller의 역할이 view와 비즈니스의 로직은 연결하는 것이라고 생각한다. 그 관점에서 보더라도, 검증 역할은 아니니깐 어색하다고 말할 수도 있고, 따져보면 차라리 Missions에서 검증하는 것이 더 좋을 것 같다!

 

네오는 이게 왜 필요한지 모르겠으면 일단 빼보고, 불편하다 싶을 때 다시 넣으라고 한다. 

어.. 근데 그건 너무 번거롭고 힘드니 사고 실험을 해봐야겠다.

 

 

💋 객체의 책임에 대해...

 

나는 LadderGame이라는 객체에서 사다리를 타는 일을 담당하도록 했다.

 

public class LadderGame {

    private final List<Result> result;

    private LadderGame(Players players, Missions missions, Lines lines) {
        this.result = getResults(players, missions, lines);
    }

    private List<Result> getResults(Players players, Missions missions, Lines lines) {
        List<Player> playersSortedByPosition = moveAllPlayers(players, lines);
        List<Mission> missionsSortedRandomly = getMissions(missions);

        return IntStream.range(0, missions.size())
                .mapToObj(index -> new Result(playersSortedByPosition.get(index), missionsSortedRandomly.get(index)))
                .collect(Collectors.toList());
    }

    public static LadderGame of(Players players, Missions missions, Lines lines) {
        return new LadderGame(players, missions, lines);
    }

    private List<Mission> getMissions(Missions missions) {
        return new ArrayList<>(missions.getMissions());
    }

    private List<Player> moveAllPlayers(Players players, Lines lines) {
        players.moveAllPlayers(lines);
        return players.getPlayersSortedByPosition();
    }

    public Result findResultByName(String name) {
        return result.stream()
                .filter(element -> element.getName().equals(name))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("해당 이름을 찾을 수 없습니다."));
    }

    public List<Result> findAllResult() {
        return Collections.unmodifiableList(result);
    }
}

 

보면 findResultByName() 메서드를 기준으로 위는 사다리를 타고 사용자를 움직이는 내용이 담겨 있고, 아래에는 사다리를 탄 후 결과를 조회하는 내용의 메서드가 담겨 있다.

 

나는 이 부분이 굉장히 단절되어 보여서, 이 객체의 행동에 일관성이 없어 응집도가 굉장히 떨어진다고 느꼈다. 네오의 의견에 따르면, 사다리 게임은 사다리를 타고 난 후의 결과를 알기 위해 한다는 관점에서 그렇게 어색하게 느껴지지 않는다고 했다. 깃허브의 서비스를 생각해 보면, 모든 행위는 사용자가 하는데, 그렇다고 commit, push, discussion 등등 모든 기능을 다 사용자 클래스에 저장할 수는 없을 것이다! 이렇게 생각하니 갑자기 편_안 해지면서 기분이 좋아졌다 헤헤

 

 

💋 프로그램은 현실 세계를 옮기는 일?

 

우리는 현실 세계를 생각하면서 프로그램을 읽게 된다.

 

프로그래머는 전체적 흐름을 보고 코드로 추상화해서 옮기는 작업을 하게 되는데, 나의 코드는 사다리를 생성함과 동시에 모든 사용자들의 결과를 저장하고, 그 뒤에 결과를 보고싶은 사람을 입력 받으면 조회하여 출력해 주는 방식으로 작동했다. 그런데... 어색하다! 사다리 게임에 대해서만 알고, 이 코드를 처음 보는 사람이 본다면 왜 대체 사다리를 만들면서 모든 결과를 다 저장해 놓지?라고 생각할 것이다. 

 

일반적으로, 사람들은 사다리 게임을 할 때에, 먼저 사다리를 그린 뒤에 결과를 알고 싶은 사람을 잡아서 실시간으로 잡아 끌어내려서 사다리를 타게 한다. 이 로직에 맞게 코드를 수정했다. 이 과정에서, name과 position을 갖고 있던 Player 클래스를 삭제해 버렸다. position을 클래스 변수로 갖고 있어서, 같은 사람을 두 번 알고 싶다고 하는 경우에 계속해서 결과가 변해서 잘못된 결과를 반환했기 때문이다. 

 

물론, 한 5명이 게임을 한다고 생각해 본다면, 깃짱의 결과를 알고 싶어서 한 번 실행하고 후에 모두의 결과를 알고 싶어서 한 번 다시 실행한다면 같은 행위를 2번 반복하는 것이기 때문에 프로그램의 성능 면에서는 좋지 않다고 느낄 수 있다. 그치만, 만약에 만 명이 게임을 한다고 생각하면 또 그때그때 한 명씩 알아가는 것도 나쁘지 않을 것 같다(?) 모든 건 선택이다. 일단 나는 흐름 상, 자연스럽게 느껴지는 방식으로 바꿨다.

 

 

 

💋 표준 예외 VS 커스텀 예외

 

저번 예외 처리 글에서 자백했듯, 나는 너무 IllegalArgumentException을 남발하는 것 같다. 생각도 안해보고 무조건 그렇게 작성하는 것 같은데, 표준 예외에 대해서 좀 더 공부해 보고 사용하도록 하자

 

표준 예외를 사용해야 한다 VS 커스텀 예외를 사용해야 한다는 두 파 사이에 치열한 논쟁이 있다고 한다. 표준 예외는 나만 사용하는 것이 아닌, 다른 개발자들도 많이 사용하기 때문에 내가 표준 예외를 발생시켰다 해도, 현재 발생한 예외가 내가 발생시킨 '그' 예외인지 확신할 수는 없다는 것이 커스텀 예외 파의 입장이라고 한다. 메세지까지 확인하면 되지 않느냐? 메세지는 항상 변하기 때문에 그것까지 확인하기에는 너무 힘들다(ㅠㅠ) 물론 커스텀 에외는, 프로그램이 점차 커지면서 수도 없이 많은 예외의 종류가 발생하는데 그때마다 모든 예외를 계속 생성해 줄 것이냐는 의문이 들긴 한다. 나는 아무래도 아직 표준 예외 파인 것 같다.

 

 

💋 테스트 코드는 도메인의 설명서다.

 

테스트 코드는 일종의 설명서 역할을 한다. domain을 이해하기 위해 도움을 주게 되는데, 따라서 DisplayName이나 테스트 메서드 이름을 정할 때에는 내부에서 동작하는 내용보다는 domain의 일을 설명하는 이름이 좋다.

예를 들어, 1 이상 100 이하의 층 수를 테스트한다.보다는 1에서 100을 초과하면 사다리를 생성할 수 없다.같은 이름이 더 좋다.

 

 

💋 추가적으로 자잘하게 공부해볼 내용

- 반복문과 Stream은 대부분의 경우에 대체해서 사용할 수 있지만, Stream이 반복문의 대용으로 등장한 것은 아니다. Stream이 나오게 된 배경은 뭘까? 이거 역시 공부하고 내가 왜 대체해서 사용하는 지에 대한 이유를 생각하고 사용하자!

 

- 개행을 똑바로 하자...! 무심코 지나치는 개행.. 다른 사람에게는 큰 고통...

 

- get으로 시작하는 이름은 자바에서는 관례적으로 getter에서 많이 쓴다. get으로 시작하는 메서드는 내부에 반환을 제외한 어떤 로직도 없을 것이라고 예상을 하게 되니, 무언가를 더 한다면 내부에서 어떤 역할을 하는지 서술하는 이름이면 좋을 것 같다.

 

- getter는 view에서 출력할 정보를 쓸 때를 제외하고는 최대한 자제한다.

 

- 객체를 만들었으면 잘 사용하자! Position 객체를 만들어 놓고, 잘 사용하지 않고 있다! 알차게 사용해야 잘 만든 객체이다. 안 쓸거면 없애버리자!

 

- 공백, 정렬에 일관성을 줘야만 한 사람이 쓴 코드같이 보인다.

 

- 코드에서 확장성을 고려해서 하는 일은 하지 않는다. 예를 들어, 하나의 메서드 내에서만 사용하는 변수를, 이후에 생길 다른 메서드를 고려해서 미리 필드로 빼는 것은 미래를 위한 코드인데, 최대한 하지 않는 것이 좋다. 개발자는 필요한 순간에 만들어야 한다. 

 

 

💋 공통 피드백

 

접근 방식에 대한 부분에서 많이 배웠다.

 

도메인을 잘 모르겠는 상태에서, 만들 수 있는 가장 작은 단위부터 작성하는 방식을 In -> Out 방식이라고 한다. 그와 반대로 전체 프로그램에 대한 이해를 한 뒤에 작은 단위로 나누어 들어가는 방식은 Out -> In 방식이다. 

 

요구사항을 처음 받았을 때, 아무 사전 지식이 없는 경우에는 큰 그림을 통해서 볼 수 밖에 없다. 도메인에 대한 지식이 없으면 Out -> In 방식으로 많이 접근한다. 도메인 분석 능력이 뛰어난 사람은, 전체 사다리를 보고도 한 줄 한 줄을 분리해서 도메인이 될 것이라고 접근할 수 있는데, 이런 사람에게는 In -> Out 방식이 적합하다. 

 

Out -> In 접근 방식은 도메인 지식이 없거나 요구사항을 객체로 도출할 수 없는 경우 적합
In -> Out 접근 방식은 도메인 지식이 있거나 요구사항을 객체로 도출할 수 있는 경우 적합

 

Out -> In으로 하다가 도메인에 대한 지식이 쌓이게 되면 In -> Out으로 변경할 수도 있고, In -> Out 방식으로 하다가도 도메인에 대해 혼란스럽게 되면 변경할 수 있다. 이번 미션은 TDD 방식으로 진행되었는데, 이 방식에는 In -> Out 방식이 적합하다. 하지만 Out -> In으로 처음에는 시작해서 삽질하는 과정에서 도메인에 대한 지식을 늘려 갔다. 

 

애플리케이션 개발 단계에서 Out -> In, In -> Out 방식 중 하나만 사용되지 않는다.
두 방식이 핑퐁처럼 주고 받으며 개발이 진행된다.

 

TDD를 하기 좋은 방식은, 의존성이 없는 객체로부터 시작하는 것이다. Object Graph 중 가장 마지막 노드는 주로 의존성을 가지는 객체가 하나도 없기 때문에, 여기서 구현을 시작하는 것이 TDD로 접근하기 수월하다.

 

사다리를 처음 보면, 어떤 것을 가장 작은 단위로 생각해야 할 지 조금 혼란스럽다... 

 

처음에는 Line을 한 줄로 생각하기로 했다. 그런데, 사다리가 있으면 true, 없으면 false..? 그렇다면 Line은 List<Boolean>을 가지고 있어야 하는데, 그렇다면 더 작은 단위인 LadderStep 한 칸 한 칸으로 가야 한다....! 

 

작은 단위에 대한 개발을 조금 뒤에 해야지 하고 미룬 채로 Line을 먼저 작성하게 되면, LadderStep을 만든 뒤에 이미 작성한 코드가 많이 깨지게 된다. 그러면 개발이 매우 힘들어질 수 있으니, 작은 단위부터...!

 

LadderGame을 만들다가 아, Line이라는 더 작은 객체가 있네? 해서 LadderGame은 이따 해야지~ 하고 Line을 만들기 시작한다. 그러다가 Point라는 더 작은 단위가 있다는 것을 깨닫고 아 Line 이따 해야지~ 하면 Point를 작성하면서 LadderGame, Line을 이미 어떻게 사용할지 알게 되어서 머릿속에 실시간으로 부채가 쌓인다. 

 

Point는 그 자체로 보았을 때 완벽한 그 자체로 존재해야 하는데, 상위의 개념을 생각하면서 만들다 보면 외부의 기준으로 설계하게 되어서 Line 전용 객체가 되어 버린다. 상위 개념이 하위 개념의 발목을 잡지 않는 방식으로 리팩터링 할 수 있다면 하는 것도 괜찮지만, 때로는 그냥 지우고 다시 쓰는게 더 쉬울 때가 많다. 따라서, 더 작은 단위가 있다면 큰 단위의 코드를 지우던지 주석 처리 하고 생각하지 않는 것이 좋다. 어렵다면, Out -> In으로 모두 구현한 후에는 domain에 대한 이해가 늘어나기 떄문에 후에 In -> Out 방식으로 다시 구현해 보는 것도 좋다.

 

그렇다면, 작은 단위를 찾기가 어렵거나, 개발을 하다가 복잡하다고 느낄 때 어떻게 해야 할까?

어렵다고 느껴서 더이상 좋은 생각이 안 난다고 느낀다면, 구현후 리팩토링을 추천하셨다. 그 단계에서 종이와 펜으로 그려가면서 작은 단위를 최대한 찾아보려고 노력하고, 그래도 작은 단위를 못찾았다면, 그냥 그대로 구현한다.

 

 

 

 

 

사다리 타기를 TDD로 진행하였는가? 그 과정에서 어려웠던 점은 없는가?

 

사다리를 탄다는 로직 자체를 처음에 어떻게 구현해야 할 지에 대해 굉장히 막막하게 느껴졌다. 그래서 작은 단위로 구분하려 해도, 어떻게 작은 단위로 구분해야 할 지 잘 감이 오지 않았다. 또 TDD는 실시간으로 수정하고 계획을 변경하는 일이 잦았는데, Top Down 방식으로 모든 설계를 하고 시작하지 않았기 때문에 계획 변경을 통해서 머리속에 부채가 실시간으로 쌓이는 것을 느낄 수 있었고, 당장 만들고자 하는 것에 집중하기가 어려웠다.

 

네오의 말처럼, 다음 미션부터는 TDD를 사용한다면, Line을 작성하다가 더 작은 단위인 LadderStep이 떠오른 경우 Line에 작성하던 코드를 과감히 버릴 수 있어야 할 것 같다. 당장 몇 줄 지우기가 너무 죽도록 아쉽지만 결국에는 그게 제일 빠른 방법일 것이라고 생각한다.

 

본인이 정한 개발을 잘한다는 기준은 무엇인가? 그 기준을 지키기 위해 노력한 부분은 무엇인가?

 

내가 정한 개발을 잘한다는 기준은, 요구 사항을 보고 현재 상황에서 가장 효과적인 방법을 생각해낼 수 있는 능력이다. 또, 그 방법을 차분하게 구현해내는 끈기도 있어야 한다고 생각한다. 

 

피드백을 받으며 리팩토링하는 과정에서 어려움을 느낀 부분이 있는가? 그 문제를 어떠한 방식으로 해결했는가?

 

항상 느끼는 거지만, (1주차보다는 훨씬 나아지긴 했지만) 내가 어떤 방식을 사용할 때 항상 그 이유에 대해서 생각을 충분히 해보지 않고, 그냥 멋져 보인다거나 당장 유용해서 사용한다. 단점에 대해 열심히 생각해보지 않고 사용해서 이후에 문제가 될 때가 종종 있다. 항상 이유를 생각하면서 사용할 수 있도록 노력해야 겠다.

 

또 이번에 원시값 포장의 단점에 대해서 생각해 보라는 피드백을 받았는데, 2명의 크루한테 물어보고 내 생각을 종합해 봐도 값을 반환하기 귀찮다는 하찮은 이유밖에 떠오르지 않았다. 원시값 포장이 이토록 장점뿐인 방법이려나..?

 

해당 미션에서 작성한 본인의 코드가 만족스러운가? 다음 미션에선 어떠한 목표로 코드를 작성할 예정인가?

 

아마리 만족스럽쟈나이ㅠㅠㅠ 다음 미션에서는 좀 더 컨벤션을 숨쉬듯 지키는 사람이 되어야겠다. 또, 객체에 메세지를 보낸다는 부분에 대해서 더 고민해봐야 겠다.

반응형