허수빈
@tnqls10350684
수강평 작성수
-
평균평점
-
블로그
전체 7#카테고리
- 웹 개발
- 백엔드
#태그
- 워밍업클럽
- 인프런워밍업클럽
![[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] 4주차 발자국](https://cdn.inflearn.com/public/files/blogs/13efef22-3060-4f73-ab14-d8f89b86b851/인프런 워밍업 클럽 2기 이미지.png?w=260)
2024. 10. 27.
0
[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] 4주차 발자국
해당 글은 [인프런 워밍업 클럽 2기 클린 코드 & 테스트 코드]에 참가하여 박우빈님의 강의를 듣고 작성한 글입니다. ✍ 이번주 강의 요약Spring & JPA 기반 테스트서비스의 핵심 로직이 있는 Business Layer는 여러 시나리오에 따라 다양하게 테스트를 진행해야 한다.외부세계의 요청을 가장 먼저 받는 Presentation Layer는 클라이언트로부터 넘겨 받은 값들에 대한 유효성 검증을 수행해야 한다.Mock을 마주하는 자세@Mock은 가짜 객체@MockBean은 스프링 빈으로 등록되는 가짜 객체@Spy는 실제 객체의 특정 메서드를 모킹, 변경 가능.@SpyBean은 스프링 빈으로 등록된 실제 객체의 특정 메서드를 모킹, 변경 가능7 ~ 9. 테스트 코드는 한 문단 당 한 주제를 담자!독립적이어야 하고 순서가 있으면 안된다.테스트 코드는 프로덕션 코드의 문서화다.귀찮음을 이겨내고 제대로 작성해야 한다! 🏃 미션Day15사실 강의만 들었을 때는 이해가 잘 안돼서 여기 저기 찾아보고 정리했다. 아직도 완벽한 건 아니고 실제로 내가 프로덕션 코드를 짜고 테스트 코드를 짜야만 완전히 이해할 수 있을 것 같은 느낌이다... ㅜ Day18다른 사람이 테스트 코드를 볼때 정말 없어도 되는, 해당 코드가 있든 말든 테스트 코드 자체를 읽는 데는 문제 없는 부분만 setup에 넣어야 한다고 생각해서 준비 내용만 setup으로 뺐는데 좀 더 고민해보니까...void writeComment() : 댓글 작성void updateComment() : 댓글 수정void cannotUpdateCommentWhenUserIsNotWriter() : 타인의 댓글 수정 시도 실패해당 주제를 파악하는데만 필요한 것들을 given 절에 두고 나머지는 setup으로 빼도 되지 않을까? 댓글 작성이나 댓글 수정은 댓글을 쓰고 수정하는 것이 목적이니까 사용자 생성이나 게시글 생성 같은 것도 setup에 둬도 될 것 같다.. 🗒️ 회고드디어 약 한 달간의 여정이 끝났다. 나 혼자 강의를 수강했다면 훨씬 더 오래 걸렸을텐데 계획이 짜여져 있고 미션을 수행해야하니까 좀 더 빠르게 들을 수 있던 것 같다. 결코 쉬운 과정은 아니었지만... 복습해야하지만... ㅜㅜ 그래도 완강했으니까 뿌듯하다!아주 조금씩이지만 성장하는 게 느껴진다. 나태해지지 말고 남은 2024년도 열심히 보낼 수 있기를! 마지막으로 좋은 강의를 제작해주신 박우빈님께 감사인사 드립니다.
웹 개발
・
워밍업클럽
![[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] Day18 과제](https://cdn.inflearn.com/public/files/blogs/c1c81180-2ee5-4ba2-9c74-8e1bc66fe35c/인프런 워밍업 클럽 2기 이미지.png?w=260)
2024. 10. 25.
0
[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] Day18 과제
해당 글은 [인프런 워밍업 클럽 2기 클린 코드 & 테스트 코드]에 참가하여 박우빈님의 강의를 듣고 작성한 글입니다. @Mock는 독립적인 단위 테스트에서 가짜 객체를 생성할 때 사용한다. 이때, @Mock으로 생성된 가짜 객체를 @InjectMocks 객체에 자동으로 의존성 주입해준다. @MockBean은 주로 통합 테스트에서 사용되며 가짜 객체를 생성하여 스프링 컨텍스트에 등록하여 빈으로 사용할 수 있게 해준다. @Spy는 단위 테스트에서 사용되고, 가짜 객체를 생성하는 것이 아니라 실제 구현체를 사용하여 특정 메서드만 모킹할 수 있다. 즉, 특정 메서드의 동작을 원하는 대로 바꿀 수 있다. @SpyBean은 @Spy와 비슷하지만 실제 스프링 컨텍스트에 등록된 빈을 사용한다. @BeforeEach void setUp() { 사용자 생성에 필요한 내용 준비 게시물 생성에 필요한 내용 준비 댓글 생성에 필요한 내용 준비 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-2. 사용자 생성 1-4. 게시물 생성 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-2. 사용자 생성 2-4. 게시물 생성 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-2. 사용자1 생성 3-4. 사용자2 생성 3-6. 사용자1의 게시물 생성 3-8. 사용자1의 댓글 생성 // when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 }
웹 개발
・
워밍업클럽
![[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] Day15 과제](https://cdn.inflearn.com/public/files/blogs/c2d5eb96-d28c-4c5a-8a2b-5e2701bf7a94/인프런 워밍업 클럽 2기 이미지.png?w=260)
2024. 10. 22.
0
[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] Day15 과제
해당 글은 [인프런 워밍업 클럽 2기 클린 코드 & 테스트 코드]에 참가하여 박우빈님의 강의를 듣고 작성한 글입니다. Layered Architecture소프트웨어 시스템을 관심사에 따라 여러 계층으로 분리한 것 Persistence Layer특징데이터베이스와의 상호 작용을 관리하는 계층 테스트 방법단순히 쿼리가 제대로 작동하는 지 확인하는 테스트가 대부분이다.CRUD 작업 테스트가 많다.필요한 객체들을 생성하고 쿼리를 실행, 비교함. Business Layer특징서비스의 핵심 로직이 있는 계층 테스트 방법여러 시나리오에 따라 다양하게 테스트를 진행한다.해피 케이스와 예외 케이스를 섬세하게 다뤄야 한다. (경계값 테스트로!)더미 객체를 생성할 때, 중복 예외가 발생할 수 있으므로 데이터 클렌징 작업을 해야한다. Presentation Layer특징외부 세계의 요청을 가장 먼저 받는 계층 테스트 방법클라이언트로부터 넘겨 받은 값들에 대한 유효성 검증을 수행한다.MockMVC를 이용하여 하위 레이어들을 처리한다.
웹 개발
・
워밍업클럽
![[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] 3주차 발자국](https://cdn.inflearn.com/public/files/blogs/9df75bb1-5728-4109-93ff-5db8d67e3c25/인프런 워밍업 클럽 2기 이미지.png?w=260)
2024. 10. 20.
0
[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] 3주차 발자국
해당 글은 [인프런 워밍업 클럽 2기 클린 코드 & 테스트 코드]에 참가하여 박우빈님의 강의를 듣고 작성한 글입니다. ✍ 이번주 강의 요약테스트는 왜 필요할까?테스트 코드를 작성하지 않는다면안정성 보장 X유지 보수 어려움테스트 코드로 작성하면 소프트웨어의 빠른 변화에 유연하게 대처 가능! 단위 테스트간단한 카페 키오스크 프로그램을 만들어 수동 테스트와 자동 테스트 비교하기JUnit5를 사용하자해피 케이스와 예외 케이스 모두 검증해야 한다. -> 경계값 테스트로! TDD : Test Driven Development실패하는 테스트 작성 -> 구현부 없는 프로덕션 코드최소한의 코딩으로 테스트를 통과하게끔 구현리팩토링 테스트는 [ ]다.테스트는 문서다.테스트 코드는 프로덕션 기능을 설명하는 문서가 될 수 있다.@DisplayName을 이용하여 코드를 설명하자BDD -> Given, When, Then을 이용한 시나리오 기반 테스트 5-1. Spring & JPA 기반 테스트 : Persistance Layer여러 모듈이 협력하는 기능은 통합 테스트를 진행한다.Persistance Layer는 데이터베이스와 상호 작용하는 부분이다.JPA 코드를 테스트하는데, 단위 테스트와 유사하다. 🏃 미션Day12Readable Code에서 진행했던 StudyCafe 프로그램을 기반으로 단위 테스트를 작성했다. 뭘 테스트하는게 좋을까... 고민했는데 일단 가장 중요한 건 돈💸이니까 주문 금액을 테스트해야겠다고 생각했다! 이용권별(시간권, 주간권, 고정권) 로 합계를 계산하는 로직을 테스트하기로 결정!BDD를 이용하여Given : 각 시간 또는 주간을 기준으로 SeatPass를 생성하고 (고정권은 LockerPass도 같이) 이를 기반으로 Order 객체를 생성했다.When : getTotalPrice()를 실행시켰다.Then : totalPrice의 값이 계산 결과와 맞는 지 확인했다.그 다음엔 파일에서 이용권 목록을 읽어왔을 때, 각 이용권 타입에 맞는 이용권 목록을 제대로 추출하는 지 테스트 했다.BDD를 이용하여Given : seatPass 파일을 읽어와서 리스트에 담았다.When : findPassBy()를 실행시켰다.Then : 각 이용권 타입과 일치하는 지 확인했다.마지막으로 이용권의 할인된 가격을 제대로 계산하는 지 테스트했다.BDD를 이용하여Given : 할인율이 존재하는 seatPass를 생성했다.When : getDiscountPrice()를 실행시켰다.Then : discountPrice의 값이 계산 결과와 맞는 지 확인했다. 🗒️ 회고이번주는 제법 부지런히 강의를 들어서 뿌듯했다👍👍미션 구현 자체는 그렇게 어렵지 않았는데 내 테스트 코드가 맞는 건지는 모르겠다......ㅜㅜㅋㅋㅋㅋ 아무튼 제출의 의미를 두는 걸로...클린 코드 강의보다 테스트 코드 강의가 더 재밌다. 매번 프로젝트 할 때마다 테스트 코드 쪽은 손도 못대서 그런지 더 많이 제대로 알고 싶은 마음이 든다. 완강까지 화이팅!
웹 개발
・
워밍업클럽
![[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] 2주차 발자국](https://cdn.inflearn.com/public/files/blogs/889178ed-ac69-474f-af5a-8a43c571da38/인프런 워밍업 클럽 2기 이미지.png?w=260)
2024. 10. 13.
0
[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] 2주차 발자국
해당 글은 [인프런 워밍업 클럽 2기 클린 코드 & 테스트 코드]에 참가하여 박우빈님의 강의를 듣고 작성한 글입니다. ✍ 이번주 강의 요약객체 지향 적용하기상속은 부모와 자식 간의 결합이 높다. -> 확장과 변경이 어려움조합과 인터페이스를 사용하자!VO는 불변성, 동등성, 유효성 검증이 필수일급 컬렉션은 컬렉션을 객체로 다룬다.컬렉션만을 유일하게 변수로 가진다.가공 로직을 추가할 수 있음!일급 시민과 유사Enum은 상태와 행위를 한번에 정의한다. 코드 다듬기대부분 코드로 풀어내고, 그럼에도 전달해야 할 정보가 있을 때 주석을 쓰자!변수는 사용 순서로, 메서드는 public, private 순서로.상태 변경 -> 판별 -> 조회 순 StudyCafe 리팩토링!추상화 레벨 잘 맞춰주기무지성 getter, setter NO! 이왕이면 객체에 메시지를 보내는 형태로 해결하자.컬렉션이 의미가 있고, 가공 로직이 필요한 경우 일급 컬렉션을 쓰자!외부에서 데이터를 가져올 때,방법에 대해 초점을 맞추지 않는다. (기존에는 파일을 읽는 행위가 클래스로 만들어져 있었음)어떤 데이터가 필요한 지에 대한 추상을 적용하자! (Provider(규격)를 두고 필요한 것(구현체)을 제공.) 기억하면 좋은 조언들어떤 코드를 이해하려고 할 때, 모든 방법을 동원하자. -> 도메인 지식을 늘리고 선대의 의도 파악하는 것이 중요!완벽한 기술은 없다! 🏃 미션Day7다양한 리팩토링 조건이 있었는데 잘... 안됐다.. 중복을 제거하고 추상화 레벨을 맞추는 것만 해보려고 노력했다.일단 run()에 모든 로직이 모여 있어서 공동 로직을 추출했다.이용권 목록 파일 읽어와서 선택한 이용권 타입에 맞는 이용권 리스트 반환이용권 리스트 출력이용권 선택public void run() { try { outputHandler.showWelcomeMessage(); outputHandler.showAnnouncement(); //이용권 타입 선택 outputHandler.askPassTypeSelection(); StudyCafePassType studyCafePassType = inputHandler.getPassTypeSelectingUserAction(); //이용권 목록 파일 읽어오기 StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); List studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); //이용권 타입에 맞는 이용권 리스트 반환 List filteredPasses = filterStudyCafePassesByType(studyCafePasses, studyCafePassType); //이용권 선택 outputHandler.showPassListForSelection(filteredPasses); StudyCafePass selectedPass = inputHandler.getSelectPass(filteredPasses); if (studyCafePassType == StudyCafePassType.HOURLY) { outputHandler.showPassOrderSummary(selectedPass, null); } else if (studyCafePassType == StudyCafePassType.WEEKLY) { outputHandler.showPassOrderSummary(selectedPass, null); } else if (studyCafePassType == StudyCafePassType.FIXED) { List lockerPasses = studyCafeFileHandler.readLockerPasses(); StudyCafeLockerPass lockerPass = lockerPasses.stream() .filter(option -> option.getPassType() == selectedPass.getPassType() && option.getDuration() == selectedPass.getDuration() ) .findFirst() .orElse(null); boolean lockerSelection = false; if (lockerPass != null) { outputHandler.askLockerPass(lockerPass); lockerSelection = inputHandler.getLockerSelection(); } if (lockerSelection) { outputHandler.showPassOrderSummary(selectedPass, lockerPass); } else { outputHandler.showPassOrderSummary(selectedPass, null); } } } catch (AppException e) { outputHandler.showSimpleMessage(e.getMessage()); } catch (Exception e) { outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); } }스터디 카페 타입에 맞는 이용권 리스트를 반환하는 로직은 메서드로 추출했다.private List filterStudyCafePassesByType(List studyCafePasses, StudyCafePassType studyCafePassType) { return studyCafePasses.stream() .filter(studyCafePass -> studyCafePass.getPassType() == studyCafePassType) .toList(); }그리고 현재 분기문이 스터디 카페 타입에 따라 나눠지는데,공통적인 부분 : 선택한 이용권 내역 출력고정권일 때, 사물함을 추가적으로 받는다.잠깐 출력 메서드인 outputHandler.showPassOrderSummary()을 알아보자!public void showPassOrderSummary(StudyCafePass selectedPass, StudyCafeLockerPass lockerPass) { .... }이 메서드는 선택한 이용권과 사물함 여부를 인수로 받는다.즉, 공통적으로 이 메서드를 쓰고시간권, 주간권일 때는 lockerPass를 null고정권이고 사물함을 쓸 때만 lockerPass 값을 넣자.run()public void run() { try { outputHandler.showWelcomeMessage(); outputHandler.showAnnouncement(); //이용권 타입 선택 outputHandler.askPassTypeSelection(); StudyCafePassType studyCafePassType = inputHandler.getPassTypeSelectingUserAction(); //이용권 목록 파일 읽어오기 StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); List studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); //이용권 타입에 맞는 이용권 리스트 반환 List filteredPasses = filterStudyCafePassesByType(studyCafePasses, studyCafePassType); //이용권 선택 outputHandler.showPassListForSelection(filteredPasses); StudyCafePass selectedPass = inputHandler.getSelectPass(filteredPasses); //이용권 내역 출력 showPassOrderDetails(selectedPass); } catch (AppException e) { outputHandler.showSimpleMessage(e.getMessage()); } catch (Exception e) { outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); } }showPassOrderDetails()private void showPassOrderDetails(StudyCafePass selectedPass) { StudyCafeLockerPass lockerPass = getLockerPassIfFixed(selectedPass); outputHandler.showPassOrderSummary(selectedPass, lockerPass); }getLockerPassIfFixed()private StudyCafeLockerPass getLockerPassIfFixed(StudyCafePass selectedPass) { if (selectedPass.getPassType() == StudyCafePassType.FIXED) { //사물함 목록 파일 읽기 List lockerPasses = studyCafeFileHandler.readLockerPasses(); //고정권 타입 조건에 맞는 사물함 반환 StudyCafeLockerPass lockerPass = lockerPasses.stream() .filter(option -> option.getPassType() == selectedPass.getPassType() && option.getDuration() == selectedPass.getDuration()) .findFirst() .orElse(null); //사물함이 있다면, 사물함 선택 여부를 입력 받기 boolean lockerSelection = false; if (lockerPass != null) { outputHandler.askLockerPass(lockerPass); lockerSelection = inputHandler.getLockerSelection(); } //사물함을 선택한다면 if (lockerSelection) { return lockerPass; } else { return null; } } return null; }이용권 타입이 고정권일 때,사물함을 선택한다면 lockerPass 반환사물함을 선택하지 않는다면 null 반환이용권 타입이 고정권이 아니라면 null 반환getLockerPassIfFixed() 내부가 너무 복잡하므로 추상화를 하기로 했다.FileHandler는 변하지 않고 공통적으로 쓰이므로 맨 위로 private final로 빼주었다.고정권 타입에 맞는 사물함을 반환하는 부분을 메서드로 추출사물함 선택 여부 메서드로 추출getLockerPassIfFixed()private StudyCafeLockerPass getLockerPassIfFixed(StudyCafePass selectedPass) { if (selectedPass.getPassType() == StudyCafePassType.FIXED) { //사물함 목록 파일 읽기 List lockerPasses = studyCafeFileHandler.readLockerPasses(); //고정권 타입 조건에 맞는 사물함 반환 StudyCafeLockerPass filterdLockerPass = filterLockerPassByTypeAndDuration(lockerPasses, selectedPass); //사물함이 없다면, null 반환 if (filterdLockerPass == null) { return null; } //사물함을 선택한다면 if (askForLockerUsage(filterdLockerPass)) { return filterdLockerPass; } } return null; }filterLockerPassByTypeAndDuration()private static StudyCafeLockerPass filterLockerPassByTypeAndDuration(List lockerPasses, StudyCafePass selectedPass) { return lockerPasses.stream() .filter(option -> option.getPassType() == selectedPass.getPassType() && option.getDuration() == selectedPass.getDuration()) .findFirst() .orElse(null); }askForLockerUsage()private boolean askForLockerUsage(StudyCafeLockerPass filterdLockerPass) { outputHandler.askLockerPass(filterdLockerPass); return inputHandler.getLockerSelection(); }그리고 run()에는 행위별로 나뉘어져 있으면 좋겠다고 생각해서안내메시지 출력이용권 타입 선택이용권 선택이용권 내역 출력으로 정리했다. public class StudyCafePassMachine { private final InputHandler inputHandler = new InputHandler(); private final OutputHandler outputHandler = new OutputHandler(); private final StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); public void run() { try { //스터디 카페 안내 메시지 outputHandler.showWelcomeMessage(); outputHandler.showAnnouncement(); //이용권 타입 선택 StudyCafePassType studyCafePassType = getPassTypeFromUser(); //이용권 선택 StudyCafePass selectedPass = getPassFromUser(studyCafePassType); //이용권 내역 출력 showPassOrderDetails(selectedPass); } catch (AppException e) { outputHandler.showSimpleMessage(e.getMessage()); } catch (Exception e) { outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); } } private StudyCafePassType getPassTypeFromUser() { outputHandler.askPassTypeSelection(); return inputHandler.getPassTypeSelectingUserAction(); } private StudyCafePass getPassFromUser(StudyCafePassType studyCafePassType) { List studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); List filteredPasses = filterStudyCafePassesByType(studyCafePasses, studyCafePassType); outputHandler.showPassListForSelection(filteredPasses); return inputHandler.getSelectPass(filteredPasses); } private List filterStudyCafePassesByType(List studyCafePasses, StudyCafePassType studyCafePassType) { return studyCafePasses.stream() .filter(studyCafePass -> studyCafePass.getPassType() == studyCafePassType) .toList(); } private void showPassOrderDetails(StudyCafePass selectedPass) { StudyCafeLockerPass lockerPass = getLockerPassIfFixed(selectedPass); outputHandler.showPassOrderSummary(selectedPass, lockerPass); } private StudyCafeLockerPass getLockerPassIfFixed(StudyCafePass selectedPass) { if (selectedPass.getPassType() == StudyCafePassType.FIXED) { List lockerPasses = studyCafeFileHandler.readLockerPasses(); StudyCafeLockerPass filterdLockerPass = filterLockerPassByTypeAndDuration(lockerPasses, selectedPass); if (filterdLockerPass == null) { return null; } if (askForLockerUsage(filterdLockerPass)) { return filterdLockerPass; } } return null; } private static StudyCafeLockerPass filterLockerPassByTypeAndDuration(List lockerPasses, StudyCafePass selectedPass) { return lockerPasses.stream() .filter(option -> option.getPassType() == selectedPass.getPassType() && option.getDuration() == selectedPass.getDuration()) .findFirst() .orElse(null); } private boolean askForLockerUsage(StudyCafeLockerPass filterdLockerPass) { outputHandler.askLockerPass(filterdLockerPass); return inputHandler.getLockerSelection(); } }🗒️ 회고드디어 완강했다! 그런데 반쯤은 이해 못해서 무조건 복습해야 할 것 같다..ㅎㅎ미션을 하면서 너무 헤매서 괴로웠다! ㅜㅜ 나는 지금껏 뭘 들은거지... 싶었다. 강의자님이 하신 리팩토링을 보면서 내 코드가 너무 바보같고 한심했다..... 꼭 복습해서 완벽히 내 것으로 만들고 싶다!아무튼 이번 클린 코드 강의를 통해서 지금까지 내가 작성했던 코드가 얼마나 엉망진창이었는지 알게 됐다..... 그래도 내가 지금 제대로 하는 건지, 이게 맞는 건지 항상 의문이 있었는데 나아갈 방향이 생겨서 좋다.다음주부터 시작하는 테스트 코드도 화이팅! 밀리지 않고 열심히 듣기~!!🥰🥰🥰🥰
웹 개발
・
워밍업클럽
![[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] 1주차 발자국](https://cdn.inflearn.com/public/files/blogs/2ed3f183-0029-4311-98c2-b373e9978c74/인프런 워밍업 클럽 2기 이미지.png?w=260)
2024. 10. 06.
0
[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] 1주차 발자국
해당 글은 [인프런 워밍업 클럽 2기 클린 코드 & 테스트 코드]에 참가하여 박우빈님의 강의를 듣고 작성한 글입니다.✍ 이번주 강의 요약추상외부 세계는 추상화 레벨이 높고, 내부 세계는 추상화 레벨이 낮다.추상화 레벨이 높다! -> 좀 더 포괄적임.추상화 레벨이 낮다! -> 좀 더 구체적임.추상화 레벨은 주변과 맞춰줘야 한다. 논리, 사고의 흐름else 대신 early return 을 쓰자! -> 이전 것들을 신경쓰지 않고 사고의 깊이를 줄일 수 있음.부정어 대신 의미가 명확한 메소드를 쓰자! 객체 지향 패러다임객체 지향이란 곧, 관심사를 분리하여 캡슐화를 잘하는 것.solid 규칙을 명시하자.결합도를 낮추고 응집도를 높이기 🏃 미션Day2일상생활에서 쉽게 찾을 수 있는 추상과 구체를 생각하려고 했다. '음식을 먹다' 라는 추상을 음식을 입에 넣는 과정, 씹는 과정, 삼키는 과정으로 구체화하여 적으려고 했다. Day4먼저 해당 메서드의 요구사항이 무엇인지 파악하려고 했다. 이 주문 검증 메서드의 요구사항은 아래와 같았다.주문 항목이 0 이상이어야 한다.총 주문 금액이 0 이상이어야 한다.주문을 하는 사용자 정보가 있어야 한다.기존 메서드는 2번째 요구사항과 3번째 요구사항이 합쳐서 구현되어 있었으므로 일단 각 요구사항을 기능별로 분리하였다. 그 다음, early return을 이용하여 불필요한 else 메서드를 지웠다. public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } return true; } 가독성을 위해 각 기능별로 메서드를 추출했다.public boolean validateOrder(Order order) { if (hasItems(order)) return false; if (hasCustomerInfo(order)) return false; if (isTotalPriceInvalid(order)) return false; return true; } private static boolean isTotalPriceInvalid(Order order) { if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return true; } return false; } private static boolean hasCustomerInfo(Order order) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return true; } return false; } private static boolean hasItems(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return true; } return false; } 부정어를 지우고 각각의 기능을 만족했을 때만 true가 되도록 했다.public boolean validateOrder(Order order) { return hasItems(order) && hasCustomerInfo(order) && isTotalPriceInvalid(order); } private static boolean hasItems(Order order) { if (order.getItems().size() > 0) { return true; } log.info("주문 항목이 없습니다."); return false; } private static boolean isTotalPriceInvalid(Order order) { if (order.getTotalPrice() > 0) { return true; } log.info("올바르지 않은 총 가격입니다."); return false; } private static boolean hasCustomerInfo(Order order) { if (order.hasCustomerInfo()) { return true; } log.info("사용자 정보가 없습니다."); return false; } 🗒️ 회고현재 공채 시즌이라 진도를 빨리 나가지 못해서 아쉬웠다.그래도 어찌저찌 미션도 제출하고 회고도 적어서 뿌듯했다.첫 번째 미션은 쉬웠는데 두 번째 리팩토링 미션이 꽤 오래 걸렸다. 대신 좋은 코드, 읽기 쉬운 코드... 가독성이 좋은 코드에 대해 오랫동안 생각할 수 있어서 좋았다. 내 코드가 맞는 건지는 잘 모르겠지만ㅎㅎ.. 앞으로 강의를 들으면서 더 깊고 넓게 생각할 수 있는 능력을 기르고 싶다!
웹 개발
・
워밍업클럽
![[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] Day4 과제](https://cdn.inflearn.com/public/files/blogs/868c0759-edc2-49ba-8535-4e238e4b57c2/인프런 워밍업 클럽 2기 이미지.png?w=260)
2024. 10. 03.
0
[인프런 워밍업 클럽 2기 클린코드 & 테스트 코드] Day4 과제
해당 글은 [인프런 워밍업 클럽 2기 클린 코드 & 테스트 코드]에 참가하여 박우빈님의 강의를 듣고 작성한 글입니다. 1. 코드 리팩토링/* * 주문 검증 메서드 * 1. 주문 항목이 0 이상이어야 한다. * 2. 총 금액이 0 이상이어야 한다. * 3. 주문을 하는 사용자 정보가 있어야 한다. */ public boolean validateOrder(Order order) { return hasItems(order) && hasCustomerInfo(order) && isTotalPriceInvalid(order); } private static boolean hasItems(Order order) { if (order.getItems().size() > 0) { return true; } log.info("주문 항목이 없습니다."); return false; } private static boolean isTotalPriceInvalid(Order order) { if (order.getTotalPrice() > 0) { return true; } log.info("올바르지 않은 총 가격입니다."); return false; } private static boolean hasCustomerInfo(Order order) { if (order.hasCustomerInfo()) { return true; } log.info("사용자 정보가 없습니다."); return false; } 2. SOLID를 자기만의 언어로 정리하기SRP : Signle Responsibility Principle단일 책임 원칙하나의 클래스는 하나의 책임만 가진다.하나의 일 & 하나의 주제만 가져야 한다!OCP : Open - Closed Principle개방 폐쇄 원칙확장에 열려 있고 수정에 닫혀 있다.기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있다.LSP : Liskov Substitution Principle리스코프 치환 원칙자식 클래스는 부모 클래스를 대체할 수 있어야 한다.부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 변경했을 때, 프로그램이 잘 작동해야 한다.자식이 부모의 기능을 잘 물려 받아야 한다. -> 자식은 부모의 의지를 잘 담고 있어야 한다.ISP : Interface Segregation Principle인터페이스 분리 원칙사용하지 않는 기능을 의존하지 않도록 인터페이스를 여러 개로 분리한다. -> 필요한 기능만 구현하게 한다.인터페이스를 잘게 쪼개자!DIP : Dependency Inversion Principle의존 관계 역전 원칙고수준 모듈(추상화 레벨 높음)이 저수준 모듈(추상화 레벨 낮음)에 의존하면 안된다. 추상화에 의존해야 한다. -> 둘다 인터페이스에 의존해야 한다.고수준 모듈이 저수준 모듈에 직접적으로 의존하지 않고 인터페이스만 의존시켜 구현체를 쉽게 갈아끼울 수 있도록 한다. 전부 결합도를 낮추고 응집도를 높인다!
백엔드
・
인프런워밍업클럽




