[우아한 테크 코스 8기] 프리코스 2주차

2025. 10. 27. 02:07·우아한 테크 코스 8기

0. 개요

2주차 과제는 자동차 경주 게임이다.

이전 기수에도 있었던 거 같은데?? 머지

3주차도 비슷하고 이번에 달라진 4-5주차가 정말 중요할 거 같은 느낌이 든다.

 

이번 주차에서 가장 크게 얻은 내용은.

"내 주제에 확고한 생각을 가지지 말자."이다.

 

조금 더 정확히 말하면, 문제를 보았을 때, 이거 이렇게 하면 되겠는데?라는 마음을 섣부르게 가지지말자.

다양한 설계 방식을 고려해보자.

 

1. 초기 설계

1 - 1. 구현 기능 나열

초기에는 우선 기능 목록을 나열했습니다.

저번주는 git clone 한 뒤에, 먼저 개발한 다음에 어느정도 됐다 싶을 때, 문서를 작성했습니다.

이번에는 그러지 않기 위해서 구현해야할 기능 목록을 나열했습니다.

이렇게 나열하는 게 맞는 건가 싶긴합니다. (README에 작성한 파일이다.)

## 3. 게임 라운드
> - [설명] 참가 자동차 그룹을 가진다.
> - [출력] 매 라운드가 끝나면 실행 결과를 출력한다.
> - [우승자] 마지막 라운드가 끝나면, 우승자를 구한다.
>   - [알고리즘] 가장 멀리간 자동차 위치를 구한다.
>   - [알고리즘] 가장 멀리간 자동차 위치와 같은 또 다른 자동차를 구한다.
>   - [반환] 우승자 목록 문자열을 반환한다.

## 4. 자동차
> - [검증] 자동차 이름은 빈 문자열일 수 없다.
>   - [검증] `빈 문자열`인 경우 IllegalArgumentException을 발생시킨다. 
> - [검증] 자동차 이름은 `1자 이상`, `5자 이하`이다.
>   - [검증] 6자 이상인 경우 IllegalArgumentException을 발생시킨다.
> - [이동] 자동차는 매 라운드 이동을 시도한다.
>   - [랜덤] 0과 9사이의 랜덤 값을 고른다.
>   - [이동] 랜덤 값이 4이상일 경우 이동한다.
>     - [전진] 이동 시 `한 칸` 전진한다.
>   - [이동] 랜덤 값이 `4`미만일 경우 이동하지 않는다.
> - [비교] 자동차의 순위 비교할 때, 위치를 기준으로 비교한다.
>   - [비교] 위치가 더 높은 자동차가 순위가 높다.
>   - [비교] 위치가 같은 자동차는 공동 순위이다.
> - [출력] 자동차 이름은 그대로 출력한다.
> - [출력] 자동차 위치는 한 칸당 `-`로 대신하여 출력한다.

 

자동차 경주에서 구현해야할 기능들을 리드미에서 뽑아왔습니다.

위를 토대로 객체를 식별했습니다.

 

1 - 2. 객체 식별

 

근로 시간에 기능 나열 목록을 보면서 객체를 식별했습니다.

우선 Car 객체와 자동차 이름을 가지고 있는 Owner, 자동차 위치를 나타내는 Space 객체를 뽑아냈습니다.

객체지향의 사실과 오해를 읽으면서 Alice -> drink() -> beverage ->  drunken() 부분을 보았습니다.

객체의 `상태`와 `행동` 그리고 서로 의존적임과 동시에 내부적으로 알 수 없음을 감명 깊게 읽었습니다.

 

그래서 이런 식으로 Car 내부의 `이름`과 `위치`를 Wrapper Class로 뽑아내어서 작성했습니다.

 

1 - 3. 섣부른 마음

그리고 나서 자동차의 `이동`을 어떻게 구현할 까 생각하고 있었습니다.

이때 우테코의 요구사항 중에서

  • 3항 연산자를 쓰지 않는다.

 라는 프로그래밍 요구사항 2를 보았습니다.

이번 과제에서 3항 연산자를 쓸만한 곳은 `랜덤값이 4이상인지 아닌지` 판단하는 부분 밖에 없습니다.

 

따라서 이 요구사항은 오히려 힌트라고 생각했습니다.

단독 if문으로 4이상인 지 판단하고 Early Return을 하던가, 혹은

전략 패턴을 사용하던가

 

이때부터 전략패턴에 꽂힌채 개발을 시작했습니다.

섣부른 마음의 시작이었습니다.

 

1 - 4. 초안은?

그래서 합성을 통한 Car 객체, Round 객체, MoveStrategy로 Car의 이동을 다루도록 설계했습니다.

 

- 참고로 Round 객체는 `자동차 경주 게임` - `Car` 라는 두 관계가 가지는 의미 수준차이가 너무 심해서 식별한 객체입니다.

 

 

 

2. 객체에 집중

2 - 1. 초안을 적용해보니

실제로 초안 설계대로 작성했습니다.

View, Car 등등 어느정도 잘 구상되었습니다.

그런데 MoveStrategy를 구현하면서 알 수 없는 어색함이 느껴졌습니다.

public interface MoveStrategy {

    boolean support(int random);

    IntUnaryOperator move();

}

 

public class Go implements MoveStrategy {

    @Override
    public boolean support(int random) {
        return 4 <= random && random <= 9;
    }

    @Override
    public IntUnaryOperator move(){
        return (position) -> position + 1;
    }

}

 

public class Stop implements MoveStrategy {

    @Override
    public boolean support(int random) {
        return random < 4;
    }

    @Override
    public IntUnaryOperator move(){
        return position -> position;
    }

}

 

public class MoveStrategyFactory {

    private static final MoveStrategyFactory INSTANCE = new MoveStrategyFactory();
    private final List<MoveStrategy> strategyList;

    private MoveStrategyFactory() {
        strategyList = List.of(new Go(), new Stop());
    }

    public static MoveStrategyFactory getInstance() {
        return INSTANCE;
    }

    public MoveStrategy create() {
        return strategyList.stream()
                .filter(s -> s.support(rollForMove())) // 지금 보니 이렇게 하면 안되는데 에궁..
                .findFirst()
                .orElse(new Stop());
    }

    public int rollForMove() {
        return Randoms.pickNumberInRange(0, 9);
    }

}

 

우선 이렇게 만들고 나서 Car에 적용하려고 하다보니 너무나 어색함이 들었습니다.

지금 생각해보면, '자동차'라는 객체가 이동하는 데 MoveStrategy라는 외부 전략에 매우 의존적인 상황이 어색하게 느껴졌던 것 같습니다.

 

위는 자동차가 움직이기 위해서 꼭 MoveStrategy가 필요한 설계입니다.

따라서 나 움직이고 싶어 -> MoveStrategyFactory에서 MoveStrategy를 생성하고 전달받음 -> 해당 MoveStrategy가 Go이든 Stop이든 Car는 모르고 일단 move()만 해야함.

 

객체가 자신의 상태 (Position)을 결정하는 데 자신의 행동에 의해서가 아니라, MoveStrategy라는 외부 객체에 의존하여 결정하고 있었습니다.

(객체지향의 사실과 오해를 읽고...)

 

이러한 어색함을 해결하기 위해서는 어떻게 해야할까요?

 

2 - 2. 의존 대신 연관

MoveStrategy라는 자신의 상태를 변경하는 객체와 의존적인 관계를 맺는 대신,

내부적으로 가지고 있다면 즉, 연관관계를 맺고 있다면 해결되지 않을까요?

 

따라서 MoveStrategyFactory를 Car 내부로 옮기는 재설계를 수행했습니다.

이 과정에서 현실 세계의 자동차를 잘 설명할 수 있는 Engine이라는 객체를 식별했습니다.

 

현실세계에서는 내가 엑셀을 밟으면 앞으로 갑니다.

그러나 엑셀을 밟았을 때, 내부적으로 Engine이 어떤 동작을 수행하는 지는 모릅니다. (블랙박스)

내가 엑셀을 밟을 때, (움직이기를 시도한다면) Engine 내부적으로 랜덤값을 생성하여, 전진할 지 멈출 지 결정하도록 한다면?

이는 제가 식별한 객체의 상태가 또 다른 객체의 상태를 결정짓는 결과입니다. Engine -> Position

상태가 행동을 일으키고 그 행동이 상태를 변화시킵니다.

 

이 생각이 든 뒤로, 전략패턴에 대한 고집보다는 객체 내부를 잘 설명할 수 있는 행동, 상태, 네이밍에 집중했습니다.

 

2 - 3. 그래서 최종적으로

그래서 최종적으로는 Engine이라는 객체를 식별했고,

Engine이라는 객체는 FuelGenerator와 fuel를 가지도록 설계했습니다.

이렇게 하니, 자동차가 자유롭게 `이동`이라는 행위를 행할 수 있다고 느껴졌습니다. (기존에는 외부 전략 패턴에 의존적)

특히 네이밍도 현실 세계에서 사용되는 Engine, Fuel로 바꾸니 더 잘 이해되는 코드라고 생각합니다.

뿌듯하네요. ㅎㅎ

 

public class Car {

    private final CarName name;
    private final Engine engine;
    private Position position;

	// 생성자 생략

    public void tryToMove() {
        engine.refuel();
        if (engine.canRun()) {
            drive();
        }
    }
    
    private void drive() {
        position = position.move();
    }

}

 

특히 tryToMove()가 제 의도를 잘 드러낸 것 같습니다.

 

engine.refuel() 행위를 실행하고 engine 상태 내부는 모릅니다.

engine.canRun() 행위를 실행하여 엔진 가동이 가능한지 여부만 물어봅니다.

그러고 나서 Car는 drive합니다.

물론 drive() 역시 Position의 move() 메서드를 호출하고 Position의 내부 상태는 전혀 알지 못합니다.

 

객체지향의 사실과 오해에서 읽은 `기계 비유`에 적합한 코드라고 느껴져 좋았습니다 ㅎㅎ

"처음부터 전략패턴을 사용해야지!"라는 마음이 없었다면 조금 더 빠르게 결론에 도달할 수 있었을거라 생각합니다.

 

3. 입력 테스트 코드 작성

이번에는 테스트 코드 관련 요구사항이 있었습니다.

JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.

 

따라서 이번에는 검증, 기능에 대한 테스트 코드도 작성해보려 노력했습니다.

 

3 - 1. 입력 테스트는 어떻게...?

시작부터 당황했습니다.

입력에 대한 테스트는 어떻게 작성하는 걸까...

 

일단 우테코에서 기본으로 작성하는 ApplicationTest 코드를 살펴봤습니다.

    @Test
    void 기능_테스트() {
        assertRandomNumberInRangeTest(
            () -> {
                run("pobi,woni", "1");
                assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi");
            },
            MOVING_FORWARD, STOP
        );
    }

 

음 우선 run()을 실행할 때 매개인자로 입력값을 넣어주는 것을 확인할 수 있었습니다.

따라서, 해당 메서드를 확인해보면 우테코가 작성한 테스트 코드를 확인할 수 있겠죠!

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

public abstract class NsTest {
    private PrintStream standardOut;
    private OutputStream captor;

    public NsTest() {
    }

    @BeforeEach
    protected final void init() {
        this.standardOut = System.out;
        this.captor = new ByteArrayOutputStream();
        System.setOut(new PrintStream(this.captor));
    }

    @AfterEach
    protected final void printOutput() {
        System.setOut(this.standardOut);
        System.out.println(this.output());
    }

    protected final String output() {
        return this.captor.toString().trim();
    }

    protected final void run(String... args) {
        try {
            this.command(args);
            this.runMain();
        } finally {
            Console.close();
        }

    }

    private void command(String... args) {
        byte[] buf = String.join("\n", args).getBytes();
        System.setIn(new ByteArrayInputStream(buf));
    }

    protected abstract void runMain();
}

 

코드가 조금 길어서 runException 코드는 없앴습니다.

여기서 표준 입력과 표준 출력에대해서 다룬 걸 확인할 수 있었습니다. 

 

3 - 2. command()

    private void command(String... args) {
        byte[] buf = String.join("\n", args).getBytes();
        System.setIn(new ByteArrayInputStream(buf));
    }

 

우선 command() 에서는 여러 문자열을 입력받아 이를 배열에 담습니다.

그 뒤, 표준 입력의 소스를 방금 만든 바이트 배열로 설정합니다.

기존 System.in은 키보드 입력을 사용하지만, 여기서는 우리가 설정한 문자열을 표준 입력의 자원으로 사용합니다.

 

3 - 3. System.out

출력이 제대로 나오는 지 확인해야합니다.

우테코는 이를 위해서 스냅샷 개념을 사용한 것으로 추정했습니다.

 

    @BeforeEach
    protected final void init() {
        this.standardOut = System.out;
        this.captor = new ByteArrayOutputStream();
        System.setOut(new PrintStream(this.captor));
    }

    @AfterEach
    protected final void printOutput() {
        System.setOut(this.standardOut);
        System.out.println(this.output());
    }

 

우선 테스트 코드를 실행하기 전에 원래의 표준 출력을 standardOut에 저장합니다.

그리고 나서 출력의 목적지를 captor로 변경했습니다.

이는 콘솔창에 출력하는 게 아니라, 메모리에 출력을 저장한 뒤, 나중에 검증하기 위해서 사용하려는 의도로 보여집니다.

    protected final String output() {
        return this.captor.toString().trim();
    }

 

실제로 output()를 구현하여, 현재 captor에 저장된 출력들을 문자열로 반환합니다.

이 값을 받아서 실제 우리가 의도한 출력과 비교하면 되겠네용

    @AfterEach
    protected final void printOutput() {
        System.setOut(this.standardOut);
        System.out.println(this.output());
    }

 

그리고나서 테스트 코드가 끝나면 맨처음 저장했던 스냅샷(standardOut)을 표준 출력으로 되돌립니다. (표준 출력 -> 콘솔)

그리고 나서 output()을 콘솔에 출력할 수 있습니다.

 

저는 위 과정을 이용해서 테스트 코드를 작성했습니다.

 

3 - 4. 테스트 코드 작성

@DisplayName("자동차 테스트")
public class CarTest {

    public CarFactory carFactory = new CarFactory();
    private PrintStream standardOut;
    private OutputStream captor;

    protected final String output() {
        return this.captor.toString();
    }

    @BeforeEach
    protected final void init() {
        this.standardOut = System.out;
        this.captor = new ByteArrayOutputStream();
        System.setOut(new PrintStream(this.captor));
    }

    @AfterEach
    protected final void printOutput() {
        System.setOut(this.standardOut);
        System.out.println(this.output());
    }

    @Test
    @DisplayName("자동차 진행 결과를 비교한다.")
    public void testTryToMoveMethod() {
        String input = "car1,car2,car3";
        List<Car> carGroup = carFactory.createCarGroup(input);

        for(Car car : carGroup) {
            car.tryToMove();
            System.out.println(car);
        }

        List<String> list = output().lines().toList();
        for(int i = 0 ; i < carGroup.size() ; i++) {
            String line = list.get(i);
            Car car = carGroup.get(i);
            Assertions.assertThat(line).contains(car.toString());
        }
    }

}

 

맨 처음에는 우테코가 구현해둔 NsTest 추상 클래스를 상속받으려 했습니다.

그러나 테스트에 문제가 있었습니다.

 

바로

protected final String output() {
    return this.captor.toString().trim();
}

 

output()에 있는 trim()이었습니다.

NsTest를 상속받아 그대로 사용하는 경우 다음과 같은 문제가 발생했습니다.

// 실제 콘솔 출력
car1 : 
car2 : -
car3 : 
// 실제 콘솔 출력

드래그 해보시면 알 수 있습니다.

car1과 car3은 빈칸이 포함되어 출력됩니다.

즉 "car1 : " 이렇게 출력됩니다.

그러나 output()을 사용하는 NsTest는 "car3 : "의 마지막 공백이 사라져 "car3 :"로 출력되고 이는 출력 불일치가 되어버렸습니다.

따라서 trim()을 제거한 output()을 직접 구현하여 이 문제를 해결했습니다.

(물론 carName 출력 시 trim()을 하면 되긴 하지만, 자동차 위치가 0일 때 "car1 : "로 출력되는 게 맞다고 생각했습니다.)

 

 

4. 테스트를 위해 추상화하다.

테스트 코드를 작성하던 중 또 다른 문제를 마주했습니다.

바로 우승자를 구하는 로직에 대한 테스트 코드입니다.

 

4 - 1. 문제

자동차는 랜덤값에 의존하여, 전진하기 때문에 우승자를 제가 지정할 수 없었습니다.

즉, round를 실행한 뒤 우승자를 제가 알 수 없었기에, announceWinners() 메서드에 대한 테스트 코드를 작성할 수 없었습니다.

 

4 - 2. 해결책

근본적인 문제는 자동차의 이동이 랜덤값에 의존한다는 것.

그러면 테스트코드에서는 자동차의 이동을 제가 지정할 수 있어야합니다.

 

이를 위해서 자동차의 이동을 담당하는 Engine에 다시 집중했습니다.

public interface Engine {
    void refuel();
    boolean canRun();
}

 

추상화는 공통점을 모으거나, 중요한 부분을 강조하기 위해 세부적인 요소를 덜어냅니다. (객체지향의 사실과 오해)

자동차 경주에서 Engine은 다음과 같은 중요한 부분이 존재합니다.

 

연료를 재보급받는 행위

엔진이 가동 가능한 지 확인하는 행위

 

따라서 이 두가지 행위를 추상화했습니다.

이렇게 하고 난 뒤, 테스트 코드를 위한 TestEngine을 작성했습니다.

public class TestEngine implements Engine {

    private final Queue<TestMoving> moveQueue;

    public TestEngine(TestMoving... signals) {
        this.moveQueue = new LinkedList<>(List.of(signals));
    }

    @Override
    public void refuel() {
        // do nothing
    }

    @Override
    public boolean canRun() {
        if(this.moveQueue.isEmpty()) {
            return false;
        }
        TestMoving testMoving = this.moveQueue.poll();
        return testMoving.canMove();
    }

}
public enum TestMoving {

    GO(true),
    STOP(false);

    private final boolean move;

    TestMoving(boolean move) {
        this.move = move;
    }

    public boolean canMove() {
        return move;
    }

}

 

이렇게 테스트 코드를 위한 TestEngine을 작성했습니다.

이렇게하면 Car에 Engine을 주입할 때, 전진과 정지에 대해서 내가 지정할 수 있습니다.

// 예시
Car car1 = Car.of("car1", new TestEngine(GO, STOP, STOP));
Car car2 = Car.of("car2", new TestEngine(GO, GO, GO));
Car car3 = Car.of("car3", new TestEngine(GO, GO, STOP));

 

그리고 원래는 true, false를 넣었는데, 이 또한 불리언 리터럴이라고 생각했습니다.

매개인자로 넣어주는 true, false가 TestEngine에서 어떤 의미로 사용되는 지 직관적이지 않았습니다.

따라서 저는 TestMoving이라는 enum을 만들어서 GO, STOP으로 된 직관적인 네이밍을 사용했습니다.

(FORWARD, STOP이 더 나았으려나...)

 

 4 - 3. 테스트 코드

실제로는 다음과 같이 car2를 생성할 때, GO를 3번 넣어주었습니다.그리고 announceWinner()가 제대로 car2를 호출하는 지 확인했습니다.

    @Test
    @DisplayName("최종 우승자 발표 메서드를 테스트한다.")
    public void testAnnounceWinnersMethod() {
        Car car1 = Car.of("car1", new TestEngine(GO, STOP, STOP));
        Car car2 = Car.of("car2", new TestEngine(GO, GO, GO));
        Car car3 = Car.of("car3", new TestEngine(GO, GO, STOP));
        Round round = Round.from(List.of(car1, car2, car3));

        int gameRound = 3;
        for(int i = 0; i < gameRound; i++) {
            round.start();
        }
        String winner = round.announceWinner();
        Assertions.assertThat(winner).isEqualTo(car2.toCarName());
    }

 

5. 상수

5 - 1. 1주차에서 느낀 상수 처리 방식의 아쉬움

매직 넘버, 매직 리터럴을 어떻게 처리할 지 고민이 많았습니다.

1주차는 DelimiterConstants, NumberConstants라는 관련 도메인끼리 묶은, 별도의 상수 관리 클래스를 만들었습니다.

이렇게 하니 하나의 클래스에 같은 도메인에 대한 상수를 관리하니 편리했습니다.

그러나 이게 과연 좋은 방식인가? 라는 의문이 들었습니다.

 

비슷한 의미로 사용되는 상수를 하나의 클래스에 두어서 관리하는 건 편리했습니다.

그러나 다른 의미로 사용되는 같은 매직넘버를 하나의 클래스에 둘 필요가 있을까? 라는 의문이 들었습니다.

 

예를 들어 '0'이라는 매직 넘버는

덧셈의 항등원으로써의 역할,

빈문자열이 들어왔을 대 0으로 대체하기 위한 역할

두가지 의미로 사용됐습니다.

 

다음과 같이 하나의 상수 클래스에 다른의미로 사용되는 같은 매직넘버를 두는 게 과연 옳을까?라는 의문이 여기서 들었습니다.

public class NumberConstants {
    static final Integer ADD_IDENTITY = 0;
    static final Integer EMPTY_STRING_TO_NUMBER = 0;
}

 

같은 매직넘버에 대해서 중복적으로 상수 처리하고 있는데, 과연 Constants로 관리하는 의미가 있을까요?

 

5 - 2. 2주차에서는 매직넘버를

2주차에서는 1주차와 다르게 접근해봤습니다.

 

이번에는 사용되는 해당 클래스에 상수를 정의했습니다.

public class CarName {

    private static final Integer CAR_NAME_MAX_LENGTH = 5;

    private final String value;

    private CarName(String value) {
        validateNameIsNotBlank(value);
        validateNameLength(value);
        this.value = value;
    }

    public static CarName from(String name) {
        return new CarName(name.trim());
    }

    public void validateNameIsNotBlank(String name) {
        if (name.isBlank()) {
            throw new IllegalArgumentException(CAR_NAME_IS_NOT_BLANK.getMessage());
        }
    }

    public void validateNameLength(String name) {
        if (name.length() > CAR_NAME_MAX_LENGTH) {
            throw new IllegalArgumentException(CAR_NAME_MAX_LENGTH_IS_5.getMessage());
        }
    }

 

 예를 들어 위와 같이, 숫자 5는 CAR_NAME_MAX_LENGTH로써 자동차의 최대 길이라는 의도가 잘 드러납니다.

그리고 해당 값이 5라는 것을 바로 해당 클래스에서 확인할 수 있었습니다.

 

억지로 이 숫자5를 재사용할 수 는 있지만 이는 의도와 너무 달라지기 때문에 그러지 않는 게 좋겠죠!

이렇게 매직넘버는 확실히 해당 사용되는 해당 클래스에 두는 게 더 좋다고 느껴졌습니다.

 

5 - 3. 2주차에서는 매직리터럴을

 

그러나 안내 메세지와 에러 메세지 같은 매직 리터럴은 enum으로 관리하는 게 더 나은 거 같습니다.

그 이유는 1주차와 마찬가지로 format입니다.

public enum ViewMessage {

    CAR_NAME_INPUT_MESSAGE("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)",false),
    ROUND_COUNT_INPUT_MESSAGE("시도할 횟수는 몇 회인가요?",false),
    PROCESS_MESSAGE(System.lineSeparator() + "실행 결과", false),
    ROUND_RESULT_MESSAGE("%s" + System.lineSeparator(),true),
    GAME_RESULT_MESSAGE("최종 우승자 : %s", true);

    private final String message;
    private final boolean isFormatted;

    ViewMessage(String message, boolean isFormatted) {
        this.message = message;
        this.isFormatted = isFormatted;
    }

    public String getMessage() {
        return message;
    }

    public String getMessage(String... messages) {
        if (isFormatted) {
            return String.format(message, messages);
        }
        return message;
    }

}

 

단순히 String message말고도 boolean isFormatted를 추가할 수 있다는 장점이 있습니다.

String.format() 값을 반환하도록 한다면, OutputWriter가 format을 사용해야할지 말지는 신경쓰지 않고 String message만 그대로 출력하면 된다는 장점이 확실히 좋았습니다.

 

 

6. 느낀점

이번 주차는 "객체지향의 사실과 오해"의 영향을 많이 받았습니다.

글 내용에 공감할 수 있음에 기뻤고, 해당 내용을 적용하려고 노력했습니다.

 

서로 의존적인 '상태'와 '행위'에 대해서 깊게 공감했습니다.

또한 추상화의 두가지 목적도 매우 공감할 수 있었습니다.

 

그러나 아쉬운 점은 객체지향 설계를 할 때, 행위를 기준으로 설계해야 한 다는 것. 상태를 우선으로 하면 결합도가 올라간 다는 글의 내용은 크게 와닿지 않았습니다.

 

늘 그랬듯이, 경험치가 쌓이고, 반복해서 읽다보면 색다르게 읽힐거라 생각합니다.

 

책 읽는 속도가 느려서, 시간을 짬내지 않으면 읽을 시간이 없었습니다.

그래서 살면서 처음으로 지하철에서 책을 읽었는데, 꽤 재밌습니다.

얼른 객체지향과 사실과 오해를 1회독하고 '오브젝트'를 읽어보고 싶습니다.

 

3주차도 화이팅입니다.

 

'우아한 테크 코스 8기' 카테고리의 다른 글

우아한 테크 코스 8기 Level 1 - 1주차  (0) 2026.03.03
[우아한 테크 코스 8기] 최종 합격  (0) 2026.01.23
[우아한 테크 코스 8기] 1차 합격 및 최종 코딩테스트 후기  (0) 2025.12.30
[우아한 테크 코스 8기] 프리코스 3주차  (0) 2025.11.03
[우아한 테크코스 8기] 프리코스 1주차  (0) 2025.10.20
'우아한 테크 코스 8기' 카테고리의 다른 글
  • [우아한 테크 코스 8기] 최종 합격
  • [우아한 테크 코스 8기] 1차 합격 및 최종 코딩테스트 후기
  • [우아한 테크 코스 8기] 프리코스 3주차
  • [우아한 테크코스 8기] 프리코스 1주차
koreaioi
koreaioi
  • koreaioi
    koreaioi
    koreaioi
  • 글쓰기 관리
  • 전체
    오늘
    어제
    • 분류 전체보기 (187)
      • JAVA (3)
      • 알고리즘 (90)
        • 백준 (11)
        • String(문자열) (12)
        • Array(1, 2차원 배열) (13)
        • Two pointers, Sliding windo.. (6)
        • HashMap, TreeSet(해쉬, 정렬지원 S.. (5)
        • Stack, Queue(자료구조) (8)
        • Sorting and Searching(정렬, 이.. (10)
        • Recursive, Tree, Graph(DFS,.. (14)
        • DFS, BFS 활용 (6)
        • 다시 시작! (2)
        • 기초 수학 (1)
      • 일상 (26)
      • Github (1)
      • MSA 공부 (4)
      • 경제, 금융, 디지털, 시사 (3)
      • 라즈베리파이 (10)
      • 프로젝트에서 일어난 일 (22)
      • FrontEnd 공부 (9)
        • React (8)
      • Spring (2)
      • 기술 세미나 (2)
      • DB (3)
      • 우아한 테크 코스 8기 (10)
      • 잡생각 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
koreaioi
[우아한 테크 코스 8기] 프리코스 2주차
상단으로

티스토리툴바