로드맵
전체 1블로그
전체 32025. 03. 19.
1
살짝 서두른 Spring 기반 테스트 코드 - 인프런 워밍업 클럽 3기 백엔드 코드✨
인프런 워밍업 클럽 3기 백엔드 코드 발자국 1주차인프런 워밍업 클럽 3기 백엔드 코드 발자국 2주차인프런 워밍업 클럽 3기 백엔드 코드 발자국 3주차🎼 알레그레토 : 조금 빠르게 3주 차부터는 미션과 발자국을 조금 서둘러 진행할 예정이다.앞서 언급했듯이, 이번 주부터 항해 플러스가 시작되어 최대한 진도를 빠르게 빼보려고 한다.현재 날짜(3월 17일) 기준으로 Day 16 미션과 3주 차 발자국을 작성 중이며,이번 주 안에 Day 18 미션과 4주 차 발자국도 작성을 완료하는 것이 목표이다.(가능할지는 모르겠지만 ㅎㅎ)조금 급하게 진행하는 감이 있어 개인적으로 많이 아쉽다.워밍업 클럽에만 온전히 집중할 수 있었다면 더 많은 성장을 할 수 있었을 텐데 말이다...하지만, 후회는 하지 않는다. 다음 기수의 존재는 우빈님만 아시겠지만, 다음에 또 없을 수도 있는 기회를 놓치고 싶지 않았기 때문이다. 👍나는 위의 이미지처럼 Trello를 활용해 인프런 워밍업 클럽에 참여하고 있다.미션 제출 날짜가 일정하지 않다 보니, 제출 하루 전에 노티를 받도록 설정해 두고 유용하게 사용 중이다. 🙃💡 자기만의 언어로 키워드 정리하기 섹션 6. Spring & JPA 기반 테스트Layered Architecture레이어드 아키텍처의 단점 : 기술에 대한 강결합이 심하다는 단점이 존재Hexagonal Architecture도메인 모델은 외부의 것들을 아예 모른다.도메인 모델 중심 (멀티 모듈 및 시스템이 커진다면..)단위테스트 vs. 통합테스트단위테스트 만으로는 커버하기 어려운 영역이 존재 (여러 모듈 및 여러 객체가 협력하기 때문에)통합테스트란?여러 모듈이 협력하는 기능을 통합적으로 검증하는 테스트단위 테스트만으로는 기능 전체의 신뢰성을 보장할 수 없다.IoC, DI, AOPORM, 패러다임의 불일치, HibernateSpring Data JPAQueryDSL@SpringBootTest vs @DataJpaTest@DataJpaTest는 @SpringBootTest보다 가볍다.@DataJpaTest보다는 @SpringBootTest를 더 선호@DataJpaTest는 @Transactional이 있어 롤백이 된다.@SpringBootTest는 클렌징을 해주어야 한다.@SpringBootTest vs @WebMvcTest@SpringBootTest는 E2E 테스트, 즉 통합테스트 시 사용하는 어노테이션이다.@WebMvcTest는 Presentation Layer에 대한 단독 테스트시 사용하는 어노테이션이다.다른 레이어들은 mocking을 통해 동작을 제어한다.@Transactional(readOnly = true)테스트에서 사용 시, 롤백 되는 것에 유의 해야 한다.트랜잭션 경계 설정을 해야한다.엔드포인트를 잘 설계해야 한다.Optimistic Lock, Pessimistic Lock낙관적 락 : 데이터 충돌이 자주 발생하지 않을 것이라 낙관적으로 가정하고, 트랜잭션을 진행하는 방식데이터를 읽을 때는 락을 걸지 않고, 데이터를 업데이트 시 버전 비교하여 충돌 여부 판단성능 저하를 최소화, 동시성을 높이는 데 유리비관적 락 : 데이터 충돌이 자주 발생할 것이라 비관적으로 가정하고, 트랜잭션이 데이터를 사용할 때 미리 잠금을 거는 방식데이터 일관성을 유지하는 데 초점트랜잭션이 완료될 때까지 다른 트랜잭션이 데이터를 수정할 수 없음데드락 발생 가능CQRS명령 조회 책임 분리 : Command Query Responsibility Segregation읽기(조회)와 쓰기(명령)의 책임을 분리하는 소프트웨어 아키텍처 패턴장점성능 최적화확장성 증가데이터 모델 최적화비지니스 로직의 명확한 분리단점복잡성 증가데이터 동기화 문제트랜잭션 관리 어려움@RestControllerAdvice, @ExceptionHandler@RestControllerAdvice : ControllerAdvice의 기능을 하는데 JSON으로 응답을 해주는 Advice커스텀 예외를 던지고 @RestControllerAdvice의 @ExceptionHandler에서 예외를 처리할 수 있다.Spring bean validation@NotNull, @NotEmpty, @NotBlank도메인 요구사항에서 나오는 validation과 책임 분리해야한다.Controller 단에서는 최소한의 validation을 통한 검증이 이루어져야 한다.@WebMvcTestObjectMapperJackson 라이브러리에서 제공하는 클래스로, Java 객체와 JSON 간의 변환을 담당하는 역할직렬화, 역직렬화를 수행Mock, Mockito, @MockBeanMock : 실제 객체 없이 동작을 모방하여 단위 테스트를 수행하는 가짜 객체Mockito : Java에서 Mock 객체를 쉽게 생성하고 관리할 수 있는 라이브러리@MockBean : Spring 컨텍스트에 Mock 객체를 등록하여 실제 빈을 대체@Mock : 순수한 자바에서 Spring 컨텍스트가 필요하지 않을 때 사용@MockBean : Spring 컨텍스트에서 특정 빈을 Mocking 하고 싶을 때 사용👨🏻💻 미션 회고[미션 Day 11][미션 PR]#6스터디 카페 이용권 선택 시스템 단위 테스트 작성미션 조건은 다음과 같다.✔각 프로젝트 모두 강의 중에 작성한 tobe 패키지 코드를 기준으로 함 (lesson 6-4 가 가장 마지막 버전)✔3개 이상의 서로 다른 클래스 & 총 7개 이상의 테스트 작성 ➡ 단, 같은 인터페이스를 구현하고 있는 구현체들은 1개 클래스로 간주한다.✔무엇을 테스트하고자 했는지를 잘 나타낸 @DisplayName 작성하기✔BDD(given/when/then) 스타일 따르기 (주석으로 표기)가장 작은 단위의 메서드 부터 단위 테스트를 작성하면서,테스트 커버리지를 높이기 위해, 모든 주요 로직에 대한 단위테스트를 작성하고자 했다.단위테스트를 작성하면서 강의에서도 강조한 내용 중에 하나인 @DisplayName을 잘 작성하기 위해 많이 고민했다.'권'이라는 Pass의 의미를 '패스'로 통일하여 일관성있게 작성하였다.읽는 사람으로 하여금 뇌 메모리를 적게 쓰게 하기 위해 @DisplayName도 최대한 추상화해서 작성하려고 노력했다.테스트의 현상을 중점적으로 작성하지 않으려고 하였다.하지만, 사용자 입력을 받는 클래스인 InputHandler의 단위테스트를 작성하는 과정에서 어려움이 있었다.InputHandler 내부에서 static으로 선언되어 있어 mocking도 하기 어려웠다.public class InputHandler { private static final Scanner SCANNER = new Scanner(System.in); }프로덕션 코드를 수정하지 않는다는 요구사항을 지키면서는 테스트가 어려웠다. 만약 프로덕션 코드를 수정한다면 테스트가 가능해질 것이다.👆 Scanner를 외부세계로 분리하면 테스트가 가능할 것 같다.✌ InputHandler 상위의 인터페이스를 생성 후, 테스트 전용 support 성격의 구현체를 만들어서 테스트가 가능할 것 같다. 미션 제출 후, 차후에 프로덕션 코드를 수정해서 테스트를 작성 해볼 예정이다.🏃 돌아보며..Day 11 미션은 제출이 끝이 아니라, 중간점검 라이브에 이어 마지막 라이브에서 코드 리뷰 단계가 남아있다.중간 점검 때 세심한 코드 리뷰를 받고 수정하면서 많은 성장을 했기에 이번에도 신청하지 않을 이유가 없었다.다른 러너 분들도 꼭 받아봤으면 한다. 😊아직까지 혼자 신청해서 뻘줌해서가 아니라... 진짜 좋은 기회이자 경험이다... 🤣코드 리뷰 대상자로 뽑히길 기대하며.. 마지막 4주차도 화이팅 💪[출처]인프런 워밍업 클럽 : https://www.inflearn.com/course/offline/warmup-club-3-be-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
백엔드
・
인프런
・
워밍업클럽
・
백엔드
・
3기
・
발자국
・
박우빈
・
테스트코드
・
실용적인테스트가이드
2025. 03. 15.
2
우빈님의 세심한 코드 리뷰 - 인프런 워밍업 클럽 3기 백엔드 코드 ✨
인프런 워밍업 클럽 3기 백엔드 코드 발자국 1주차 🙊 시작이 반이다... 벌써 중간점검 ?인프런 워밍업 클럽이 시작한지 2주가 지났다.. 앞으로 남은 발자국이 2개 뿐이다.. 👣이번주에는 중간점검으로 온라인 라이브가 진행 되었다. 온라인 라이브에 대한 내용은 다음과 같다.미션 Day 4 에 대한 공통 피드백Q&A에 대한 답변미션 Day 7 코드리뷰 진행각 세션은 놀라울 정도의 세심한 우빈님의 피드백 덕분에 많은 인사이트를 얻게 되어 좋은 시간이었다. 나도 첫번째로 코드리뷰를 신청하고 라이브 마지막에 코드리뷰를 받았는데 너무 좋은 경험이었다. 💪(커피를 좋아하시기로 유명한 우빈님께 Q&A 세션 도중 저가 커피 브랜드 중 어디 브랜드가 제일 맛있냐는 질문이 나왔는데.. 드셔 보신 적이 없다고 답변해 주신 부분이 인상적이었다.. ㅋㅋ 😁)✨ 우빈님의 세심한 코드 리뷰위에서 이야기했듯이 코드리뷰를 첫번째로 신청해서 우빈님께 온라인 라이브 시간에 코드리뷰를 받았다.해당 미션은 "스터디 카페" 프로그램을 리팩토링하는 미션이였는데.. 작년 4분기에 강의를 수강했을 당시에 3번이나 진행하였다.첫 번째 리팩토링, 강의를 듣기 전에 리팩토링두 번째 리팩토링, 강의를 수강하며 리팩토링세 번째 리팩토링, 강의를 수강 후 정리하며 다시 리팩토링이번이 네 번째 리팩토링이였는데 할 때마다 왜 새로운 것인지.. 🥲그래도 손이 기억이라도 한 듯 나름 순조롭게(?) 미션을 진행하게 되었고, 추가적으로 리팩토링을 진행하였고 해당 부분을 리뷰를 받고 싶어 신청하게 되었다.(우빈님께서 4번이라는 부분에 놀라셨는지(?).. 디스코드 스레드에 댓글을 남겨주셨다! 🤣)다시 돌아와서.. 코드리뷰 받은 내용은 아래와 같다.1⃣ 중요 도메인 StudyCafePassType의 구조화 🔗 Github PR 링크StudyCafePassType 구조화 리팩토링♻ 리팩토링 코드public enum StudyCafePassType implements PassTypeSelectable, PassTypeFormatter { // 📝 인터페이스 구조화 HOURLY("시간 단위 이용권") { // 📝 사용자 입력에 대한 구조화 @Override public boolean selected(String userInput) { return "1".equals(userInput); } // 📝 사용자 출력 포맷에 대한 구조화 @Override public String format(StudyCafePass pass) { return String.format("%s시간권 - %d원", pass.getDuration(), pass.getPrice()); } } }✏ 우빈님 리뷰Q. 클래스 내부에서 사용자 입력값 및 출력값에 사용하는 인터페이스를 구현함으로써 오버 엔지니어링이 된 것 같은 느낌이 드네요.. 🤦♂️ A. 저도 그렇게 생각해요.. ㅋㅋㅋㅋ 오버 엔지니어링이기보다 PassType은 중요한 도메인 모델인데, Input에서만 의미를 가지는 사용자 선택지가 침투하고 있다. 사용자 선택 방법이 "a", "b", "c"로 바뀐다면? 단순히 입력 방식을 바꿨을 뿐인데 무료 도메인 모델이 수정되어야 하는 엄청난 사태가 발생한다. 항상 구조화를 하는 것이 정답은 아니다. Output format도 마찬가지이다. 책임이 우선이다. 적절한 책임의 분배가 객체의 결합도를 낮추고 응집도를 높이는 것이다. 🤔 돌아보기단순히, OCP를 적용하기 위해 접근해서 리팩토링 했었는데..적절한 객체 책임 분리를 하지 못했으며, 중요한 도메인 모델을 수정하는 엄청 큰 사이드 이펙트가 일어날 수 있다는 점을 간과 했다는 것이다.다음부터는 구조화를 남발하지 않고 책임에 집중해서 리팩토링 해야겠다..2⃣ 이용권을 읽는 부분과 읽은 부분의 개념을 추출하여 객체 분리 🔗 Github PR 링크ReadLockerPasses 객체 분리♻ 리팩토링 코드public class ReadLockerPasses { // 📝 LockerPasses를 해석하는 객체 분리 private final List passes; private ReadLockerPasses(List passes) { this.passes = passes; } // 📝 lines을 해석하여 List 객체를 만들어준다. public static ReadLockerPasses ofLines(List lines) { List passes = lines.stream() .map(ReadLockerPasses::ofLine) .toList(); return new ReadLockerPasses(passes); } private static StudyCafeLockerPass ofLine(String line) { String[] values = line.split(CSV_SPLITTER); // ⭐️ CSV라는 방식에 종속적 StudyCafePassType studyCafePassType = StudyCafePassType.valueOf(values[0]); int duration = Integer.parseInt(values[1]); int price = Integer.parseInt(values[2]); return StudyCafeLockerPass.of(studyCafePassType, duration, price); } // 📝 StudyCafeLockerPasses를 생성해준다. public StudyCafeLockerPasses toPasses() { return StudyCafeLockerPasses.of(passes); } }✏ 우빈님 리뷰Q. 일급 컬렉션을 적용하기 위해 toPasses() 메서드를 생성했는데 Read~라는 네이밍을 가진 클래스에 많은 책임이 부여된 것 같아 네이밍이 모호한 것 같습니다. 좋은 방법이 있을까요..? 🧐 A. 많은 책임이라고 생각하신 이유가 있을까요? "ReadLockerPasses는 어디선가 읽은 lines를 가지고 StudyCafeLockerPasses를 만들어준다"의 책임으로 보여서, 어색하지 않으며 테스트 코드 작성도 가능하다. 그와 별개로 CSV라는 방식에 종속되어있다. CSV형식이 다른 방식으로 바뀌었을 때 같이 바뀌어야 하는 부분이 CSV_SPLITTER 부분이다. 의도한 것 이라면 상관없다. 🤔 돌아보기Read라는 클래스명을 가지고 있어 StudyCafeLockerPasses를 생성해주는 메서드가 존재해 많은 책임이 있다고 생각했는데..우빈님 리뷰 이후에 다시 보니.. 그렇게 어색한가 싶기도 하다.. ㅎㅎ해당 클래스의 작성 당시 CSV 방식을 의존하려는 의도는 없었다. 단순히 읽은 부분의 개념을 추출한 것인데.. 위의 클래스는 CSV_SPLITTER상수가 사용되어 의도하지 않게 CSV라는 방식에 종속적이게 된 것이다.CSV라는 방식이 변경되면 객체 로직이 바뀌어야 한다.객체 구현 시, 종속성에 대해서 방어적으로 접근할 필요가 있어보인다.3⃣ ProvideException 커스텀 예외🔗 Github PR 링크ProvideException 커스텀 예외♻ 리팩토링 코드// 📝 이용권을 가져오는 과정에서 생긴 에러의 커스텀 예외 클래스 생성 public class ProvideException extends RuntimeException { public ProvideException(String message) { super(message); } } public class StudyCafePassMachine { public void run() { try { outputHandler.showPassOrderSummary(order); } catch (AppException e) { outputHandler.showSimpleMessage(e.getMessage()); } catch (ProvideException e) { // 📝 커스텀 예외 catch outputHandler.showSimpleMessage("이용권을 제공받을 수 없습니다."); } catch (Exception e) { outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); } } }✏ 우빈님 리뷰Q. AppException 성격이랑 다른 것 같다고 생각되어 Provider 인터페이스에서 발생하는 예외 클래스 ProvideException를 별도로 생성하였습니다. A. 혹시 어떻게 다르다고 생각셨나요?? AppException의 의도는, 프로그램에서 발생할 수 있는 대부분의 애플리케이션 상황을 정의하는 최상위 예외 클래스이다. 만약 ProvideException을 별도로 표기하여 더 구체적인 상황을 나타내고 싶으면, AppException을 상속받아서 구성해야 한다. 그렇지 않으면 커스텀 예외 클래스가 늘어남에 따라 catch절도 같이 늘어날 것이다. 추가적으로, "이용권을 제공받을 수 없습니다."라는 메시지가 사용자 친화적이지 않다. 🤔 돌아보기리팩토링 당시, 초기 이용권을 가져와야만 프로그램이 실행된다는 관점에서 ProvideException의 커스텀 예외 클래스를 작성하였다.하지만 이용권을 가져오는 부분은 프로그램 내부에서 필요한 시점마다 호출하고 있어우빈님 리뷰대로 AppException 클래스를 상속받아서 작성하는 것이 더 나은 설계 같다.예외 메세지도 사용자 관점에서는 친화적이지 않은 것이 분명하다.내가 키오스크 시스템을 사용하다가 저런 메세지를 마주한다면... 화가 날 것 이다... 😡프로그램의 의도를 정확히 파악할 필요가 있어보인다. 또한 예외 메세지도 누가 보는지에 따라 고민해보는 습관을 길러야겠다.이렇게, 요청한 3개의 리뷰와 2개의 추가 리뷰를 받아 보았다..고작 3일 만에 7명이나 리뷰를 해주셨는데 세심하고 또 세심했다... 퀄리티가 상당했다.. ✨이번 온라인 라이브를 통해 우빈님에 대한 팬심과 존경심이 더욱 커졌다....! 📈리뷰해주신 내용으로 다시 리팩토링을 함으로써 한층 더 Readable Code에 대한 성장을 경험할 수 있었다. 🚀💡 자기만의 언어로 강의 키워드 정리하기 섹션 6. 코드 다듬기좋은 주석 - 주석의 양면성주석이 많다는 것 : 추상화가 덜 되고 가독성이 좋지 않은 코드 (코드 품질 저하 📉)주석이 필요한 경우 : 히스토리를 알 수 없을 경우, 주석으로 상세히 설명변수와 메서드 나열 순서변수 : 사용하는 순서대로 위치한다. (인지적 경제성 / 뇌 메모리 줄이기)객체의 공개/비공개 메서드 : 공개 메서드를 상단에 위치하고, 비공개 메서드 하단에 위치한다. 공개 메서드 중에서도 중요도의 순서에 따라 배치한다.공개 메서드 : 객체의 상태를 변경 하는 부분이 가장 상단에 위치하도록 - 상태 변경 >>> 판별 >= 조회비공개 메서드 : 출현한 순서대로패키지 나누기여러 파일들의 네임 스페이스를 관리하기 때문에 적당한 수준으로 잘 나누어야 한다.대규모 패키지 변경은 팀원과의 합의 필요 -> 추후 conflict가 생길 수 있다.기능 유지보수하기정렬 단축키, linting, style - sonarlint, editorconfig섹션 7. 리팩토링 연습메서드 추출로 추상화 레벨 맞추기Optionalreturn null / Optional 파라미터 사용은 안티패턴이다.객체에 메시지 보내기객체를 존중하고 메시지를 보내자.객체의 책임과 응집도⭐️ 추상화 관점의 차이 - FileHandler구현에 초점을 맞춘 추상화 VS 도메인 개념에 초점을 맞춘 추상화File을 read하는 부분의 로직들은 전부 FileHandler에 들어갈 것이다. 잘못된 객체 응집일 수도 있다..방법에 초점을 맞춘 설계 방식이 아닌 어떤 데이터를 가져오는 가에 대한 초점을 맞추는 것이 좋다.섹션 8. 기억하면 좋은 조언들능동적 읽기가지고 있는 리팩토링 기법들을 총동원해서 읽자. -> 리팩토링하면서 읽기눈으로만 보는 수동적 읽기는 권장하지 않는다.도메인 지식을 늘리기 위해서 능동적 읽기가 필요하다. (작성자의 의도 파악)오버 엔지니어링필요한 적정 수준보다 더 높은 수준의 엔지니어링예시 1. 구현체가 하나인 인터페이스구현체가 수정할 때마다 인터페이스도 수정해야 함코드 탐색의 어려움예시 2. 너무 이른 추상화정보가 숨겨지기 때문에 복잡도가 높아진다.후대 개발자들이 선대의 의도를 파악하기가 어렵다.은탄환은 없다클린 코드도 은탄환이 아니다.실무에서의 줄다리기지속 가능한 소프트웨어 품질 VS 기술 부채를 안고 가는 빠른 결과물 -> 클린 코드를 대비한 코드 센스가 필요하다.모든 기술과 방법론은 적정 기술의 범위 내에서 사용되어야 한다.항상 정답인 기술은 없다.한계까지 연습해보고, 적정 수준, 적정 시점을 깨닫는 것이 필요하다.섹션 3. 단위 테스트단위 테스트작은 코드(클래스 또는 메서드) 단위를 독립적으로 검증하는 테스트 -> 가장 기본이 되는 테스트검증 속도가 빠르고, 안정적수동 테스트, 자동화 테스트 -> 인지 필요사람이 검증하는 수동 테스트 -> sout으로 출력하고 눈으로 직접 확인기계가 검증하는 자동화 테스트Junit5, AssertJJunit5 : 단위 테스트를 위한 테스트 프레임워크AssertJ : 테스트 코드 작성을 원할하게 돕는 테스트 라이브러리 - 풍부한 API 메서드 체이닝 지원해피 케이스, 예외 케이스 -> 테스트 케이스 세분화예외 케이스 : 암묵적 혹은 드러나지 않은 요구사항에서 발견경계값 테스트범위, 구간, 날짜 경계값들로 테스트를 해야한다.테스트하기 쉬운/어려운 영역 (순수함수)테스트 하기 어려운 영역을 구분하고 분리하기외부로 분리할수록 테스트 가능한 코드는 많아진다.테스트하기 어려운 영역관측할 때마다 다른 값에 의존하는 코드 : 현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력외부 셰계에 영향을 주는 코드 : 표준 출력, 메시지 발송, 데이터베이스에 기록하기순수 함수 - 테스트하기 쉬운 영역같은 입력에는 항상 같은 결과외부 세상과 단절된 형태lombok@Data, @Setter, @AllArgsConstructor 지양양방향 연관관계 시 @ToString 순환 참조 문제섹션 4. TDD: Test Driven DevelopmentTDD 테스트 주도 개발 (Test Driven Development)로, 프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론선 기능 구현, 테스트 작성의 문제점 (일반적인 개발) - 구현순서 : 기능 -> 테스트테스트 자체의 누락 가능성해피 케이스만 검증할 가능성잘못된 구현을 다소 늦게 발견할 가능성선 테스트 작성, 기능 구현 (TDD) - 구현순서 : 테스트 -> 기능복잡도(유연하며 유지보수가 쉬운)가 낮은 테스트 가능한 코드로 구현할 수 있게 한다.테스트가 힘든 코드를 위한 코드 작성이 가능예를 들면 LocalDateTime.now()의 경우 외부세계로 분리해서 테스트를 하기 편한 코드를 작성할 수 있다.프로덕션 코드를 작성한 후 테스트 코드를 작성하기 귀찮을수도..쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.구현에 대한 빠른 피드백을 받을 수 있다.과감한 리팩토링이 가능해진다.테스트 코드가 보장TDD의 관점이전의 관점 : 테스트는 구현부 검증을 위한 보조 수단변화된 관점 : 테스트와 상호 작용하며 발전하는 구현부레드 - 그린 - 리팩토링Red : 실패하는 테스트 작성Green : 테스트 통과 하는 최소한의 코딩Refactor : 구현 코드 개선 테스트 통과 유지애자일 방법론 vs 폭포수 방법론애자일 방법론 https://agilemanifesto.org/iso/ko/manifesto.html반복적 개발(Iterative Development): 짧은 개발 주기(스프린트)를 반복하며 지속적으로 개선.유연성: 요구사항 변경을 수용할 수 있도록 유동적으로 진행.고객 참여: 개발 과정에서 지속적인 피드백을 반영하여 사용자 중심 개발 가능.자율적인 팀 구성: 개발팀이 자체적으로 의사 결정을 하며, 빠르게 문제를 해결함.폭포수 방법론단계적 개발: 요구 분석 → 설계 → 구현 → 테스트 → 배포 → 유지보수 순서로 진행됨.문서 중심: 각 단계마다 문서화가 철저하게 이루어짐.선형 구조: 이전 단계가 완료되어야 다음 단계로 넘어갈 수 있음.변경이 어려움: 초기에 요구사항을 확정하면 이후 변경이 어렵고 비용이 많이 듦.익스트림 프로그래밍XP(Extreme Programming, 익스트림 프로그래밍)는 애자일 방법론 중 하나로, 빠른 개발 주기와 지속적인 피드백을 중심으로 하는 소프트웨어 개발 방법론이다. 고객의 요구사항 변화에 빠르게 대응할 수 있도록 짧은 개발 반복 주기(Iteration)와 강한 협업 문화를 강조한다.스크럼, 칸반스크럼애자일 프레임워크로, 일정한 스프린트 동안 작업을 계획하고 진행하는 반복적이고 점진적인 개발 방식이다. 짧은 개발 스프린트를 통해 빠르게 결과물을 만들고 지속적으로 개선하는 것이 핵심이다.1⃣ 백로그 작성 – 제품 백로그에 모든 요구사항을 정리2⃣ 스프린트 계획 – 스프린트 기간 동안 수행할 작업 선정3⃣ 스프린트 진행 – 개발 진행 및 매일 스탠드업 미팅4⃣ 스프린트 리뷰 – 개발 완료된 기능을 검토5⃣ 회고(Retrospective) – 개선점을 찾고 다음 스프린트에 반영칸반Workflow와 가시성을 중심으로 한 애자일 프레임워크로, 지속적인 개선과 작업량 관리를 중점적으로 다룬다. 작업을 시각적으로 표현하여 현재 진행 상황을 쉽게 파악할 수 있도록 합니다.1⃣ Backlog: 해야 할 작업을 모아둠2⃣ To Do: 현재 진행할 작업3⃣ In Progress: 진행 중인 작업4⃣ Review/Test: 리뷰나 테스트가 필요한 작업5⃣ Done: 완료된 작업섹션 5. 테스트는 []다.테스트 코드는 문서다.프로덕션 기능을 설명해주는 것이 테스트 코드 문서다.다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완할 수 있다.고민했던 내용(테스트 코드)을 팀 자산(소스 코드)으로 공유할 수 있다.@DisplayName - 도메인 정책, 용어를 사용한 명확한 문장메서드명만으로 어떤 것을 검증하고자 하는 의도 파악이 어려움Junit5에 추가한 어노테이션이다.문장 형태로 섬세하게 테스트 검증에 대한 내용을 어노테이션안에 작성한다.섬세한 DisplayName특정 시간 이전에 주문을 생성하면 실패한다. ❌영업 시작 시간 이전에는 주문을 생성할 수 없다. ✅도메인 용어를 사용하여 추상화된 내용을 담기 -> 메서드 자체의 관점 보다 도메인 정책 관점 (특정 시간 -> 영업 시작 시간 ✅)테스트의 현상을 중점으로 기술하지 말 것 (~실패한다 ❌)Given / When / Then - 주어진 환경, 행동, 상태 변화Given : 시나리오 진행에 필요한 모든 준비 과정When : 시나리오 행동 진행Then : 시나리오 진행에 대한 결과 명시, 검증TDD vs BDDBDDTDD에서 파생된 개발 방법시나리오 기반한 테스트 케이스 자체에 집중하여 테스트한다.Junit vs SpockSpock은 Groovy언어로 BDD 패턴을 적용해서 테스트 코드를 작성할 수 있다.언어가 사고를 제한한다.명확하지 못한 테스트 코드는 사고를 제한할 수 있다.문서로서의 테스트를 신경 쓸 필요가 있다. 🏃 돌아보며..미션과 발자국을 정신없이 진행하다 보니 벌써 남은 인프런 워밍업 클럽도 2주밖에 남지 않았다.처음에 OT 라이브 당시 러너가 120명 정도였는데, 이번 중간 점검 라이브 때는 60명 정도로 줄어들었다.강의 내용 자체는 어렵지 않지만.. 2개의 강의(14시간 + 12시간)를 한 달만에 들으면서 미션과 발자국을 진행하는 건 쉽지 않아 보인다..나는 미리 강의를 수강해서 다행이다라는 생각이 든다.. 😅하지만, 쉽지 않은 만큼 성실히 참여한다면 단기간 내 성장하는 데 큰 도움이 될 것이다.그리고 이번 주에 코드리뷰를 신청하기 잘했다는 생각이 들었다. 🍀위에서도 여러 번 언급했지만 우빈님의 세심한 리뷰 탓(?)에내가 미션을 수행하는 데 있어 우빈님보다 세심하게 집착 했었나..? 반성하게 된다... 😭마지막 주차 온라인 라이브에서도 테스트 코드에 대한 코드리뷰가 진행된다고 한다.기회가 된다면 또 한 번 코드리뷰를 받아 단골 손님이 되고 싶다. 😂2주를 걸쳐, 읽기 좋은 코드의 스터디 과정은 이번주로 막을 내렸다.다음 주차부터는 테스트 코드에 대해 본격적으로 스터디하는 과정이 진행된다.남은 2주도 화이팅하며 좋은 성장을 이루길 기대해 본다. 🔥끝으로, 3월 중순이 되니 이제 슬슬 봄 내음이 나는 것 같다.. 🌸얼어붙은 개발 시장에도 봄이 찾아왔으면 좋겠다.. 🧊발자국 2주차 끄읕 ![출처]인프런 워밍업 클럽 : https://www.inflearn.com/course/offline/warmup-club-3-be-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
백엔드
・
인프런
・
워밍업클럽
・
백엔드
・
3기
・
발자국
・
박우빈
・
클린코드
・
읽기좋은코드
2025. 03. 09.
1
[인프런 워밍업 클럽 3기 - 백엔드 코드] 발자국 1주차
🤔 인프런 워밍업 클럽 스터디 신청 계기나는 인프런 워밍업 클럽 스터디 신청하기 한 달 전에 스터디 과정에 쓰이는 로드맵 강의를 이미 완강하였다.그럼에도 불구하고 강의를 수강하기 전부터 우빈님의 팬(?)이였기 때문에 이메일을 받자마자 신청을 할 수 밖에 없었다.항해 플러스 8기 백엔드 과정을 앞두고 약 2주간 겹치는 구간이 있어 잠시나마 고민을 하긴 했지만?..복습 차원에서 미션과 발자국을 통해 학습내용을 더 딥하게 파고들어 성장하고자 신청하게 되었다.👣 발자국이란?인프런 워밍업 클럽에서 이야기하는 발자국이란 다음과 같다.주 1회, 일주일 동안 배운 내용을 바탕으로 남기는 학습 일기이자 회고이다.매주 강의를 요약하고 기록하면서 강의 내용을 제대로 이해했는지 확인하고, 보완할 부분을 찾는다.기록을 통해 배운 내용에 대해 메타인지 하는 시간을 가진다.나는, 이번 과정에서는 강의를 재수강하지는 않고섹션 별로 우빈님께서 제공해주신 키워드 정리를 통해 학습을 진행할 예정이다.💡 자기만의 언어로 키워드 정리하기섹션 2. 추상추상과 구체 : 이름 짓기 + 메서드 선언부 = 추상화 행위실제 코드를 작성하다보면 이름 짓기에 시간을 가장 많이 투자한다.이름 짓기의 중요성이 크기 때문에 반복되는 훈련을 통해 이름 짓기의 스킬 향상이 필요해 보인다. 추상화 레벨메서드를 추출하는 과정에서 외부셰계와 내부세계가 분리가 되고, 이 과정에서 추상화 레벨을 잘 고려해야한다.추상화 레벨이 동등해야 자연스럽게 코드가 읽힌다.[다른 추상화 레벨]public class Order { public void process() { paymentService.pay(); // 고수준 추상화 📈 System.out.println("결제 완료"); // 저수준 추상화 📉 } } [동등한 추상화 레벨]public class Order { public void process() { paymentService.pay(); // 고수준 추상화 📈 printCompletedPay(); // 메서드를 분리하여 고수준의 추상화를 동등하게 유지 ✅ } private void printCompletedPay() { System.out.println("결제 완료"); } } 매직 넘버 + 매직 스트링 : 상수 혹은 Enum을 활용하여 이름 짓기를 통해 가독성이 좋아지게 한다.섹션 3. 논리, 사고의 흐름뇌 메모리 적게 쓰기 (인지적 경제성)읽기 좋은 코드는 읽는 사람으로 부터 뇌를 편안하게 한다.반대로 읽기 힘든 코드는 읽는 사람의 뇌를 피곤하게 한다. Early return : Early return은 아래쪽 내용을 읽을 필요 없어 뇌 메모리 적게 쓰기에 효과적이다.사고의 depth 줄이기중첩 분기문가 중첩 반복문을 무조건 depth를 줄이기보다, 추상화를 통한 사고의 depth를 줄이는 것이 중요하다.사용할 변수는 가깝게 선언한다.공백 라인 : 공백 라인도 리팩토링 시 중요한 요소이다. 공백 라인이 존재하지 않은 코드는 읽기 어렵다.부정어부정연산자는 가독성을 해친다. 여러 번의 사고를 거치면서 코드를 읽어야 한다.한 번에 사고할 수 있도록 부정연산자를 제거하고 메서드로 추출했다면 이름 짓기를 적절하게 수정해야한다.해피 케이스, 예외 처리해피 케이스보다 예외처리에 더 신경써서 코드를 작성해야 한다.예외는 의도하지 않은 예외랑 의도한 예외가 있다.섹션 4. 객체 지향 패러다임객체, 협력과 책임, 관심사의 분리, 높은 응집도와 낮은 결합도협력과 책임 : 객체간의 협력, 객체가 담당하는 책임관심사의 분리 : 관심사에 따라 객체를 만들어 낼 수 있다.높은 응집도 : 특정한 관심사로만 이루어진 설계낮은 결합도 : 각 관심사끼리는 독립적이여야 한다.[낮은 응집도]public class OrderService { public Order createOrder(Product product) { // 주문 생성 로직 } public void sendEmail(Order order) { // 이메일 전송 로직은 OrderService의 관심사가 아니다. ❌ } } [높은 응집도로 해결]객체를 분리함으로써 응집도가 올라간다.public class OrderService { public Order createOrder(Product product) { // 주문 생성 로직 } } public class OrderEmailSender { // 객체를 분리하여 높은 응집도로 설계 ✅ public void sendEmail(Order order) { // 이메일 전송 로직 } } [높은 결합도]public class OrderService { private EmailService emailService = new EmailService(); // 높은 결합도 ❌ } [낮은 결합도로 해결]인터페이스와 DI를 활용하여 결합도를 낮춘다.public interface EmailSendable { } public class OrderEmailSender implements EmailSendable { } public class OrderService { private final EmailSendable emailSendable; public OrderService(EmailSendable emailSendable) { this.emailSendable = emailSendable; // 높은 결합도를 해결한다. ✅ } }객체끼리의 협력과 책임을 통해 프로그램을 만들 수 있다.가장 중요한 것은 관심사의 분리이고 높은 응집도와 낮은 결합도를 가진 설계를 가지는 것이 중요하다. getter/setter 자제하기, 객체에 메시지 보내기setter는 최대한 지양하는 것이 좋다.getter를 남발하는 건 객체를 존중하지 않는 것이며 무례하면서 폭력적인 행위이다.SOLID: SRP, OCP, LSP, ISP, DIP아래의 미션 Day 4 과정에 대한 내용을 통해 자세히 다룬다.DI/IoC섹션 5. 객체 지향 적용하기상속과 조합 : 상속은 결합도가 높기 때문에 조합을 활용하는 것이 더 좋은 설계이다.Value Object, EntityVO는 불변성, 동등성, 유효성을 보장해야하며 도메인의 개념을 추상화한 객체이다. (ex. Money 객체)Entity는 식별자가 존재한다. 식별자가 같으면 동등한 객체로 취급한다.일급 컬렉션 : 컬렉션을 Wrapping한 객체를 뜻한다. 컬렉션을 추상화하여 의미를 담을 수 있고, 가공 로직의 보금자리가 생긴다. (VO와 비슷하다.)Enum : 상수의 집합, 상수들에 대한 로직을 담을 수 있다.추상화와 다형성 활용하여 반복되는 if문 제거 -> OCP 지키기변하는 것 : 조건 & 행위 (구체)변하지 않는 것 : 조건을 만족하는가? / 행위를 수행한다. (추상)변하는 것과 변하지 않은 것을 구분해서 보는 훈련이 필요하다.숨겨져 있는 도메인 개념 도출하기 : 변경이 많이 일어날 것 같은 미래를 예견하고 이런 것을 도입해보면 어떨까? 하고 숨겨진 도메인 개념을 도출도메인 지식은 만드는 것이 아니라 발견하는 것이다.객체지향은 흉내내는 것이다.미래를 예견하고 도메인 개념을 도출해보자.[이메일 전송 개념에서 미래를 예견하고 숨겨진 도메인 개념을 도출]현재의 최선에서는 이메일만 발송하지만 미래에 카카오톡 알림톡이나 혹은 다른 알림에 대한 도메인 개념을 도출해보자public interface Sendable { // 미래를 예견하고 도매인 개념을 도출 ✅ void send(String message); } public interface EmailSendable extends Sendable { @Override void send(String message); }👨🏻💻 미션 회고 [미션 Day 2]미션 PR : https://github.com/discphy/warm-up-backend-code/pull/11⃣ 추상과 구체 예시강의 내용에서도 계속 등장하듯이 추상과 구체는 이번 강의에서 가장 핵심이 되는 단어들이다.이 미션을 접했을 때, 단순히 추상과 구체에 대한 예시를 들기보다 개발적인 관점에서 추상과 구체에 대해 접근하려고 노력했다. (적절한 예시인지는 잘 모르겠으나..😂)해당 미션을 통해, 추상화가 단순히 쉽지 않다는 걸 느꼈으며 개발적인 관점에서도 더 읽기좋은 코드를 작성하기 위해선 추상화를 잘 해야겠다라는 생각이 들었다. [미션 Day 4]미션 PR : https://github.com/discphy/warm-up-backend-code/pull/21⃣ validateOrder 메서드를 읽기 좋은 코드로 리팩토링 하기해당 메서드를 봤을 때는 엄청 어지러웠다.. 🤣 본능적으로 빨리 리팩토링 하고 싶다는 생각이..추상화 기법들을 통해 도메인 객체를 분리하고 도메인 내부세계로 검증 로직을 구현하였다.객체 분리 : 일급 컬렉션으로 의미있는 Items의 객체로 분리하였고, Member라는 객체를 생성하여 customerInfo에 대한 검증 로직을 구현하였다.사고의 depth 줄이기, 부정 연산자 지양 : 중복 분기문과 부정 연산자를 포함한 조건식을 리팩토링하여 읽는 사람으로 하여금 뇌 메모리 적게 쓰게 끔 리팩토링했다.미션 PR에도 코멘트를 달았지만 도메인 레이어의 로깅이 하는 구현 부가 포함된 게 신경이 쓰인다.도메인 레이어에서 의존성을 주입받지는 않아 객체 분리가 맞는지 모호하다.[기존 코드]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; }[리팩토링 이후 코드]public class Order { private static final Logger log = Logger.getLogger(Order.class.getName()); private final Items items; private final Member member; private final int totalPrice; private Order(Items items, Member member, int totalPrice) { this.items = items; this.member = member; this.totalPrice = totalPrice; } public boolean validate() { if (items.isEmpty()) { log.info("주문 항목이 없습니다."); return false; } if (totalPrice 2⃣ SOLID에 대하여 자기만의 언어로 정리사실, 객체 지향에 대해 학습한 사람들 중 SOLID를 모르는 사람은 없을 것 이다.그치만 여기서의 포인트는 자기만의 언어이다.위에서도 강의에 대한 정리내용을 작성할 때 "💡 자기만의 언어로 키워드 정리하기" 라는 제목으로 작성하였다.학습한 내용을 온전히 내 것으로 만들기 위해선 반드시 나의 언어로 작성해야 내 것이 되고, 차후에 정리한 내용을 다시 봤을 때도 금방 기억을 더듬을 수 있을 것 이다.그래서 나는 텍스트 보다 코드로 봤을 때의 이해도가 더 빠르기 때문에 SOLID 원칙의 내용을 정리할 때 불필요한 텍스트를 줄이고 코드로 내용을 정리하였다.🏃 돌아보며..위에서도 언급했지만, 항해 플러스 백엔드 스터디 2개도 병행하고 있어 참여를 망설였지만.. 신청하기 잘한 것 같다.(2기가 마지막이 될까봐 조마조마 했던 1인.. 우빈님 감사합니다.. 🙇♂)온라인 밋업을 통한 우빈님과 소통하는 것도 그렇고..여러가지 미션의 내용도 단조롭지 않고 수준이 높은 것 같아 다시 한번 메타인지를 경험하게 해준다.강의의 경우, 시간상 처음부터 끝까지 다시 보지는 못하겠지만.. 불과 몇 개월 전 수강한 강의임에도 단어들이 어색하다... 😂어색했던 부분은 다시 강의를 보며 이해하고 보완하였다.읽기 좋은 코드를 작성하기 위해 학습한 내용을 기반으로 적절한 추상화를 적용하는 훈련을 하며 내 코드가 읽기 좋은 코드가 되도록 노력해야겠다. [출처]인프런 워밍업 클럽 : https://www.inflearn.com/course/offline/warmup-club-3-be-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
인프런
・
워밍업클럽
・
백엔드
・
3기
・
발자국