블로그
전체 8#카테고리
- 백엔드
- 웹 개발
2025. 03. 30.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 1주차 발자국
회고록강의가 시작되었다. 사실 전체적인 내용은 코드 작성이나 분석보다도 기존에 알고 있는 정보를 정리하고 어떻게 적용하지는지에 대하 예시가 많았다. 1. 무엇을 배웠나?이번 주에는 다음과 같은 내용을 학습했다. 섹션 2 추상과 구체추상과 구체자세한 내용은 아래에서 섹션 3 논리, 사고의 흐름뇌 메모리 적게쓰기early return, 사소의 depth 줄이기 (메서드 분리)개발시 의식적으로 쓰고있다.공백 라인, 부정어해피 케이스, 예외처리예외 케이스를 찾아내는게 쉽지 않은데, 방안이 좀 상세 했으면 좋았을텐데...Optional섹션 4 객체 지향 패러다임객체, 협력과 책임, 관심사의 분리, 높은 응집도와 낮은 결합도getter/setter 자제하기, 객체에 메시지 보내기개인적으로는 getter에는 관대해도 된다고 생각하는데 잘 모르겠다. 노출하지 않으려면 field 자체가 없어야 하지 않을까?SOLIDDI/IoC섹션 5 객체 지향 적용하기상속과 조합Value Object, Entity일급 컬렉션Enum추상화와 다형성 활용하여 반복되는 if문 제거, OCP 지키기if문 제거는 생각보다 어렵다. if문 제거를 위해서 라기보다는 전반적인 로직 개선 관점에서 고려해야 될것이다숨겨저 있는 도메인 개념 도출하기 2. 무엇이 인상적이었나?추상과 구체 부분은 보니 처음 개발업을 시작했을때가 생각났다. 붕어빵과 틀 예시를 처음 들었을때를 기억하면서 미술까지 생각하는 것은 나와 비슷한 생각을 하는 사람이 있었구나와 어쩌면 다들 저렇게 생각하고 있었구나 의 사이정도의 감정이 들었다. 3. 이번 주 학습을 통해 얻은 것전반적으로 기존의 인지하고 있는 내용을 반복하는 시간이 되었다.
2025. 03. 30.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 4주차 발자국
회고록4주차로 드디어 스터디가 종료 되었다. 이번 스터디를 진행하면서 진행한 개선방향을 실무에 어떻게 적용할지 생각해보고 반복해서 진행해봐야 할것이다. 1. 무엇을 배웠나?이번 주에는 다음과 같은 내용을 학습했다. 섹션 5~7 BDD(Behavior-Driven Development) 요약BDD는 소프트웨어의 행동(Behavior) 에 초점을 맞춘 개발 방법론이다. Given-When-Then 형식을 사용해 테스트 시나리오를 작성한다. 비즈니스 이해관계자와 개발자 간의 원활한 의사소통을 돕는다. 테스트를 문서화하는 효과가 있어 가독성과 유지보수성이 향상된다테스트 더블(Test Double) 요약Dummy: 사용되지 않지만 인자로 전달되는 객체 (예: null 반환).Fake: 실제 동작하지만 간단한 구현을 제공하는 객체 (예: 인메모리 DB).Stub: 미리 정의된 값을 반환하는 객체 (예: 특정 입력에 대한 고정 응답).Spy: 메서드 호출 내역을 기록하는 객체 (예: 호출된 인자 저장).Mock: 호출 여부 및 횟수를 검증하는 객체 (예: 특정 메서드 호출 확인). 섹션 8~10테스트 진행시 고려해볼 상황과 기타 툴 사용법 2. 무엇이 인상적이었나?서포팅 받을 수 있는 툴 사용 방법이 인상적이었다. 3. 이번 주 학습을 통해 얻은 것사실 개발이라는 작업은 잘 정리된 글쓰기에 가깝다. 정리된 글은 문학이 되지만 정리되지 않은 글은 노트 메모만도 못한 취급을 받는다. 문학적으로는 아니더라도 노트정도는 될수 있도록 코드를 작성하도록 하자.
2025. 03. 27.
0
Day 18 Mission [Layered Architecture 에 대하여 정리]
1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다. 1. @Mock vs @Spy (Mockito)이 두 개는 Mockito에서 제공하는 어노테이션이고, 단위 테스트에서 가짜 객체(Mock 또는 Spy)를 생성할 때 사용된다.@Mock완전히 가짜(Mock) 객체를 생성해서 메서드 호출 시 기본적으로 null, 0, false 같은 값을 반환한다.호출 기록을 확인할 수 있고, 특정 동작을 when(...).thenReturn(...)으로 지정 가능하다.실제 객체의 메서드는 호출되지 않는다.예제:@Mock private UserRepository userRepository; @Test void testMock() { when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "John"))); Optional user = userRepository.findById(1L); System.out.println(user.get().getName()); // John } @Spy실제 객체를 감싸는(Mock하지 않는) 가짜 객체(Spy) 생성한다.기본적으로 실제 객체의 메서드를 실행하지만, 특정 메서드는 when(...).thenReturn(...)을 사용하여 동작을 변경할 수 있다.일부 동작만 모킹하고 싶을 때 유용하다.예제:@Spy private List spyList = new ArrayList(); @Test void testSpy() { spyList.add("one"); spyList.add("two"); when(spyList.size()).thenReturn(100); // 특정 메서드만 모킹 System.out.println(spyList.get(0)); // one (실제 메서드 호출) System.out.println(spyList.size()); // 100 (모킹된 값 반환) } 2. @MockBean vs @SpyBean (Spring Boot)이 두 개는 Spring Boot에서 제공하는 어노테이션이다.Spring 컨테이너에 있는 빈(Bean)을 Mock 또는 Spy로 대체하는 역할을 한다.@MockBeanSpring 컨텍스트에서 기존에 등록된 빈(Bean)을 Mock 객체로 교체.모든 메서드는 기본적으로 null, 0, false를 반환한다.@Mock과 비슷하지만, Spring 컨테이너에 등록된 Bean을 대상으로 한다는 차이점이 있다.예제:@SpringBootTest class MyServiceTest { @MockBean private UserRepository userRepository; @Autowired private MyService myService; @Test void testMockBean() { when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "John"))); User user = myService.getUserById(1L); System.out.println(user.getName()); // John } } ✅MockBean을 사용하면 UserRepository의 실제 빈이 Mock으로 대체됨.@SpyBeanSpring 컨텍스트에서 기존에 등록된 빈(Bean)을 Spy 객체로 교체된다.기본적으로 실제 객체의 메서드를 실행하지만, 특정 메서드는 when(...).thenReturn(...)을 사용해 동작을 변경할 수 있다.@Spy와 비슷하지만, Spring 컨테이너의 실제 Bean을 대상으로 한다는 점이 다르다.예제:@SpringBootTest class MyServiceTest { @SpyBean private UserService userService; // 기존 Bean을 Spy로 감싸서 일부 메서드만 Mock 가능 @Test void testSpyBean() { when(userService.getUserName(1L)).thenReturn("Mocked Name"); System.out.println(userService.getUserName(1L)); // Mocked Name (모킹된 메서드) System.out.println(userService.getUserCount()); // 실제 메서드 실행 } } ✅SpyBean을 사용하면 UserService의 실제 빈이 Spy로 감싸짐.3. @InjectMocks@Mock이나 @Spy로 생성된 객체들을 자동으로 주입해 줌.테스트 대상 클래스에 의존하는 객체들을 자동으로 설정해 주기 때문에, new 키워드 없이 의존성을 주입할 수 있음.@Mock 또는 @Spy로 선언된 객체를 @InjectMocks가 붙은 객체의 생성자, 필드, setter 등을 이용해 주입함.예제:class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public String getUserName(Long id) { return userRepository.findById(id).map(User::getName).orElse("Unknown"); } } class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; // userRepository가 자동 주입됨 @Test void testInjectMocks() { when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "Alice"))); System.out.println(userService.getUserName(1L)); // Alice } } ✅userService를 직접 new로 생성하지 않아도 @InjectMocks가 @Mock된 userRepository를 자동으로 주입해 준다. 2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)댓글이 목적이니 댓글 이외의 통합할수 있는 부분은 통합처리@BeforeEach void setUp() { 1-1. 2-1. 3-1. 사용자 생성에 필요한 내용 준비 1-3. 2-3. 3-5. 게시물 생성에 필요한 내용 준비 1-2. 2-2. 3-2. 사용자 생성 1-4. 2-4. 3-6. 게시물 생성 } @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 검증 }
2025. 03. 25.
0
Day 16 Mission [Layered Architecture 에 대하여 정리]
1. 레이어 별로 어떤 특징이 있는가?보통 3계층 혹은 4계층으로 구조를 분리하는데 강의에서는 3계층으로 표현하고 있다.프레젠테이션 계층 (Presentation Layer)사용자와 직접 상호작용하는 부분이다. (외부 세계의 요청을 가장 먼저 받는 계층이다.)파라미터에 대한 최소한의 검증을 진행한다.비즈니스 로직 계층 (Business Logic Layer)애플리케이션의 핵심 기능과 규칙을 처리합니다.사용자 요청에 따라 데이터 처리, 검증, 트랜잭션 관리 등을 수행한다.영속성 계층 (Persistence Layer)데이터베이스나 외부 저장소와의 통신을 담당합니다.가공 로직이 포함되어서는 안된다.SQL 쿼리 실행, ORM 매핑 등이 이 계층에서 이뤄집니다.2. 어떻게 테스트 하면 좋을까?각 계층이 다른 계층에 침투하지 않도록 해야한다. 또한 각 레이어의 목표에 맞게 진행해야한다.프레젠테이션 계층 (Presentation Layer)사실 테스트하기 가장 난감하다. 단순 restfull api 서버라면 가능하지만 화면을 제공한다면 테스트가 복잡해진다.화면의 구조나 UI/UX 측인 부분도 검증이 필요하다.restfull 서버라는 가정하에는 MockMvc와 @MockBean을 사용하여 Mocking 처리가 가능하다.비즈니스 로직 계층 (Business Logic Layer)사실상 중간단계의 통합테스트가 많다. @MockBean을 사용하여 Repository를 Mocking하고 비즈니스 로직 검증이 가능하지만 fake객체를 만들어 단독으로 검증할 수 있도록 하는것이 더 좋다.(속도가 월등하다)데이터 접근 계층 (Data Access Layer)@Transactional를 이용한 CRUD 테스트가 가능하다.인메모리 디비를 사용해 테스트가 가능하지만 역시다 fake객체를 이용하여 빠른 속도로 검증이 가능하다.영속성 객체의 속성을 잘못 인지하면 테스트는 통과하지만 실제로는 오류가 발생한다거나 부하테스트 등을 정상적으로 처리다 안되고 영속성 1차 캐시에서만 처리되 무의미한 테스트가 발생할수 있으니 주의가 필요하다.
2025. 03. 23.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 3주차 발자국
회고록3주차로 넘어가면서 벌써 한 강의가 끝났다. 언제나 로직을 구현하고 테스트 코드를 구현하는 것은 어렵고도 귀찮은 일이다 이주 과제인 테스트 코드 과제는 나에게 어떤 의미가 있었나 생각해보는 시간이 필요하다. 1. 무엇을 배웠나?이번 주에는 다음과 같은 내용을 학습했다. 섹션 3 단위 테스트테스트 도구JUnit 5: 단위 테스트를 위한 프레임워크 (XUnit 계열)AssertJ: 풍부한 API 및 메서드 체이닝 지원, 테스트 코드 작성 보조 테스트 케이스 세분화해피 케이스: 정상적인 상황에서 예상된 동작 확인예외 케이스: 에러나 예외 상황 검증경계값 테스트: 특정 범위(이상, 이하, 초과, 미만)나 날짜 등 경계값 테스트 테스트하기 어려운 영역 구분 및 분리테스트하기 어려운 코드 예시현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등표준 출력, 메시지 발송, DB 기록 등 외부 세계에 영향을 주는 코드해결 방법: 테스트하기 어려운 요소를 외부로 분리하여 테스트 가능한 코드로 변환 순수 함수(Pure Function)같은 입력에는 항상 같은 결과를 반환하며, 외부 세계와 단절된 형태 → 테스트하기 쉬운 코드섹션 4 TDDTDD의 핵심 사이클TDD는 RED → GREEN → REFACTOR 3단계로 반복 수행됨.RED (실패하는 테스트 작성)새로운 기능을 추가하기 전에 먼저 실패하는 테스트를 작성GREEN (테스트 통과, 최소한의 코드 작성)테스트를 통과할 수 있도록 최소한의 코드 작성REFACTOR (구현 코드 개선, 테스트 유지)중복 제거, 성능 개선 등의 최적화 수행테스트는 계속 통과해야 함 2. 무엇이 인상적이었나?이번 주 학습 중 가장 인상 깊었던 내용 보다는 궁금한 점이 있었다. 기존 레거시 리펙토링에 대한 테스트 보장을 어떻게 할 것인가? 해당 내용에 대한 해결책은 차주 내용에 포함시켜 보겠다.3. 이번 주 학습을 통해 얻은 것전반적으로 기존의 인지하고 있는 내용을 반복하는 시간이 되었다. 차주에도 꾸준히 이어가길... 4. 다음 주 목표금주 발생한 궁금증을 해소시켜 보기
2025. 03. 16.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 2주차 발자국
회고록2주차로 넘어가면서 벌써 한 강의가 끝났다. 개발을 업으로 하면서 항상 깔끔한 코드에 대한 갈증 있는데 이번에 완료한 강의로 그 갈증을 모두 채우기에는 역시나 아쉬움이 있었다. 이것은 아마도 강의의 문제가 아닌 클린코드 라는 작업 자체에 대한 어려움일 것이다. 그리고 나 자신의 작문 실력의 부족함 때문일것이다. 1. 무엇을 배웠나?이번 주에는 다음과 같은 내용을 학습했다. 섹션 5 객체 지향 적용하기상속과 조합 : 상속보다는 조합을 사용하여 유연한 구조를 설계하는 것이 좋다.내 경험에선 상속을 사용한 클래스를 보기 힘들었다. (사실 본적이 없다.) 아마도 숨겨진 변수에 찾기에 대한 어려움 때문일것이라 생각한다.Value Object (VO) :도메인 개념을 값으로 표현하는 객체로, 불변성과 동등성을 보장해야 한다.개인적으로는 VO객체를 많이 사용하려고 노력한다. 값이 스스로 검증 할 수 있다는 것이 매력적이기 때문일까?DTO 내부에 들어가게 될 경우 어떻게 써야할지 좀 막막하기는 하다. 해당 내용도 있었으면 좋았을텐데일급 컬렉션 :컬렉션을 객체로 감싸서 의미를 부여하고, 가공 로직을 포함할 수 있도록 한다.잘 사용하지 않는 패턴이긴하다. 이건 새로 도입할지 여부를 결정해봐도 좋을것 같다.Enum의 특성과 활용 :상수의 집합이며, 관련된 로직을 포함할 수 있는 추상화된 객체로 활용된다.개인적으로 Enum을 사랑하는 사람이다. 한글로 명확히 표현 할 수 있는건 매력적이다.interface를 적용하는 방법은 적절히 사용하면 좋은 패턴인것 같다.다형성 활용하기 :반복적인 if 문을 제거하고, 변화하는 부분을 분리하여 OCP(Open-Closed Principle)를 지킨다.사실 코드 depth를 줄이기 위해 자주 애용하는 패턴이다.숨겨져 있는 도메인 개념 도출하기 :도메인 개념을 발견하여 설계에 반영하고, 완벽한 설계가 아닌 최선의 설계를 지향해야 한다.자세한 내용은 아래에서 섹션 6 코드 다듬기주석의 양면성 :주석은 코드의 가독성을 높일 수도 있지만, 코드 품질이 낮다는 신호가 될 수도 있으며, 꼭 필요한 경우에만 작성해야 한다.자세한 내용은 아래에서변수와 메서드의 나열 순서 :변수와 메서드는 사용 순서대로 배치하며, 공개 메서드를 상단에 배치해 객체의 협력을 더 쉽게 이해할 수 있도록 해야 한다.사실 배열 순서가 꽤나 개발자 취향을 타는 문제라 내 배열 순서랑 한번 비교해 보는 시간이라 좋았다. (나는 목록,상세,생성,수정,삭제, 기능으로 자주 호출되는 순서로 정리한다.)패키지 나누기 :패키지는 문맥을 제공하므로 적절한 크기로 분리하되, 과도한 세분화나 무분별한 변경은 피해야 한다.패키지 나누기는 어려운 문제다. 대부분은 사내 컨벤션이 있기때문에 마음대로 조정하기 어렵다. 기능 유지보수하기 (1) - 버그 잡기 :기존 기능의 오류를 찾아 수정할 때는 코드의 변경이 최소화되도록 해야 한다.기능 유지보수하기 (2) - 알고리즘 교체하기 :알고리즘을 변경할 때는 성능과 메모리 사용을 고려하여 적절한 방법을 선택해야 한다.사실 리펙토링의 핵심은 이런 부분 때문이 아닐까 싶다. 리팩토링 잘된 코드는 추가 요구사항을 적용할때 때로는 코드 한줄 만으로 처리 가능하게 한다. 개발자라면 그럴때 느끼는 감정은 따로 말하지 않아도 잘 알지 않을까 싶다.(물론 항상 그렇지는 않다.)IDE의 도움 받기 :코드 정렬, linting, 스타일 가이드를 활용하여 가독성과 유지보수성을 높인다.개인적으로 코드 스타일 같은 경우는 툴에 의존되야 되는것이 맞다고 생각한다. (코드 컨벤션은 사람이 지키기에는 정말 어렵다.) 섹션 7 리팩토링 연습리팩토링 (1) - 추상화 레벨 :중복 코드를 제거하고, 메서드를 추출하여 적절한 추상화 레벨을 맞춘다.리팩토링 (2) - 객체의 책임과 응집도 :IO 처리와 화면 출력(display) 기능을 분리하고, 일급 컬렉션과 Order 객체를 도입하여 책임을 명확히 한다. 리팩토링 (3) - 관점의 차이로 달라지는 추상화 :같은 기능이라도 구현 중심과 도메인 개념 중심으로 접근하는 방식에 따라 추상화가 달라질 수 있다.강의를 따라하면서 의문이 들었던 부분은 MVC, MVVC 와 같은 아키텍처 부분이 나올줄 알았는데 안나왔다는 것이다. 강의 분량 때문에 안 나온건지 아니면 일부러 포함을 안 시킨 건지 잘 모르겠지만 중간에 "Controller"라는 표현을 한것 을 보면 후자에 가까운 것으로 보인다.섹션 8 기억하면 좋은 조언들능동적 읽기 : 핵심 목표는 도메인 지식을 늘리는 것이게 정답이다. 중요한 건 도메인이지 기술이 아니다.오버 엔지니어링 : 적정 수준보다 더 높은 수준의 엔지니어링자세한 내용은 아래에서은탄환은 없다 : 클린 코드도 은탄환이 아니다.자세한 내용은 아래에서2. 무엇이 인상적이었나?이번 주 학습 중 가장 인상 깊었던 내용은 숨겨져 있는 도메인 개념 도출하기, 주석의 양면성, 오버엔지니어링, 은탄환은 없다. 이었다.숨겨져 있는 도메인 개념 도출하기업무를 하면서 도메인의 관점에서 생각을 해보지 않아서 새로웠다.주석의 양면성주석이 참 업무를 하다보면 매력적으로 다가올때가 많다. 하지만 레거시에서는 그렇게 보기 싫더라... 히스토리 용으로만 쓰면 좋다는 내용은 동의하지만 고객에게 전달할때 어떻게 코드만으로 전달 수있을지는 여전히 잘 모르겠다.오버엔지니어링사실 개발하면서 신규 요구사항을 한방에 해결하기 위해 오버엔지니어링을 많이 하게되는것 같다. "필요하기전에 만들지 마라"라는 말이 있듯이 참 지키기 어려운 부분인것 같다.은탄환은 없다."머리는 애자일, 몸은 워터풀" 인 상황이 많았다. 사실 강의에서는 "지속 가능한 소프트웨어의 품질 VS. 기술 부채를 안고 가는 빠른 결과물" 이지만 클라이언트의 요구사항은 거의 대부분 "지속 가능한 소프트웨어의 품질을 가진 빠른 결과물" 인 경우가 많다. 그래서 아마 대부분의 개발자가 품질을 높이기 위해 작업 공수를 실 공수보다 높게 부르는 것이라 생각한다. 이게 보통 성과에 직결되는 부분이라 참 어렵다.3. 이번 주 학습을 통해 얻은 것이번 주 학습을 통해 나는 Enum 활용법에 대해 배웠고 도메인에 대한 생각을 더 깊게 해야겠다고 생각했다.특히, 도메인에 대한 생각을 깊게 하는 연습을 게을리 하면 안될것이라 생각한다. 4. 다음 주 목표다음 주에는 테스트 코드 방법론 적용을 달성하고 싶다.항상 로직을 먼저 작업하고 테스트 코드를 작성하는 버릇이 있는데 차주에는 그런 버릇을 의식적으로 수정해 보리라..
백엔드
2025. 03. 07.
0
Day 4 mission 2 [SOLID에 대하여 자기만의 언어로 정리해 봅시다.]
우리는 왜 SOLID한 코드를 작성해야 하는지 논의 해볼 필요가 있다.SOLID의 근원을 찾아보면 애자일에서 찾아볼수 있다. 애자일 선언을 보면 다음과 같다 공정과 도구보다 개인과 상호작용을포괄적인 문서보다 작동하는 소프트웨어를계약 협상보다 고객과의 협력을계획을 따르기보다 변화에 대응하기를 상호작용과 변환에 대응 하며 작동하는 소프트웨어가 우리는 필요하다. 점차 고도화되는 레거시에 대응하기 위한 방법이 SOLID 원칙을 지킨 코드라고 할 것이다. 로버트 마틴의 Agile Software Development, Principles, Patterns, and Practices: Pearson New International Edition 을 보면 유지보수하기 힘든 코드는 아래와 같은 취약점을 가진다고 한다. 경직성(Rigidity) : 설계를 변경하기 어려움취약성(Fragility) : 설계가 망가지기 쉬움부동성(Immobility) : 설계를 재사용하기 어려움점착성(Viscosity) : 제대로 동작하기 어려움불필요한 복잡성(Needless Complexity) : 과도한 설계불필요한 반복(Needless Repetition) : 마우스 남용불투명성(Opacity) : 혼란스러운 표현 SOLID는 경직성, 취약성, 부동성, 점착성을 보완하기 위한 방법이라 할 수 있다. 중요한것은 해당 원칙은 언제나 보완책이며 절대적인 이론이 아니다.그러면 SOLID 원칙은 어떠한 것이 있는지 확인하고 어떤 방식으로 해당 취약점을 보완하는지 살펴보자.SOLID원칙은 아래와 같다.Single Responsibility Principle (SRP, 단일 책임 원칙) - 경직성(Rigidity), 점착성(Viscosity) 보완Open/Closed Principle (OCP, 개방-폐쇄 원칙) - 경직성(Rigidity), 부동성(Immobility) 보완Liskov Substitution Principle (LSP, 리스코프 치환 원칙) - 취약성(Fragility) 보완Interface Segregation Principle (ISP, 인터페이스 분리 원칙) - 취약성(Fragility) , 부동성(Immobility) 보완Dependency Inversion Principle (DIP, 의존 역전 원칙) - 경직성(Rigidity), 부동성(Immobility) 보완 어떻게 줄이는지 아래의 코드를 보자.Single Responsibility Principle (SRP, 단일 책임 원칙)책임을 나누어 코드의 경직성을 줄인다.public static void main(String[] args) { 사람 사람 = new 사람(); 사람.커피사서회사간다(); } public class 사람 { public void 커피사서회사간다() { System.out.println(" 커피를 산다."); System.out.println(" 회사에 간다."); } }위 코드에서 커피말고 녹차를 마신다면, 저 코드는 필연적으로 수정을 수반한다.반면 메소드를 구분하면 동일한 부분은 수정을 하지 않을 수 있다. public static void main(String[] args) { 사람 사람 = new 사람(); //사람.커피산다(); 사람.녹차산다(); 사람.회사간다(); } public class 사람 { public void 커피산다() { System.out.println(" 커피를 산다."); } public void 녹차산다() { System.out.println(" 녹차를 산다."); } public void 회사간다() { System.out.println(" 회사에 간다."); } }Open/Closed Principle (OCP, 개방-폐쇄 원칙)다형성을 이용하여 클라이언트 코드를 변경하지 많고 확장하여 부동성(Immobility)을 줄인다. public static void main(String[] args) { 한국인 _한국인 = new 한국인(); if(_한국인 instanceof 한국인){ _한국인.녹차산다(); } if(_한국인 instanceof 미국인){ _한국인.커피산다(); } _한국인.회사간다(); } } public class 한국인{ public void 커피산다() { System.out.println(" 커피를 산다."); } public void 회사간다() { System.out.println(" 회사에 간다."); } } public class 미국인{ public void 커피산다() { System.out.println(" 커피를 산다."); } public void 회사간다() { System.out.println(" 회사에 간다."); } } 위 코드에서 중국인이나 일본인이 추가되면 우리는 if문을 계속 추가해줘야한다.하지만 다형성을 이용하면 클라이언트의 코드의 변경을 따로 확인하지 않아도 처리 할 수 있다.public static void main(String[] args) { 회사 _한국인 = new 한국인(); _한국인.회사가기(); } public class 한국인 implements 회사{ public void 녹차산다() { System.out.println(" 녹차를 산다."); } public void 회사간다() { System.out.println(" 회사에 간다."); } @Override public void 회사가기() { 녹차산다(); 회사간다(); } } public class 미국인 implements 회사{ public void 커피산다() { System.out.println(" 커피를 산다."); } public void 회사간다() { System.out.println(" 회사에 간다."); } @Override public void 회사가기() { 커피산다(); 회사간다(); } } public interface 회사 { void 회사가기(); }Liskov Substitution Principle (LSP, 리스코프 치환 원칙)자식이 부모의 기능도 구현가능하게 하여 상속 구조의 안전성을 보장해 취약성(Fragility)을 줄인다 public static void main(String[] args) { 사람 _사람 = new 눈사람(); _사람.말하기(); } abstract class 사람 { abstract void 말하기(); } public class 눈사람 extends 사람{ @Override void 말하기() { System.out.println("나는 말을 못해요"); } }눈사람은 말을 할 수 없다. 하지만 사람이기 때문에 해당 기능을 구현해야 했고 예상하지 못한 오류나 마찬가지다.public static void main(String[] args) { 인간 _사람 = new 일본인(); _사람.말하기(); } public class 일본인 extends 사람 implements 인간{ @Override public void 말하기() { System.out.println("나는 말 해요"); } } public interface 인간 { void 말하기(); } 사람이면서 인간이기 때문에 일본인은 말할수 있다. Interface Segregation Principle (ISP, 인터페이스 분리 원칙)ISP는 많은 기능을 가진 인터페이스를 개별 인터페이스로 분리하여 부동성(Immobility)을 줄인다. public static void main(String[] args) { 강아지 _강아지 = new 강아지(); _강아지.말하기(); } public class 강아지 implements 포유류{ @Override public void 달리기() { System.out.println("가능"); } @Override public void 말하기() { System.out.println("불가능"); } } public interface 포유류 { void 달리기(); void 말하기(); }강아지가 말을 하면 좋겠지만 할 수 없다. 이건 인터페이스가 너무 많은 기능을 가져서 그렇다. 분리하자 public static void main(String[] args) { 강아지 _강아지 = new 강아지(); _강아지.달리기(); } public class 강아지 implements 포유류{ @Override public void 달리기() { System.out.println("가능"); } } public interface 포유류 { void 달리기(); } public interface 인간 { void 말하기(); } 5. Dependency Inversion Principle (DIP, 의존 역전 원칙)DIP와 결합도를 낮춰 부동성(Immobility)을 줄인다. public static void main(String[] args) { 자동차 자동차 = new 자동차(); 한국인 _한국인 = new 한국인(); 자동차.운전하기(_한국인); } public class 자동차 { public void 운전하기(한국인 _한국인){ _한국인.운전하기(); } } public class 한국인 { public void 운전하기() { System.out.println("부웅"); } } 자동차는 한국인이 다른 사람으로 바뀌면 운전을 할 수 없다. 이건 자동차가 한국인에 의존하고 있기 때문이다. 그렇다면 한국인을 추상화하여 의존하게 하면된다. public static void main(String[] args) { 자동차 자동차 = new 자동차(); 인간 _인간 = new 일본인(); 자동차.운전하기(); } public class 자동차 { public void 운전하기(인간 _인간){ _인간.운전하기(); } } public class 일본인 implements 인간{ @Override public void 운전하기() { System.out.println("부웅"); } } public interface 인간{ void 운전하기(); } 한때 "유지보수 못하게 하는 코드만들기"라는 책이 유명했던 적이 있다. 그 책은 그런 코드를 만들지 말자는게 주 목적이었다.사람들은 생각보다 유지보수를 중요하게 생각한다. 적당히 돌아가는 코드는 미래의 나를 힘들게 한다는걸 명심하자. 참조 : https://redutan.github.io/2017/06/07/clean-software-part02https://dl.ebooksworld.ir/motoman/Pearson.Agile.Software.Development.Principles.Patterns.and.Practices.www.EBooksWorld.ir.pdfhttps://gall.dcinside.com/board/view/?id=programming&no=2823825&exception_mode=recommend&page=1
2025. 03. 07.
0
Day4 misson 1
GOAL : 아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.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; }가장 좋은 코드는 Order에 검증 기능을 넣어 사용하는 객체에서 몰라도 되도록 캡슐화 하는 것이 좋다.하지만 어떤 처리를 하는지 예측은 가능하도록 또는 가감할 수 있도록 구현은 감추고 체이닝을 도입했다. public boolean validateOrder(Order order) { return order.hasItems() .isLessEqualThan(0) .hasCustomersInfo() .validate(); } public class Order { ... private boolean isValid; public Order hasItems(){ long count = items.stream().filter(Objects::nonNull).count(); this.isValid = count > 0; if(false == this.isValid) log.info("주문 항목이 없습니다. {}", id); return this; } public Order isGreaterThan(int price) { int totalPrice = items.stream() .mapToInt(Item::getPrice) .sum(); this.isValid = totalPrice > price; if(false == this.isValid) log.info("올바르지 않은 총 가격입니다. {}", totalPrice); return this; } public Order hasCustomer(){ this.isValid = hasCustomerInfo(); if(false == this.isValid) log.info("사용자 정보가 없습니다. {}", customer.toString()); return this; } public boolean validate(){ return this.isValid; } }
웹 개발