워밍업 클럽 3기 BE 클린코드&테스트 - 1주차 발자국
Day 2섹션1. Intro구름톤 딥다이브 스터디에서 진행하는 온라인 서점 프로젝트를 리팩토링 해보려고 계획중이다.클린 코드, 리팩토링 과정의 베스트 프랙티스는 위와 같으니 더할 나위 없이 좋은 타이밍같다.이전부터 도메인이라는 단어는 많이 들었지만, 도메인이 도대체 뭔지는 명확하게 알지 못했다.하지만 해결하고자 하는 문제 영역이 도메인이다 라는 것을 알게되었다.섹션2. 추상우리가 클린 코드를 추구하는 이유나도 가독성을 위해서 개행문자를 사용했다.하지만 단순히 이런 것을 얘기하기는게 아니었고,수많은 클린코드 원칙이 있지만 사실 가독성을 위함이었고이것을 왜 지켜야 하는가를 알아야 한다.수많은 조언들을 관통하는 아주 중요한 주제추상에 대해 알아보자. 프로그램의 정의대학 때 프로그램 = 데이터 + 코드라고 배웠는데, 똑같이 말씀해주셔서 놀랐다. 추상과 구체추상은 항상 구체적인 실재에서 시작해야한다.추상은 하늘에서 뚝 떨어지는게 아니라는 것이다.코드를 짤 때도 경험이 많다면 추상이 바로 이루어지겠지만,경험이 없는 주니어들은 구체적인 실재에서 시작을 하며 추상 경험을 키우는 것이 중요할 것 같다.리팩토링을 많이 해봐야겠다.우리가 일상생활에서 아무렇지 않게 하던 대화들 또한 추상이 이루어지고 있었다.정보 함축, 제거를 통해 추상화를 할 수 있고유추, 정보 재현, 이해를 통해 구체화를 할 수 있는데추상화 과정에서 중요한 정보를 부각시키지 못하면 추상화에 실패한다.또한 해석자가 동일하게 공유하는 문맥이 없어도 추상화에 실패한다.예를 들어, 뜬금없이 밤이라고 한다면먹는 밤인지 깜깜한 밤인지 모른다.잘못된 추상화가 야기하는 사이드 이펙트는 생각보다 정말로 크다.해당 도메인의 문맥안에서 정말 중요한 핵심 개념만 남겨서 표현하는 것바로 적절한 추상화가 중요하다. 이름 짓기이름을 짓는다는 행위는, 추상적 사고를 기반으로 한다.우리 도메인의 문맥 안에서 이해되는 용어를 사용하는 것이 중요하다.팁 4가지단수, 복수 구분하기이름 줄이지 않기은어/방언 사용하지 않기좋은 코드를 보고 습득하기반복문을 처음배울 때 사용하는 i, j, k 같은 변수명에도 이름을 지어주어 추상화를 시켜주자.의미가 명확하게 전달되지 않는 변수명 또한 의미가 명확하게 전달될 수 있도록 변경하자. 메서드와 추상화한 문단의 주제는 반드시 하나다.한 메서드의 주제는 반드시 하나다. 메서드 선언부(중요)반환타입 메서드명 (파라미터)메서드명은 추상화된 구체를 유추할 수 있는, 적절한 의미가 담겨있어야한다.파라미터와 연결지어서 더 풍부한 의미를 전달할 수 있다.메서드 명명 규칙을 따지기보다, 질적으로 더 좋은 추상화를 해냈느냐가 중요한 포인트이다.파라미터의 타입, 개수, 순서를 통해서도 의미를 전달할 수 있다.파라미터는 외부 세계와 소통하는 창반환타입메서드 시그니처에 납득이 가는 적절한 타입의 반환값을 돌려주어야 한다.반환값이 있다면 테스트도 용이해지니 void 대신 반환할 만한 값이 있나 고민해보자.Q. 한 줄인데도 추상화를 해야하나?A. 그렇다. 코드의 양이 중요하다기 보다 추상화하는 의미가 중요한 것check라는 이름은 보통 void의 반환타입을 가진다.리팩토링 후엔 항상 테스트 코드로 검증하자. 추상화 레벨(중요)메서드를 추출한다는 것 자체가 외부 세계와 내부 세계를 나누고 추상화 레벨이 갈린다는 것추상화 레벨이 낮은 부분을 추출하고 경계를 만든다는 것하나의 세계 안에서는 추상화 레벨이 동등해야한다.추상화 레벨이 맞지 않는 순간 읽는 사람은 멈칫하게 된다.코드 흐름을 보고 추상화 레벨이 맞지 않는 부분이 있다면 추상화 레벨을 맞춰주자.메서드로 추출한다는 것의 의미는추상화 레벨을 동등하게 맞춰줌으로써 읽는 사람이 멈칫하지 않도록 만드는 기법 매직 넘버, 매직 스트링매직 넘버, 매직 스트링이란 추출되지 않은 날 것의 숫자나 문자열이름을 주고 의미를 부여하여 상수(변하지 않는 값)로 추상화시키자.가독성, 유지보수성이 올라간다.자바의 상수 네이밍 컨벤션은 대문자, 언더스코어Day 3섹션3. 논리, 사고의 흐름 뇌 메모리 적게 쓰기“정리 시스템에서 중요한 과제는최소의 인지적 노력으로 최대의 정보를 제공하는 것이다.”뇌 또한 추상화를 하고 있다는 것“뇌는 한 번에 한 가지 일 밖에 하지 못한다.”뇌도 CPU처럼 한 번에 한 가지 일 밖에 하지 못하고멀티태스킹을 한다는 것은 사실 CPU처럼 문맥교환이 일어나는 것이었다.인지적 경제성: 최소한의 인지만으로 최대의 효율을 내보자 early return이 함수를 메서드로 추출하고 early return 을 적용 시키면?인지적 경제성: 우리가 인지해야 할 정보를 최대한 줄이자 사고의 depth 줄이기중첩 반복문, 중첩 분기문중첩 반복문, 중첩 분기문의 depth를 줄이는 방법을 메서드로 추출하는 방법이 있었다.내부 세계와 외부 세계를 분리함으로써 사고를 쪼개는 것주의할 점보이는 depth가 아니라 사고의 depth를 줄이는 것무조건 2중 중첩을 줄이는 게 아니고 추상화의 의미가 있는지 꼭 살펴봐야한다.2중 중첩이 사고과정에 도움이 된다면 그냥 냅두는게 나을 수 있다.사용할 변수는 가깝게 선언하기사용할 변수를 가깝게 선언하자안그러면 인지해야 할 정보가 많아진다.리팩토링 꿀팁수정할 메서드를 바로 수정하면 컴파일 에러가 막 난다.컴파일 에러를 최소화하면서 리팩토링 하는게 에너지를 덜 쓰기때문에수정할 메서드를 복제해서 리팩토링을 진행한다.나는 강사님과 다르게 메서드 참조가 익숙치 않아서 람다로 냅두었다. 공백 라인을 대하는 자세공백 라인도 의미를 가진다.단락 또는 의미 구간마다 공백을 넣어주자부정어를 대하는 자세부정어구를 안써도 되는지 체크하기부정의 의미를 담은 다른 단어가 존재하는지 or 부정어구로 메서드명 구성하기ex) !isLeftDirection()을isRightDirection() 또는 isNotLeftDirection()으로 구성해서 인지적 경제성을 지키자부정연산자를 제거할 수 있다면 베스트 해피 케이스와 예외 처리사람은, 해피 케이스에 몰두하는 경향이 있다.예외처리를 꼼꼼하게 하는 것이 개발자의 역량사용자 입력은 무조건 불신을 깔고가자의도한 예외와 예상하지 못한 예외를 구분해서 잘 처리하자 Null을 대하는 자세항상 NPE를 방지하는 방향으로 경각심 갖자.return null을 자제하자.Optional은 비싼 객체이다. 꼭 필요한 상황에서 반환 타입에 사용하자.메서드 반환 타입으로만 사용되게 설계가 됐다.isPresent() - get()은 안티패턴이다.orElse, orElseThrow, ifPresent 등을 사용하자.orElse, orElseGet은 잘 알고 써야한다. 괄호안을 언제 실행하나?orElse(): 항상 괄호안 실행, 확정된 값일 때 사용orElseGet(): null인 경우 괄호안 실행, 값을 제공하는 동작(Supplier) 정의performanceHeavy()가 orElse일때는 항상 실행됨 orElseGet일때는 null일 때만 실행e.printStackTrace();는 실무에서는 안티패턴이다. 섹션4. 객체 지향 패러다임추상의 관점으로 바라보는 객체 지향객체: 추상화된 데이터 + 코드협력과 책임객체간의 협력과 객체가 담당하는 책임오브젝트에서 본 개념이다. 협력, 책임관심사의 분리높은 응집도, 낮은 결합도결합도: 하나가 바뀌었을 때 다른 하나가 영향을 받는 정도객체를 설계할 때 관심사의 분리, 추상화를 기반으로 어떻게 설계하면 좋을지 알아보자 객체 설계하기 (1)전체 로직에서 공통 관심사를 분리해서 객체를 만든다객체의 내부를 보면외부 세계에서 이 객체를 사용할 때 혹은 협력할 때 이 공개 메서드 선언부를 통해 책임을 드러낸다.이런 객체들이 모여서 객체간 협력이 발생객체는 관심사가 한 군데로 모여 유지보수성이 올라간다. 새로운 객체를 만들 때 주의할 점1개의 관심사로 명확하게 책임이 정의되었는지 확인하자.생성자, 정적 팩토리 메서드에서 유효성 검증이 가능하다.setter는 사용을 자제하자.getter도 처음에는 사용을 자제하자. 반드시 필요한 경우에 추가하자.객체에 메시지를 보내라. 객체는 책임을 가지고 협력하는 자율적인 존재이다.필드는 적을수록 좋다. 단, 미리 가공하는 것이 성능 상 이점이 있다면 필드로 갖고있는 것이 좋을 수 있다. 객체 설계하기 (2)캡슐화 되어있는 데이터를 바깥에서 알고 있다고 생각하지 말자모르기 때문에 짐작해서 물어보는 것이 최선인 것 → 메시지를 보내는 것리팩토링 중에 도메인 지식을 얻었다.(열렸다/닫혔다 | 사용자가 체크했다 는 다른 개념)도메인 지식은 만들어가는게 아니라 발견하는 것이다.기존에 원래 있던 도메인 지식인데 우리가 아직 발견하지 못한 것뿐이다 라고 얘기를 한다.새로운 도메인 지식을 발견했을 때 과감하게 우리의 사고를 전환할 수 있어야 한다. Day 4SOLID객체지향 설계를 우리가 더 이해하기 쉽고 유연한 형태로 유지보수하기 쉽게 만드는데 도움을 주는 원칙들하나씩 알아보자 SRP: Single Responsibility Principle하나의 클래스는 단 한 가지의 변경 이유만을 가져야 한다.변경이유 = 책임책임이라는 것을 발견해내는게 굉장히 어렵다.OCP: Open-Closed Principle확장에는 열려있고, 수정에는 닫혀있어야 한다.추상화와 다형성을 활용해서 OCP를 지킬 수 있다.LSP: Liskov Substitution Principle상속 구조에서, 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환할 수 있어야 한다.자식 클래스는 부모 클래스의 책임을 준수하며, 부모 클래스의 행동을 변경하지 않아야 한다.ISP: Interface Segregation Principle클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다.인터페이스를 잘게 쪼개라ISP를 위반하면 불필요한 의존성으로 인해 결합도가 높아진다.DIP: Dependency Inversion Principle상위 수준의 모듈은 하위 모듈에 의존해서는 안 된다.둘 모두 추상화에 의존해야 한다.의존성: 하나의 모듈이 다른 모듈을 참조하는 것의존성의 순방향: 고수준 모듈이 저수준 모듈을 참조하는 것DIP, DI, IoC의 차이점에 대해서 인지하자. Day 5섹션5. 객체 지향 적용하기상속과 조합상속보다 조합을 사용하자상속은 부모와 자식의 결합도가 높아서 수정이 어렵다. Value Object도메인의 어떤 개념을 추상화하여 표현한 값 객체값으로 취급하기 위해서, 불변성, 동등성, 유효성 검증 등을 보장해야한다.불변성: final, setter 금지동등성: 동일성이 달라도 내부 값으로 같은 값 객체 취급, equals() & hashCode() 재정의 필요유효성 검증: 객체가 생성되는 시점에 값에 대한 유효성을 보장하기이 강의를 듣고나서,왜 equals() & hashCode()를 재정의 해야하는지,만약 둘 중 하나만 재정의 하는 경우엔 어떻게 되는지,어떻게 hash 자료형을 구현하는지에 대해 스스로 공부할 수 있었다.VO vs. EntityEntity는 식별자가 존재, VO는 식별자 없음 일급 컬렉션처음 강의를 들었을 때는 왜 오류가 나는지 일급 컬렉션쪽을 의심하고있었다.하지만 아예 다른 메서드에 인자로 넘겨줄 때 같은 객체를 넘기고 있었기 때문에 발생하는 문제였다.그리고 new ArrayList<>()로 새로운 리스트 객체를 넘겨주는데어떻게 CellPosition 들은 똑같지? 궁금해져서 찾아보았더니new ArrayList<>(positions)는 리스트만 새로 만들고, 내부 원소들은 기존 객체를 참조한다고 한다.만약 복사본을 만들지 않고 원본 객체를 계속 가져다 쓰면 어떻게 될까 직접 해보았는데,예상했을땐 지뢰가 일렬로 붙어있는걸 생각했는데 별로 문제는 없어보였다.아마 CellPosition을 이미 만든 뒤에 섞었기 때문에 상관없을듯 하다.꼭 새로운 복사 리스트를 만들어서 반환해야 하는 경우는 무엇이 있을까 궁금하다. Enum의 특성과 활용상수의 집합이며, 상수와 관련된 로직을 담을 수 있는 공간상태와 행위를 한 곳에서 관리할 수 있는 추상화된 객체변경이 정말 잦은 개념은 Enum보다 DB로 관리하는 것이 나을 수 있다. 다형성 활용하기반복적인 if문을 단순하게 만들어볼 수 없을까?→ 어떤 조건을 만족하면, 조건에 해당하는 행위 수행변하는 것은 조건과 행위처음 들었을 때는 enum value마다 인터페이스 구현을 할 수 있는 것을 몰랐다.두번째 들었을 때는 까먹었다.역시 복습이 중요하다. 숨겨져 있는 도메인 개념 도출하기도메인 지식은 숨겨져있는 것을 발견하는 것이다.완벽한 설계는 없다. 그 당시의 최선이 있을 뿐 미션Day 2https://github.com/p-seonggeun/readable-code추상과 구체의 예시추상과 구체의 예시 문제를 해결하면서 내가 오늘 한 일에 대해서 생각해보았다.오늘 나는 하얀풍차에 가서 빵을 사왔는데 이것에 대해서 추상화와 구체화를 해봐야겠다고 생각했다.추상화 레벨에 따라 적어보았다.Day 4아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.public boolean validateOrder(Order order) { if (order.doesNotHaveAnyItem()) { log.info("주문 항목이 없습니다."); return false; } if (order.doesNotHaveCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } if (order.hasNegativeTotalPrice()) { log.info("올바르지 않은 총 가격입니다."); return false; } return true; }처음에 논리, 사고의 흐름에서 배운 인지적 경제성을 기반으로 early return을 적용시켜보았다. 그리고 사고의 Depth를 줄이기 위해서 총 가격이 0보다 큰지 확인하고 사용자정보가 있는지 확인하는 로직을 분리해냈다. 그리고 부정어를 대하는 자세를 떠올려 여러가지 메서드명들을 생각해보았다. isInvalid, isEmpty(), totalPriceIsGreaterThanZero() 등 여러개를 생각하고 고민해보았다. 너무 포괄적이거나 길어서 최종적으로 doesNotHave, has로 선택했다. SOLID에 대하여 자기만의 언어로 정리해 봅시다.SRP: 하나의 클래스는 하나의 책임만 가져야 한다. 변경이 있을 때 파급효과가 적으면 SRP를 잘 지킨것이다.OCP: 확장에는 열려있고, 변경에는 닫혀있다. 추상화와 다형성을 활용하면 지킬 수 있다.LSP: 컴파일 성공을 넘어서 인터페이스 기능을 보장해야한다. 다형성에서 구현 클래스들은 인터페이스 규약을 지켜야 한다는 것ISP: 특정 클라이언트에 맞는 여러개의 인터페이스가 범용 인터페이스 하나보다 낫다. 인터페이스가 명확해지고 대체 가능성이 높아진다.DIP: 추상화에 의존해야하고 구체화에 의존하지 말자 구체화에 의존하면 변경이 어려워진다.미루지 않고 잘 참여했다.다음주도 이렇게만 하면 될 것 같다.두번째 듣는것인데도 까먹은 부분들이 많이 보였다.역시 복습이 중요하다.모두 파이팅