우테코 프리코스 5~6주차 회고

김한준 (Hanjun Kim)·2022년 12월 4일
0

회고

목록 보기
4/4
post-thumbnail

들어가며

정식적인 프리코스 일정은 11월 23일에 마무리되었다. 다만 1차 결과 발표가 12월 14일인 바람에 3주 간 불안 속에 살게 되었다. 개인적으로 프리코스때 얻게 된 학습 습관을 놓치고 싶지 않기도 했고 아직 결과 발표도 나지 않았기 때문에 자체적으로 프리코스 기간을 7주차까지 연장하기로 했다.

발표가 나기 전까지는 프리코스에서 학습한 내용을 정리하고 지난 미션들을 다시 구현해보거나 새로운 미션들에 도전해보면서 시간을 보낼 생각이다.

또한 개발자로 내 진로를 결심한 순간 좋은 글을 쓰는 역량은 매우 중요할 수 밖에 없기에 지속적으로 글을 쓰려고 한다. 글쓰기 실력을 늘리는 정도는 꾸준히 좋은 글을 읽고, 많이 쓰는 방법 뿐이라고 생각한다.

무엇보다도 회고를 작성함으로 써 개발자로서 무엇을 배웠는지 무엇이 부족한지 확인해볼 수 있기도 하지만, 나 자신에 대해 돌아보는 시간을 가질 수도 있어서 참 좋은 것 같다. 난 정말 지난 한 주간 열심히 살았는지, 더 나은 날을 보내기 위해선 어떤 노력이 더 필요할지에 대해 냉정하게 생각해 볼 수 있었다.

사실 이러한 회고 시스템이 너무 맘에 들어서 일기도 쓰고 있다. 우테코 4기 인원들의 깃헙을 구경하러 다니다가 우연히 Today 라는 좋은 시스템을 발견하게 되었다. 하루의 계획을 미리 세워두고 하루를 마치면서 그 날 느낀 것들을 조금씩 기록해두신 것을 봤는데 정말 좋은 방법이라고 생각해서 바로 따라했다 :)

나는 무엇을 배웠는가

람다와 스트림

프리코스 기간동안 스트림을 열심히 사용해서 그런지 손에 조금씩 익어가는 느낌이다. 물론 아직 활용 범위가 한정적이라는 생각이 들기도 했다. 그래서 최대한 자바 8 이후의 문법들을 자유자재로 다루고자 <모던 자바 인 액션> 책을 구매해 학습했다.

책을 읽으며 람다와 스트림의 원론적인 부분부터 학습하기 시작했다. 어떤 이유로 등장하게 되었고, 어떤 식으로 동작하는지 등등... 지금까지 스트림을 사용하면서 그 활용성이 제한적이었던 이유는 스트림의 동작 방식을 잘못 이해하고 있었기 때문이었다.

스트림에 대해 제대로 학습하기 이전에는 쇼트 서킷루프 퓨전에 대한 개념을 알지 못했다. 그래서 기존에 for-loop 처럼 스트림도 한 줄 한 줄 순차적으로 동작하는 것으로 생각하고 사용해왔다.

예를 들어 스트림의 모든 요소에 대하여 filter 작업이 모두 끝나면 그 다음 연산으로 넘어가는 식으로 동작하는 줄만 알았다. 그래서 for-loop 에 비해 스트림이 갖는 이점이 그렇게 크지 않다고 느꼈던 것 같다. 스트림의 동작 방식에 대해 알기 전에는 아래와 같은 코드를 스트림으로 구현하는게 불가능하다고 생각해왔다.

// 루프 퓨전에 대해 모를 때
public Map<Coin, Integer> convertBalanceToCoins() {
	Map<Coin, Integer> convertedCoins = new HashMap<>();
    for (Coin coin : Coin.values()) {
    	if (isConvertableBy(coin)) {
        	convert(convertedCoins, coin);
        }
    }
    return convertedCoins;
}

// 루프 퓨전을 알게 된 후 스트림으로 구현할 수 있었다
public Map<Coin, Integer> convertBalanceToCoins() {
    Map<Coin, Integer> convertedCoins = new HashMap<>();
    Arrays.stream(Coin.values())
            .filter(this::isConvertableBy)
            .forEach(coin -> convert(convertedCoins, coin));

    return convertedCoins;
}

여전히 스트림 사용이 미숙하다고 생각한다.
collect 를 제대로 활용할 수 있다면 위와 같이 Map을 따로 선언하지 않고
바로 Map 에 내용을 담아서 반환할 수 있지 않을까?...

스트림에 대해 공부하면서 내 학습 방식의 문제점을 하나 발견할 수 있었다. 바로 지레 짐작하는 것이다. 새로운 내용을 배우면 해당 내용을 직접적으로 학습하지 않고 이미 알고있었던 사실들에 기반하여 '그럼 이것도 이런 식으로 동작하겠네' 라고 멋대로 판단해버리는 것이다.

이러한 나쁜 습관을 꼭 고치겠다고 다짐했다. 의문이 생기는 지점은 그냥 지나치지 않고 확실하게 알고 넘어갈 수 있도록 의식적으로 노력할 것이다. 조금 느릴 수도 있지만 다시 돌아오는 것보단 훨씬 확실한 학습 방법이 될 수 있을거라 생각한다.

스트림을 공부 하면서 참고한 자료 / 학습 내용 정리 링크

ComparableComparator

자동차 경주 미션을 진행하면서 얻은 가장 큰 수확은 ComparableComparator 를 알게된 것이었다. 항상 값 객체를 활용하면서 두 객체를 자유자재로 비교할 수 있었으면 좋겠다고 생각해왔다.

테코블에서 getter() 에 관한 내용을 살펴보다가 우연히 Comparable 에 대해 알게되었는데 조금 과장해서 말하자면 뒷통수를 얻어 맞은 느낌이었다. 너무 신선한 충격이었다.

프리코스를 진행하면서 원시 값을 포장하는 것의 중요성을 알게되었다. 특히나 객체지향적인 설계를 할 수록 객체끼리 비교하는 일이 정말 많아졌다. 그렇기 때문에 ComparableComparator 과 같은 인터페이스의 존재는 너무 소중했다.

우선 ComparableComparator 를 활용해서 객체 자체를 자유자재로 비교할 수 있게 됐다. 비교 기준도 상황에 따라 다르게 설정해줄 수 있기 때문에 활용 방식이 무궁무진할 것 같다.

특히나 자바에서 제공하는 정렬, 비교와 관련된 API는 기본적으로 Comparable, Comparator 를 인자로 받을 수 있게 설계되어 있다. 즉, 두 인터페이스 덕분에 비교 기준을 세워줄 수 있었고 이로 인해 원시 값만 가능할 줄 알았던 정렬, 비교 API 를 값 객체에도 활용할 수 있다는 게 좋았다. 이를 계기로 내 코드도 한 단계 성장할 수 있을 것 같다. 빨리 미션을 진행해보고 싶은 마음이 굴뚝같다.

Comparable, Comparator 학습 내용 정리 링크

JVM 기초 및 GC

이전 미션에서 객체를 재할당하는 것에 대해 리뷰를 받았던 적이 있다. 이때 재할당과 관련된 자료들을 찾아보다가 우연히 Garbage Collection 에 대해 알게됐다. 자바가 알아서 참조되지 않는 데이터를 제거한다는 것 자체가 너무 신기했다. 그래서 GC 에 대해 찍먹이라도 해보자 라는 마인드로 공부를 해봤다.

알고보니 GCJVM 과 밀접한 관련이 있었다. 우연히 테코블에서 JVM 기초에서 시작하여 간단한 GC 의 개념까지 이어지는 좋은 포스팅이 있어서 스무스하게 학습할 수 있었다.

JVM 의 존재에 대해선 인지하고 있었다. 다만 JVM 이 정확이 무슨 일을 하는가? 라고 묻는 다면 쉽게 대답하지 못할 것 같았다. 현재JVM 에 대해서 학습하고 내용을 정리했음에도 여전히 JVM 에 대해서 제대로 설명하는 것은 어렵다.

하지만 개략적인 자바의 동작 순서라던지 클래스가 탑재되는 방식, 런타임 이후 영역이 어떻게 나뉘며 어떤 식으로 실행되고 어떤 역할을 하는지 인지하고 나니 지금껏 배워왔던 내용을 이해하는 데에 큰 도움이 되었다. 예를 들어 getter()를 지양해야 하는지 Stack 영역과 Heap 영역의 차이에 대해 학습하면서 더 자세히 공감할 수 있었고, 상수를 왜 static final 로 선언해야 하는지 Method 영역과 Class Loader 의 동작 순서를 학습하면서 제대로 이해할 수 있게 되었다.

JVM 은 간단히 이해하려고 하면 쉽게 넘어갈 수 있겠지만 원론적으로 파고들면 끝없이 어려워지는 기분이다. 아직 내 수준에 어디까지 이해를 하면 좋을 지 잘 모르겠지만 자바를 다루는 개발자를 지망한 이상 언젠간 JVM 에 대해서 면밀히 이해하는 게 필요한 단계가 반드시 올거라 생각한다. JVM 에 대한 공부를 놓지 말고 주기적으로 다시 찾아와서 몸에 녹여야겠다.

JVM, GC 학습 내용 정리 링크

Getter 활용에 대하여

프리코스 내내 Getter 의 활용에 대해 고민해왔다. 처음 객체에 메시지를 보내라 라는 피드백을 받고난 뒤엔 무조건적으로 Getter 를 배제하려고만 했다. 그런데 미션을 진행하다 보니 Getter 없이 구현하는게 정말 힘들어지는 경우가 꽤 많이 발생했다.

예를 들어 객체의 내부 상태가 특정 로직의 연산에 필요한 경우, 아니면 객체의 상태를 출력해야 하는 경우 객체로부터 직접 값을 꺼내오는 것이 불가피했다.

객체의 상태를 출력하기 위해 toString() 을 사용하는 것도 고려해봤다. 현재 객체의 상태를 UI 요구사항에 맞게 변형해서 출력해야 하는 상황이라면 toString() 을 오버라이딩해서 사용하는 것은 부적절하다고 생각했다.

Java 에서 toString 메소드의 올바른 사용 용도에 대하여

예를 들어 이번 자동차 경주 미션에서 일급 컬렉션의 값을 직접 꺼내와야 하는 상황이 발생했다.

// Cars 는 List<Car> 를 포장한 일급 컬렉션이다.
private String getScore(Cars cars) {
	return cars.get().stream()
    	.map(this::toScoreBoard)
        .collect(Collectors.joining(NEW_LINE));
}

위와 같이 Cars 내부의 상태를 변형시켜 출력하고자 할 때는 Getter 를 사용하는게 불가피하다고 느껴졌다.

그래서 Getter 를 무조건적으로 배제하지 말고 최대한 지양하는 방식으로 사용하자는게 프리코스 기간동안 내가 내린 결론이다. 그렇다면 Getter 를 어떻게 써야 현명하게 사용하는 것인지를 고민해보기로 했다.

우선 Getter 를 지양해야 하는 이유에 초점을 맞췄다.

  • 객체 내부의 상태를 캡슐화 하기 위해서
  • 객체의 상태를 외부에서 변경하지 않도록 하기 위해

결국 핵심은 객체의 자율성이었다. 객체의 상태는 객체 스스로의 행동으로 결정지어야지 외부에서 쉽게 접근해서 제 멋대로 조작하도록 냅두면 안된다는 것이다.

객체의 내부 상태를 캡슐화하는 것도 간과하지 말아야 한다.
다른 도메인에서 특정 객체의 내부 상태를 하나하나 전부 알고 있다면, 객체 내부의 상태가 변했을 때 해당 상태를 인지하고 있는 외부 도메인에서도 변경이 불가피하게 일어날 수 밖에 없다. 즉, Getter 를 무분별적으로 사용하면 객체 간 결합도를 높이고 유지보수를 힘들게 만든다.

그렇다면 어떻게 객체의 자율성을 보장하면서 Getter 를 활용할 수 있을까?

public List<Car> get() {
	return Collections.unmodifiableList(cars);
}

위와 같이 불변 객체로 내부 컬렉션을 반환해줬다. 이렇게 하면 Getter 를 통해서 값을 반환하더라도 read-only 성격으로 활용할 수 있었다.

Unmodifiable Collection 이 항상 정답인 것은 아니다.

프로그래밍을 하면서 Getter 는 끊임없이 마주칠 수 밖에 없다. 특히 DTO 를 적극적으로 활용하게 되었을 때는 Getter 를 더 활발하게 사용할 것 같다. 그럴때마다 Getter 를 사용함으로써 객체의 자율성을 해치진 않는지 지속적으로 의식하고 의심할 생각이다.

학습 관련 자료

마치며

프리코스 4주차를 마무리하는 코수타 시간에 포비님이 다음과 같이 말씀하셨던 기억이 난다.

이미 수행했던 미션을 다른 방식으로 새롭게 구현해보는 것이 중요하다. 이미 구현해봤던 문제라 재미가 없을 순 있지만 그 과정에서 더 큰 성장을 할 수 있다.

이 말을 듣고도 나는 새로운 미션을 계속 갈망했다. 지금까지 배운 내용을 새로운 요구사항에 적용해보고 싶다는 생각이 강했다. 물론 미션을 구현하면서 배우는 것도 있었지만 뭔가 텅빈 공부를 하고있다는 느낌이 들었다.

그래서 초심으로 돌아와 시도해보지 못했던 다양한 방식을 기존에 풀어봤던 문제들에 적용해보자! 라는 마음가짐으로 숫자 야구, 로또, 그리고 상대적으로 난이도가 평이한 4기의 자동차 경주 미션을 구현했다.

숫자 야구 미션과 로또 미션에서는 TDD 를 활용해보는 것에 집중했고 자동차 경주 미션에서는 값 객체를 적극적으로 활용하는 것에 초점을 맞췄다. 확실히 요구사항을 이해하고 초반 단계를 설계하는 부담감에서 벗어나니 새로운 시도를 하는 것에 집중해서 미션을 구현할 수 있었다.

이런 식으로 학습하는 것이 오히려 더 큰 도움이 됐다. 작성한 회고를 다시 읽어봐도 자판기 미션이 하나도 없는 것을 보면 어디서 더 많은 깨달음과 배움을 얻었는지 한 눈에 알 수 있다.

남은 프리코스 기간동안엔 DTO 를 활용해보려고 한다. 또한 TDD 도 더 갈고 닭아서 테스트를 먼저 작성하지 않으면 몸에 가시가 돋을 정도로 연습해보려고 한다.

사실 전부터 DTO 활용에 대해 끊임없이 고민하고 회의감을 가졌던 것 같다. 그래서 직접 적용해보기 전에 DTO 에 대해서 학습하고 고민하는 시간들을 가졌다. DTO 활용기는 다음 회고에서 다뤄볼 예정이다.

(포버지 이제야 깨달아요...)

마지막으로 내가 읽었던 좋은 회고를 공유하며 마무리 짓겠다. 우테코 4기 후디님의 글인데 초반에 나오는 내용들을 정말 인상깊게 읽었다. 나도 나중엔 꼭 저런 글일 써서 누군가에게 영감을 주고 싶다.

hudi | 우아한 테크코스 4기 - 4주차 회고

5~6 주차 기간동안 재구현했던 미션들의 코드는 여기서 확인할 수 있습니다.

profile
조금 느려도 꾸준한 성장을 추구합니다.

0개의 댓글