블로그
전체 7#카테고리
- 백엔드
#태그
- 백엔드
- 스터디2기
- 인프런
- 인프런워밍업클럽
- 워밍업클럽
- 테스트코드
- 클린코드
- 미션
2024. 11. 06.
0
[워밍업 클럽 스터디 2기 - BE] (클린코드, 테스트코드) 완주 후기
0. 워밍업 클럽 참가 계기평소에 혼자 공부하고, 혼자 써보고 그렇게 개발을 해왔고, 업무도 했었는데, 뭔가 점점 성장에 한계가 오고 있다는 걸 느끼고, 또 이직을 하기 위해 공부를 어떻게 할까 고민하다가 2년차 고졸 개발자의 한계를 넘어선 성장이야기를 읽고 평소에 관심이 좀 있던 테스트 코드에 대해 강의도 듣고, 또 이 강의를 미션을 통해 추가 학습 할 수 있는 기회가 있어서 바로 신청하게 되었다. 1. 강의강의는 Readable Code, 테스트 코드 두 개의 강의를 4주 동안 들어야 한다. 일정이 좀 타이트하긴 했는데 퇴근 후, 주말에 몰아서 들어서 일정은 맞출 수 있었다.듣다 보면 이제 예제를 가지고 강의를 진행하시는데, 잠깐 한눈 팔면 어느새 흐름을 놓쳐버리기 때문에, 적어가면서, 멈춰 놓고 따라 쳐보면서 강의를 학습했고 이러한 방식이 기억에 더 남아서 마음에 들었다.또 강의를 학습하다 보면 코드를 바라보는 시각? 생각이 넓어지고 있다는게 느껴진다. 2. 미션미션 자체는 강의를 열심히 들었다면 크게 어렵진 않지만, 코드 구현하는 미션보다 뭔가 내 생각으로 키워드를 정리하는 미션들이 좋았다. 이거에 대해 깊게 생각해 볼 수 있게 되고, 또 한번 정리를 할 수 있게 된다.또 코드 미션 제출하고, 라이브 세션에서 우빈님이 신청자에 한해 코드 리뷰를 해주셔서 좋았다. 코드 리뷰를 처음 보는데 이런 식으로 진행되는 구나 느꼈다. 3. 후기많은 걸 배우고 느낀 4주간 과정이었다. 기간이 길다면 길고, 짧다면 짧은데 이 기간 동안 꾸준히 공부를 해야 하기 때문에 습관을 만들기에도 좋다고 생각했다.비록 적극적으로 질문하고 이러지는 못했는데, 다음 스터디 주제가 마음에 들면 또 참가해서 적극적으로 참여하고 싶다. (그래서 우수러너로 선정되고 ㅎㅎ) 끝으로 이런 자리를 만들어주신 인프런과 강의 및 진행 해주신 우빈님께 감사드립니다.
백엔드
・
백엔드
・
스터디2기
・
인프런
・
인프런워밍업클럽
2024. 10. 27.
0
[워밍업 클럽 스터디 2기 - BE] (클린코드, 테스트코드) 4주차 발자국
인프런 워밍업 클럽 스터디 2기 - 백엔드 클린코드, 테스트 코드(Java, Spring Boot)Practical Testing: 실용적인 테스트 가이드해당 강의를 학습하며 정리하는 내용입니다. 드디어 마지막 주차 발자국을 작성하는 시간이다. 이번주에는 Mock관련 내용과, 테스트를 작성하기 위한 팁, Spring REST Docs에 대해 배우는 주차였다. Mock을 마주하는 자세Test Doble외부 API같이 내가 직접 테스트 하기 어려운 것들을 가짜로 대체해서 테스트 하는 테스트 방법론Dummy아무것도 하지 않는 깡통 객체Fake단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체 (ex. FakeRepository)Stub테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 않는다.SpyStub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다.Mock행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Stub : 상태 검증 (State Verification)Mock : 행위 검증 (Behavior Verification) Classicist VS. Mockist라는 말을 처음 들어봤는데, 실제 객체로 통합 테스트를 해가면서 필요할 때만 Mocking해서 사용하자는 Classicist파와, 각각 따로따로 다 목킹해서 순수 코드만 빠르게 테스트 하자 라는 Mockist파의 싸움 같은 거다.나도 이에 대해 생각을 해봤는데, 나는 Mockist에 좀 더 가까운 거 같다. Mockist가 좀 더 뭔가 작은 블럭을 쌓아가서 전체를 만드는 느낌이 든다.하지만 분명히 Mockist여도 통합테스트 해야하고 Classicist여도 목킹을 통한 단위테스트는 해야한다. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks이거에 관하여 미션이 나갔다. @Spy는 진짜 처음봐서 좋은 기능 배웠다고 생각한다.day-18 미션그리고 테스트 분류 미션에 관해서도 깜짝 라이브를 통해 우빈님의 의도를 들었었는데, setUp, given, when, then에 두어야 할 것에 대한 생각을 정리할 수 있게 된것 같다.더 나은 테스트를 작성하기 위한 구체적 조언여기서는 다양한 테스트 꿀팁들을 배웠다.키워드한 문단에 한 주제글쓰기와 같다. 한 테스트에 한 주제완벽하게 제어하기LocalDateTime.now() 같이 달라질 수 있는 값들은 사용하는 것을 지양하자.테스트 환경의 독립성을 보장하자지금 하고 있는 테스트의 주제를 잘 생각하고, when, then절이 아닌 다른 곳에서 테스트가 깨지지 않도록 하자.테스트 간 독립성을 보장하자A → B, B → A 순서가 바뀐다고 테스트가 바뀌면 안된다.한 눈에 들어오는 Test Fixture 구성하기given절에 생성한 모든 객체들Test Fixture 클렌징deleteAllInBatch() vs deleteAll() vs @Tranjactional@ParameterizedTestif 분기문, 반복문 등 → 생각의 사고를 추가 해야 하는, 즉 읽기 힘든 테스트가 된다. 값이나 환경만 바꿔가면서 테스트를 여러번 반복 하고 싶을 때 사용하면 좋다.@DynamicTest일련의 시나리오를 테스트 하고 싶을 때가 있다. 그럴때 사용하면 좋은게 @DynamicTest이다.테스트 수행도 비용이다. 환경 통합하기@SpringBootTest를 사용할 때 조금이라도 프로파일이라던지 이런 설정이 달라지면 다 다른 설정으로 스프링이 올라간다.Q. private 메서드의 테스트는 어떻게 하나요?결론은 안하는게 맞다.Q. 테스트에서만 필요한 메서드가 생겼는데 프로덕션 코드에서는 필요 없다면?만들어도 된다. 하지만 보수적으로 접근하기!하나 같이 막힐때 큰 도움이 될 거같은 조언이다.Appendix학습 테스트잘 모르는 기능, 라이브러리, 프레임워크를 학습하기 위해 작성하는 테스트여러 테스트 케이스를 스스로 정의하고 검증하는 과정을 통해 보다 구체적인 동작과 기능을 학습할 수 있다.관련된 문서만 읽는 것보다 훨씬 재미있게 학습할 수 있다.Spring REST Docs테스트 코드를 통한 API 문서 자동화 도구API 명세를 문서로 만들고 외부에 제공함으로써 협업을 원활하게 한다.기본적으로 AsciiDoc을 사용하여 문서를 작성한다.REST Docs장점테스트를 통과해야 문서가 만들어진다. (신뢰도가 높다.)프로덕션 코드에 비침투적이다.단점코드 양이 많다.설정이 어렵다.Swagger장점적용이 쉽다.문서에서 바로 API 호출을 수행해볼 수 있다.단점프로덕션 코드에 침투적이다.테스트와 무관하기 때문에 신뢰도가 떨어질 수 있다. Spring REST Docs는 문서화를 도와주는 도구인데 설정이 좀 많이 어려운 듯 하다. 추가적인 공부가 많이 필요한 것 같다.한번은 배워보고 싶은 도구였어서 좋은 계기였다.전체 회고드디어 4주의 학습 스터디가 끝났다. 개인적으로 퇴사를 앞두고 있는데, 이제서야 이런 좋은 학습 기회가 있다는 것을 알아서 좀 아깝지만 늦었다고 생각할 때가 가장 빠르니까 좋게 생각한다.2년동안 개발을 하면서 테스트나 클린 코드를 생각안해본건 아닌데, 솔직히 현재 업무에서 살짝살짝 내가 혼자 학습해서 조금씩 적용해본 정도지, 진지하게 공부하고 사용해 본적은 없었다.이번 강의가 클린코드, 테스트 코드 두 강의나 돼서 좀 듣기 빡세긴 한데, 진짜 도움이 많이 되는 것 같았다. 클린 코드는 내가 코드를 보는 생각이 좀 더 열린 계기가 된 것 같고, 테스트 코드는 평소에 배우고 싶다는 갈망이 많이 채워졌다.앞으로 두 내용에 대해 좀 더 학습해 나아가면서 지금 배웠던 것들이 기초가 될 것이라고 생각한다. 다음에도 이런 좋은 주제가 있으면 언제든지 참가하고 싶다.
백엔드
・
워밍업클럽
・
테스트코드
・
클린코드
・
백엔드
2024. 10. 25.
0
[워밍업 클럽 스터디 2기 - BE] (클린코드, 테스트코드) day 18 미션
출처 : 인프런 워밍업 클럽 스터디 2기 - 백엔드 클린코드, 테스트 코드(Java, Spring Boot)Practical Testing: 실용적인 테스트 가이드 1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 이 애노테이션들을 한번 정리하고 차이점을 알아보자. @Mockorg.mockito.Mock 이며 Mockito.mock() 을 애노테이션화 한 것으로 볼 수 있다.사용하려면 junit5기준으로 @ExtendWith(MockitoExtension.class)을 클래스 위에 달아주어야 한다.역할은 내가 어떤 테스트를 하고 싶은데, 그 테스트를 하려면 또 다른 의존성(클래스)를 끌고 와야 하는 상황에서 이 의존성을 가짜로 바꿔치기 (Stub)해 주는 것이다.그래서 내가 테스트 하고 싶은 코드만 테스트 할 수 있게 되는 것이다. @MockBeanorg.springframework.boot.test.mock.mockito.MockBean 패키지이며(!) spring-boot-test가 제공하는 애노테이션 이다.이걸 사용하면 스프링 컨텍스트가 관리하는 빈을 대체할 수 있다. → 즉 @SpringBootTest를 사용해야함.mock객체를 스프링 컨텍스트에 대신 등록하는 것이다. 그래서 Autowired에 의존성이 주입됨.역할은 @Mock과 유사하다. @InjectMocksorg.mockito.InjectMocks이며 @Mock이나 @Spy가 붙은 의존성 객체를 내가 테스트 할 객체에 주입할 때 사용하는 애노테이션이다. @Mock private MailSendClient mailSendClient; // 의존성(가짜 객체) @InjectMocks private MailService mailService; //실제 테스트할 객체코드에서 보면 mailSendClient라는 가짜 객체를 테스트를 위해 mailService에 주입하겠다는 것이다. @Spyorg.mockito.Spy이며 Mockito.spy()를 애노테이션화 한 것으로 볼 수 있다.의존성을 대체하는 @Mock과 같은 역할을 하지만 한 가지 큰 차이점이 있다.@Spy를 사용하면 일부 기능은 가짜로 Stub할 수 있고, 일부 기능은 실제로 동작 시킬 수 있다. @Slf4j @Component public class MailSendClient { // 메일 전송 public boolean sendEmail(String fromEmail, String toEmail, String subject, String content) { log.info("메일 전송"); throw new IllegalArgumentException("메일 전송"); } public void a() { log.info("a"); } public void b() { log.info("b"); } public void c() { log.info("c"); } }다음과 같은 가짜로 대체하고 싶은 클래스가 있다. 여기서 sendEmail()만 가짜로 Stub하고 나머지 a(), b(), c()는 실제 메서드를 실행하고 싶은 거다.public class MailService { private final MailSendClient mailSendClient; public boolean sendMail(String fromEmail, String toEmail, String subject, String content) { boolean result = mailSendClient.sendEmail(fromEmail, toEmail, subject, content); if (result) { // 추가 로직 } mailSendClient.a(); mailSendClient.b(); mailSendClient.c(); ... } }이런 식으로 MailService에서 sendMail()을 테스트를 할 때 mailSendClient의 sendMail()만 가짜로 쓴다는 의미이다. @Spy private MailSendClient mailSendClient; @InjectMocks private MailService mailService; @Test @DisplayName("메일 전송 테스트") void sendMail() { doReturn(true) .when(mailSendClient) .sendEmail(anyString(), anyString(), anyString(), anyString()); boolean result = mailService.sendMail("", "", "", ""); }이렇게 doReturn().when().스터빙할메서드()이라는 메서드 체이닝을 통해 스터빙할 수 있다. @Spy를 사용하면 일부 기능은 가짜로 Stub할 수 있고, 일부 기능은 실제로 동작 시킬 수 있다. @SpyBean이것도 @MockBean과 마찬가지로 org.springframework.boot.test.mock.mockito.SpyBean spring-boot-test가 제공하는 애노테이션 이다. 즉 @SpringBootTest를 사용해야함.가짜 객체의 일부만 stub하고, 나머지는 실제 메서드를 사용하고 싶을 때 사용.2. 다음과 같은 테스트가 있을 때 이걸 분류해보자.@BeforeEach void setUp() { ❓ } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { 1-1. 사용자 생성에 필요한 내용 준비 1-2. 사용자 생성 1-3. 게시물 생성에 필요한 내용 준비 1-4. 게시물 생성 1-5. 댓글 생성에 필요한 내용 준비 1-6. 댓글 생성 // given ❓ // when ❓ // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { 2-1. 사용자 생성에 필요한 내용 준비 2-2. 사용자 생성 2-3. 게시물 생성에 필요한 내용 준비 2-4. 게시물 생성 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 2-7. 댓글 수정 // given ❓ // when ❓ // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { 3-1. 사용자1 생성에 필요한 내용 준비 3-2. 사용자1 생성 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 3-6. 사용자1의 게시물 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 3-9. 사용자2가 사용자1의 댓글 수정 시도 // given ❓ // when ❓ // then 검증 }여기서 @DisplayName으로 뭘 테스트할지 알 수 있다.이번 테스트는 댓글에 대한 테스트를 하려는 것으로 느낄 수 있다.그럼 given절에는 댓글을 달기 위한 사전 준비를 해야 하는데, 반복적으로 준비해야 하는 것은 setUp()으로 빼보자.1-1. 사용자 생성에 필요한 내용 준비 1-2. 사용자 생성 1-3. 게시물 생성에 필요한 내용 준비 1-4. 게시물 생성 2-1. 사용자 생성에 필요한 내용 준비 2-2. 사용자 생성 2-3. 게시물 생성에 필요한 내용 준비 2-4. 게시물 생성 3-1. 사용자1 생성에 필요한 내용 준비 3-2. 사용자1 생성 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 3-6. 사용자1의 게시물 생성이 내용은 계속 반복된다.@BeforeEach void setUp() { 사용자 생성에 필요한 내용 준비 사용자 생성 게시물 생성에 필요한 내용 준비 게시물 생성 } 이렇게 준비해보자. 첫 번째 테스트는 댓글을 작성할 수 있다가 핵심이기 때문에 다음과 같이 나타냈다.@DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } 두 번째 테스트는 댓글을 수정이 핵심이기 때문에 댓글 생성까지는 준비하고, 핵심인 댓글 수정을 when에서 실행했다.@DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } 마지막 테스트는 타인이 댓글을 수정 시도한다.이기 때문에given절에선 타인 사용자 2를 만들어주고, 내가 댓글을 생성해 놓는다.그리고 when절에서 타인이 내 댓글을 수정 시도한다.@DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 // when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 } 전체 코드는 다음과 같다.@BeforeEach void setUp() { 사용자 생성에 필요한 내용 준비 사용자 생성 게시물 생성에 필요한 내용 준비 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 // when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 }
백엔드
・
테스트코드
・
워밍업클럽
・
미션
2024. 10. 22.
0
[워밍업 클럽 스터디 2기 - BE] (클린코드, 테스트코드) day 15 미션
출처 : 인프런 워밍업 클럽 스터디 2기 - 백엔드 클린코드, 테스트 코드(Java, Spring Boot)Practical Testing: 실용적인 테스트 가이드 Layered Architecture 핵심은 관심사의 분리. 역할을 나누어 유지보수성을 늘린다. Persistence Layer데이터와 DB가 만나는 계층JPA, MyBatis 등등 결국 최종 쿼리를 DB로 전달해야 함.이 계층은 DB에 쿼리를 전달하는 역할만 해야지 여기서 뭔가 데이터를 가공한 후에 쿼리를 만들면 안됨.데이터 가공은 비즈니스 계층에서 하는 것이 바람직하다고 생각.테스트 시 스프링 컨테이너를 사용하지만 쿼리 자체를 테스트하는 것이기 때문에 단위 테스트 성격을 띈다.테스트 방법@SpringBootTest를 사용 시 @Transaction이 없기 때문에 tearDown()을 구현@DataJpaTest 사용 시 @Transaction이 있기 때문에 이에 따른 트랜잭션 전파를 잘 고려해서 판단해야 함.이걸 고려하지 않고 테스트 작성 시 테스트는 잘 통과하는데 실제로는 안될 수도 있음.테스트 시 샘플 객체를 만들어서 저장 후 (`given`) 테스트할 메서드를 호출 (`when`)검증(`then`)시 단일 객체라면 그냥 비교하면 되는데, List같은 자료구조에 담긴 여러 객체라면 1. 먼저 사이즈를 체크2. extracting()으로 검사할 항목 체크3. contains*() + tuple(데이터)로 비교@Test @DisplayName("원하는 판매상태를 가진 상품들을 조회한다.") void findAllBySellingStatusIn() { // given Product product1 = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000); Product product2 = createProduct("002", HANDMADE, HOLD, "카페라떼", 4500); Product product3 = createProduct("003", HANDMADE, STOP_SELLING, "팥빙수", 7000); productRepository.saveAll(List.of(product1, product2, product3)); // when List products = productRepository.findAllBySellingStatusIn(List.of(SELLING, HOLD)); // then assertThat(products).hasSize(2) .extracting("productNumber", "name", "sellingStatus") .containsExactlyInAnyOrder( tuple("001", "아메리카노", SELLING), tuple("002", "카페라떼", HOLD) ); } Business Layer핵심적인 비즈니스 로직을 처리하는 계층Persistence Layer와 상호작용한다.트랜잭션이 보장되어야 한다.테스트 방법Persistence Layer를 묶어서 통합 테스트Persistence Layer를 Mocking 해서 단위 테스트Business Layer는 트랜잭션이 중요하기 때문에 테스트시에도 이에 따른 트랜잭션 전파를 잘 고려해서 판단 해야 함. Presentation Layer클라이언트로부터 데이터를 받아 Business Layer로 요청을 넘기는 계층데이터에 대한 기본적인 검증을 수행 (필수 입력 값, 데이터 타입, null 등)Business Layer로부터 받은 데이터를 사용자에게 반환Business Layer가 Presentation Layer를 모르도록 설계하면 좋다.Business Layer에 클라이언트에게 받은 데이터를 바로 넘기는 것 보다 서비스 전용 DTO로 래핑해서 넘기기.클라이언트에게 값을 넘겨줄 때는 정해진 포맷(errorCode, message, data 등)으로 넘겨주는 것이 좋다.테스트 방법데이터에 대한 기본적인 검증을 수행Business Layer는 Mocking해서 테스트 진행mockMvc를 활용하여 Http 응답에 대한 검증을 진행한다.@Test @DisplayName("신규 주문을 등록한다.") void createOrder() throws Exception { // given OrderCreateRequest request = OrderCreateRequest.builder() .productNumbers(List.of("001")) .build(); // when // then mockMvc.perform(post("/api/v1/orders/new") .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.status").value("OK")) .andExpect(jsonPath("$.message").value("OK")); }
백엔드
・
테스트코드
・
워밍업클럽
・
미션
2024. 10. 20.
0
[워밍업 클럽 스터디 2기 - BE] (클린코드, 테스트코드) 3주차 발자국
인프런 워밍업 클럽 스터디 2기 - 백엔드 클린코드, 테스트 코드(Java, Spring Boot)Practical Testing: 실용적인 테스트 가이드해당 강의를 학습하며 정리하는 내용입니다. 2주차 발자국에 이어서 이번주도 배운 내용을 정리하고 회고를 작성한다. 이번주는 Practical Testing: 실용적인 테스트 가이드를 수강을 시작했다. 평소에도 테스트 코드를 작성해야 좋다 라는건 알고 있었지만 각종 핑계로 작성을 잘 안했었는데, 이번 강의를 통해 테스트 코드를 잘 작성하는 습관을 기르고자 한다. 왜 테스트 코드를 사용할까? 점점 기능을 추가할 때마다 새로운 사람들이 새로운 수동 테스트를 진행하고, 이전 기능이 잘 동작하는지 이전 테스트도 또 진행 해야한다.그럼 시간이 지날수록커버할 수 없는 영역 발생경험과 감에 의존늦은 피드백 유지보수 어려움결국 소프트웨어 신뢰 ↓라는 결과가 나타난다. 그래서 테스트 코드로 얻고자 하는 것은빠른 피드백자동화안정감이다. 테스트 코드는 귀찮다 → 귀찮지만 해야한다. 단위 테스트작은 코드 단위를 독립적으로 검증하는 테스트작은 → 클래스 or 메서드다른 코드에 의존적이지 않는검증 속도가 빠르고, 안정적인테스트 코드 세분화해피케이스만 생각하지 말자. 예외 케이스도 검증해야 한다.경계 값 : 범위(이상, 이하, 초과, 미만), 구간, 날짜 등 테스트 하기 어려운 영역을 분리현재 시간 : 이건 테스트 할 때마다 바뀌는 값.. 관측할 때마다 다른 값에 의존하는 코드현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등외부 세계에 영향을 주는 코드표준 출력, 메시지 발송, 데이터 베이스에 기록 등 Order order = cafeKiosk.createOrder(LocalDateTime.of(2024, 10, 13, 10, 0));이렇게 테스트하기 어려운 값들을 외부에서 받아오도록 분리하면 테스트가 가능해진다.[CafeKiosk 요구조건 추가 - 특정 시간에만 주문 가능 - 테스트하기 어려운 영역을 분리하기](https://github.com/iamminseongKim/PracticalTesting/commit/4cbb22efb698f77796bdc52c84237d2bf61d4c2c) TDD프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론Red - Green - Refactor 단계 개발Red : 실패하는 테스트 작성Green : 테스트 통과를 위한 최소한의 코딩Refactor : 구현 코드 개선, 테스트 통과 유지 테스트는 [문서]다.프로덕션 기능을 설명하는 테스트 코드 문서다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다.BDD : Behavior Driven DevelopmentGiven / When / Then Spring & JPA 기반 테스트Layered Architecture관심사의 분리를 위해 레이어를 분리A모듈과 B모듈이 만나서 AB, BA, C? 뭐가 나올지 모른다. 통합 테스트가 필요 Persistence Layer 테스트쿼리를 테스트. 스프링 서버를 띄워서 테스트해야 하지만 계층을 분리해서 테스트 하기 때문에 일종의 단위 테스트 성격을 가지고 있다.@DataJpaTest : @SpringBootTest 에서 JPA 관련 기능만 올려서 빠르게 테스트 할 수 있도록 도와줌. 하지만 @Transactional이 들어가 있기 때문에, 테스트 시 이 점을 고려하고, 이해하고 사용해야함.리스트 테스트 할 때 팁 : size를 먼저 체크하고, extracting&contain 으로 안에 내용물을 검증 Business Layer비즈니스 로직을 구현하는 역할Persistence Layer와의 상호작용(Data를 읽고 쓰는 행위)를 통해 비즈니스 로직을 전개시킨다.트랜잭션을 보장해야 한다. [CafeKiosk - Business Layer 테스트 (1)](https://github.com/iamminseongKim/PracticalTesting/commit/70a421e2eec5bb2eedafea368f08148ac486c3cb)[CafeKiosk - Business Layer 테스트 (2)](https://github.com/iamminseongKim/PracticalTesting/commit/779e2c0ac4a92a317dc96a5cd52dea6934518b1a)[CafeKiosk - Business Layer 테스트 (3)](https://github.com/iamminseongKim/PracticalTesting/commit/ab172150610a70445e19f2a7b8ab587db63e43f5)미션readable code때 사용한 지뢰찾기/키오스크 둘 중 하나에 대한 test코드를 작성해보는 미션이였다. 미션을 하면서 내가 작성한 테스트 코드가 이게 진짜 잘 되는지 아직 감이 살짝 안 오긴 한다. 이건 경험의 문제 같고 아직 느끼지 못하는 것 같지만, 꾸준히 작성해보려 한다.그리고 미션을 하면서 다른 분들의 코드를 보는데 jacoco라는 테스트 커버리지 도구가 있어서 이것도 사용해 보았다.jacoco를 사용하니깐 테스트가 어디가 진행 됐고, 어딘 안됐고 이걸 시각적으로 볼 수 있는데, 이걸 보니깐 또 다른 게임을 하는 것 같아서 매우 재밌었다. 그리고 이번 미션이 스프링이 아니라 순수 자바로 된 코드를 테스트 하는 것이기 때문에 단위 테스트를 모두 작성해서 커버리지를 90퍼센트 이상 찍게 되었다.회고이번주는 드디어 내가 이 워밍업 클럽을 신청한 이유인 테스트 코드에 대해 배울 수 있게 되어서 좋았다.현재 내 실무에서는 테스트를 잘 작성하지 않는 환경이라 테스트에 대한 갈망도 있었고, 호기심도 있었다. 이번주는 테스트 코드가 이런거구나~ 정도를 학습하고 이해했다. 다음주는 이제 실질적으로 자주 사용하는 스프링 환경에서 테스트 코드를 작성하는 방법 같은걸 배우니 더 집중해서 학습해야 할 듯 하다.
백엔드
・
워밍업클럽
・
테스트코드
・
클린코드
・
백엔드
2024. 10. 13.
0
[워밍업 클럽 스터디 2기 - BE] (클린코드, 테스트코드) 2주차 발자국
인프런 워밍업 클럽 스터디 2기 - 백엔드 클린코드, 테스트 코드(Java, Spring Boot)Readable Code : 읽기 좋은 코드를 작성하는 사고법해당 강의를 학습하며 정리하는 내용입니다. 1주차 발자국에 이어서 이번주도 배운 내용을 정리하고 회고를 작성한다.이번주는 지난주에 이어 Readable Code 나머지 모든 강의를 수강하는 한주였다.키워드 형식으로 정리한다. 객체 지향 적용하기상속과 조합상속보다 조합을 사용하자!상속은 시멘트처럼 굳어지는 구조다. 수정이 어렵다.부모와 자식의 결합도가 높다.조합과 인터페이스를 활용하는 것이 유연한 구조상속을 통한 코드의 중복 제거가 주는 이점보다, 중복이 생기더라도 유연한 구조 설계가 주는 이점이 더 크다.[객체지향 적용하기 - 상속과 조합 - 셀을 상속에서 조합으로 바꾸기](https://github.com/iamminseongKim/readable-code/commit/86a49db553a752eca745a0d462125ee0d0b4ad70)Value Object도메인의 어떤 개념을 추상화하여 표현한 값 객체값으로 취급하기 위해서 불변성, 동등성, 유효성 검증 등을 보장해야 한다.불변성 : final 필드, setter 금지동등성 : 서로 다른 인스턴스여도(동일성이 달라도), 내부의 값이 같으면 같은 값 객체로 취급한다. equals() & hashCode() 재정의 필요유효성 검증 : 객체가 생성되는 시점에 값에 대한 유효성을 보장하기만원 지폐가 일렬번호(인스턴스)가 다르다고 가치가 다른 즉 다른 만원인가?객체의 동등성을 보장해주자! VO vs Entityclass UserAccount { private String userId; // 식별자 private String 이름; private String 생년월일; private Address 집주소; }이건 Entityclass Address { private String 시도; private String 시군구; private String 도로명; private String 건물번호; }이건 VO이다. Entity는 식별자가 존재한다. 식별자가 아닌 필드의 값이 달라도, 식별자가 같으면 동등한 객체로 취급한다.equals() & hashCode()도 식별자 필드만 가지고 재정의할 수 있다.식별자가 같은데 식별자가 아닌 필드의 값이 서로 다른 두 인스턴스가 있다면, 같은 Entity가 시간이 지남에 따라 변화한 것으로 이해할 수 있다.VO는 식별자 없이 내부의 모든 값이 다 같아야 동등한 객체로 취급한다.개념적으로, 전체 필드가 다 같아야 식별자 역할을 한다고 생각해도 된다.[Value Object - Cell의 상태를 value object로 만들기.](https://github.com/iamminseongKim/readable-code/commit/b667b43de3dfa2fa78fc6a4baf24af570acf69d6) 일급 컬렉션 일급 시민다른 요소에게 사용 가능한 모든 연산을 지원하는 요소변수로 할당 될 수 있다.파라미터로 전달될 수 있다.함수의 결과로 반환될 수 있다.ex) 일급 함수함수형 프로그래밍 언어에서, 함수는 일급 시민이다. 함수는 변수에 할당될 수 있고, 인자로 전달될 수 있고, 함수의 결과로 함수가 반환될 수 있다.일급 컬렉션 컬렉션을 포장하면서, 컬렉션만을 유일하게 필드로 가지는 객체컬렉션을 다른 객체와 동등한 레벨로 다루기 위함단 하나의 컬렉션 필드만을 가진다.컬렉션을 추상화하며 의미를 담을 수 있고, 가공 로직의 보금자리가 생긴다.가공 로직에 대한 테스트도 작성할 수 있다.만약 getter로 컬렉션을 반환할 일이 생긴다면, 외부 조작을 피하기 위해 꼭 새로운 컬렉션으로 만들어서 반환해주자. [일급 컬렉션 - Cell을 가진 Cells 일급 컬렉션](https://github.com/wbluke/readable-code/commit/e72454d3cacea5fb818317b7755551f8a6ae1459)[일급 컬렉션 - CellPosition을 가진 CellPositions 일급 컬렉션](https://github.com/wbluke/readable-code/commit/f297f57d950389deda905a001a94919ef45d25c6)Enum의 특성과 활용Enum은 상수의 집합이며, 상수와 관련된 로직을 담을 수 있는 공간이다상태와 행위를 한 곳에서 관리할 수 있는 추상화된 객체특정 도메인 개념에 대해 그 종류와 기능을 명시적으로 표현해줄 수 있다.만약 변경이 정말 잦은 개념은, Enum보다 DB로 관리하는 것이 나을 수 있다.다형성 활용하기 변하는 것과 변하지 않는 것을 분리하여 추상화하고, OCP를 지키는 구조[다형성 활용하기 - CellSign을 다형성을 가져서 개발해보기](https://github.com/iamminseongKim/readable-code/commit/83f472152ec7a4b82d5d92c17bdb742089b381f6) 숨겨져 있는 도메인 개념 도출하기도메인 지식은 만드는 것이 아니라 발견하는 것객체 지향은 현실을 100% 반영하는 도구가 아니라, 흉내내는 것이다.현실 세계에서 쉽게 인지하지 못하는 개념도 도출해서 사용해야 할 때가 있다.설계할 때는 근시적, 거시적 관점에서 최대한 미래를 에측하고, 시간이 지나 만약 틀렸다는 것을 인지하면 언제든 돌아올 수 있도록 코드를 만들어야 한다.완벽한 설계는 없다. 그 당시 최선이 있을 뿐.코드 다듬기주석의 양면성주석은 죄악이다! VS. 주석 좀 남겨줘라!주석이 많다는 것은, 그만큼 비즈니스 요구사항을 코드에 잘 못 녹였다는 이야기.코드를 설명하는 주석을 쓰면, 코드가 아니라 주석에 의존한다. 주석에 의존하여 코드를 작성하면, 적절하지 않은 추상화 레벨을 갖게 되어 낮은 품질의 코드가 만들어진다.아니 그럼 주석은 언제 씁니까? 좋은 주석은 뭘까우리가 리팩토링 할 때, 정말 큰 난관 중 하나는 히스토리를 전혀 알 수 없는 코드다.후대에 전해야 할 의사 결정의 히스토리를 도저히 코드로 표현할 수 없을 때 주석으로 상세하게 설명한다.주석을 작성할 때, 자주 변하는 정보는 최대한 지양해서 작성한다.만약 관련 정책이 변하거나 코드가 변경되었다면, 주석도 잊지 않고 함께 업데이트 한다.주석이 없는 코드보다, 부정확한 주석이 달린 코드가 더 치명적이다.우리가 가진 모든 표현 방법을 총동원해 코드에 의도를 녹여내고, 그럼에도 불구하고 전달해야 할 정보 남았을 때 사용하는 주석[주석 제거 - gameStatus - 제거 후 책임 변경](https://github.com/iamminseongKim/readable-code/commit/5810a74a9cbfc41b2d2bc638f58a437f26bec2b5) 변수와 메서드의 나열 순서변수는 사용하는 순서대로 나열한다.인지적 경제성메서드의 순서도 고려해보야햐 하는데, 객체의 입장에서 생각해보자.상태변경 >> 판별 >= 조회 메서드 순서비공개 메서드는 공개 메서드에서 언급한 순서 대로 배치공통으로 사용하는 메서드라면 (가장 하단 같은) 적당한 곳에 배치한다.중요한 것은, 나열 순서로도 의도와 정보를 전달할 수 있다는 것[코드 다듬기 - 변수와 메서드의 나열 순서](https://github.com/iamminseongKim/readable-code/commit/c3fea40a581cfd0a418114473350fe430686a0d9) 패키지 나누기패키지는 문맥으로써의 정보를 제공할 수 있다. 패키지를 쪼개지 않으면 관리가 어려워진다.패키지를 너무 잘게 쪼개도 마찬가지로 관리가 어려워진다.대규모 패키지 변경은 팀원과의 합의를 이룬 시점에서 하자.[코드 다듬기 - 패키지 나누기](https://github.com/iamminseongKim/readable-code/commit/8f650421839f7b68021dc1a983a2a1cf29a64b8f) 기능 유지보수하기 (1) - 버그 잡기이전 코드에선 지뢰판에 깃발을 전부 꽂으면 승리했다. 이걸 수정해보는 과정인데 셀을 체크하는 과정의 핵심이 뭔지 파악하고 고치는 작업이었다. [코드 다듬기 - 기능 유지보수하기 (1) - 버그 잡기 - 객체의 책임을 명확하게 했기 때문에 고치기 쉽다.](https://github.com/iamminseongKim/readable-code/commit/8654bb3d727a9bffe4f7b2b8ab06211446a3cae8)객체의 책임을 명확하게 분리했기 때문에 고치기 매우 쉬웠다. 기능 유지보수하기 (2) - 알고리즘 교체하기재귀함수를 Stack으로 변경해서 주변 셀을 여는 로직을 개선했다.[코드 다듬기 - 기능 유지보수하기 (2) - 알고리즘 바꾸기. 재귀함수 -> stack](https://github.com/iamminseongKim/readable-code/commit/3bbcfe61b7f0dca6fcee51efc2f3adbfb298aa3d)원리는 이해하겠는데, 솔직히 이걸 일상에서 떠올릴 수 있을지 내 실력이 부족하다는걸 또 한번 느끼는 시간이었다. IDE의 도움 받기코드 포맷 정렬 : Option + Cmd + L | Ctrl + Alt + L코드 품질 : Sonarlintlint(linting) : 잠재적인 문제가 될 수 있는 오류, 버그, 스타일 등을 미리 알려주는 코드 품질 체크 도구포맷 규칙 : .editorconfig (https://editorconfig.org/)여러 사람과 협업을 염두하면 IDE 기본 포맷팅에 익숙해지는 것이 좋다.스타일은 혼자 결정하는 것이 아니라, 팀 내 합의도 도출되어야만 한다.한번 정해지면 절대적인 것이 아니라, 사용하면서 계속 의견을 듣고 개선/반영하는 것이 좋다.[코드 다듬기 - IDE의 도움 받기, 코드 정렬, Sonarlint, .editorconfig](https://github.com/iamminseongKim/readable-code/commit/11469a97f84c4a2d04e6a499fdb5f37b106ebbff)리팩토링 연습 리팩토링 (1) - 추상화 레벨중복 제거, 메서드 추출[의미 단위로 공백넣기](https://github.com/iamminseongKim/readable-code/commit/f3d08ed8cae33db74b0a5d7c812da9f81c28d9e2)[중복 로직 제거, 메서드 추상화](https://github.com/iamminseongKim/readable-code/commit/0e7504e10dca82d411937631c470cc8dd8e779f7)객체에 메시지 보내기[null대신 Optional넘기기](https://github.com/iamminseongKim/readable-code/commit/716b682d2ea7149448adc8a149dd89067a99b3fe)[객체에게 정중히 묻기. getter로 무례하게 하지 말고!](https://github.com/iamminseongKim/readable-code/commit/fd468510e9d3c144d4e938494825105e02624232)이 순서대로 리팩토링하면 좀 정리하기 쉽다.리팩토링 (2) - 객체의 책임과 응집도IO 통합[Input, Output 하나로 통합](https://github.com/iamminseongKim/readable-code/commit/4e2f36eceed58ada1d22316fe6f4b54ffca989ce)일급 컬랙션[StudyCafePass, LockerPass에 대한 일급컬랙션 생성](https://github.com/iamminseongKim/readable-code/commit/82a05d1493c516a31e41cb5bc9dc9b7aef2f6e07)display()의 책임[display를 IOHandler에서 하도록 하고, 하다보니 pass 인터페이스로 추상화](https://github.com/iamminseongKim/readable-code/commit/ae2546b1b76a44a5186333ec3bb66c165bb8b4b0)Order 객체[계산 로직을 IO에서 할게 아니라 PassOrder라는 주문 객체를 만들어서 위임](https://github.com/iamminseongKim/readable-code/commit/b74355e67420c138a0ac4ca31ea913667e52eba6)코드를 이해하고 도메인을 이해한다면 다음과 같이 로직을 수정할 수 있다. 이 기법들을 자유자재로 사용할 수 있다면 해당 도메인을 이해했다고 생각한다!리팩토링 (3) - 관점의 차이로 달라지는 추상화FileHandler를 바라보는 관점provider 인터페이스를 만들었는데, 구현체는 io에 provider 패키지를 만들어서 따로 분리 보관했다.io에 만드는 구현체는 파일을 읽는, 그런 내용이 중요한것이기 때문에 따로 보관했고,나중에 파일이 아니라 DB에서 읽는다면, 또 다른 패키지에서 인터페이스를 구현해서 갈아 끼우기만 하면된다. 핵사고날 아키텍쳐 - 포트(인터페이스)와 어댑터(구현체) [FileHandler를 Provider 개념으로 리팩토링](https://github.com/iamminseongKim/readable-code/commit/e33dadae25724a61cf52713cc1fcaea5ed81c40e)해당 챕터에선 추가적으로 개선할 방향을 찾고, 클린 아키텍쳐를 위한 새로운 사고를 열어 볼 수 있었다.기억하면 좋은 조언들능동적 읽기핵심 목표는 우리의 도메인 지식을 늘리는 것. 그리고 이전 작성자의 의도를 파악하는 것.오버 엔지니어링필요한 적정 수준보다 더 높은 수준의 엔지니어링은탄환은 없다클린 코드도 은탄환이 아니다.실무 : 2가지 사이의 줄다리기지속 가능한 소프트웨어의 품질 vs 기술 부채를 안고 가는 빠른 결과물도구라는 것은, 일단 그것을 한계까지 사용할 줄 아는 사람이 그것을 사용하지 말아야 할 때도 아는 법이다.Day 7 미션 섹션 7에 나오는 StudyCafe를 이전 까지 배운 내용으로 스스로 리팩토링 하는 과제였다.아쉬운점은 내가 기한을 착각하고 마감일에 리팩토링을 시작했다는 점이고, 다행인건 그날이 한글날이라 그래도 시간이 좀 있었다.내가 이 미션을 하면서 중점적(순서)으로 생각한 점은 다음과 같다.의미단위로 공백 끊기큰 단위로 메서드 추출하기의존성 주입을 할 수 있는 부분을 찾기 (DIP)객체 단위로 추상화(추출)할게 있는지 찾기메서드의 책임이 올바른 위치에 있는지 판단하기일급 컬랙션 적용하기도메인을 이해해서 추가적인 개념을 도출하기해당 순서를 중요하게 생각하면서 리팩토링을 진행했다.그리고 다음날 깨끗한 정신으로 다시한번 보니깐 진짜 좀 별로였다 ㅋㅋ ㅠ 그래서 다음날 내가 추가한 부분은 다음과 같다. 기존에는 OutputHandler 문자를 출력하는 부분에서 계산을 해서 콘솔에 출력하였다.그래서 나는 PassCost라는 계산하는 객체를 만들어서 계산은 여기서 다 하고 출력할 때는 여기서 getter를 통해 값만 얻어 오도록 수정했다.[PassCost 만들기](https://github.com/iamminseongKim/readable-code/commit/4c01ec8cf31203349d3ebacadf5fe4cdf0299bb2#diff-2a97fd57d1b72b30fddf91a8cb3453fff97b46bab75082461641cfe8b4a491ff)전체 회고이번주를 끝으로 Readable Code 읽기 좋은 코드를 작성하는 사고법 강의가 끝났다.솔직히 시간이 좀 있었는데 TestCode강의를 시작하지 못한게 조금 아쉽긴 하다. 그래도 이번 강의를 통해 진짜 좋은 코드가 무엇인지, 물론 이 강의에서 나오는 개념들이 정답은 아닐지 몰라도해당 강의를 통해 사고를 넓일 수 있는 계기가 되어서 너무 좋았다. 메서드를 추상화 하는 행위가 이 강의를 듣기 전까지는 명확하게 인지하진 못했다. 그냥 남들이 하니깐, 조금 하고 솔직히 많이 안했다. 하지만 내가 추상화를 해보면서도 코드가 더 읽기 쉬워지고 도메인을 이해하고 있다는걸 느껴서 적극적으로 적용해보려고 한다. 다음주에는 공휴일이 없으니깐 출 퇴근 후에 빡세게 들어야한다 ㅠㅠ. 그래도 좀 열심히 들어서 여유로운 주말을 맞이하고 싶다~
워밍업클럽
・
클린코드
2024. 10. 05.
0
[워밍업 클럽 스터디 2기 - BE] 1주차 발자국
[인프런 워밍업 클럽 스터디 2기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)]([Readable">https://www.inflearn.com/course/offline/warmup-club-2-be-wb)[Readable">[Readable Code: 읽기 좋은 코드를 작성하는 사고법](https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95)해당 내용을 바탕으로 작성된 블로그 입니다.추상우리가 클린 코드를 추구하는 이유가독성 - Readability글이 잘 읽힌다 = 이해가 잘 된다. = 유지보수 하기 수월해진다. = 우리의 시간과 자원이 절약된다. 클린코드를 관통하는 아주 중요한 주제바로 추상(抽象) 추상과 구체도메인 영역에서 핵심만 남기는 행위가 추상, 이를 보고 유추할 수 있는게 구체중요한 정보는 가려내어 남기고, 덜 중요한 정보는 생략하여 버린다 추상화의 가장 대표적인 행위 : 이름 짓기 이름 짓기이름을 짓는다는 행위는, 추상적 사고를 기반으로 한다. 이름 짓기 팁단수와 복수를 구분하기이름 줄이지 않기은어/방언 사용하지 않기좋은 코드를 보고 습득하기단수와 복수를 구분하기끝에 '(e)s'를 붙여 어떤 데이터(변수, 클래스 등)가 단수인지, 복수인지를 나타내는 것만으로도 읽는 이에게 중요한 정보를 같이 전달할 수 있다.이름 줄이지 않기줄임말이라는 것은 가독성을 제물로 바쳐 효율성을 얻는 것으로, 대부분 잃는 것에 비해 얻는 것이 적다.은어/방언 사용하지 않기농담에서 파생된 용어, 일부 팀원/현재의 우리 팀만 아는 용어 금지Q. 새로운 사람이 합류했을 때 이 용어를 단번에 이해할 수 있는가?도메인 용어 사용하기도메인 용어를 먼저 정의하는 과정(ex. 도메인 용어 사전)이 먼저 필요할 수도 있다.좋은 코드를 보고 습득하기비슷한 상황에서 자주 사용하는 단어, 개념 습득하기→ ex. pool, candidate, threshold 등 https://github.com/iamminseongKim/readable-code/commit/3d1497c43f386f10b8d312623d9a26bf5879b97d이름을 지으면서 코드를 읽을 수 있게 됐다. 메서드와 추상화국어/영어 독해할 때 잘 쓰여진 글이라면 한 문단의 주제는 반드시 하나다.메서드의 이름으로 구체를 추상화 하는 것이다.잘 쓰여진 코드라면 한 메서드의 주제는 반드시 하나다. 메서드 선언부* 메서드 시그니처 : 메서드명 + 파라미터 (메서드 오버로드)메서드명추상화된 구체를 유추할 수 있는, 적절한 의미가 담긴 이름파라미터와 연결지어 더 풍부한 의미를 전달할 수도 있다.파라미터파라미터의 타입, 개수, 순서를 통해 의미를 전달파라미터는 외부 세계와 소통하는 창반환타입메서드 시그니처에 납득이 가는, 적절한 타입의 반환값 돌려주기반환 타입이 boolean인데, 이게 이 메서드에서 무엇을 의미하는지?void 대신 충분히 반환할 만한 값이 있는지 고민해보기반환값이 있다면 테스트도 용이해진다.[지뢰찾기 - 메서드 추출](https://github.com/iamminseongKim/readable-code/commit/66523910ce932a0e103f22bc3ecf2d93f418e116#diff-9f9444b48fc52a78913dc852f9b4c9a5cc42aa4e5525c09fe92c5d24b6e41182) 추상화 레벨하나의 세계 안에서는, 추상화 레벨이 동등해야 한다.public static void main(String[] args) { showGameStartComments(); initializeGame(); showBoard(); if (gameStatus == 1) { System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } ... checkIfGameIsOver(); }자 이 코드에서 메서드를 쭉쭉 호출하다가 갑자기 gameStatus == 1 이런 코드가 나와 멈칫하게 된다.이러한 점이 레벨이 안맞다는 점이다.그럼public static void main(String[] args) { showGameStartComments(); initializeGame(); showBoard(); if (doesUserWinTheGame()) { System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } ... checkIfGameIsOver(); } private boolean doesUserWinTheGame() { return gameStatus == 1; }다음과 같이 레벨을 맞춰주자. 매직 넘버, 매직 스트링 매직 넘버, 매직 스트링?의미를 갖고 있으나, 상수로 추출되지 않는 숫자, 문자열 등상수 추출로 이름을 짓고 의미를 부여함으로써 가독성, 유지보수성 증가 [추상화 레벨 & 매직 넘버, 매직 스트링](https://github.com/iamminseongKim/readable-code/commit/ccdb4d9894b82c6f340acfb7543414423669a296)상수를 뽑아내므로써 가독성, 유지보수성이 증가한걸 느낌. 논리, 사고의 흐름뇌 메모리 적게 쓰기정리하는 뇌 : 정리 시스템에서 중요한 과제는 최소의 인지적 노력으로 최대의 정보를 제공하는 것이다.도둑맞은 집중력 : 뇌는 한 번에 한 가지 일밖에 하지 못한다. 멀티테스킹? 그건 저글링일 뿐.인지적 경제성뇌 메모리 적게 쓰기.. 읽는 사람이 뇌를 적게 쓰기 위해 만들 때 잘 쓰자.. Early returnif (a > 3) { doSomething1(); } else if (a 1) { doSomething2(); } else { doSomething3(); }이 if문을 쭉쭉 내려가면서 생각해보면마지막 else를 생각할 때 쯤이면 모든 if 조건을 다 인지하고 있어야 그 예외를 생각하고 doSomething3()이 실행되겠구나 생각할 것이다.이걸 해결하기 위해서 사용하면 좋은 것이 Early return이다. extracted(); void extracted() { if(a > 3) { doSomething1(); return; } if(a1) { doSomething2(); return; } doSomething3(); } Early return으로 else의 사용을 지양, else를 쓰지 않아도 되면 쓰지 않도록 노력하자.[Early return 코드](https://github.com/iamminseongKim/readable-code/commit/31dc6569a71028141c665e2f341eb7d05cb0896f)사고의 depth 줄이기중첩 분기문, 중첩 반복문중첩 for문을 지양하고 바깥이 어떻게 도는지 알 필요 없게 만들자. 주의"무조건 1depth로 만들어라"가 아니다.보이는 Depth를 줄이는데 급급한 것이 아니라 추상화를 통한 사고 과정의 depth를 줄이는 것이 중요2중 중첩 구조로 표현하는 것이 사고하는데에 더 도움이 된다고 판단한다면, 메서드 분리보다 그대로 놔두는 것이 더 나은 선택일 수 있다. 떄로는 메서드를 분리하는 것이 더 혼선을 줄 수 있다.[예제 코드 - 중첩 for문 (Stream 사용)](https://github.com/iamminseongKim/readable-code/commit/37eaf9ebc8f5c4774cf295d5e3be3dfb82b1c9bb) 사용할 변수는 가깝게 선언하기int i = 10; // 저 ~~아래 한 20줄 int j = i + 30;이런 식으로 사용하지 말자.// 저 아래 20줄 int i = 10; int j = i + 30;가깝게 선언하자.[예제 코드 - scanner 수정(상수로 또 뺌)](https://github.com/iamminseongKim/readable-code/commit/bd15a345918cf602506cb8182a4aa5b90a826877) 공백 라인을 대하는 자세공백 라인도 의미를 가진다복잡한 로직의 의미 단위를 나누어 보여줌으로써 읽는 사람에 추가적인 정보를 제공할 수 있다.부정어를 대하는 자세부정어구를 쓰지 않아도 되는 상황인지 체크부정의 의미를 담은 다른 단어가 존재하는지 고민하기 or 부정어구로 메서드명 구성부정 연산자(!) 는 가독성이 떨어질 수 있다.[예제 코드 - isLandMineCell()에 부정연산자 제거한 과정](https://github.com/iamminseongKim/readable-code/commit/1070112c4dfd0d230f1edbda6ad0a2d8f612509c) 해피 케이스와 예외 처리사람은 해피 케이스에 몰두하는 경향이 있다.예외처리를 꼼꼼히 해야 소프트웨어가 견고해진다.예외가 발생할 가능성 낮추기어떤 값의 검증이 필요한 부분은 주로 외부 세계와의 접점사용자 입력, 객체 생성자, 외부 서버의 요청 등의도한 예외와 예상하지 못한 예외를 구분하기사용자에게 보여줄 예외와, 개발자가 보고 처리해야 할 예외 구분Null을 대하는 자세항상 NullPointException을 방지하는 방향으로 경각심을 가지자.메서드 설계 시 return null을 자제한다.만약 어렵다면, Optional 사용을 고민해본다.Optional에 관하여Optional은 비싼 객체이다. 꼭 필요한 상황에서 반환 타입에 사용한다.Optional을 파라미터로 받지 않도록 한다. 분기 케이스가 3개나 된다.Optional이 가진 데이터가 null인지 아닌지, + Optional 그 자체가 Null인지Optional을 반환 받았다면 최대한 빠르게 해소한다.Optional을 해소하는 방법분기문을 만드는 isPresent()-get() 대신에 풍부한 API 사용ex) orElseGet(), orElseThrow(), ifPresent(), ifPresentOrElse()orElse(), orElseGet(), orElseThrow()의 차이 숙지orElse() : 항상 실행, 확정된 값일 때 사용orElseGet() : null인 경우 실행, 값을 제공하는 동작(Supplier) 정의orElseThrow() : 그냥 쓰면됨, 없으면 예외 만들어서 던짐.[해피 케이스와 예외처리 - 예제](https://github.com/iamminseongKim/readable-code/commit/cb02070c415ed2f2c70cf417cca0701015fe2f18) 객체 지향 패러다임정해진 순서 차례대로 진행하는 프로그래밍 절차 지향객체라는걸 만들어서 상호 작용을 하도록 개발하는 객체지향사이드 이펙트가 없는 a를 넣으면 항상 같은 리턴을 주는 순수 함수이런 함수를 기반으로 개발하는 함수형 프로그래밍 추상의 관점으로 바라보는 객체 지향객체 : Object, 추상화된 (데이터 + 코드)협력과 책임객체간의 협력 + 객체가 담당하는 책임캡추상다캡슐화 : 객체가 가지고있는 데이터, 로직을 숨기고 일부만 보여줌추상화 : 요약상속 : 진짜 필요한 곳에만 사용다형성 : 인터페이스화 추상화관심사의 분리Seperation Of Concern높은 응집도, 낮은 결합도a라는 관점을 모아서 관리 -> 유지보수성 증가그리고 이렇게 모인 관심사들 끼리는 결합도가 낮게 개발.a를 수정했는데 b에 영향이 없게 개발. 객체 설계하기 1오브젝트도 데이터나 로직은 숨기고, 이를 공개적인 메서드를 통해서만 외부에서 소통할 수 있도록 추상화 해야 한다.비공개 필드 (데이터), 비공개 로직(코드)공개 메서드 선언부를 통해 외부 세계와 소통각 메서드의 기능은 객체의 책임을 드러내는 창구객체의 책임이 나뉨에 따라 객체간 협력이 발생객체가 제공하는 것절차 지향에서 잘 보이지 않았던 개념을 가시화관심사가 한 군데로 모이기 때문에, 유지보수성 증가객체 내부에서 객체가 가진 데이터의 유효성 검증 책임을 가질 수 있다.여러 객체를 사용하는 입장에서는, 구체적인 구현에 신경쓰지 않고 보다 높은 추상화 레벨에서 도메인 로직을 다룰 수 있다.새로운 객체를 만들 때 주의할 점1개의 관심사로 명확하게 책임이 정의되었는지 확인하기메서드를 추상화 할 때와 비슷하다.객체를 만듦으로써 외부 세계와 어떤 소통을 하려고 하는지 생각해보자.생성자, 정적 팩토리 메서드에서 유효성 검증이 가능하다.도메인에 특화된 검증 로직이 들어갈 수 있다. setter 사용 자제데이터는 불변이 최고다. 변하는 데이터더라도 객체가 핸들링할 수 있어야 한다.객체 내부에서 외부 세계의 개입 없이 자체적인 변경/가공으로 처리할 수 있는지를 확인만약 외부에서 가지고 있는 데이터로 데이터 변경 요청을 해야하는 경우, 'set~'이라는 단순한 이름보다는 update~같이 의도를 드러내는 네이밍을 고려하자.getter도 처음에는 사용 자제. 반드시 필요한 경우에 추가하기외부에서 객체를 내 데이터가 필요하다고 getter를 남발하는 것은 무례한 행동이다!객체에 메시지를 보내라! 필드의 수는 적을수록 좋다.불필요한 데이터가 많을 수록 복잡도가 높아지고 대응할 변화가 많아진다.필드 A를 가지고 계산할 수 있는 A'필드가 있다면, 메서드 기능으로 제공단, 미리 가공하는 것이 성능 상 이점이 있다면, 필드로 가지고 있는 것이 좋을 수도 있다.도메인 지식은 만드는 것이 아니라 발견하는 것[Board 객체화](https://github.com/iamminseongKim/readable-code/commit/2c04b02413a879e3135a1fd905abb445a4d9dcd9)[Board - Sign Cell 넣기](https://github.com/iamminseongKim/readable-code/commit/7a593983ce867b9233172344189b781c6bf207c1)[cell을 도메인 지식을 통해 리팩토링](https://github.com/iamminseongKim/readable-code/commit/295a3d84a208216fc630e52dee08cbfb1385abfc) SOLIDSRP : Single Responsibility PrincipleOCP : Open-Closed PrincipleLSP : Liskov Substitution PrincipleISP : Interface Segergation PrincipleDIP : Dependency Inversion Principle [SRP - 메인 메서드 분리](https://github.com/iamminseongKim/readable-code/commit/2000d6314228806ebd478954cdd822dff38699a9)[SRP - 사용자 입출력 클래스 분리](https://github.com/iamminseongKim/readable-code/commit/50a1eba65893e74e931cfa5b7c332c031a07e9ea)[SRP - GameBoard 클래스로 분리](https://github.com/iamminseongKim/readable-code/commit/53521ae2db988d90cca080b7890ae4ce9531f7ed) [OCP - 난이도 조절하기 전에 가로 세로 갯수 대응하기](https://github.com/iamminseongKim/readable-code/commit/28d0ea71ea8a21b69cd9dff601ca0cc1a0488e00)[OCP - 난이도 조절하기 내용 구현](https://github.com/iamminseongKim/readable-code/commit/56d37d6e469874f5955209307288506717637c35)[LSP - Cell 역할에 따라 분리](https://github.com/iamminseongKim/readable-code/commit/e3edbcb8c21b7c9b922f98e2e463f4f3f74c90b0) [ISP-게임 인터페이스 구현 및 기능 쪼개기](https://github.com/iamminseongKim/readable-code/commit/47d2a2c4d7c135e4dc8bb9c5c3f4a8fb57cf3a2b) [DIP - 사용자 입력 방식 인터페이스로 바꾸기](https://github.com/iamminseongKim/readable-code/commit/a560adbb557dc4f368a566126b5ec591f98219fd) [DIP - OutputHandler 의 용어 정리 - show, print](https://github.com/iamminseongKim/readable-code/commit/582199fa6907cc470139bcb8868aad9678f1e8c8) 객체 지향 적용하기상속과 조합상속보다 조합을 사용하자Value Object도메인의 어떤 개념을 추상화하여 표현한 값 객체값으로 취급하기 위해서 불변성, 동등성, 유효성 검증 등을 보장해야 한다.불변성 : final 필드, setter 금지동등성 : 서로 다른 인스턴스여도(동일성이 달라도), 내부의 값이 같으면 같은 값 객체로 취급한다. equals() & hashCode() 재정의 필요유효성 검증 : 객체가 생성되는 시점에 값에 대한 유효성을 보장하기만원 지폐가 일렬번호(인스턴스)가 다르다고 가치가 다른 즉 다른 만원인가? VO vs Entity class UserAccount { private String userId; // 식별자 private String 이름; private String 생년월일; private Address 집주소; }이건 Entityclass Address { private String 시도; private String 시군구; private String 도로명; private String 건물번호; }이건 VO 이다. Entity는 식별자가 존재한다. 식별자가 아닌 필드의 값이 달라도, 식별자가 같으면 동등한 객체로 취급한다.equals() & hashCode()도 식별자 필드만 가지고 재정의할 수 있다. 식별자가 같은데 식별자가 아닌 필드의 값이 서로 다른 두 인스턴스가 있다면, 같은Entity가 시간이 지남에 따라 변화한 것으로 이해할 수 있다.VO는 식별자 없이 내부의 모든 값이 다 같아야 동등한 객체로 취급한다.개념적으로, 전체 필드가 다 같아야 식별자 역할을 한다고 생각해도 된다. [Value Object - Cell의 상태를 value object로 만들기.](https://github.com/iamminseongKim/readable-code/commit/b667b43de3dfa2fa78fc6a4baf24af570acf69d6) 미션미션 1 추상과 구체 예시내가 생각한 추상은 모두가 이해할 수 있도록 말을 요약이라 생각해서내가 좋아하는 야구에서 이 예시를 찾아봤다.야구를 좀 본사람은 4-6-3 병살 저 6자만 봐도 어떤 일이 일어났는지 바로 이해할 것이다.그래서 나는 이걸로 미션을 제출했다. 미션 2 코드 리팩토링주문관련 코드를 리팩토링 하는 미션이였다.코드를 보면 if-else로 많이 감싸져 있어서 그걸 제일 먼저 Early return을 이용해서 바꿔줬고,그 다음엔 return false보다는 예외로 값이 잘못된 것을 알려줬다.마지막으로 if문 안에 추상화 레벨을 맞추기 위해 메서드를 추출했다. 아쉬운 점은 예외를 커스텀 예외로 만드는 걸 안했고, if문 검증 로직을 굳이 service단이 아니라 order객체 내에서했어도 좋았을 것 같다는 생각을 했다. 느낀점자바를 2년이상 써오면서 원래 알았던 개념도 있고, 처음 봤던 개념도 있었다.그런데 이번 내용들을 학습하면서 항상 클린코드 클린코드 해야지 생각만 하던걸이젠 직접 어떻게 작성 해야하는지, 어떤 점을 고려해야 하는지 좀 감을 잡을 수 있었던 것 같다. 솔직히 공부를 많이 안했던 것 같아서 좀 부끄럽지만 이번 계기로 클린코드와 테스트 코드에 자신감을 가질 수 있도록 노력하겠다.
백엔드
・
워밍업클럽