읽기 좋은 코드를 작성하는 사고법
강의 돌아보기
스터디 1주차에 접어들면서 내가 오해하고 있었던 부분들과 모호한 부분들을 맞춰 갈 수 있는 주차였다.
요즈음에는 어떤 지식을 습득하고 적용을 하기 위해서 가장 좋은 방법이 무엇일까에 대해서 고민을 하고 있다.
그 와중에 마인드 맵으로 카드 형태로 정리하는 방식이 내가 공부한 지식을 나만의 방식대로 기억 할 수 있게 해주는 좋은 도구가 아닐까 하여 옵시디언을 사용하였다.
누군가는 이것이 무엇을 의미할까? 라는 생각이 들 수 있다.
하지만 확실히 카드 형태로 공부한 내용을 정리했을 때 해당 키워드를 바라보면 그때 공부했던 그 장면이 머릿속에 재생된다.
하지만, 아직까지는 마인드 맵에 너무 많은 세부 내용들이 노출되고 있다라는 느낌이 든다. 세부내용을 "별도의 페이지를 만들어서 노출시키는 것은 어떨까?" 라는 생각이 든다. 이것 또한 추상화가 아닐까.
핵심
특히, 가장 코드를 짜면서 헉! 했던 강의 파트는 추상화 레벨이다.
누군가의 코드를 보다보면 뭔가 어색하다라고 느껴지는 상황이 있다. 그리고 그 상황이 바로 추상화 레벨이 다르기 때문이 벌어진 일이었다.
어딘가는 외부 세계를 만들어서 객체로 처리하고 있는데 어딘가에는 private method로 밖에서 처리하고, 어딘가는 로직의 구현체가 내부에 있다보니 내가 어떤 방향으로 코드를 읽고 따라가야 하는지에 대한 방향을 잃었다고 생각이 든다.
미션 회고
1주차에는 두 가지 미션이 있었다.
추상을 구체화 하기
코드를 적절한 추상화 레벨에 맞춰 리팩토링 하기
추상을 구체화 하기
미션을 수행하면서 최근에 굉장히 중요하다고 생각하고 있는 CS 지식을 얼마나 잘 알고 있는가. 그리고 실제로 나는 어떠한 사고를 가지고 있는가를 판단하기 위해 미션에 접목했다.
세상에 모든 일은 추상화 되어있다고 생각한다. 그리고 고수준 레벨의 언어, 프레임워크가 잘 만들어지게 되면서 이제는 코드를 짤 때 내부의 구체를 갈수록 보기 어려운 형태가 되어가고 있다.
과연 어떠한 우리가 웹에서 어떠한 작업을 할 때 얼마나 많은 일들이 발생하고 빨리 처리되고 있는 것일까? 를 기준으로 수행했다.
하지만, 추상화 레벨을 너무 높게 잡아서 였을까? 추상화에 1뎁스 구체 -> 2뎁스 구체 -> 3뎁스 구체 너무 깊은 레벨의 구체까지 바라보게 되는 느낌이 있었다. "코드의 컴파일 단계까지 적어야하나?" 라는 생각도 들었는데, 그건 너무 깊게 갔다고 생각하여 해당 구체 레벨까지 내려가지는 않았다.
이 미션을 수행하면서도 사람이 읽기 좋도록 추상화 레벨을 신경쓰는 것이 좋다. 라는 생각이 한편으로 들었다.
추상화 레벨에 맞춰 리팩토링 하기
미션을 보고 가장 먼저 눈이 갔던 부분은 boolean 타입의 반환 타입이었다.
"객체의 상태 검증을 외부로 노출하는 것이 맞는 것일까?", "이 객체는 어떻게 존재 할 수 있지?" 를 관점으로 코드를 바라봤다. 특히, 경험적인 부분이 많이 반영이 되기도 했다.
회사 내에서 금액은 보통 대부분의 도메인이 회사 내부적으로 금액을 우리가 어떻게 처리 할 것인가로 처리를 하게 되는 상황이 많았다. 예를 들어, 소수점은 없앤다거나, 100원 단위는 절삭한다. 같은 비즈니스 요구사항이 다음과 같은 예이다.
적절하게 책임을 가질 수 있는 객체로 분리하고 코드를 작성했다. 미션에서 검증해야 할 요구사항은 객체의 생명주기에 따른 사전 조건에서 검증을 해야 된다고 생각하고 생성자에서 검증을 하도록 하였다.
하지만, 그 부분에서 궁금증이 생긴 부분이 있었다.
"어떠한 경우에는 객체에게 지금 나의 상태에 대해서 물어 볼 상황이 생길 수도 있지 않을까? 그렇다면, 그 경우는 언제지?"
명확한 기준이 잡혀있지 않았고, 해당 내용을 우빈님에게 질문을 하게 되었다.
다음과 같은 내용으로 질문을 드렸고, 우빈님께서 '유효성 검증', '상태 확인'의 개념을 분리하는 것이 좋을 것 같다는 피드백을 주셨습니다.
말씀을 듣고 보니 결국 이것도 비즈니스 요구사항에 따라서 도메인의 생명 주기가 어떻게 이루어져야하는가에 따라서 달라질 수 있다는 결론을 내렸습니다.
아쉬운 점
totalPrice를 계산하는 부분을 메서드로 호출 할 때마다 연산하는 형태로 구현을 해두었습니다.
return orderItems.stream()
.map(OrderItem::getMoney)
.reduce(Money.from(BigDecimal.ZERO), Money::add)
호출 할 때마다 매번 연산 로직이 수행 될 것입니다. 추후에는 totalPrice가 coupon에 적용되는 비율이 달라지는 등 여러 형태로도 사용 될 수 있겠다는 생각이 들었어요. 아래와 같은 방법으로 하는 것은 어땠을까? 라는 생각이 들었습니다.
private Order(List<OrderItem> orderItems, ...) {
this.totalPrice = calcTotalPrice(orderItems);
}
private Money calcTotalPrice(List<OrderItem> orderItems) {
return orderItems.stream()
.map(OrderItem::getMoney)
.reduce(Money.from(BigDecimal.ZERO), Money::add)
}
위와 같이 작성한다면 객체의 생성 시점에 한 번 totalPrice를 생성하고 여러 방면으로 재활용 할 수 있지 않을까 라는 생각이 들었습니다.
결론
다음 미션도 너무나도 기대되고, 해당 스터디를 하게 되어 정말 다행이라고 생각하고 있습니다.
한 가지 아쉬운 점은 스터디의 의존도가 멘토 분들에게 많이 의존되어 있다는 생각이 들었습니다. 같이 스터디를 하는 많은 분들이 많은 의견을 공유 할 수 있는 환경이 되었으면 좋겠다? 라는 개인적인 아쉬움이 조금 있었네요.
댓글을 작성해보세요.