🔥딱 8일간! 인프런x토스x허먼밀러 역대급 혜택

블로그

codestudy

[인프런 워밍업 스터디 클럽 3기 PM/PO] 4주차 발자국

[인프런 워밍업 스터디 클럽 3기 PM/PO] 4주차 발자국  인프런 워밍업 스터디 클럽 3기 PM/PO 4주차 발자국인프런 워밍업 스터디 클럽 3기 PM/PO 4주차 발자국📚 주간 학습 내용 요약1. 제품 발견(Product Discovery)의 개념과 중요성제품 발견이란 "무엇을 만들지 결정하는 과정" (deciding what to build)많은 제품들이 stakeholder-driven, sales-driven 방식 때문에 실패함제품팀(PM, 디자이너, 엔지니어)이 단순히 요구사항 수행보다 권한과 자율성을 가져야 함구시대적 프로세스(아이디어→로드맵→요구사항→디자인→개발)를 탈피해 이터레이션이 필요2. 성공적인 제품의 4가지 필수 요소Valuable(가치): 고객이 구매하고 유저가 사용하고자 하는 제품Usable(사용성): 사람들이 사용법을 익히고 사용할 수 있는 제품Feasible(실현 가능성): 우리 기술력으로 구현할 수 있는 제품Viable(지속 가능성): 사업적으로 타당하고 규제 및 이해관계자 요구를 충족하는 제품3. 제품 발견의 두 단계 프로세스Problem/Opportunity Discovery: 어떤 문제/기회에 집중할지 찾는 과정Solution Discovery: 문제를 어떻게 해결할지, 기회를 어떻게 활용할지 찾는 과정PM은 문제 정의에, 디자이너와 엔지니어는 문제 해결에 오너십을 갖되 긴밀한 협업 필요4. 가설(Assumptions)과 검증모든 제품 개발은 여러 가정에 기반함가치(Value), 사용성(Usability), 실현 가능성(Feasibility), 사업적 타당성(Viability) 관련 가정을 검증리스크가 크고 근거가 부족한 가정부터 우선적으로 검증해야 함검증 방법: 심층 인터뷰, 사용성 테스트, 데이터 분석, 프로토타이핑, A/B 테스트 등5. 제품 발견을 위한 전략적 프레임워크기회 솔루션 트리(Opportunity Solution Tree):고객 니즈/페인 포인트/욕구를 발굴하고 그룹핑기회를 상위 기회 밑에 구조화하여 집중할 영역 선정솔루션 아이디에이션, 가설 검증 및 실행으로 진행북극성 프레임워크(North Star Framework):지표에 초점을 맞춘 제품 발견 방법론북극성 지표: 제품팀이 영향을 끼칠 수 있고 장기적 사업 성과에 도움되는 지표인풋-아웃풋 관계 파악을 통한 가설 검증 중요6. 프로덕트 그로스(Product Growth)의 이해PMF(Product Market Fit) 달성 후에야 진정한 그로스 의미 가짐그로스 워크의 4가지 영역: Feature Work, Growth Work, PMF Expansion, Scaling Work그로스 레버(Growth Lever): Acquisition(획득), Retention(유지), Monetization(수익화)7. Acquisition(고객 획득) 전략마케팅/세일즈 외에도 제품을 통한 고객 획득 방법 중요신규 고객을 충분히 많이, 비용 효율적으로 획득하는 것이 목표비용 효율성 측정 지표: CAC, LTV, Payback Period네트워크 효과, UGC(User Generated Content), 검색엔진 최적화 활용B2B 제품에서도 Product-Led Growth(PLG) 방식 확산 중제품을 통한 획득의 장점: 자연스러운 성장, 광고효과, 마케팅 비용 절감8. Retention(유지)과 Activation(활성화)"Retention is King" - 리텐션이 그로스의 핵심 요소리텐션 측정 방식: 코호트 리텐션, Day N 리텐션, Bracket/Unbounded 리텐션리텐션이 좋은 제품은 장기적으로 큰 성장 차이를 만듦Activation은 리텐션을 위한 첫 번째 레버로, 사용자가 핵심 가치를 경험하는 과정Setup → Aha → Habit Moment의 3단계 구조:Setup Moment: 사용자가 제품 가치 경험을 위한 준비 완료Aha Moment: 사용자가 처음으로 제품의 핵심 가치 경험Habit Moment: 제품의 핵심 가치 경험이 습관화유저 온보딩은 단순한 기능 소개가 아닌, 핵심 가치 경험을 돕는 전체 과정사용자 행동 모델 (Nir Eyal의 'Hooked'): Trigger → Action → Reward → Investment9. Engagement(참여도) 측정과 개선리텐션을 위한 두 번째 레버로, 사용자의 제품 사용 활발도인게이지먼트 지표:Breadth(넓이): DAU, WAU, MAU (사용자 수)Depth(깊이): 기능 사용 깊이, 사용 시간Frequency(빈도): DAU/MAU, 재방문율Efficiency(효율성): 과업 성공률BJ Fogg의 행동 모델: Behavior = Motivation × Ability × PromptMotivation(동기): 즐거움, 기대, 소속감Ability(능력): 시간, 비용, 노력 등 행동 용이성Prompt(트리거): 행동 유도 알림인게이지먼트 핵심 시스템: 알림(Trigger) → 액션(Action) → 보상(Reward) → 투자(Investment)10. Monetization(수익화) 전략어디서 수익을 얻을 것인가?: 사용자, 광고주, 제3자무엇에 대한 대가로 돈을 받을 것인가?: Value Unit 정의가격 책정 고려 요소: 비용(Cost), 경쟁(Competition), 가치(Value)Usage-Based Pricing vs Seat-Based PricingIncentive Alignment: 고객과 회사의 이익 방향 일치시키기수익 극대화 전략:무료 사용자 그룹 늘리기 (Freemium, Free Trial)사용자가 지불하고 싶게 만들기 (Perceived Value 강화)인지편향 활용하기 (앵커링, 손실 회피 등)적절한 시점에 Prompt 제공하기11. 그로스 모델(Growth Model)제품 성장 메커니즘을 도식화한 개념적 지도제품의 성장에 영향을 끼치는 요소들을 시각화그로스 모델 예시: AARRR, Growth Loop, North Star Framework정성적 모델링부터 시작하여 각 요소의 영향력 이해12. PM/PO의 팀 협업 방식Hand-Off 방식보다 Collaboration 중심으로 접근엔지니어와 협업: 스테레오타입 버리기, 기술 이해하기디자인 피드백 주기: 프로젝트 이해 → 사용자 관점 경험 → 이슈 파악 → 우선순위화시도, 회고, 개선의 사이클 구축"PM이 항상 정답을 가질 필요는 없다" - 함께 좋은 방안 찾기🌟 핵심 학습 인사이트배운 점제품 성공의 핵심은 무엇을 만들지 결정하는 Product Discovery 과정에 있음성공적인 제품은 가치, 사용성, 실현 가능성, 지속 가능성 모두를 충족해야 함리텐션은 "왕(King)"으로서 장기적 성장의 핵심이며, Activation과 Engagement를 통해 개선그로스 모델링을 통해 제품 성장 메커니즘을 이해하고 전략적으로 접근 가능PM, 디자이너, 엔지니어는 각자의 영역만 담당하는 것이 아닌 협업이 핵심적용할 점문제/기회 발견과 솔루션 발견의 두 단계로 제품 발견 과정 체계화Setup → Aha → Habit의 명확한 경로 설계로 사용자 활성화 강화리텐션 개선을 위한 인게이지먼트 지표(Breadth, Depth, Frequency, Efficiency) 모니터링제품을 통한 Acquisition, Retention, Monetization 전략 구체화디자이너, 엔지니어와의 효과적인 협업 방식 도입BJ Fogg 행동 모델을 활용한 사용자 행동 유도 설계💭미션에 대한 회고이번 미션에서는 "코촉촉" 반려동물 돌봄 서비스의 Opportunity Solution Tree를 만들어보았습니다. 처음으로 OST를 작성해보는 경험이었는데, 생각보다 복잡했습니다.제품의 근본적인 문제와 기회를 식별하는 것부터 시작해서, 가장 중요한 기회를 선택하고 솔루션을 도출하는 과정을 경험했습니다. 특히 "바쁜 현대인의 반려동물 돌봄 시간 부족"과 "낯선 사람에게 반려동물 맡기는 불안감"이라는 두 가지 핵심 기회에 집중했습니다. 어려웠던 점은 너무 많은 기회와 솔루션이 떠올라서 정말 중요한 것을 선택하는 것이었습니다. 하지만 "전략의 핵심은 무엇을 하지 않을 것인가를 정하는 것"이라는 마이클 포터의 말을 떠올리며 집중할 부분을 좁혀갔습니다.앞으로 실제 프로젝트에서 OST를 활용한다면, 더 많은 사용자 리서치와 데이터를 기반으로 기회를 발굴하고, 팀원들과 함께 더 다양한 솔루션을 도출해보고 싶습니다. -------------------------수고 많으셨습니다!

기획 · PM· PO워밍업스터디클럽3기PM

인프런 워밍업 스터디 클럽 3기 백엔드-code 4주 차 마지막 발자국

이 글은 박우빈님의 Practical-Testing 강의 를 참조하여 작성한 글 입니다.또 다시 일주일이 지나 어느새 마지막 발자국만을 남겨두었다.반신반의하며 시작했지만, 한 달 동안 무려 2개의 강의를 완강하고 미션까지 모두 수행한 내 자신이 참 대견하다! 히히 😊벌써 1분기가 끝난게 믿기지 않지만, 그래도 이것저것 공부하며 알차게 살았더니 이번 1분기는 아쉽지 않게 보내줄 수 있을 것 같다. 특히 테스트 코드와 관련해 많은 지식을 얻을 수 있었다.강의 내용이 알찼던 것은 물론이고, 미션을 수행하며 쌓은 지식을 정리하고 다른 사람의 코드를 읽는 과정에서도 많은 배움이 있었다.무엇보다 우빈님께 직접 코드 리뷰를 받으면서 내 코드의 개선점을 확인하고, 작성 과정에서 궁금했던 부분을 직접 물어볼 수 있었던 경험이 정말 값졌다.테스트 코드를 작성할 때마다 내가 올바르게 작성하고 있는 지에 대한 의심이 항상 존재하였는데, 코드 리뷰를 통해 테스트 코드 작성에 대한 자신감이 한층 더 생긴 것 같다!ㅋㅋㅋ또한 리뷰를 통해 많은 고민 및 궁금증을 해결할 수 있었고, 가독성 및 유지보수 하기 좋은 테스트 코드 작성법 및 테스트 작성의 중요성을 한층 더 깨닫게 된 것 같다!다음에 스터디 클럽이 또 열린다면,,무조건 참여하길 바란다👍🏻학습 내용 요약 Mock을 마주하는 자세@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이위 링크에 미션 수행하면서 학습한 내용을 정리해 두었으니 참고 바란다! 더나은 테스트를 작성하기 위한 구체적 조언완벽하게 제어하기테스트 코드를 작성할 때 모든 조건들은 완벽히 제어가 가능해야 한다.LocalDateTime.now()와 같이 제어할 수 없는 값은 최대한 지양하자!  테스트 간 독립성을 보장하자공유 변수 사용x 한 눈에 들어오는 Test Fixture 구성하기Test Fixturegiven절에서 생성했던 모든 객체들을 의미테스트를 위해 원하는 상태로 고정시킨 일련의 객체BeforeEach, BeforeAll, AfterEach, AfterAll셋업에 유치한 이런 공통의 픽스처들은 테스트와 결합도 생기게 만듦픽스처들을 수정하거나 하는 경우에 모든 테스트에 공통으로 영향을 주기 때문에 지양하는 것이 좋음사용하는 기준:각 테스트 입장에서 봤을 때 아예 몰라도 테스트 내용을 이해하는 데 문제가 없을 때만 사용하기수정해도 모든 테스트에 영향을 주지 않는 경우에만 사용하기테스트 시 sql로 given 객체 생성하지 말자!given절이 파편화되어 뭘 테스트 해야하는지 파악하기 어려워짐프로젝트가 커질수록 데이터 구조, 필드 등 변경이 발생하면 sql문 관리가 어려워짐 Test Fixture 클렌징deleteAllInBatch테이블 전체를 bulk성으로 날릴 수 있는 좋은 메서드순서를 잘 고려를 해야함(중간테이블 먼저 삭제해주어야 함)deleteAllselect후 delete하기 때문에 테이블에 있는 데이터 수 만큼 쿼리가 실행됨순서 고려x 테스트 수행도 비용이다. 환경 통합하기service와 repository부분을 하나의 추상클래스(e,g,.IntegrationTestSupport)를 상속받게 함으로 서버 실행 횟수를 줄이기! Q. 테스트에서만 필요한 메서드가 생겼는데 프로덕션 코드에서는 필요 없다면? 무엇을 테스트하고 있는지를 명확히 인지하기현재 동작하고 있는 프로덕션 코드를 테스트 한다면 테스트에서는 필요한데 프로젝트에서는 필요 없는 그런 메소드들이 나올 수가 있다.이런 경우 만들어도 된다. 하지만 최대한 지양 하자. getter, 기본 생성자, 생성자 빌더, 사이즈 이런 것들 어떤 객체가 마땅히 가져도 될 마땅히 가져도 되는 행위라고 생각이 되면서 미래에도 충분히 사용될 수 있는 성격의 메소드일 것 같다. Spring REST Docs테스트 코드를 통한 API 문서 자동화 도구 API 명세를 문서로 만들고 외부에 제공함으로써 협업을 원할하게 함 기본적으로 AsciiDoc을 사용하여 문서 작성  장점테스트를 통과해야 문서가 만들어짐(신뢰도 높음)프로덕션 코드에 비침투적단점코드 양이 많다설정이 어렵다 미션Day 18 - @BeforeEach, given절, when절에 항목 배치나의 코드: https://inf.run/fK9MY 🧐 고려한 부분남아있는 데이터가 테스트에 영향을 끼치지 않도록 각 테스트 수행 전 @BeforeEach를 통해 데이터를 초기화 시켜주었다.모든 댓글 테스트에서 필요한 사용자와 게시물은 테스트의 기본 전제 조건이므로, 사용자 생성 및 게시물 생성 메서드를 별도로 분리하여 중복을 제거하였다. 우빈님의 코드 리뷰 (Feat. Day 11 - 스터디 카페 이용권 선택 시스템 테스트 코드 작성하기 )나의 코드: https://inf.run/xPthb Q. 나의 질문: @CsvSource내 enum(passType) 문자열 직접 작성으로 인한 유지보수 비용 증가 문제A. 우빈님 답변: passType이 변경되면, 이를 테스트하고 있는 부분이 같이 영향을 받게 되고변경한 사람은 테스트 수행 시점에 테스트가 깨진 것을 보고 영향 범위를 인지할 수 있다.따라서 테스트 코드가 없어서 영향도를 인지하지 못하는 것보다 나은 것이라 생각한다!물론 테스트 코드를 수정해야 하는 비용은 들겠지만, 테스트 코드는 원래 프로덕션 코드와 같이 상생하는 코드이므로,프로덕션 코드를 팔로업하면서 같이 변경되는 것이 더 자연스럽다!@ParameterizedTest @CsvSource({ "HOURLY, HOURLY, true", "HOURLY, WEEKLY, false" }) @DisplayName("이용권 내 이용권 타입이 비교하는 타입과 같은지 비교할 수 있다.") void isSamePassType(StudyCafePassType passType, StudyCafePassType expectedPassType, boolean expectedResult) { // given StudyCafeSeatPass pass = StudyCafeSeatPass.of(passType, 2, 4000, 0.0); // when boolean result = pass.isSamePassType(expectedPassType); // then assertThat(result).isEqualTo(expectedResult); } Q. 나의 질문: 일급컬렉션 내 테스트만을 위한 메서드 추가 없이 테스트 한 방법A. 우빈님 답변: 기본적으로 프로덕션에 없는 코드를 테스트 코드만을 위해 추가하는 것은 '지양'하는 것이 맞다.하지만 해당 기능이 단순하고, 미리에도 충분히 활용될 수 있다면 아주 보수적으로 추가해도 좋다고 생각한다.e,g,. 단순히 일급컬렉션 내부의 원소 개수를 반환하는 size(), 내부 항복이 존재하는지 확인할 수 있는 isEmpty() 등 해당 테스트에서 검증하고 싶은 것은 전체 개수가 아닌 타입별 개수이므로, 타입별 개수를 반환하는 메서드를 추가하기에는 애매해 보인다.따라서 어쩔 수 없이 아래 코드 처럼 작성하는 것이 맞으나, findPassBy()와 결합도가 생기는 것을 감안하면 될 것 같다고 하셨다.하지만 findPassBy() 단위 테스트는 필수!!@DisplayName("파일을 읽어 이용권 목록을 가져올 수 있다.") @Test void getSeatPasses() { // given SeatPassFileReader seatPassFileReader = new SeatPassFileReader(); // when StudyCafeSeatPasses seatPasses = seatPassFileReader.getSeatPasses(); // then assertAll( () -> assertThat(seatPasses.findPassBy(StudyCafePassType.HOURLY)).hasSize(6), () -> assertThat(seatPasses.findPassBy(StudyCafePassType.WEEKLY)).hasSize(5), () -> assertThat(seatPasses.findPassBy(StudyCafePassType.FIXED)).hasSize(2)); } 아래 테스트는 변수명 잘 지었다고 받은 칭찬 리뷰이다😆 마무리한 달동안 정말 많은 것을 배웠다,,,희희단순히 일방적으로 인풋만 넣는게 아니라,매일 강사님 그리고 스터디원분들과 소통하면서 함께 학습하니 더욱 단기간에 성장할 수 있었던 것 같다ㅎㅎㅎ미션도 너무너무 재밌었고, 그에 대한 우빈님의 피드백까지 받으니 정말이지 알차고 소중한 경험이었다.다음에 워밍업 스터디 클럽이 또 하게 된다면, 주변에 적극적으로 홍보해야겠다!!스터디를 기획 및 운영하신 모든분들, 강사님, 그리고 스터디에 참여한 스터디 분들 모두 고생 많으셨습니다☺ 

백엔드클린코드테스트코드발자국워밍업스터디클럽

인프런 워밍업 스터디 클럽 3기 백엔드-code 3주 차 발자국

이 글은 박우빈님의 Practical-Testing 강의 를 참조하여 작성한 글입니다.어느덧 수료까지 일주일만을 앞두고 있다. 벌써 75%나 진행되었다니,,,이때까지 강의 안 밀리고 시간 안에 미션 제출한 내 자신 칭찬한다👍🏻 (아직 강의 섹션3개, 미션 2개 남음)남은 한 주도 열심히 해서 수료해야지!! 이번 주는 테스트 코드가 필요한 이유 및 레이어드 아키텍쳐 내에서 각 레이어드별 테스트 코드 작성하는 법에 대해 배웠다.강의를 듣고 테스트 코드를 작성해야 하는 이유에 대해서 완전 설득이 되었다.기존에는 테스트 코드 작성하는 것이 너무 귀찮고 시간이 오래 걸린다는 이유에서 꺼려했지만, 점점 리팩토링 또는 기능을 추가할 때마다 수동으로 테스트 하는게 더욱 귀찮았다. 또한 수동으로 테스트를 했어도 항상 찝찝함이 존재했다.그래서 우빈님 강의를 들으면서 엄청난 공감을 느꼈고, 강의를 열심히 학습해서,,,많이 배워야겠다는 다짐을 다시 하게된 것 같다.다음으로 readable-code 강의에서 만든 스터디 카페 이용권 프로그램에 대해 스스로 테스트 코드를 작성해 보는 시간을 가졌다.이번에는 열심히 작성한 후 리뷰 신청도 하였다.테스트 코드 관련해서는 리뷰를 한번도 받아본 적이 없기도 하고, 코드 작성 중 궁금한 점이 생겨 리뷰 신청을 하는 용기를 내보았다..ㅋㅋㅋ다음 주 중간점검 때 리뷰를 해주실 예정인데 기대된다!!!학습 내용 요약 테스트는 왜 필요할까?빠른 피드백리팩토링, 신규 기능 추가 등 변화가 생기는 매순간마다 테스트 코드를 통해, 기존 코드가 정상 동작하는 지 빠르게 피드백을 받을 수 있다.만약 테스트 코드를 작성하지 않는다면?변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야 한다.자동화자동화 테스트로 비교적 빠른 시간 안에 버그를 발견할 수 있고, 수동 테스트에 드는 비용을 크게 절약할 수 있다.(인간이 수동으로 테스트를 하게 된다면 실수할 확률이 매우 높음)안정성빠르게 변화하는 소프트웨어에서 테스트 코드를 통해 100%는 아니지만 안정성을 보장할 수 있다. 단위테스트단위테스트란?작은 코드 단위를 독립적으로 검증하는 테스트통합테스트에 비해 준비해야 할 코드가 적으며, 검증 속도가 빠르고 안정적이다.단위 테스트만으로는 기능 전체의 신뢰성을 보장할 수 없다는 단점이 존재한다.JUnit5: 단위 테스트를 위한 테스트 프레임 워크AssertJ: 테스트 코드 작성을 원활하게 돕는 테스트 라이브러리 (풍부한 API, 메서드 체이닝 지원) TDD: Test Driven Development선 기능 구현, 후 테스트 작성테스트 누락 가능성 존재해피 케이스만 검증할 가능성 존재잘못된 구현을 늦게 발견할 가능성 존재선 테스트 작성, 후 기능 구현 (TDD)복잡도가 낮은, 테스트 가능한 코드로 구현할 수 있게 한다(유연하며 유지보수 쉬운 코드)쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해줌구현에 대한 빠른 피드백을 받을 수 있음 -> 과감한 리팩토링 가능해짐 Spring / JPA 훑어보기 & 기본 엔티티 설계라이브러리내 코드가 주체가 돼서 필요한 기능이 있다면 외부에서 끌어와서 사용하게 되는데, 이 때 외부에 있는 것들을 라이브러리라 한다.따라서 라이브러리는 내 코드가 주체가 되는 환경이고 능동적인 특징을 지닌다.프레임워크이미 갖춰진 동작할 수 있는 그런 환경들이 구성이 돼 있고, 내 코드가 이 프레임 안에 들어가는 수동적인 역할을 한다.따라서 프레임워크에서는 내 코드가 수동적인 존재가 된다.ORM 객체 지향 프로그래밍(OOP)과 관계형 데이터베이스(RDB) 간의 구조적 차이를 해소하기 위해 사용되는 기술이다.데이터베이스에 CRUD하는 작업을 객체 기반으로 처리함으로써 개발자들이 기본적인 쿼리 작성하는 단순 작업을 줄이고 비지니스 로직에 집중할 수 있게 해준다.JPAJava진영의 ORM 기술 표준인터페이스이며, 여러 구현체가 있지만 주로 Hibernate를 많이 사용한다. Spring 진영에서는 JPA를 한번 더 추상화한 Spring Data JPA제공한다. Persistence Layer 테스트데이터에 접근하는 역할로 데이터 CRUD와 연관된 메서드들이 위치해 있다.비지니스 로직이 포함돼서는 안됨!@DataJpaTestJPA와 관련된 의존성들만 주입해준다 -> @SpringBootTest보다 가볍다.어노테이션 내부에 @Transactional이 포함되어 있어 테스트 후 데이터가 롤백된다. Business Layer 테스트비지니스 로직에 관련된 메서드들이 위치해 있다.@Transactional vs sql을 이용한 데이터 삭제실제 코드에서는 @Transactional을 사용하지 않는데, 단순히 롤백만을 위해 테스트코드에서 @Transactional을 사용하면, 실제 작동 방식과 다르게 작동할 수 있다.따라서 테스트 코드 작성시 Transactional의 부작용에 대해 인지하고 사용할 것!!리포지토리 테스트와 로직이 많이 없는 얇은 서비스 테스트는 결이 비슷하다왜냐하면 리포지토리에 대한 결이 그대로 오기 때문이다.하지만서비스가 더 기능이 추가될수록 발전을 하기 때문에, 동일한 테스트라고 생각이 되더라도 작성을 하는 게 좋다! (검증이 추가될 수도 있기 때문)클래스 상단에 @Transactional(readOnly = true) 로 표현하고 실제 트랜잭션이 사용되는 메서드에 @Transactional로 표현해주기! 미션Day 11 - 스터디 카페 이용권 선택 시스템 테스트 코드 작성하기나의 코드: https://github.com/Jiihyun/readable-code/pull/3🤔 고민사항 1. StudyCafeSeatPassTest 클래스테스트 케이스 반복을 줄이기 위해 @CsvSource를 사용해 보았다.이로 인해 데이터만 추가하면되니 테스트 케이스를 쉽게 확장할 수 있다는 장점을 느꼈다.하지만 StudyCafePassType을 직접 문자열로 작성했다보니, 후에 passType이 수정될 경우 테스트 코드 내 passType을 직접 수정해야 하기 때문에 유지보수 비용이 증가할 것 같다는 생각이 들기도 했다..그래서 현업에서는 어떤 방식으로 테스트를 하지? 하는 궁금증이 생겼던 것 같다.두 방법에 대해 트레이드오프가 존재하는데 어떤 걸 더 중요시하게 여겨 테스트하는지 궁금하다!2. FileReaderTest 클래스파일을 잘 읽어와 데이터를 의도한 대로 파싱하였는지 테스트하고 싶었다.하지만 반환 타입이 일급컬렉션으로 되어 있고,컬렉션 내의 데이터를 확인하는 메서드는 프로그램에서 사용되지 않기 때문에 존재하지 않았다.테스트를 공부하면서 배운 것 중 하나는 테스트만을 위한 메서드는 최대한 자제해야 한다고 했다.그래서 오로지 테스트를 위해 컬렉션의 크기 등을 확인할 수 있는 메서드를 추가하고 싶지 않았고, 그래서 일급 컬렉션의 메서드를 직접 호출하여 테스트를 진행했다.그치만 이렇게 짜여진 테스트도 본 적이 없는데....이런 방식으로 테스트를 해도 괜찮은지 궁금하다 ㅋㅎㅎㅋ마무리앞으로 이제 일주일 남았다..!! 벌써...?다음 주 학습 내용에는 평소에 궁금했던 내용들에 대해 다루기 때문에 매우 기대가 된다.배워본 적 없는 내용이라 시간을 많이 투자해야 할 것 하지만,,,열심히 학습해서 내 것으로 만들어야지!

백엔드클린코드테스트코드발자국워밍업스터디클럽

[인프런 워밍업 스터디 클럽 3기 FE] 2주차 발자국

React의 등장 배경 React는 상태(state) 기반으로 UI를 효율적으로 관리하도록 설계된 라이브러리 기존 자바스크립트의 문제점- DOM을 조작하는 방식이 비효율적 - 변경이 필요할때마다 전체 DOM을 다시 그려야 함 - 전체가 아닌 일부 DOM만 변경시키고 싶으면 복잡한 로직이 필요- 상태 관리의 어려움 - 복잡한 UI에서는 DOM과 데이터의 일관성을 유지하기 어려움 - 데이터와 UI가 양방향으로 상호작용하며 예측하기 어려운 상태가 됨➡ 결과적으로 유지보수가 어렵고, 재사용성이 낮음 React의 해결책- 상태(state) 기반 UI 관리 - useState를 통해 UI가 변경될 때만 렌더링 - batching : 상태를 한번에 모아서 처리- 가상 DOM을 사용한 최소한의 업데이트 - 이전 가상돔과 현재 가상돔을 비교해 필요한 부분만 업데이트 (diff) - 불필요한 DOM 조작을 최소화하여 성능 향상- 컴포넌트 단위 개발 - UI를 작은 단위로 분리하여 재사용 가능 - 각 컴포넌트의 생명주기를 관리할 수 있음 리액트 컴포넌트 생명주기컴포넌트가 생성되고 사용되고 소멸될때까지의 일련의 과정- 마운트 -> 업데이트 -> 언마운트 - 마운트: 컴포넌트를 DOM에 삽입 - 업데이트: 컴포넌트의 props나 state가 변경될 때 발생 - 언마운트: 컴포넌트를 DOM에서 제거 클래스형 컴포넌트 - React.Component를 상속받아 사용- 생명주기별로 메서드가 있으며, 각 메서드를 오버라이드 할 수 있음 함수형 컴포넌트 - 생명주기 메서드를 직접 제공하지 않음.- 훅을 사용하여 생명주기와 유사한 기능 구현 훅(Hook)은 함수형 컴포넌트에서 상태(state)와 생명주기(lifecycle) 기능을 사용할 수 있도록 해주는 React 내장 함수훅의 종류 : useEffect, useState, useMemo, useCallback특히 생명주기에 가장 직접적인 영향을 미치는 훅은 useEffect- useEffect(() => {...}, []) : componentDidMount- useEffect(() => {...}, [state]) : componentDidUpdate- useEffect(() => {{return ()=>{...};}, [state]) : componentWillUnmount  리액트 훅 useState컴포넌트의 상태(state)를 관리하는 Hook- 함수형 컴포넌트에서 상태를 관리할 수 있도록 함- 상태가 변경되면 해당 컴포넌트가 리렌더링됨- useState로 관리하는 값은 **React 내부에서 관리됨- setState를 호출하면 비동기적으로 업데이트(batch 처리) 됨 - batch 처리란? : 리액트 랜더링 최적화 기법중 하나로, 여러번의 setState()호출이 있을때, 각각 개별적으로 랜더링을 발생시키지 않고 한번만 렌더링 되도록 묶어서 처리 상태란 무엇인가?- 사용자의 응답, api 응답 등에 의해 변경되며, ui를 동적으로 변경시키는 핵심 데이터- 리액트는 useState로 상태를 내부적으로 저장하고, 이 상태가 바뀔때 컴포넌트를 리랜더링한다.  상태의 종류- 로컬상태 : 개별 컴포넌트 내부에서 관리되는 상태- 전역상태 : 여러 컴포넌트가 공유하는 상태- 서버상태 : 서버에서 가져오는 상태- UI상태 : UI관련상태 (모달이 열렸는지, 토글이 열렸는지)  useState와 리액트 생명주기- 초기 렌더링 (mounting) → useState의 초기값 설정- 업데이트 (updating) → setState 호출 시 리렌더링됨- 언마운트 (unmounting) → 상태 해제  useEffect컴포넌트의 부수 효과(side effect)를 관리하는 Hook- 함수형 컴포넌트에서 컴포넌트 생명주기를 관리할때 사용됨- API 호출, 이벤트 리스너 등록, DOM 조작 등 비동기 작업을 처리할 때 사용- useEffect는 렌더링 후 실행되며, 의존성 배열(`deps`)을 통해 실행 조건을 제어 가능- 함수를 리턴할 경우, 언마운트 이후 실행됨useEffect(() => {...}); // 매 렌더링마다 실행 useEffect(() => {...}, []); // 마운트 시 1회 실행 useEffect(() => {...}, [count]); // count가 변경될 때만 실행 useEffect(() => { return () => {...}; }, []); // 언마운트 시 실행  useCallback> 함수를 메모이제이션하여 불필요한 리렌더링을 방지하는 Hook- 함수가 매번 새로 생성되는 것을 방지- React.memo()와 함께 사용하여 최적화 가능 - 상태가 변경되거나 props가 변경되면 리랜더링 발생 - 이때 props로 객체나 함수(참조형)을 넘기게 되는 경우 props는 얕은 복사를 진행하기에 매번 리랜더링을 할 수밖에 없음 이때 메모이제이션을 사용하면 불필요한 랜더링 방지 가능 useCallback과 리액트 생명주기- 컴포넌트가 마운트될 때 초기화됨- 의존성 배열이 변경되면 새로운 함수로 업데이트됨 useRef> DOM 요소에 직접 접근하거나, 값이 유지되지만 리렌더링을 유발하지 않는 변수를 관리하는 Hook- DOM 요소를 직접 조작하는 데 사용- 렌더링 간 유지되는 값을 저장할 때도 사용- 값이 변경되어도 컴포넌트가 리렌더링되지 않음 useRef와 리액트 생명주기- 컴포넌트가 마운트될 때 초기화됨- 리렌더링과 무관하게 값을 유지   

프론트엔드워밍업스터디클럽워밍업스터디클럽리액트

인프런 워밍업 스터디 클럽 3기 백엔드-code 2주 차 발자국

이 글은 박우빈님의 readable-code강의 를 참조하여 작성한 글입니다.정신없이 진도표를 따라 강의를 수강하고 미션을 진행하였더니 일주일이 순식간에 지나가버렸다.처음에는 할 만하다고 느꼈었는데,,,강의 내용을 습득하여 스스로의 힘으로 코드에 적용까지 하려니 우빈님이 당부하신 대로 쉽지 않았던 나날들이었던 것 같다,,🥹특히 이번 스터디카페 리팩토링 미션에 뻥 안치고 10시간 이상은 투자한 것 같은데,,,ㅋㅋㅋㅋ(미션 당일 + 자고 읽어나서 맑은 정신으로 한 번 더 도전)이후에 리팩토링 강의를 수강하며 내가 전혀 고려하지 못했던 부분을 리팩토링하신 것을 보고 놀랐다. 뿐만 아니라, 리팩토링을 뚝딱 해내시는 모습에서도 감탄했다.원래 누군가가 뭔가를 쉽게 해내는 것처럼 보이면 진짜 고수라는 말이 딱 맞는 것 같다...!강의 속에서 리팩토링 하시는 모습은 굉장히 쉬워보였는데...혼자서 해내려니 정말 막막했다 ㅋㅋㅋㅋ그래도 나도 경험을 더 쌓고 나면, 지금보다 더 짧은 시간 안에 더 객체지향적으로 리팩토링을 해낼 수 있겠지!!이를 위해 다른 분들이 리팩토링 하신 코드도 많이 읽으면서 여러 번 리팩토링 해봐야겠다. 학습 내용 요약 주석의 양면성주석의사 결정의 히스토리 를 도저히 코드로 표현할 수 없을 때, 주석으로 상세하게 설명하자주석이 많다 == 비지니스 요구사항을 코드에 잘못 녹였다 는 의미가 성립됨주석을 작성할 때, 자주 변하는 정보는 최대한 지양해서 작성하자 (그렇지 않으면, 주석도 신경써서 계속 업데이트 해줘야 하는 단점 존재)변수와 메서드의 나열 순서상태 변경 > 불리언 등 판별 >= 조회 메서드 순으로 메서드 순서 나열 패키지 나누기패키지: 문맥으로써의 정보를 제공할 수 있음대규모 패키지 변경은 팀원과의 합의를 이룬 후 하자본인만 사용하는 부분이면 괜찮지만, 여러 사람과 공통으로 사용하는 클래스들의 패키지를 한번에 변경하면 충돌이 발생할 수 있음! 은탄환은 없다클린코드가 은탄환은 아니다 (클린코드가 무조건적인 정답은 아니다)요구사항이 변경될 일이 없는 코드는 절차지향적인 코드가 오히려 정답일 수 있음 (e.g., 체스는 500년동안 규칙이 변하지 않았음)실무는 지속가능한 소프트웨어의 품질 vs 기술부채를 안고 가는 빠른 결과물 사이의 줄다리기무조건적으로 클린 코드를 추구하기보다는 주어진 기간을 최대한 맞추게끔 결과물을 내놓고, 이후 미래에 잘 고치도록 할 수 있는 코드 센스가 필요 (e.g., 주석으로 리팩토링 할 부분 남겨 놓기 등) 미션 Day 4 미션 피드백기존 코드 (return 타입 - boolean)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; } 내가 리팩토링 한 코드 중 일부 (1)public class Order { private long id; private List<Item> items; private Customer customer; public Order(final List<Item> items, final Customer customer) { validateOrder(items, customer); this.items = items; this.customer = customer; } private void validateOrder(final List<Item> items, final Customer customer) { if (doesNotHaveItems(items)) { throw new RuntimeException("주문 항목이 없습니다."); } if (doesNotHaveCustomerInfo(customer)) { throw new RuntimeException("사용자 정보가 없습니다."); } } public boolean doesNotHaveItems(final List<Item> items) { return items.isEmpty(); } public boolean doesNotHaveCustomerInfo(final Customer customer) { return customer == null; } } => boolean을 return하고 있는 기존 메서드에 대한 리팩토링으로 예외를 던지는 것으로 변경하는 것은 좋을 수도, 나쁠 수도 있다.(자칫하면 오버엔지니어링이 될 수 있음)📌 예외를 던지는 것은 비싸기 때문에, 신중하게 메서드의 사용현황을 파악 후 상황에 맞게 리팩토링 할 것!! 내가 리팩토링 한 코드 중 일부 (2)public class Item { private static final int MINIMUM_VALUE = 1; private long id; private String name; private int price; public Item(final long id, final String name, final int price) { validatePositivePrice(price); this.id = id; this.name = name; this.price = price; } private void validatePositivePrice(final int price) { if (price < MINIMUM_VALUE) { throw new RuntimeException("올바르지 않은 가격입니다."); } } }고민이었던 부분: Order 클래스 내에 Item 클래스를 새로 만들어, 이를 리스트 형태로 Order에 의존성 주입하도록 코드 리팩토링을 진행하였다. 이 때 Item 객체를 생성할 때 마다 금액이 양수인지 검증하고 있는데, Order 클래스에서 전체 총 금액이 양수인지 검증을 다시 한번 해주는 게 좋을지 고민이 됐었다.=>  우빈님 답변: 이미Item 객체를 생성할 때 금액의 유효성을 보장하고 있으니, Order 클래스는 굳이 하지 않아도 될 것 같다.물론 비지니스 로직이 복잡해지면, 오히려 필요하다고 느끼는 순간이 올 수 있으니, 그 때 추가해도 늦지 않다! Day 7 미션미션 한 줄 소개: 스터디 카페 이용권 선택 시스템 리팩토링 하기 리팩토링 한 부분 코드 중복 제거 및 메서드 추출StudyCafePassMachine의 의존성 config에서 주입: StudyCafePassMachine은 필요한 의존성을 외부에서 주입받기만 하고, 내부에서 어떻게 사용하는지는 외부에 노출하지 않을 수 있다!일급 컬렉션 활용: 일급 컬렉션으로 분리함으로써, 원래는 private 메서드라 테스트하지 못했던 로직도 테스트 가능해짐!무분별한 getter 사용이 아닌, 객체에 메세지 보내기public boolean isSamePassTypeWith(final StudyCafePassType studyCafePassType) { return passType == studyCafePassType; }passType를 비교해야 하는 곳에서 passType를 게터를 통해 비교해주는 게 아닌, isSamePassTypeWith 메서드와 같이 객체에 메세지를 전달하자!스터디 카페 이용권 인터페이스 적용: 이부분은 리팩토링을 잘 하였는지 감이 안온다..시도에 의의를 두자 ㅎ헤ㅔㅎpublic interface StudyCafePassHandler { boolean isAppliable(final StudyCafePassType studyCafePassType); StudyCafePasses findCandidateStudyCafePasses(final StudyCafePasses studyCafePasses); } ========= # StudyCafePassMachine private void processUserSelection(final StudyCafePassType studyCafePassType) { final StudyCafePasses availablePasses = getAvailablePasses(studyCafePassType); outputHandler.showPassListForSelection(availablePasses); final StudyCafePass selectedPass = inputHandler.getSelectPass(availablePasses); if (studyCafePassType == StudyCafePassType.FIXED) { checkLockerPass(selectedPass); return; } outputHandler.showPassOrderSummary(selectedPass, null); }Hourly, Weekly, Fixed라는 3종류의 카페 이용권이 존재하기 때문에, 인터페이스를 정의하여 if문 사용을 자제하고, 상황에 맞는 이용권을 가져왔다. 리팩토링 놓친 부분FileHandler: 데이터를 어디로부터 어떻게 가져올 것인가에만 초점이 맞춰져 있음-> File관련 로직이 들어나면 FileHandler 가 방대해질 것개선 방향: provider를 통해 어떤 데이터를 필요로 하는가에 초점을 맞출 것SeatPassProviderLockerPassProvider domain영역에 view관련 로직 침투public enum StudyCafePassType { HOURLY("1", "시간 단위 이용권"), WEEKLY("2", "주 단위 이용권"), FIXED("3", "1인 고정석"); private final String command; private final String description; StudyCafePassType(final String command, final String description) { this.command = command; this.description = description; } public static StudyCafePassType from(final String userInput) { return Arrays.stream(StudyCafePassType.values()) .filter(studyCafePassType -> studyCafePassType.command.equals(userInput)) .findAny() .orElseThrow(() -> new AppException("잘못된 입력입니다.")); } } PassType은 중요한 도메인 모델인데, Input과 관련된 의미를 지닌 command가 침투되었다. passType을 선택하는 command가 변경된다면, 단순히 입력 방식을 바꿨을 뿐인데 도메인 모델을 수정해야 하는 좋지 않은 상황이 발생한다.StudyCafePassOrder 도메인 추출스터디 카페 좌석 이용권 + 사물함 이용권을 합친 Order 도메인을 새로 추출할 수 있다.이로 인해 FIxedPassType에만 적용되는 사물함 로직 분기문을 간단하게 처리할 수 있었다!StudyCafePass 내 LOCKER_TYPES 상수 선언public enum StudyCafePassType { HOURLY("시간 단위 이용권"), WEEKLY("주 단위 이용권"), FIXED("1인 고정석"); private static final Set<StudyCafePassType> LOCKER_TYPES = Set.of(FIXED); private final String description; StudyCafePassType(String description) { this.description = description; } public boolean isLockerType() { return LOCKER_TYPES.contains(this); } public boolean isNotLockerType() { return !isLockerType(); } }전혀 생각지도 못했지만, 알아두면 참 좋은 객체에 메세지를 보내는 방법에 대해 배웠다.LOCKER_TYPES를 StudyCafePassType enum내에 적용하여 처리할 수 있다니도메인 지식이 부족해서 그랬나, 나는 생각지도 못했던 방법이다.나는 상위 도메인에서 if문을 통해 매번 확인해주었는데, 우빈님이 하신 방법이 더 책임 분리가 잘되어있고 객체에 메세지를 보내는 좋은 방법인 것 같다!!또한 Locker type이 늘어나도, set에 내용만 추가해주면 된다는 점에서 유지보수도 훨씬 쉬울 것 같다! 마무리강의를 굉장히 빠른 시간 안에 완강했다!!!그렇지만 강의 내용을 완전히 내 것으로 만들었다기엔 부족하다,,,,강의 볼 때는,,'이럴 때 조합, 일급 컬렉션, VO 등을 적용 하는구나~!' 를 배우면서, 앞으로 스스로 잘 판단해 낼 줄 알았는데,,,혼자서 해 보려니까 너무나도 막막했다.그래도 강의 내에서 주신 미션을 통해 내가 어느 부분이 부족한지 파악할 수 있었던 것 같다. 무엇보다도 너무 재밌었다,,,시간 가는 줄 모르고 했던 것 같다.이제 강의는 끝이 났지만 지뢰찾기랑 스터디카페 코드에 대해 복습할 것이다. 리팩토링 적용하기 어려웠던 부분을 반복 작성해 보면서 체득시킬 생각이다. 이렇게 반복하다보면 우빈님의 사고법을 체득할 수 있겠지요...?다음 주부터는 테스트 코드에 대해 배우는데,,평소 테스트 코드에 대해 공부를 많이 하지 않았어서 새로 배우는 양이 어마어마 할 것 같다.강의 내용을 잘 습득할 수 있도록 메타인지 열심히 해야겠다!나 자신 아자아자 화이팅이다💪🏻 [출처]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/dashboard

백엔드클린코드테스트코드발자국워밍업스터디클럽

김혜경

[인프런 워밍업 스터디 클럽 1기_디자인] 4주차 마지막 발자국

인프런 워밍업스터디클럽 디자인 1기 4주차 마지막 발자국을 남긴다.그간 다른 인강 사이트의 여러 강좌를 들어봤지만, 볼드 강사님의 피그마 강의가 가장 인상깊게 남았다.피그마가 업데이트 될때마다 강사님께서 정성스럽고 디테일한 자료를 공유해주셨고,강의 실습에 적용해보고 한단계 더 성장할 수 있는 계기가 되었다.물론, 실무에서도 당연히 잘 사용할 수 있을것 같아서 너무 뿌듯하다. 4주차 마지막 실습1. 모든 컴포넌트를 조합하여 페이지화2. Mobile/ Tablet/ Desktop 반응형 적용3. Light/ Dark 모드화 이 모든 작업은 이 강좌의 주제인 베리어블에서부터 시작된다.볼드님 강의를 들으면 모든 UI 화면을 손쉽게 전환할 수 있다.앞으로는 강사님 말씀대로 이 모든 베리어블이 데이터베이스화 되서 엑셀로도 연동되어UI 화면을 직접 수정하지 않아도 해당 베리어블의 값만 수정하면 디바이스별/ 모드별 화면이 단 한 번의 클릭으로 짜잔! 바뀌게 될 것이라는 점에 백퍼 공감!!!피그마의 최신 업데이트 관련해서 공부해보고 싶은 사람은이 강좌 무조건 추천한다!인프런 워밍업스터디클럽에 처음 참여했는데 너무 유익한 시간이었다.다음 기회가 있다면 다시 참여해보고 싶다. 회고칭찬  미션 모두 클리어강좌 완강아쉬움피그마 업데이트본을 전부 반영하지 못한 점개선점 컬러 베리어블에 이어 폰트 베리어블을 디자인시스템에 적용해서 디벨롭!    

UX/UI인프런워밍업스터디클럽디자인1기볼드피그마디자인시스템

채널톡 아이콘