블로그
전체 62025. 03. 30.
0
[워밍업 클럽 3기 BE 클린코드&테스트] - 4주차 발자국
회고드디어 1달간의 과정이 끝이 났습니다. 1달동안 강의 2개를 완강 한것이 보람찬 부분인것 같습니다. 강의를 듣기전에는 클린코드와 테스트코드에 대해 이해만 하고있었지 어떤 상황에서 어떻게 적용해야하는지에 대해서는 잘 몰랐습니다. 강의를 열심히 듣고 , 미션을 해결해나아가다 보니 이제는 클린코드와 테스트코드가 두렵지 않게 되었습니다. 그리고 라이브세션으로 많은 인사이트를 얻을수 있었습니다 ㅎㅎ 클린코드와 테스트코드에 더 알고싶고, 부족하다 생각하시면 이 강의를 추천드립니다!미션Day 16 - Layered Architecture에서 레이어별로 1) 어떤 특징이 있고, 2) 어떻게 테스트를 하면 좋을지, 자기만의 언어로 정리Day 18 1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다.https://inf.run/eNqJk2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요? (@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)https://inf.run/i3MoR
2025. 03. 27.
0
[워밍업 클럽 3기 BE 클린코드&테스트] - Day 18 미션
1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다.1. @Mock vs @MockBean@MockMockito에서 제공하는 애너테이션.해당 필드에 가짜(Mock) 객체를 생성해 준다.주로 순수한 단위 테스트(단일 클래스 테스트)에서 사용한다.스프링 컨테이너와는 무관하게 동작하므로, 원하는 경우 @InjectMocks를 통해 직접 의존성 주입(필드/생성자/Setter)이 일어날 수 있게끔 설정해줘야 한다.ExtendWith(SpringExtension.class) class MyServiceTest { // 1) Repository를 Mock 객체로 만든다. @Mock private MyRepository myRepository; // 2) MyRepository가 필요한 MyService에 @InjectMocks로 Mock을 주입받는다. @InjectMocks private MyService myService; @Test void testFindById() { // given when(myRepository.findById(1L)).thenReturn(Optional.of(new MyEntity(1L, "Test Data"))); // when MyEntity result = myService.findById(1L); // then assertThat(result.getName()).isEqualTo("Test Data"); } } @MockBeanSpring Boot에서 제공하는 애너테이션 (org.springframework.boot.test.mock.mockito).스프링 컨테이너에 Mock 객체로 등록해준다. 즉, @Autowired나 다른 Bean들이 해당 Mock 객체를 주입받는다.주로 @SpringBootTest, @WebMvcTest 등 스프링 컨텍스트가 필요한 통합 테스트에서 사용한다.실제 Bean 대신 MockBean이 컨테이너에서 주입되는 형태이다.@SpringBootTest class MyServiceIntegrationTest { @Autowired private MyService myService; // 스프링 컨테이너에 MyRepository를 MockBean으로 등록 @MockBean private MyRepository myRepository; @Test void testFindById() { // given when(myRepository.findById(1L)).thenReturn(Optional.of(new MyEntity(1L, "Integration Test"))); // when MyEntity result = myService.findById(1L); // then assertThat(result.getName()).isEqualTo("Integration Test"); } } 2. @Spy vs @SpyBean@SpyMockito에서 제공하는 애너테이션.부분(Mock) 테스트에 사용되며, 실제 객체를 부분적으로 Mocking한다.즉, 스파이(Spy)는 기본적으로 진짜 메서드를 실행하지만, 원하는 메서드만 when(...).thenReturn(...) 형태로 오버라이딩해서 결과를 바꿀 수 있다.@Mock과의 차이는 @Spy는 기본 동작이 실제 객체라는 점이다.@ExtendWith(SpringExtension.class) class MyServiceSpyTest { // Repository 스파이 객체 // => 실제 MyRepository 구현체 인스턴스를 생성 + 원하는 부분만 stub 가능 @Spy private MyRepository myRepository = new MyRepositoryImpl(); // 스파이 객체를 주입받을 MyService @InjectMocks private MyService myService; @Test void testFindByIdWithSpy() { // spy는 실제 동작을 수행하므로, // 특정 메서드만 오버라이딩해서 동작을 가짜로 만들 수 있다. doReturn(Optional.of(new MyEntity(2L, "Spy Data"))) .when(myRepository).findById(2L); // 2L의 경우에는 Stub이 적용되어 Mock 동작, // 1L의 경우에는 실제 MyRepositoryImpl 동작. MyEntity result1 = myService.findById(1L); // 실제동작 MyEntity result2 = myService.findById(2L); // Stub으로 오버라이딩 assertThat(result1).isNotNull(); // 실제 Repo 동작 결과 assertThat(result2.getName()).isEqualTo("Spy Data"); // 오버라이딩된 결과 } } @SpyBeanSpring Boot에서 제공.@MockBean과 유사하지만, 실제 스프링 Bean을 스파이로 만들어 컨테이너에 등록한다.즉, 컨테이너가 관리하는 Bean을 부분 Mocking하고 싶은 경우에 사용.다른 Bean들이 해당 @SpyBean을 주입받아 사용할 때는, 기본적으로 진짜 로직을 타게 되지만 원하는 메서드만 Stub 할 수 있다.@SpringBootTest class MyServiceSpyBeanTest { // MyService는 진짜 Bean @Autowired private MyService myService; // MyRepository를 스파이 Bean으로 등록 @SpyBean private MyRepository myRepository; @Test void testFindByIdWithSpyBean() { // 실제 Bean이지만 특정 메서드를 Stub doReturn(Optional.of(new MyEntity(999L, "SpyBean Data"))) .when(myRepository).findById(999L); MyEntity result = myService.findById(999L); // 999L일 때만 Stub 동작 → "SpyBean Data" assertThat(result.getName()).isEqualTo("SpyBean Data"); } } 3. @InjectMocksMockito에서 제공하는 애너테이션.@Mock 또는 @Spy로 만들어진 Mock/Spy 객체들을 해당 클래스(필드, 생성자 등)에 자동 주입해 준다.스프링과는 무관하게 Mockito 레벨에서만 동작하며, 생성자 주입/필드 주입/Setter 주입 방식으로 의존성을 주입해 준다.@ExtendWith(SpringExtension.class) class MyServiceTest { @Mock private MyRepository myRepository; // MyService 생성 시 @Mock으로 만든 myRepository가 자동 주입됨 @InjectMocks private MyService myService; @Test void testSomeLogic() { // ... } }2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치) /*** * ✔️ 게시판 게시물에 달리는 댓글을 담당하는 Service Test * ✔️ 댓글을 달기 위해서는 게시물과 사용자가 필요하다. * ✔️ 게시물을 올리기 위해서는 사용자가 필요하다." * */ class Test { @BeforeEach void setUp() { // 사용자 생성에 필요한 내용 준비 (1-1, 2-1, 3-1) // 사용자 생성 (1-2, 2-2, 3-2) // 게시물 생성에 필요한 내용 준비 (1-3, 2-3, 3-5) // 게시물 생성 (1-4, 2-4, 3-6) } @DisplayName("사용자가 댓글을 작성할 수 있다.") @org.junit.jupiter.api.Test void writeComment() { // given // 1-5. 댓글 생성에 필요한 내용 준비 // when // 1-6. 댓글 생성 // then // 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @org.junit.jupiter.api.Test void updateComment() { // given // 2-6. 댓글 생성 // when // 2-7. 댓글 수정 // then // 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @org.junit.jupiter.api.Test void cannotUpdateCommentWhenUserIsNotWriter() { // given // 3-3. 사용자2 생성에 필요한 내용 준비 // 3-4. 사용자2 생성 // 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 // 3-8. 사용자1의 댓글 생성 // when // 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then // 검증 } }
2025. 03. 25.
0
[워밍업 클럽 3기 BE 클린코드&테스트] - 레이어드 아키텍처 주요 특징
Spring에서 애플리케이션을 설계할 때 흔히 사용하는 Layered Architecture(레이어드 아키텍처)는 다음과 같은 3가지 주요 계층으로 구성[Presentation Layer] → [Business Layer] → [Persistence Layer] (Controller) (Service) (Repository) Layered Architecture의 핵심 특징계층 분리 : 각 계층이 독립된 책임을 가지며, 역할이 명확히 나뉨독립성 : 상위 계층은 하위 계층을 의존하지만, 하위 계층은 상위 계층을 모름유연성 : 특정 계층을 변경하더라도 다른 계층에 영향이 적음 테스트 용이성 : 계층별 단위 테스트가 가능해 테스트 작성이 명확하고 쉬움1. Presentation Layer (UI 계층 - Controller)✅ 역할사용자 요청을 받아서 적절한 응답을 반환 (주로 JSON 형태)HTTP 관련 처리 (@RestController, @GetMapping 등)✅ 테스트 전략@WebMvcTest를 사용하여 컨트롤러만 테스트MockMvc + ObjectMapper로 HTTP 요청/응답 흐름을 검증Service는 @MockBean으로 주입📌 예시 코드WebMvcTest(controllers = OrderController.class) class OrderControllerTest { @Autowired MockMvc mockMvc; @Autowired ObjectMapper objectMapper; @MockBean OrderService orderService; @Test void 신규_주문_등록_성공() throws Exception { OrderCreateRequest request = OrderCreateRequest.builder() .productNumbers(List.of("001")) .build(); mockMvc.perform(post("/api/v1/orders/new") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.message").value("OK")); } @Test void 상품번호_없을때_예외() throws Exception { OrderCreateRequest request = OrderCreateRequest.builder().build(); mockMvc.perform(post("/api/v1/orders/new") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.message").value("상품 번호 리스트는 필수입니다.")); } } 2. Business Layer (서비스 계층 - Service)✅ 역할비즈니스 로직 처리의 중심트랜잭션 처리, 여러 레포지토리 호출, 외부 API 조합 등✅ 테스트 전략@SpringBootTest 또는 순수 JUnit 테스트로 비즈니스 로직 검증Repository는 필요 시 가짜(Mock) 또는 실제 DB 사용Happy Path + 예외 케이스 + 경계값 등 다양한 시나리오 테스트📌 예시 코드@SpringBootTest class ProductServiceTest { @Autowired private ProductService productService; @Autowired private ProductRepository productRepository; @AfterEach void tearDown() { productRepository.deleteAllInBatch(); ; } @DisplayName("신규 상품을 등록한다. 상품번호는 가장 최근 상품의 상품번호에서 1증가한 것이다.") @Test void createProduct() { //given Product product = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000); productRepository.save(product); ProductCreateServiceRequest request = ProductCreateServiceRequest.builder() .type((HANDMADE)) .sellingStatus((SELLING)) .name("카푸치노") .price(5000) .build(); //when ProductResponse productResponse = productService.createProduct(request); //then assertThat(productResponse) .extracting("productNumber", "type", "sellingStatus", "name", "price") .contains("002", HANDMADE, SELLING, "카푸치노", 5000); List products = productRepository.findAll(); assertThat(products).hasSize(2) .extracting("productNumber", "type", "sellingStatus", "name", "price") .containsExactlyInAnyOrder( tuple("001", HANDMADE, SELLING, "아메리카노", 4000), tuple("002", HANDMADE, SELLING, "카푸치노", 5000) ); }주의: 테스트에서 @Transactional을 사용할 경우, 실제 코드와 작동 방식이 달라질 수 있음 → 필요에 따라 deleteAllInBatch()로 정리3. Persistence Layer (저장소 계층 - Repository)✅ 역할데이터베이스와 직접 상호작용 (CRUD, 조건 검색 등)JPA, MyBatis 등 ORM/쿼리 도구 사용✅ 테스트 전략@DataJpaTest를 사용하여 가벼운 JPA 테스트테스트 종료 시 자동으로 Rollback 처리됨실제 DB 조회/저장 동작 검증📌 예시 코드@ActiveProfiles("test") //@SpringBootTest @DataJpaTest class ProductRepositoryTest { @Autowired private ProductRepository productRepository; @DisplayName("원하는 판매상태를 가진 상품들을 조회한다.") @Test 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) ); }요약Controller : HTTP 요청/응답 처리, @WebMvcTest + MockMvc로 요청 파라미터, 상태 코드, JSON 응답 값 등을 테스트Service : 비즈니스 로직 처리, @SpringBootTest or JUnit + Mockito로 로직 분기, 예외 처리, 흐름 제어 등 테스트Repository : 데이터 CRUD 및 조회, @DataJpaTest로 쿼리 정확성, 연관 관계 매핑, 조건 검색 등을 테스트
2025. 03. 23.
0
[워밍업 클럽 3기 BE 클린코드&테스트] - 3주차 발자국
회고3주차에도 많은 것을 학습하였습니다. 테스트 코드 적용 실습과 함께 Persistence Layer, Business Layer, Presentation Layer 계층마다의 특징과 테스트코드 작성법을 직접 적용해보는 점이 좋았습니다. 마지막 주차도 열심히 수강하여 완강을 목표로 해보겠습니다.!강의 내용 요약테스트 코드가 필요한 이유시간 절약: 수동 테스트보다 빠르고 자동화 가능안정성: 휴먼 에러 방지, 신뢰성 있는 코드인수인계 용이: 명확한 테스트가 곧 문서 역할단위 테스트작은 코드 단위를 외부 의존 없이 테스트로 빠르고 안정예외 케이스, 경계값 등을 세분화해서 테스트 단위 테스트 작성할 때 주의점암묵적이거 아직 드러나지 않은 요구사항이 있는가? 에 대한 의문을 가지며 테스트 케이스를 세분화하자해피케이스, 예외케이스에 대해서 고루 케이스를 세분화하자.테스트하기 어려운 값 대해서는 분리하거나 인터페이스화 해서 테스트에 용이한 구조로 만들자 Layered Architecture와 테스트Persistence Layer Test쿼리가 의도대로 동작하는지 검증쿼리를 구현하는 기술이 바뀌어도 기능의 동작을 보장하도록 테스트를 작성, Repository 테스트@DataJpaTest를 사용하면 DB 관련 Bean만 로딩, 테스트 후 자동 rollback(트랜잭션 보장)으로 쿼리 구현 방식이 바뀌어도 동작 보장Business Layer Test비즈니스 로직 흐름과 트랜잭션 처리 검증실제 빈 전체 로딩, 요청값을 생성해 흐름 테스트트랜잭션 rollback으로 데이터 일관성 확인@Transactional readonly=true로 성능최적화 가능 Presentation Layer Test외부 요청, 파라미터 검증 중심의존 관계를 가짜객체를 사용해서 환경을 재현합니다. Service 등은 가짜(Mock)로 주입, 요청-응답 구조, 유효성 검사 테스트에 초점 미션Day 11 - 스터디카페 프로젝트 단위 테스트 작성https://github.com/5jeong/readable-code/tree/main/src/test/java/cleancode
2025. 03. 16.
0
[워밍업 클럽 3기 BE 클린코드&테스트] - 2주차 발자국
회고2주차에도 많은 것을 학습하였습니다. 특히, 7일차 미션에서 직접 리팩토링을 진행해본점이 인상깊었습니다.강의를 들으면서 이해했다고는 생각했지만, 막상 실제로 적용해보려니 어려웠고, 배웠던 개념을 떠오르기가 쉽지않았습니다. 그 후 강의를 들으면서 부족한점을 채워가면서 복습도 되고 좋았습니다! 또한 같은 수강생분들의 코드를 보면서 배울점도 많았습니다. 남은 주차와 테스트 강의도 열심히 듣고 참여하겠습니다!강의 내용 요약섹션 6. 코드 다듬기1⃣ 주석의 양면성주석이 많다는 것은 비즈니스 요구사항을 코드에 제대로 녹이지 못했다는 신호만약 의사 결정의 히스토리를 코드로 파악할 수 없다면, 상세한 설명이 필요하다.하지만, 불필요한 주석은 오히려 가독성을 해치므로 최소화하는 것이 중요하다.2⃣ 변수와 메서드의 배치 순서변수는 사용되는 순서대로 나열하여 인지적 경제성을 높인다.공개 메서드(Public)는 기준을 가지고 배치하고, 비공개 메서드(Private)는 공개 메서드의 호출 순서에 맞춰 정리하는 것이 좋다.3⃣ 패키지 구조 설계패키지는 문맥을 제공하는 역할을 한다.패키지를 너무 크게 두면 관리가 어렵고, 반대로 너무 세분화해도 유지보수가 어렵다.처음부터 신중하게 패키지 구조를 고민하고 설계해야 한다. 4⃣ IDE의 도움받기코드 스타일을 유지하면 가독성이 향상된다.자동 포맷팅, 린트 도구, 코드 스타일 설정 등을 적극 활용하자.섹션 8. 기억하면 좋은 조언들1⃣ 능동적 코드 읽기복잡한 코드나 난잡한 코드를 읽을 때, 그냥 이해하려 하지 말고 리팩토링하며 읽자.핵심 목표는 코드를 이해하는 것이 아니라, 도메인 지식을 늘리는 것이다.2⃣ 오버 엔지니어링 피하기필요 이상의 복잡한 설계를 하는 것은 불필요하다.적정 수준의 설계를 유지하고, 꼭 필요한 곳에만 적용하자.3⃣ 은탄환은 없다현실적인 소프트웨어 개발은 빠른 결과물과 기술 부채 간의 균형을 맞추는 과정이다.모든 기술과 방법론은 적정 수준에서만 활용해야 한다. Practical Testing (실전 테스트)섹션 2. 테스트는 왜 필요할까?사람이 직접 테스트하면 예측하지 못한 사이드 이펙트(부작용)가 발생할 가능성이 높다.테스트 코드를 작성하면 빠른 피드백, 자동화, 안정감을 확보할 수 있다.테스트는 귀찮더라도 소프트웨어의 품질을 보장하기 위해 필수적이다.섹션 3. 단위 테스트 (Unit Test)✅ JUnit5로 단위 테스트하기단위 테스트: 작은 코드 단위를 독립적으로 검증하는 테스트JUnit5: 자바의 대표적인 단위 테스트 프레임워크AssertJ: 테스트 코드 작성을 더 간결하고 명확하게 도와주는 라이브러리✅ 테스트 케이스 세분화하기암묵적인 요구사항이 있는지 고민하자.해피 케이스(정상적인 입력) & 예외 케이스(잘못된 입력)에 대한 테스트를 함께 고려해야 한다.✅ 테스트하기 어려운 영역을 분리하기테스트하기 어려운 부분을 외부로 분리하면 테스트 가능성이 높아진다.예를 들어, DB 접근 로직을 인터페이스로 분리하면 더 쉽게 테스트할 수 있다.섹션 4. TDD (Test-Driven Development)TDD는 프로덕션 코드보다 테스트 코드를 먼저 작성하는 개발 방법론이다.TDD 3단계:실패하는 테스트 작성 (프로덕션 코드 없이 테스트부터 작성)테스트를 통과하는 최소한의 코드 작성 (엉터리라도 우선 통과)리팩토링하여 코드 개선섹션 5. 테스트는 문서다✅ 테스트는 곧 문서다테스트 코드는 프로덕션 코드의 동작을 설명하는 문서 역할을 한다.@DisplayName을 활용하여 테스트 의도를 명확하게 기술하자.✅ 테스트 네이밍 가이드라인명사의 나열보다는 문장으로 작성하기테스트 행위에 대한 결과까지 기술하여 명확한 의도를 전달하기도메인 용어를 사용하여 한층 추상화된 내용을 담기✅ BDD 스타일로 작성하기BDD (Behavior-Driven Development, 행위 주도 개발) 방식은 테스트를 더 직관적으로 만든다.Given / When / Then 패턴을 활용하여 테스트를 구조화하면 가독성이 향상된다.@DisplayName("주문 목록에 담긴 상품들의 총 금액을 계산 할 수 있다.") @Test void calculateTotalPrice() { //given CafeKiosk cafeKiosk = new CafeKiosk(); Americano americano = new Americano(); Latte latte = new Latte(); cafeKiosk.add(americano); cafeKiosk.add(latte); //when int totalPrice = cafeKiosk.calculateTotalPrice(); //then assertThat(totalPrice).isEqualTo(8500); }✅ 테스트가 읽기 쉬워지고, 테스트가 곧 문서 역할을 하게 됨 미션Day 7 -리팩토링 연습https://github.com/5jeong/readable-code/tree/main/src/main/java/cleancode/mission/day7리팩토링을 진행하고, 강의내용과 남들의 코드를 보니 부족함을 많이 느꼈다. 그만큼 얻는것도 많았다!
2025. 03. 09.
0
[워밍업 클럽 3기 BE 클린코드&테스트] - 1주차 발자국
회고1주차에 정말 많은 것을 학습하였습니다. 추상부터 객체지향까지 클린코드에 대해 자세하고 이해가기 쉬운 강의였습니다.클린코드에 대해 관심을 갖고는 있었지만, 어떻게 적용하고, 어디까지 적용해야하는지가 어려웠었는데, 이제는 감이 조금은 오는것 같습니다. 남은 주차와 강의도 열심히 듣고 참여하겠습니다!강의 내용 요약1. 추상우리가 클린 코드를 추구해야 하는 이유가독성이 높아지면 코드가 잘 읽히고 이해가 쉬워짐.유지보수가 용이해져 시간과 자원이 절약됨.코드의 의도를 명확히 전달하여 협업과 확장성이 향상됨.추상과 구체추상화는 불필요한 정보를 제거하고 중요한 정보만 남기는 과정.추상화 수준이 높을수록 중요한 부분만 남고 불필요한 디테일이 제거됨.메서드 이름 짓기이름은 코드의 가독성과 직결됨.명확하고 직관적인 이름 사용 (줄임말 지양, 도메인 용어 활용).단수/복수를 명확히 구분하여 의미 전달.메서드와 추상화하나의 메서드는 하나의 역할만 수행해야 함.추상화 수준을 맞춰 메서드를 설계해야 함.의미를 드러내는 적절한 이름, 파라미터, 반환값을 고려해야 함.메서드 선언부메서드명: 추상화된 구체를 유추할 수 있도록 의미 있는 이름 사용.파라미터: 타입, 개수, 순서를 통해 의미를 명확히 전달.반환 타입: void 사용을 최소화하고 가급적 값을 반환하도록 설계.추상화 레벨하나의 코드 블록 내에서 동일한 추상화 레벨을 유지해야 함.코드의 가독성과 유지보수성을 높이기 위해 상위 개념과 하위 개념을 분리.매직 넘버, 매직 스트링의미 없이 사용된 숫자(매직 넘버)와 문자열(매직 스트링)은 가독성을 해침.상수로 추출하여 의미를 부여함으로써 가독성과 유지보수성을 확보.2. 논리, 사고 흐름Early Returnelse if나 else 사용을 최소화하여 흐름을 단순하게 유지.빠르게 반환할 수 있는 경우 return을 이용해 코드의 복잡도를 줄임.switch 문도 가능하면 피하는 것이 좋음.사고의 Depth 줄이기중첩 분기문과 반복문을 줄여 가독성을 높임.복잡한 로직은 별도의 메서드로 분리하여 한눈에 이해할 수 있도록 설계.가독성 있는 공백 라인공백 라인은 로직을 구분하는 역할을 함.의미 있는 단위로 공백을 활용하여 코드의 흐름을 명확히 표현.부정어를 대하는 자세 → 긍정으로 처리부정형(!isValid) 대신 긍정형(isInvalid)으로 변경하여 가독성 향상.독자가 사고를 반전시키지 않도록 명확한 표현 사용.해피 케이스와 예외 처리예외는 최소화하고, 발생 시 명확하게 구분하여 처리.예상하지 못한 예외와 의도된 예외를 구별.null 반환을 피하고 Optional을 활용하되, 불필요한 사용을 방지.3. 객체지향 패러다임객체 설계하기캡슐화: 객체의 내부 로직을 숨기고, 공개된 메서드를 통해 외부와 소통.객체 간 협력: 객체의 책임을 분리하여 협업을 유도.높은 응집도: 관련 기능을 하나의 객체에 모아 유지보수성을 증가시킴.낮은 결합도: 객체 간 의존성을 최소화하여 확장성을 확보.객체 생성 시 주의사항단일 책임 원칙 준수: 하나의 객체는 하나의 명확한 역할만 수행해야 함.Setter 사용 최소화: 내부 상태를 직접 변경하지 않도록 관리.불가피한 경우 setX() 대신 updateX() 같은 의미 있는 이름 사용.Getter 사용 지양: 데이터를 직접 가져오는 대신, 객체 내에서 해결하도록 설계지양하라는거지, 안쓰라는 말이 아님 (꼭 필요하면 써야한다)필드 최소화: 불필요한 데이터를 줄이고, 계산 가능한 값은 메서드로 제공.단, 성능 이점이 있다면 필드로 저장 가능.SOLID 원칙SRP (단일 책임 원칙): 하나의 클래스는 하나의 책임만 가져야 함.OCP (개방-폐쇄 원칙): 확장에는 열려 있고, 변경에는 닫혀 있어야 함.LSP (리스코프 치환 원칙): 하위 클래스는 상위 클래스를 대체할 수 있어야 함.ISP (인터페이스 분리 원칙): 클라이언트에 맞는 인터페이스를 제공해야 함.DIP (의존성 역전 원칙): 추상화에 의존하고, 구체적인 구현체에 의존하지 않아야 함.4. 객체지향 적용하기상속과 조합상속(Inheritance)은 공통 기능을 재사용할 때 활용.조합(Composition)은 유연성과 확장성을 고려하여 객체 간 관계를 구성할 때 활용.무분별한 상속은 피하고, 조합을 적극 활용하여 결합도를 낮춤.Value Object (값 객체)값 자체로 불변성을 가지는 객체.equals()와 hashCode()를 오버라이딩하여 동등성 비교.예: Money, Address와 같은 값 중심의 개념.일급 컬렉션컬렉션을 감싸는 클래스로, 컬렉션과 관련된 로직을 하나의 객체에서 관리.컬렉션을 직접 노출하지 않고 캡슐화하여 무결성 유지.Enum의 특성과 활용상수 값 그룹화: 코드의 가독성과 유지보수성을 높임.행위 추가 가능: 인터페이스 구현 및 메서드 정의 가능.스위치 문 대체: enum 메서드를 활용하여 분기 처리 가능.만약 변경이 잦은 개념은 Enum보다 DB로 관리하는 것이 나다형성 활용인터페이스와 추상 클래스를 활용하여 유연한 설계.if-else 대신 enum이나, 전략 패턴(Strategy Pattern)등 을 사용하여 유지보수성 향상.의존성 주입(DI)을 활용하여 객체 간 결합도를 낮추고 테스트 용이성 확보.숨겨져 있는 도메인 개념 도출하기도메인 지식은 만드는 것이 아니라 발견하는 것이므로, 현재의 최선을 다하자.미션Day 2 - 추상과 구체미션 답변추상적 개념: "사용자가 선착순 쿠폰을 발급받는다."구체적 과정:사용자가 특정 쿠폰 발급 요청을 보냄.Redis의 Set 자료구조에서 남은 쿠폰 개수를 확인.쿠폰 수량이 남아 있다면 Redis에서 하나를 제거하고 발급 처리.발급된 쿠폰 정보는 DB에 저장되고, 사용자는 쿠폰을 발급받음.트랜잭션을 통해 동시성 제어를 수행하여 중복 발급 방지.요약서비스 제공 입장에서는 추상적 개념으로 인식.실제 개발 시에는 구체적인 과정을 설계해야 함.개발 과정에서도 다양한 추상적 사고가 필요.Day 41. 읽기 좋은 코드로 리팩토링public class Day4Mission { public boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; } } 기존 코드 문제점중첩된 if-else 구조로 인해 가독성이 떨어짐.불필요한 조건 체크가 반복됨.논리 흐름을 이해하기 어렵고 유지보수가 어려움.리팩토링 방식Early Return을 적용하여 중첩 구조 제거.의미 있는 메서드 (isOrderEmpty, hasNoCustomerInfo, isTotalPriceInvalid)를 생성하여 가독성 향상.메서드를 활용하여 논리 흐름을 단순화.리팩토링 후 코드private boolean validateOrder(Order order) { if (isOrderEmpty(order)) { log.info("주문 항목이 없습니다."); return false; } if (hasNoCustomerInfo(order)) { log.info("사용자 정보가 없습니다."); return false; } if (isTotalPriceInvalid(order)) { log.info("올바르지 않은 총 가격입니다."); return false; } return true; } private boolean hasNoCustomerInfo(Order order) { return !order.hasCustomerInfo(); } private boolean isOrderEmpty(Order order) { return order.getItems() == null || order.getItems().isEmpty(); } private boolean isTotalPriceInvalid(Order order) { return order.getTotalPrice() 1. SOLID에 대하여 자기만의 언어로 정리SOLID원칙의 정의와 예시, 그리고 스프링에서 적용되는지에 대해 작성해보았습니다.스프링에서 SOLID 원칙 알아보기
백엔드