westhan
@westhan
수강평 작성수
11
평균평점
5.0
블로그
전체 8#카테고리
- 백엔드

2025. 06. 22.
1
워밍업 클럽 4기 백엔드 - 4주차 발자국
강의 내용Practical Testing: 실용적인 테스트 가이드Test Double테스트를 위해 실제 객체나 모듈 대신 사용하는 가짜 객체/모듈Dummy, Fake, Stub, Spy, MockMockito, BDDMockitoMock 프레임워크@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 등 mocking을 편리하게 할 수 있도록 돕는 프레임워크BDDMockito는 기존의 Mockito가 제공하는 API가 BDD style에 맞지 않는다는 문제를 해결하기 위해 Mockito를 wrapping 해서 BDD 스타일로 테스트를 작성할 수 있도록 해준다. Test Fixture테스트를 위한 데이터 준비 시 테스트의 독립성이 보장되고, 테스트의 내용을 이해하는데 문제가 없도록 해야 함Given 절에 해당하는 Test Fixture가 숨어버리는 정보의 파편화로 인해 테스트의 내용을 한 번에 이해할 수 없게 되는 경우를 주의 Parameterized Test, Dynamic Test하나의 테스트는 하나의 테스트 케이스만 검증테스트에서 쓰이는 반복문, 분기문은 곧 테스트가 여러 케이스를 검증하고자 한다는 방증하나의 테스트 케이스에 속하는데 여러 값을 넣어보며 대해 검증하고자 한다면, @ParameterizedTest 사용시나리오에 따라 공유 자원의 상태를 추적하며 순차적, 단계적으로 테스트를 하고 싶다면 @DynamicTest를 이용테스트 환경 통합테스트도 비용이며, 테스트의 실행 시간을 늘리는 가장 큰 요인 중 하나는 Spring 서버를 띄우는데 소요되는 시간테스트 환경을 통합하면 서버를 다시 띄우는 횟수가 줄어들어 테스트 비용 절감 가능DataJpaTest를 사용해 Jpa 관련 Bean만 생성하는 것보다 SpringBootTest를 이용해 테스트 환경을 재구성 횟수를 줄이는 편이 더 효율적일 수 있음private 메소드의 테스트, 테스트에서만 필요한 메소드private 메소드에 대한 테스트는 기본적으로 public 메소드를 테스트하는 과정에서 함께 이루어지며, private 메소드에 대한 테스트는 불필요하다.private 메소드 테스트가 필요하다고 판단하는 경우는 객체 분리가 필요한 시점은 아닌지 고민해봐야 하는 상황테스트에서만 필요한 메소드를 정의해 사용할 수 있다.다만 trade-off가 존재하며, 객체가 마땅히 할 법한 행위이며 미래에 프로덕션 코드에서 사용될 가능성이 있는 콛으인 경우에만 사용해야 한다.학습 테스트테스트 코드를 작성하면서 새로운 기술, 라이브러리 등에 대해 익히는 것은 효과적인 학습법이다.Spring REST Docs테스트 코드를 이용해 API 문서를 작성할 수 있는 자동화 도구테스트 코드를 통과해야 문서가 만들어지기 때문에 Swagger에 비해 신뢰도가 높으며, 프로덕션 코드에 비침투적이라는 장점이 있다. 회고드디어 완강을 했다. '드디어'라는 표현이 무색하게도, 이제부터가 시작이라는 생각이 든다. 강의를 다 들었고 코드도 착실하게 따라서 쳤지만, 더 많은 고민과 연습이 필요할 것 같다. 어떻게 하면 더 읽기 좋고, 객체지향적인 코드를 짤 수 있을지, 어떻게 해야 좋은 테스트를 작성할 수 있을지 시간을 들여 고민하고, 경험을 쌓아서 체화할 필요가 있다.이전 강의도 마찬가지였지만, 이번주 강의 내용은 특히 더 많은 도움이 되었다. 워낙 익숙하지 않아 어렵게 느껴지는 Mocking에 조금이나마 익숙해질 수 있었고, 테스트를 처음 접하는 입장에서 생겼던 의문 중 상당수가 자연스럽게 해소되었다. 워밍업 클럽 스터디에 참여하면서 중간 중간 미션도 수행하고, 다른 러너 분들의 코드도 보고, 중간 점검 시간도 갖고, 발자국을 작성하며 복습도 하게 되어 강의를 따라가는 과정이 훨씬 수월했던 것 같다. 스터디 기간 동안 학습한 내용을 조금씩 실무에 응용해보면서 연습을 해봤는데, 이제 스터디가 끝났으니 개인적으로 프로젝트를 진행하면서 배운 것을 녹여내어 내 것으로 만들어볼 생각이다. 미션Day 16Day 16 미션은 Layered Architecture에서의 레이어 별 특징과 테스트 작성 방법에 대해 나만의 언어로 정리하는 미션이었다. 단순히 강의를 들으며 정리했던 내용을 복습하는 것을 넘어서, 강의에서 다룬 레이어별 테스트 방법을 떠올리며 왜 그런 방식을 소개해 주셨는지 고민하면서 미션을 수행했다. 일종의 best practice를 제시해 주신 것으로 생각하지만, 테스트 작성 경험이 없던 터라 강의에서 배운 방식 외에도 직접 다양한 방식을 시도해보고 싶은 마음이 들었다. Day 18Day 18 미션은 Mockito에서 Mock 객체 생성 및 주입을 위한 어노테이션들의 차이점을 정리하고, 예시로 주어진 테스트 코드 작업 항목을 @BeforeEach, given절, when절에 배치해보는 미션이었다. 공통적으로 필요한 데이터는 @BeforeEach 안에, 테스트 하고자 하는 핵심 기능은 when절에 작성하고, 나머지는 given절에 작성했다.2차 중간 점검에서 Day 18 미션의 핵심은 중복 제거가 아니라 '도메인'이었다는 피드백을 받았다. 사용자와 게시물을 생성하는 코드는 '댓글'이라는 테스트 대상 도메인이 아닌 부수적 요소이므로 setUp()에 작성하고, 핵심이 되는 '댓글' 관련 로직만 각 테스트 메소드에 작성할 수 있다는 관점이었다. 미션 수행 당시에 가장 고민했던 사용자 2의 데이터 생성 코드는 setUp에 넣든 3번 테스트에 남기든 크게 중요하지 않았다. 미션을 수행할 때는 테스트 1, 테스트 2에서 사용되지 않을 데이터이니 setUp으로 빼지 않기로 결정했는데, setUp에 해당 코드가 위치하더라도 테스트의 독립성이 깨지는 등의 문제가 발생하지는 않으니, 오히려 댓글이라는 핵심 도메인에 확실히 집중할 수 있겠다는 생각이 들었다. 테스트가 필요한 이유, 테스트의 목적에 대해 다시 한 번 생각해보게 하는 답변이었다. 결국 코드라는 것은 목적을 위한 수단이고, 테스트 코드 역시 마찬가지다. 강의 후반부에서도 다루었듯이, 학습 테스트나 Spring REST Docs 등 테스트는 다양한 목적으로 사용할 수 있다. 항상 목적을 잊지 않고, 그 목적에 맞게 코드를 작성하도록 주의해야겠다는 생각이 들었다.
백엔드

2025. 06. 18.
0
워밍업 클럽 4기 백엔드 - Day 18 미션
Practical Testing: 실용적인 테스트 가이드 1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다.@Mock, @MockBean은 순수 Mock 객체 생성 시에 사용실제 객체 없이 동작을 모의하는 가짜 객체 @Spy, @SpyBean은 Spy 객체 생성 시에 사용실제 객체의 동작을 그대로 실행하면서 일부 기능을 Stubbing(실제 객체를 감싸는 Spy 객체 생성)Mock과 달리 Stubbing 할 메소드를 별도로 지정해주어야 함 @MockBean, @SpyBean은 Spring Bean 형태로 객체 생성생성한 Mock, Spy 객체를 Spring Context에 등록하여 기존의 Bean을 대체Spring Boot 서버를 띄우는 테스트에 사용 가능(@SpringBootTest) @Mock, @Spy는 Spring Boot 컨텍스트 없이 객체 생성 @InjectMocks는 해당 어노테이션이 달린 객체를 생성하면서, @Mock으로 생성한 Mock 객체를 주입해주는 역할@Mock으로 생성한 객체에 의존하는 객체 생성 + Mock 객체 의존성 주입@MockBean의 경우, @WebMvcTest(*.class)와 함께 사용하며 @MockBean으로 생성한 Bean이 Spring Context에 등록되어 의존성 주입이 이루어지므로 @InjectMocks와 함께 사용하지 않음 상황별 선택 방법Spring Context 없이 POJO로 순수 로직을 검증할 때는@Mock & @InjectMocks, @Spy 사용Spring 환경에서 테스트 시@MockBean, @SpyBean 사용실제 객체를 사용하고, 일부만 스텁 시@Spy, @SpyBean 사용실제 객체를 사용하지 않는 경우@Mock(+@InjectMocks), @MockBean 사용 2. 아래 3개의 테스트가 있습니다.내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)@BeforeEach void setUp() { // 각 테스트에 공통적으로 필요한 내용 준비 0-1. 사용자 생성에 필요한 내용 준비 0-2. 사용자 생성 0-3. 게시물 생성에 필요한 내용 준비 0-4. 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-1. 댓글 생성에 필요한 내용 준비 // when 1-2. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-1. 댓글 생성에 필요한 내용 준비 2-2. 댓글 생성 // when 2-3. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-1. 사용자2 생성에 필요한 내용 준비 3-2. 사용자2 생성 3-3. 사용자1의 댓글 생성에 필요한 내용 준비 3-4. 사용자1의 댓글 생성 // when 3-5. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 } 판단 기준when 절에는 검증하고자 하는 핵심 동작을 작성given 절에서는 when 절의 동작을 위해 필요한 데이터를 준비@BeforeEach에서는 모든 테스트에서 공통적으로 필요한 데이터만 준비하고, 일부 테스트에서만 사용되는 데이터(사용자 1 댓글, 사용자 2 생성 등)는 해당 테스트 내에서 독립적으로 준비
백엔드

2025. 06. 17.
0
워밍업 클럽 4기 백엔드 - Day 16 미션
Practical Testing: 실용적인 테스트 가이드 Layered Architecture 구조의 레이어별 테스트 작성법을 알아보았습니다. 레이어별로 1) 어떤 특징이 있고, 2) 어떻게 테스트를 하면 좋을지, 자기만의 언어로 다시 한번 정리해 볼까요? Persistence Layer특징'영속성'이라는 이름처럼 영속성을 지니는 데이터에 접근하기 위한 계층컴퓨터에서 영속성을 지니는 데이터란 곧 보조기억장치에 저장되는 파일을 의미데이터에 접근한다는 말은 곧 데이터를 읽거나 쓰는 작업을 의미결과적으로 영속성 계층에 대한 테스트는, 의도한대로 데이터를 잘 읽고 쓰는지를 확인하기 위한 목적의 테스트일반적으로 JPA 등의 기술을 이용해 Persistence Layer를 구성하며, Persistence Layer가 비즈니스 로직을 포함하지 않도록 주의 테스트 방법@DataJpaTest 또는 @SpringBootTest를 이용해 Bean을 생성하고, 테스트 하고자 하는 Repository의 Bean을 주입 받아 테스트를 진행한다. 데이터 읽기, 쓰기 작업이 잘 이루어지는지 검증하는 비교적 단순한 테스트가 이루어져 약간 단위 테스트 같은 느낌@DataJpaTest를 쓰면 Repository 관련 Bean만 생성해서 서버를 띄우기 때문에 상대적으로 가볍고, @Transactional이 적용되어 있어 테스트 메소드 실행 후 자동으로 Rollback 된다. Business Layer특징서비스의 가장 핵심이 되는 계층으로, 비즈니스 로직을 포함하는 계층비즈니스 로직과 도메인 규칙은 모두 비즈니스 계층에 위치하는 것이 원칙비즈니스 로직을 처리하기 위해서는 데이터가 필요하므로, 자연스럽게 Persistence Layer에 의존한다.비즈니스 계층이 영속성 계층에 의존하는 것은 자연스러운 일이지만, JPA 기술을 사용하는 경우에는 결합도가 너무 강해지는 측면이 있으며 결합도 문제를 해결하기 위한 구조로 헥사고날 아키텍처가 고안되기도 했다.하나의 비즈니스 로직을 수행하기 위해 데이터에 여러번 접근해야 할 수 있으나, 데이터에 대한 접근은 원자성을 전제로 한다.즉, 하나의 비즈니스 로직에서 호출하는 Persistence Layer의 서비스들은 하나의 트랜잭션으로 묶는 것이 일반적이다. 테스트 방법일반적으로 비즈니스 계층의 메소드는 하위 레이어인 영속성 계층의 서비스에 의존하므로 @SpringBootTest를 이용한 통합 테스트로 진행한다.Repository에 비즈니스 로직을 처리하기 위한 데이터를 저장하고, 이를 기반으로 비즈니스 로직의 동작을 검증한다.Layered Architecture에서 정말 단순한 CRUD만 수행하는 서비스 메소드에 대한 테스트는 사실상 Repository 테스트와 크게 다르지 않을 수 있다. 그러나 비즈니스 로직은 얼마든 확장 또는 변경이 가능하므로 별도로 테스트를 작성해주는 것이 좋다.비즈니스 계층의 테스트 시에는 @Transactional 설정을 통해 트랜잭션의 범위를 명확히 설정해야 한다. Presentation Layer특징외부 세계와의 접점이 되는 계층 -> 인터페이스 cf. API외부의 요청을 받고, 비즈니스 계층의 서비스를 이용해 응답을 반환 -> 비즈니스 계층에 의존적Presentation Layer의 주요 책임은 요청 수신 & 응답 반환 테스트 방법요청을 받고 응답을 보내기 위한 계층이므로 테스트를 통해 요청 수신과 응답 반환이 정상적으로 이루어지는지를 검증한다.조금 더 자세히 풀어 설명하면, 유효한 입력 데이터가 넘어오는 경우와 그렇지 않은 경우에 각각 의도한대로 올바르게 동작하는지를 응답 데이터를 통해 검증해야 한다.Presentation Layer는 Business Layer와 Persistence Layer에 의존하지만, 비즈니스 계층과 달리 하위 계층의 서비스를 직접 이용하지 않고 Mock 객체를 사용해 테스트를 진행한다.@WebMvcTest(controllers = XxxController.class)를 이용해 직접 지정한 Bean만 로드해 테스트할 수 있다.등록한 Presentation Layer Bean은 내부적으로 Business Layer 객체에 의존하므로, Mock 객체를 생성해 대신 주입해주어야 한다.@MockitoBean을 사용해 Mock 객체를 생성해 주입해줄 수 있다.
백엔드

2025. 06. 15.
1
워밍업 클럽 4기 백엔드 - 3주차 발자국
강의 내용3주차에는 Practical Testing: 실용적인 테스트 가이드 강의의 Spring & JPA 기반 테스트 섹션을 수강했다.Layered Architecture와 테스트Presentation Layer - Business Layer - Presentation Layer 각 계층에 대한 테스트 방법테스트 하기 복잡해 보이지만, 기본적으로는 단위 테스트와 비슷테스트 하기 어려운 부분을 분리해 테스트 하고자 하는 영역에 집중하고, 명시적이며 이해하기 쉬운 테스트 코드 작성어떤 기술을 사용하는지가 중요한 게 아니고, 어떻게 테스트할 것인지에 집중여러 모듈, 여러 객체가 통합해서 동작하는 경우, 단위 테스트와 별개로 통합 시에 어떻게 동작하는지를 확인하기 위한 통합 테스트가 필요Persistence Layer영속성 계층은 Data Access 역할 수행비즈니스 로직이 포함되어서는 안 되며, CRUD 그 자체에만 집중 Business Layer비즈니스 로직을 담당트랜잭션 보장의 책임을 지님Persistence Layer와 연계하여 통합 테스트 느낌으로 테스트 작성Presentation Layer외부 세계의 요청을 가장 먼저 받는 계층Presentation Layer 테스트 시 가장 집중해야 할 부분은 파라미터에 대한 검증하위 레이어들이 정상적으로 동작할 것을 가정하고 Mocking 하여 테스트 수행Mock MVC: 목 객체를 이용해 스프링 MVC 동작을 재현할 수 있는 테스트 프레임워크 회고강의를 수강하며 테스트 코드를 사실상 처음 작성해본 입장에서 이번 3주차에 들은 강의는 정말 많은 도움이 된 것 같다. 3주차 강의를 들으면서는 강의를 멈춰 놓고 먼저 테스트 코드, 기능 구현을 해보고 강의를 재생해 확인하는 방식으로 공부했다. 테스트 코드를 직접 작성해보고 강의를 듣다 보니 왜 테스트 코드를 짜야 하는지 확실히 체감할 수 있었다. 코드 변경 시에도 테스트 코드를 통해 수시로 확인해볼 수 있어 한 층 편리하기도 하고, 어떤 테스트를 수행해야 할지 고민하는 과정에서 생각지 못 한 예외 케이스를 떠올릴 수 있었다. 복습하는 과정에서 작성했던 테스트 코드를 읽으면서 왜 테스트 코드도 문서라고 하셨는지도 이해했다.테스트 코드 강의를 수강하면서 생긴 고민이 한 가지 있는데, 여건 상 실무에서 테스트 코드를 작성하기가 쉽지 않다는 점이다. 지금 재직 중인 회사에서 '테스트'라는 건 Postman으로 API 호출해서 응답이 잘 돌아오고 데이터베이스에 값이 잘 들어가는지 확인하는 것, 브라우저로 개발한 페이지에 접속해서 기능 사용해 보는 과정을 의미한다. 물론 필요하지만, 뭐 하나 고칠 때마다 똑같은 걸 계속 하려다 보니 피로감이 컸다. 저번주 금요일에 들었던 테스트 강의 초반부에 정확히 동일한 내용이 나와서 이번주 강의 내용을 정말 기대했는데, 기대한 것 이상의 가치가 있는 내용이었다고 생각한다. 단순히 반복되는 수동 테스트의 피로도를 낮추는 것을 넘어서 코드 품질을 높일 수 있는 정말 좋은 방법 중 하나인 것 같다. 테스트 코드 도입은 어려울 것 같지만, 그렇더라도 의식적으로 테스트 용이성을 신경쓰면서 테스터블한 스타일로 개발하려는 노력 정도는 얼마든 해볼 수 있을 것 같다. 미션Day 11Day 11 미션은 Readable Code 강의에서 작성한 소스의 단위 테스트를 작성하는 것이었다. 미션 수행에 있어 '테스트가 필요하다고 판단하는 과정부터 테스트의 시작'이라는 조언을 주셨는데, 미션을 시작하려던 순간에 정말 공감되는 조언이었다. 테스트 코드 작성이 익숙하지 않았지만, 나름대로 중요한 비즈니스 로직이나 추후에 변경될 가능성이 있는 부분을 찾아 테스트 코드를 작성해보려고 노력했다. 가장 어려웠던 작업 중 하나는 @DisplayName을 작성하는 것이었다. 도메인 용어를 사용한 추상화 된 내용을 작성해야 하며, 도메인 정책의 관점으로 설명을 작성해야 한다고 했지만, 미션에서 작성한 단위 테스트는 워낙 간단한 내용들이라 처음에는 갈피를 잡지 못 했다. 얼마나 자세히 써야 할지, 자세히 쓴다고 썼지만 너무 구현 측면의 세부사항에 의존한 설명은 아닌지 계속 고민이 되었다. 많은 연습과 고민이 필요할 것 같다는 생각이 들었다.
백엔드

2025. 06. 08.
1
워밍업 클럽 4기 백엔드 - 2주차 발자국
강의 내용섹션 6에서는 코드를 조금 더 보기 좋게 다듬는 방법을 배웠다. 이전까지는 추상화에 초점을 맞추어 객체지향적인 코드를 작성하는 방법을 배웠다면, 섹션 6에서는 코드를 읽을 때 느낄 수 있는 불편함을 줄이기 위한 방법을 배웠다고 요약할 수 있을 것 같다.주석에는 의사결정 히스토리 등 코드로는 도저히 녹여낼 수 없는 정보만 작성자주 변하는 정보는 주석에 작성하지 않기 변수나 메소드의 경우 자연스러운 흐름으로 코드를 읽을 수 있도록 사용되는 순서나 중요한 순서에 따라 배치패키지 구조 또한 의미를 담을 수 있는 요소이며, 특히 변경이 어려운 부분이므로 처음부터 숙고하여 설계가장 좋은 코드는 상황에 맞는 코드, 클린 코드는 은탄환이 아니다.그러나 비즈니스 환경에서 적절한 수준으로 클린 코드 원칙을 활용하려면, 한계까지 사용할 줄 알아야 한다.이를 위해 때로는 극단적인 시도를 해보는 경험도 필요하다.테스트의 필요성새로운 기능 개발을 위해 기존의 코드를 변경할 일이 생긴다면, 코드 수정 후 기존에 테스트 했던 부분을 다시 테스트해야 하는 문제 발생테스트 코드가 없다면 사람이 모두 테스트 -> 사람이 직접 테스트하기 때문에 발생 가능한 위험 동반테스트 코드는 코드에 대한 올바른 검증이 이뤄질 수 있도록 잘 짜야 한다.JUnit 5, AssertJ테스트 케이스 세분화 -> 검증하고자 하는 로직의 해피 케이스 뿐 아니라 예외 케이스도 검증 e.g. 경계값 테스트테스트 하기 어려운 영역은 테스트 영역 외부로 분리 -> 관측 시마다 다른 값에 의존하는 코드, 외부 세계에 영향을 주는 코드 등TDD: 테스트 작성 후 기능 구현, Red-Green-Refactor테스트는 프로덕션의 기능을 설명하는 문서이며, 팀 전체의 자산 -> 상세한 DisplayName 등 문서로서의 역할을 수잘 행할 수 있도록 작성 회고Readable Code 강의를 들으면서 도메인 객체가 핵심 비즈니스 로직을 작성하는 방식을 처음 접했다. 강의 내용을 바탕으로 검색을 해보니, 이러한 코드를 Rich Domain Model이라고 한다는 사실을 알게 되었다. 국비학원에서 배웠고, 지금 다니는 회사에서 개발하는 방식, 서비스 클래스의 메소드에 절차적으로 로직을 쭉 작성하는 방식은 Transaction Script, Anemic Domain Model이라고 부른다고 한다. 올해 초에 읽었던 '함께 자라기'라는 책에서 비슷한 Rich Domain Model이라는 표현을 봤던 것 같은데, 사족에 가까운 문장이라 나중에 찾아봐야지 하고 넘겼던 것으로 기억한다. 진작에 좀 찾아볼 걸 그랬다는 생각에 약간 후회스러웠다. 그랬다면 좀 더 일찍 책임을 보고 인식하는 능력을 기를 기회가 있었을텐데.. 새삼 너무 준비가 안 된 상태로 섣부르게 취업한 게 아닌가 싶은 생각도 들었다.이번주는 개인적으로 조금 아쉬운 느낌이 강하다. 강의도, 미션도 정말 좋았지만 회사 일로 야근을 하게 되어 스스로 고민하고 연습할 시간이 너무 부족했다. 다음주부터는 비슷한 일이 생기더라도 좀 더 시간을 효율적으로 활용해 스스로 아쉬움이 남지 않도록 하고 싶다. 스스로 칭찬하고 싶은 점은 용기를 내서 코드 리뷰를 신청한 것이다. 스스로 생각하기에도 부족함이 많은 코드라서 민망하기도 하고, 다른 분들의 시간을 뺏는 게 아닌가 싶어 미안한 마음이 없지 않았다. 단순히 리팩토링을 잘했다 못했다를 떠나서, 고민이 부족했다는 생각이 들어서다. 그럼에도 불구하고 코드 리뷰라는 것을 경험해보고 싶은 마음에 용기내어 신청을 했고, 결과적으로 정말 잘한 일이라는 생각이 들었다. 미션Day 7Day 7 미션은 강의에서 배운 내용을 바탕으로 주어진 코드를 리팩토링하는 것이었다. 처음 손을 대려고 하니 뭐부터 해야 할지 감이 오지 않아 막막했다. 우선은 서로 if-else 분기문에서 중복된 코드를 뽑아서 if문 밖으로 꺼내는 것부터 시작했다. 손을 대기 시작하니 코드 중복을 줄이고 메소드를 추출하는 부분까지는 그래도 큰 어려움 없이 할 수 있었다. 이 과정에서 가장 어려웠던 것은 메소드 이름을 짓는 것이었다. 메소드를 추출한 뒤에는 객체의 책임을 어떻게 나누는 것이 합당할지 고민하면서 새로운 객체를 정의하고, 인터페이스로 추상화를 시도하고, 메소드를 다른 객체로 옮기면서 이런 저런 시도를 해봤다. 나에게는 역시 이 과정이 제일 어려웠다. 단순히 DTO에 필드를 정의하고 getter, setter를 모두 만들어 놓고 시작하던 기존의 방식을 버리고 객체가 스스로 로직을 처리하도록 하는데 집중했다.리팩토링 과정에서 놓친 부분이 많이 있었다. PassOrder 개념을 도출해 새롭게 정의한 객체가 가진 Nullable 필드에 대한 고민, 일급 컬렉션 객체의 메소드 이름을 너무 기존 로직에 특화하여 짓는 등이다. 한 번 직접 리팩토링을 해본 뒤 강의를 듣고, 내 코드에 대한 피드백까지 받아보니 확실히 많이 도움된 것 같다. 앞으로 더 깊이 고민하고, 다양한 시도를 해보면서 객체지향적인 사고와 감각을 많이 기르고 싶다.
백엔드

2025. 06. 01.
1
워밍업 클럽 4기 백엔드 - 1주차 발자국
강의 내용추상변수, 메소드, 객체를 정의하는 것을 결국 추상화의 한 과정이다.추상화를 통해 구체적인 구현 방식을 모르더라도 코드를 이해할 수 있게 되고, 이는 곧 한정된 뇌의 메모리 공간에 더 추상적인, 넓은 범위의 정보를 저장할 수 있는 인지적 경제성으로 이어진다.추상화의 이점을 제대로 활용하기 위해서는 읽는 사람으로 하여금 올바르게 추상을 이해할 수 있도록 코드를 작성해야 한다. 예를 들어, 컬렉션 이름을 단수로 짓는 등 오해할 여지를 남겨서는 안 된다. 읽는 사람이 직관적으로 유추하고 이해하기 어려운 축약어 사용을 지양하고, 도메인에 특화된 용어라면 해당 표현을 정리한 사전을 만들어 두는 것도 도움이 된다. 일반적으로 많이 사용하는 좋은 용어, 개념을 수집해 사용하는 것도 좋은 방법이다.하나의 세계 안에서는 추상화의 수준을 동등한 수준으로 맞춰줄 필요가 있다. 추상화의 수준이 다르면 코드를 읽는 흐름이 깨질 위험이 있다. 논리, 사고의 흐름사람의 뇌는 한정된 기억 공간을 갖는다. 따라서 코드를 읽을 때, 한 순간에 기억하고 있어야 하는 정보의 수를 줄이는 것이 읽기 좋은 코드를 작성하는 핵심이다. 이를 위해서는 중첩된 조건/반복문 사용을 지양하는 등 주의가 필요하다.예외에 대한 세심한 처리도 중요하다. 개발할 때는 기본적으로 해피케이스를 상정하고 코드를 작성하기 때문에, 예외적인 상황에 대해서는 의도적으로 고민하고 의심하며 처리할 필요가 있다.객체지향 패러다임객체는 추상화의 결과다. 객체를 정의해 사용함으로써 눈에 잘 보이지 않는 개념을 가시화 할 수 있고, 이를 이용해 코드에 대한 이해도 및 코드의 유지보수성을 높일 수 있다. 객체를 설계할 때는 SOLID 원칙에 입각하여 각 객체가 적절한 수준의 책임을 가지도록 해야 한다. 객체에 둘 이상의 책임을 부여해서도 안 되고, 다른 객체에 자신의 책임을 전가해서도 안 된다. 상속, 다형성 같은 개념에 대해 잘 이해하고 값 객체, 일급 컬렉션 등의 패턴을 활용하면 객체지향 패러다임의 장점을 잘 활용한 코드를 작성할 수 있다. 회고퇴근 후에 매일 강의를 챙겨 듣는 과정이 솔직히 쉽지는 않았다. 스스로 고민하면서 체득하는 과정이 필요했기에 더 그랬던 것 같다. 그래도 강의를 듣고 미션을 수행하면서 스스로 고민해보고, 코드를 개선해보면서 즐거움을 느꼈다. 사소한 의문이 들었던 순간이 종종 있었지만, 다음 주제의 리팩토링을 진행할수록 쌓였던 의문이 해소되는 과정을 경험하면서 강의 구성이 좋다는 생각이 들었다. 강의를 들으면서 많은 인사이트를 얻었는데, 특히 추상화의 레벨이나 객체지향 파트는 정말 도움이 많이 되었다. 미션Day2추상화의 예시를 작성하는 미션이었다. 이왕이면 컴퓨터나 개발과 관련된 예시를 찾고 싶어서 고민을 해봤고, 키보드 입력 예시를 들어보기로 결정했다. 처음에는 단순히 '키보드로 문자를 입력한다'라는 추상적인 문장을 써놓은 뒤에 조금씩 구체화 했다. 3-5 문장이라는 요건에 맞춰서 구체화를 했는데, 나름대로 추상화의 레벨을 맞춰서 쓰려고 노력해봤다. 대학에서 운영체제 수업을 들을 때는 추상화라는 단어를 알지 못 하고 있던 터라 미처 몰랐는데, 우리가 사용하는 컴퓨터는 끝없는 추상화의 산물이라는 생각이 들었다. 물론 컴퓨터 외에도 추상화는 언어부터 시작해 모든 분야에 필수적이지만, 특히 컴퓨터공학을 공부할 때 추상화라는 개념을 아는 것과 모르는 것의 차이는 결코 작지 않은 것 같다.Day4코드 리팩토링과 SOLID 원칙에 대해 정리하는 미션이었다. 처음에는 섹션3에서 다룬 내용만을 활용해 미션을 해결했다. early return, 중첩 조건문 제거, 부정 연산자 제거 및 메소드 이름 수정 등을 통해 리팩토링을 진행했다. 마치고 보니 조금 아쉬운 느낌이 들어서 더 나아가 섹션4에서 배운 객체지향 패러다임의 내용을 적용해 보았다. 리팩토링 된 코드를 보니까 확실히 잘 읽힌다는 생각이 들었는데, 내가 짠 코드라서 더욱 그렇게 보이는 것이 아닌가 싶은 의구심도 함께 들었다. 미션의 코드는 간단한 예제라서 더 나아졌지만, 리팩토링의 결과가 항상 더 좋은 쪽으로만 이어지지는 않을 것 같다. 다른 사람들과 공유하고 서로 리뷰하는 과정을 거치면 더 좋지 않을까 하는 생각이 들었다.
백엔드

2025. 05. 30.
0
워밍업 클럽 4기 백엔드 Day 4 미션
아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.as-ispublic 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; } to-bepublic boolean isValidOrder(Order order) { if (order.hasNoItems()) { log.info("주문 항목이 없습니다."); return false; } if (order.hasInvalidTotalPrice()) { log.info("올바르지 않은 총 가격입니다."); return false; } if (order.hasNoCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } return true; } early return추상화 레벨 맞추기부정어구 제거위 세 가지 개념을 도입해서 리팩토링 해봤습니다.개인적으로 validate라는 이름보다는 is, has 등이 boolean 타입을 반환하는 메소드 이름으로 좀 더 잘 어울리지 않나 싶어서 메소드 이름도 수정해봤습니다.위 메소드에서 검증 실패한 케이스가 결과적으로 주문 처리가 불가능한 상황이라면, 정상 흐름으로 처리할 수 없는 문제상황인 만큼 boolean을 반환해 처리 방식을 자율에 맡기는 것보다는 좀 더 명시적으로 처리가 불가능하다는 의미를 담아 예외를 던지는 편이 좀 더 낫지 않나 싶은 생각이 들었습니다.주문이 아닌 다른 도메인이라고 가정하고, 검증 실패 시 특정한 처리를 거쳐 정상 처리가 가능한 경우를 상정하더라도, 로그만 찍고 false를 넘겨버리면 호출하는 쪽에서는 무슨 문제 때문에 검증에 실패한 건지 구분할 수 없어서 불명확한 감이 있다는 생각이 들었습니다. 예외를 던지는 경우라면 사용자 정의 예외를 통해 각 케이스를 구별 가능해서 좀 더 유연한 오류 처리가 가능할 듯 합니다. 아래는 섹션 3의 내용을 적용해보니 좀 더 욕심이 생겨서 객체지향 패러다임에 대한 내용을 떠올리면서 바꿔본 내용입니다.아래 코드도 마찬가지로 사용자 정의 예외를 이용해 원인을 구분해 처리할 수 있도록 개선하면 더 좋을 것 같습니다.public class OrderItems { private final List items; public OrderItems(List items) { this.items = items; validateOrderItems(); } public validateOrderItems() { if (isNullOrEmpty()) { throw new IllegalStateException("주문 항목이 없습니다."); } if (hasInvalidTotalPrice()) { throw new IllegalStateException("올바르지 않은 총 가격입니다."); } } public boolean isNullOrEmpty() { return items == null || items.isEmpty(); } public boolean hasValidTotalPrice() { return calculateTotalPrice() > 0; } public boolean hasInvalidTotalPrice() { return !hasValidTotalPrice(); } public int calculateTotalPrice() { return items.stream().mapToInt(Item::getPrice).sum(); } // 할인 적용이나 사은품 추가 등 주문 항목의 변동 가능성이 있는 경우 이를 처리할 수 있는 메소드 제공 } public class Customer { // id, name 등 필요한 필드 선언 } public class Order { private final OrderItems items; private final Customer customer public Order(List itemList, Customer customer) { if (customer == null) { throw new IllegalStateException("사용자 정보가 없습니다."); } this.items = new OrderItems(itemList); this.customer = customer; } } 2. SOLID에 대하여 자기만의 언어로 정리해 봅시다.SOLID는 객체지향적 설계를 위한 다섯가지 기본 원칙이다.Single Responsibility Principle: 단일 책임 원칙객체는 프로그램의 목적을 달성하기 위해 다른 객체와 협력하며 주어진 책임을 수행한다. 이때 하나의 객체에는 하나의 책임만을 부여해야 한다. 객체란 하나의 관심사를 위한 데이터와 행위를 모아둔 집합으로, 서로 다른 관심사를 하나의 객체에 두는 것은 객체의 본질적인 목적에 어긋난다. 한 가지 이유로 객체를 수정하는 것이 여러 가지 이유로 객체를 수정하는 것보다 명료하다.Open-Closed Principle: 개방-폐쇄 원칙개방-폐쇄 원칙은 객체가 확장에는 개방적이되 확장으로 인한 코드의 수정에는 폐쇄적이어야 한다는 원칙이다. 자유롭게 확장을 할 수 있으면서도 확장 때문에 기존에 작성된 코드를 고쳐야 하는 일은 없어야 한다는 의미다.Liskov Substitution Principle: 리스코프 치환 원칙리스코프 치환 원칙은 자식 클래스가 부모 클래스를 대체할 수 있어야 한다는 원칙이다. 부모 클래스의 메소드에 대해 어떠한 기대를 가지고 있는 상태에서 동작의 수행 주체를 자식 클래스로 변경하는 경우, 자식 클래스가 그 기대를 배신해서는 안 된다. 즉, 자식 클래스는 부모 클래스에 정해져 있던 행동의 원칙을 위반해서는 안 된다. 부모 클래스가 discount()라는 이름의 메소드로 할인액을 계산해 제공해 주었다면, 자식 클래스에서 discount()가 할인이 적용된 가격을 반환하도록 재정의해서는 안 된다는 의미다.Interface Segregation Principle: 인터페이스 분리 원칙인터페이스 분리 원칙은 객체가 필요 없는 인터페이스에 의존해서는 안 된다는 원칙이다. 인터페이스를 구현할 때, 구현체에 불필요한 메소드가 존재한다면 그 메소드는 다른 인터페이스로 분리해야 한다. 생각해 보면 필요도 없는 메소드를 억지로 정의해 아무 의미도 없는 메소드를 만든다는 건 애초에 말이 안 된다.Dependency Inversion Principle: 의존성 역전 원칙의존성 역전 원칙은 구현이 아니라 추상에 의존해야 함을 의미한다. 헷갈릴 때는 자료구조 공부할 때 배웠던 ADT를 연상하면 될 것 같다. List ADT와 배열 리스트, 연결 리스트를 연상하자. 의존성 역전 원칙의 가장 큰 장점은 상위 모듈이 하위 모듈의 세부사항을 몰라도 상관 없다는 것이다. 클래스(구현)가 아니라 인터페이스, 추상 클래스(추상)에 의존하자.
백엔드

2025. 05. 28.
0
워밍업 클럽 4기 - 백엔드 Day 2 미션
강의 출처: Readable Code: 읽기 좋은 코드를 작성하는 사고법 "추상과 구체" 강의를 듣고, 생각나는 추상과 구체의 예시가 있다면 한번 3~5문장 정도로 적어봅시다. 추상: 키보드로 문자를 입력한다.구체:키보드의 키를 누른다.키보드의 메인보드가 입력을 감지하고, 입력이 디지털 신호로 변환되어 케이블을 통해 신호를 전달한다.컴퓨터의 메인보드가 수신한 디지털 신호를 IO 컨트롤러를 통해 OS에 전달한다.OS는 입력값을 감지하고, 활성화 된 애플리케이션 창에 키 입력을 전달한다.애플리케이션이 입력을 처리한 뒤, OS가 그래픽카드에 명령을 내려 화면을 갱신한다.




