워밍업 클럽 2기 BE 클린코드&테스트 - 회고 1회
해당 회고는 박우빈님의 'Readable Code : 읽기 좋은 코드를 작성하는 사고법' 강의를 참조하여 작성했습니다. 범위는 섹션 1 ~ 4 입니다.회고와 같은 정리는 가끔 귀찮기도 했지만 결과물에 대한 정리와 점검할 수 있는 시간을 준다고 하니 좋은 마음으로 기록하는 습관을 기르겠습니다! 미션 public boolean validateOrder(Order order) { if(notExistItemsFrom(order)){ // 주문에 상품 목록이 존재하면 log.info("주문 항목이 없습니다."); return false; } if (isNotVaildTotalPrice(order)) { // 총액이 유효한 값인지? log.info("올바르지 않은 총 가격입니다."); return false; } if (hasNotCustomerInfoFrom(order)) { // log.info("사용자 정보가 없습니다."); return false; } log.info("주문 항목이 없습니다."); return true; } private static boolean hasNotCustomerInfoFrom(Order order) { return !order.hasCustomerInfo(); } private static boolean isNotVaildTotalPrice(Order order) { return order.getTotalPrice() <= 0; } private static boolean notExistItemsFrom(Order order) { return order.getItems().size() == 0; } 주문에 대한 검증 로직1. 주문 안에 상품이 존재하는가? 없다면 잘못된 주문으로 판별2. 상품의 가격이 1원 이상이라면 정상 처리 / 아니라면 잘못된 주문으로 판별3. 주문에 대한 사용자 정보가 있어야 한다. 없다면 잘못된 로직이다. 적용할 수 있는 읽기 좋은 코드 방식1. 사고의 depth 줄이기2. 부정구 지양하기3. 최대한 추상적으로 접근하기 ( getter로 직접 접근하기 X)4. early return 사용하기 각 조건을 모두 통과해야 유효한 주문이라는 결과를 도출해야 한다. 따라서 비즈니스 로직을 지키며 위 리팩토링 기준을 모두 적용하려 했으나 'not'과 같은 부정어를 사용했다. 이에 대한 이유if 조건 안에 && 처리를 하여 긍정구로 표현하려 했으나 조건이 길어지면 오히려 기억해야 할 정보가 많을 거 같아 이와 같이 부정구를 사용하지만 depth와 if문마다 하나의 조건만 담기 위해 이와 같이 리팩토링했다. 인상적인 부분 테스트 코드와 클린 코드에 대한 중요성은 들어봤지만 기능 구현조차도 쉽지 않다 보니 평소 읽기 좋은 코드에 대해 접해볼 시도조차 없었습니다. 강의 도입 섹션에서는 읽기 좋은 코드의 중요성에 대해 설명하시는데 클린 코드에 관한 새로운 관점(?)도 알 수 있어 좋았고 읽기 좋은 코드 작성에 대한 이유와 실제 적용을 통해 진행해서 더 마음에 와닿았습니다. 학습 정리강의 내용과 제가 따로 정리한 부분이 섞여 있으니 참고해주세요.방대한 강의 내용과 강의에서 제공한 모든 내용 제시를 피하기 위해서 인상 깊었던 섹션 1,2 위주로 정리하겠습니다. 1. 클린 코드와 추상 관계 왜 클린 코드를 추구하는 것일까?결국, 클린 코드는 읽기 쉬운 코드이며 적절히 추상화가 이뤄진 코드이다. 이러한 코드는 유지 보수에 들어가는 시간과 비용을 절약해준다.극단적으로 확장 가능성이 없거나 그 순간에 개인만이 서비스에 대한 코드에 접근하고 작성한다면 보편적인 클린 코드 대신 본인이 알아보기 쉬운 형태의 코드로 작성하는 것이 더 합리적일 수도 있다.하지만 이와 같은 경우보다는 거의 없기에 클린 코드를 추구하는 것이다. 프로그램의 정의는 무엇일까?다양한 의미가 있겠지만 다음과 같이 정의할 수 있다.‘프로그램 = 데이터 + 코드’데이터는 어떠한 정보 자체를 의미하고 코드는 어떠한 논리적 행위를 의미하며 데이터와 데이터 간, 데이터와 코드 간 등 여러 관계에서 논리적 행위가 일어날 수 있다. 그럼 데이터와 코드는 실제 우리가 작성하는 코드에서는 어떤 것을 의미할까?데이터는 객체, 클래스가 가지는 값이 있으며 객체가 어떤 행위를 수행하는 의미를 가진 메서드 선언부를 나타낼 수 있다.(반환값, 파라미터, 메서드명 등)코드는 메서드의 내부 행위 값이 있고 크게는 코드 간 여러 복합적인 메서드 간 상호 작용으로도 볼 수 있다.위 코드에 대한 예시를 보면 calculateChangeMoney 메서드 선언부와 Person 필드 등이 데이터가 볼 수 있고 calculateChangeMoney, donamteMoney 등 메서드에 내부 로직을 코드로 볼 수 있다. 도대체 읽기 좋은 코드에 대한 객관적인 기준이 뭐야?읽기 좋다는 것은 바로 추상과 구체에 의해 나타난다.하나의 예시를 보자.<aside> 💡나는 누군가 쳐다본다. 일반적인 ‘보다’ 라는 느낌과는 다르다. 주위에 있으면 한순간도 놓치지 않고 어떤 표정을 짓는지, 무엇을 하는지 바라보게 된다.이 사람이 슬퍼하기 보단 웃고 행복했으면 좋겠고 이 사람을 생각하면 가슴이 두근두근 뛴다. 슬퍼하면 나도 슬프고 행복해 보이면 괜히 나도 행복해진다.이 사람이 좀 더 행복할 수 있다면 내가 좀 더 손해를 봐도 좋다. 손해를 보면 기분이 나쁘거나 우울한 것과 같이 부정적인 생각이 들어야 하는데 오히려 손해를 보는 것이 더 좋다.</aside>이러한 내용은 무엇을 나타낼까? 하나의 단어로 표현할 수 있다.‘사랑’ 이란 단어로 표현 가능하다. 여기서 ‘사랑’은 위 내용에 대한 추상이며 위 내용은 ‘사랑’이란 단어에 대한 구체이다.2가지 문장 중 어떤 것이 읽기 편하고 합리적이라 생각하는 가?<aside> 💡1번너 요즘 사랑하는 사람이 있어?2번너 요즘 어떤 사람을 보면 가슴이 콩닥콩닥 뛰고 그 사람만 쳐다 보고 싶고 다 해주고 싶고 손해를 봐도 전혀 아쉽지 않고 행복하길 바라고 감정을 나누고 싶은 사람이 있어?</aside>보편적으로 1번이 읽기 편하고 합리적일 것이다.여기서 추상과 구체의 관계를 알 수 있으며 적절한 추상화는 문장을 이해하는 데 적은 비용과 시간이 든다. 쉽게 이해할 수 있기 때문이다.이러한 추상과 구체는 우리가 작성하는 코드에도 적용되고 있다. 결론적절한 추상화는 복잡한 데이터와 복잡한 로직을 단순화하여 이해하기 쉽도록 돕는다. = 읽기가 좋다.우리는 추상과 구체의 관계를 알 수 있으며 추상이 어떤 역할을 하는 지도 알 수 있었다. 추상화를 하면 우리는 복잡한 것을 쉽게 이해할 수 있으니 무조건 추상화를 해야 할까?무분별한 추상은 좋지 않다. 과한 추상은 구체를 유추하지 못할 수 있다. 지속적으로 언급한 ‘적절한 추상화’만이 읽기 좋은 효과를 가져올 수 있다.어떠한 경우가 추상으로부터 구체를 유추하지 못할까?<aside> 💡나는 밤이 좋아졌잘싸나는 친구랑 샤우팅 갔어 </aside>위 3가지 예시에 구체적인 의미를 알 수 있는가?1번은 먹는 밤인지 시간에 따라 나타나는 밤인지 알 수 없다.2번은 ‘졌지만 잘 싸웠다’라는 의미지만 과하게 줄인 탓에 알 수 없었다.3번은 샤우팅이라는 단어의 의미를 알 수 없었다. 소리 지르는 행위에 대한 미미한 유추만 가능하다.위 예시에 대한 문제점이다.추상화 과정에서 중요한 정보를 부각시키지 못했다.상대적으로 덜 중요한 정보를 남기고 중요한 정보는 제거했다.해석자가 동일하게 공유하는 문맥이 없다.중요한 정보의 기준이 다를 수 있다.도메인 영역 별 추상화 기준이 다를 수 있다.즉, ‘잘못된 추상화’는 오히려 추상화 안 한 것보다 못할 수 있다. 야기하는 side-effect는 생각보다 정말로 크다.‘적절한 추상화’는 해당 도메인 문맥 안에서, 정말 중요한 핵심 개념만 남겨서 표현하는 것이다.이름 짓기로 추상화 하기메서드와 추상화 한 문단의 주제는 반드시 하나다.잘 쓰여진 글이라면, 한 문단의 주제는 반드시 하나다. 0개도 2개도 아닌, 무조건 1개이다.실제로 국어/영어 시험에서도 주제가 1개가 아니라면 답이 오직 1개라고 정의하기 힘들다.문장과 문장에 대한 주제는 마치 코드에서의 메서드 선언부와 메서드 로직과 같은 역할을 한다.메서드 이름으로 구체적인 내용을 추상화한 것이다. 다른 예시를 보자.메서드의 로직 내부에서는 2가지 이상의 일을 하고 있다.메서드에서 수행하는 일산책하기, 은행가서 현금 인출하기, 음식점에서 밥 먹기, 책 구매하기오른쪽 메서드를 보자. 더 큰 맥락 안에서 포괄적인 의미를 담았다.즉, 잘 쓰여진 코드라면 하나의 메서드의 주제는 반드시 하나이다.메서드 선언부반환타입메서드 시그니처에 납득이 가는, 적절한 타입의 반환값 돌려주기→ 반환 타입이 boolean인데, 이게 이 메서드에서 무엇을 의미하는거지?void 대신 충분히 반환할 만한 값이 있는지 고민하기→ 반환값이 있다면 테스트도 용이해 진다. 결과값이 없다면 상태와 행위 중, 행위밖에 검증할 수 없다.메서드명추상화된 구체를 유추할 수 있는, 적절한 의미가 담긴 이름파라미터와 연결지어 더 풍부한 의미를 전달할 수 있다.파라미터타입, 개수, 순서를 통해 의미를 전달외부 세계와 소통하는 창String createDailyShopKey(String shopId, String localDateString){ return String.format("%s_%s", shopId, localDateString); } String createDailyShopKey(String shopId, LocalDate sellingDate){ return String.format("%s_%s", shopId, sellingDate.toString()); } 위 두 메서드 중 어떤 것이 더 잘 추상화를 했을까?//S34_2024-06-01 String shopId = "S34"; LocalDate today = LocalDate.of(2024, 6, 1); //1 String dailyShopKey = createDailyShopKey(shopId, today.toString()); //2 String dailyShopKey = createDailyShopKey(shopId, today); 정답은 2번이다. 이유가 뭘까?이유날짜를 String 값으로 나타낼 때, 다양한 형식이 있다. 2024-06-01, 2024.06.01 등 어떤 형식의 문자열로 넘길지 고민하게 된다.따라서 LocalDate 자체로 넘기면 날짜의 의미를 가진 LodatDate 구체화된 타입을 넘기면 된다라는 명확함을 인지할 수 있다.sellingDate와 localDateString의 의미를 보면 메서드 행위에 대한 의미를 보면 sellingDate가 더 많은 정보를 제공하므로 파악하기 쉽다.localDateString에 비해 sellingDate는 판매날짜의 의미를 담고 있다. 추상화 레벨 서점에 위와 같이 진열대에 책이 나열되어 있다. 여기는 여러 책이 진열되어 있는 공간이며 책의 제목이란 추상을 통해 책의 내용을 유추할 수 있다. 그런데 책의 구조인 제목, 목차, 내용 순이 아닌 단순 서류 뭉치로 이루어진 책 하나가 있다.이상하지 않는가? 서류 뭉치를 본 순간에 책인지, 그냥 단순 서류 뭉치인지 알 수 없다. 저 서류 뭉치의 존재에 대해 의문이 들고 이해하는데 비용이 발생한다.void method(){ ....... T t = extracted(p); .... } T extracted(P p){ ...something.... } 우리는 메서드 구현부를 확인할 때, 위처럼 extracted 메서드와 같이 추상화된 내부 메서드를 본다. 추상화된 메서드명의 주제에 대해 더 궁금하면 해당 메서드 내부에 들어가 확인할 것이다.이는 외부 세계와 내부 세계로 나뉠 수 있으며 method 내부에 메서드 선언부로만 표현된 extracted(p)가 경계가 된다.이는 외부 세계는 추상화 레벨이 높고, 내부 세계는 추상화 레벨이 낮음을 의미한다. 당연히 메서드 구현부에는 메서드 선언부에 대한 구체적인 내용이 있기 때문이다.하나의 세계에서는 추상화 레벨이 동등해야 한다.우리는 진열된 책을 예시로 봤다. 코드로 직접 봐보자.public static void main(String[] args) { showGameStart(); 10 initializeGame(); 10 showBoard(); 10 .... if (gameStatus == 1) { 5 System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } ..... 10 } 10, 5는 예시로 추상화 단계를 수로 표현했다. 10이라는 추상화 단계를 가다가 ‘gameStatus == 1’ 이라는 구체에 가까운 5라는 추상화 단계를 만나며 코드를 이해하는데 혼란을 줄 수 있으며 추가적인 논리가 더 필요로 하다.그렇기에 추상화 단계를 맞춰주는 것은 중요하다. 진행할 점강의의 양이 매우 방대하여 깔끔하게 정리하기 쉽지 않은 거 같다. 추상과 구체의 관계에 대해 학습했으니적절한 추상화를 통해 내용을 정리해서 상대방에게 이해하기 쉬운 글을 제공할 수 있게 연습해봐야겠다.