[워밍업 클럽 스터디 2기 - BE] (클린코드, 테스트코드) 2주차 발자국
인프런 워밍업 클럽 스터디 2기 - 백엔드 클린코드, 테스트 코드(Java, Spring Boot)
Readable Code : 읽기 좋은 코드를 작성하는 사고법
해당 강의를 학습하며 정리하는 내용입니다.
1주차 발자국에 이어서 이번주도 배운 내용을 정리하고 회고를 작성한다.
이번주는 지난주에 이어 Readable Code 나머지 모든 강의를 수강하는 한주였다.
키워드 형식으로 정리한다.
객체 지향 적용하기
상속과 조합
상속보다 조합을 사용하자!
상속은 시멘트처럼 굳어지는 구조다. 수정이 어렵다.
부모와 자식의 결합도가 높다.
조합과 인터페이스를 활용하는 것이 유연한 구조
상속을 통한 코드의 중복 제거가 주는 이점보다, 중복이 생기더라도 유연한 구조 설계가 주는 이점이 더 크다.
[객체지향 적용하기 - 상속과 조합 - 셀을 상속에서 조합으로 바꾸기](https://github.com/iamminseongKim/readable-code/commit/86a49db553a752eca745a0d462125ee0d0b4ad70)
Value Object
도메인
의 어떤 개념을 추상화하여 표현한 값 객체값으로 취급하기 위해서 불변성, 동등성, 유효성 검증 등을 보장해야 한다.
불변성 : final 필드, setter 금지
동등성 : 서로 다른 인스턴스여도(동일성이 달라도), 내부의 값이 같으면 같은 값 객체로 취급한다.
equals() & hashCode()
재정의 필요유효성 검증 : 객체가 생성되는 시점에 값에 대한 유효성을 보장하기
만원 지폐가 일렬번호(인스턴스)가 다르다고 가치가 다른 즉 다른 만원인가?
객체의 동등성을 보장해주자!
VO vs Entity
class UserAccount {
private String userId; // 식별자
private String 이름;
private String 생년월일;
private Address 집주소;
}
이건 Entity
class Address {
private String 시도;
private String 시군구;
private String 도로명;
private String 건물번호;
}
이건 VO이다.
Entity
는 식별자가 존재한다. 식별자가 아닌 필드의 값이 달라도, 식별자가 같으면 동등한 객체로 취급한다.equals() & hashCode()
도 식별자 필드만 가지고 재정의할 수 있다.식별자가 같은데 식별자가 아닌 필드의 값이 서로 다른 두 인스턴스가 있다면, 같은
Entity
가 시간이 지남에 따라 변화한 것으로 이해할 수 있다.
VO
는 식별자 없이 내부의 모든 값이 다 같아야 동등한 객체로 취급한다.개념적으로, 전체 필드가 다 같아야 식별자 역할을 한다고 생각해도 된다.
[Value Object - Cell의 상태를 value object로 만들기.](https://github.com/iamminseongKim/readable-code/commit/b667b43de3dfa2fa78fc6a4baf24af570acf69d6)
일급 컬렉션
일급 시민
다른 요소에게 사용 가능한 모든 연산을 지원하는 요소
변수로 할당 될 수 있다.
파라미터로 전달될 수 있다.
함수의 결과로 반환될 수 있다.
ex) 일급 함수
함수형 프로그래밍 언어에서, 함수는 일급 시민이다. 함수는 변수에 할당될 수 있고, 인자로 전달될 수 있고, 함수의 결과로 함수가 반환될 수 있다.
일급 컬렉션
컬렉션을 포장하면서, 컬렉션만을 유일하게 필드로 가지는 객체
컬렉션을 다른 객체와 동등한 레벨로 다루기 위함
단 하나의 컬렉션 필드만을 가진다.
컬렉션을 추상화하며 의미를 담을 수 있고, 가공 로직의 보금자리가 생긴다.
가공 로직에 대한 테스트도 작성할 수 있다.
만약
getter
로 컬렉션을 반환할 일이 생긴다면, 외부 조작을 피하기 위해 꼭 새로운 컬렉션으로 만들어서 반환해주자.[일급 컬렉션 - Cell을 가진 Cells 일급 컬렉션](https://github.com/wbluke/readable-code/commit/e72454d3cacea5fb818317b7755551f8a6ae1459)
[일급 컬렉션 - CellPosition을 가진 CellPositions 일급 컬렉션](https://github.com/wbluke/readable-code/commit/f297f57d950389deda905a001a94919ef45d25c6)
Enum의 특성과 활용
Enum
은 상수의 집합이며, 상수와 관련된 로직을 담을 수 있는 공간이다상태와 행위를 한 곳에서 관리할 수 있는 추상화된 객체
특정 도메인 개념에 대해 그 종류와 기능을 명시적으로 표현해줄 수 있다.
만약 변경이 정말 잦은 개념은,
Enum
보다DB
로 관리하는 것이 나을 수 있다.
다형성 활용하기
변하는 것과 변하지 않는 것을 분리하여 추상화하고, OCP를 지키는 구조
[다형성 활용하기 - CellSign을 다형성을 가져서 개발해보기](https://github.com/iamminseongKim/readable-code/commit/83f472152ec7a4b82d5d92c17bdb742089b381f6)
숨겨져 있는 도메인 개념 도출하기
도메인 지식은 만드는 것이 아니라 발견하는 것
객체 지향은 현실을 100% 반영하는 도구가 아니라, 흉내내는 것이다.
현실 세계에서 쉽게 인지하지 못하는 개념도 도출해서 사용해야 할 때가 있다.
설계할 때는 근시적, 거시적 관점에서 최대한 미래를 에측하고, 시간이 지나 만약 틀렸다는 것을 인지하면 언제든 돌아올 수 있도록 코드를 만들어야 한다.
완벽한 설계는 없다. 그 당시 최선이 있을 뿐.
코드 다듬기
주석의 양면성
주석은 죄악이다! VS. 주석 좀 남겨줘라!
주석이 많다는 것은, 그만큼 비즈니스 요구사항을 코드에 잘 못 녹였다는 이야기.
코드를 설명하는 주석을 쓰면, 코드가 아니라 주석에 의존한다. 주석에 의존하여 코드를 작성하면, 적절하지 않은 추상화 레벨을 갖게 되어 낮은 품질의 코드가 만들어진다.
아니 그럼 주석은 언제 씁니까?
좋은 주석은 뭘까
우리가 리팩토링 할 때, 정말 큰 난관 중 하나는 히스토리를 전혀 알 수 없는 코드다.
후대에 전해야 할
의사 결정의 히스토리
를 도저히 코드로 표현할 수 없을 때 주석으로 상세하게 설명한다.주석을 작성할 때, 자주 변하는 정보는 최대한 지양해서 작성한다.
만약 관련 정책이 변하거나 코드가 변경되었다면, 주석도 잊지 않고 함께 업데이트 한다.
주석이 없는 코드보다, 부정확한 주석이 달린 코드가 더 치명적이다.
우리가 가진 모든 표현 방법을 총동원해 코드에 의도를 녹여내고, 그럼에도 불구하고 전달해야 할 정보 남았을 때 사용하는 주석
[주석 제거 - gameStatus - 제거 후 책임 변경](https://github.com/iamminseongKim/readable-code/commit/5810a74a9cbfc41b2d2bc638f58a437f26bec2b5)
변수와 메서드의 나열 순서
변수는 사용하는 순서대로 나열한다.
인지적 경제성
메서드의 순서도 고려해보야햐 하는데, 객체의 입장에서 생각해보자.
상태변경 >> 판별 >= 조회 메서드 순서
비공개 메서드는 공개 메서드에서 언급한 순서 대로 배치
공통으로 사용하는 메서드라면 (가장 하단 같은) 적당한 곳에 배치한다.
중요한 것은, 나열 순서로도 의도와 정보를 전달할 수 있다는 것
[코드 다듬기 - 변수와 메서드의 나열 순서](https://github.com/iamminseongKim/readable-code/commit/c3fea40a581cfd0a418114473350fe430686a0d9)
패키지 나누기
패키지는 문맥으로써의 정보를 제공할 수 있다.
패키지를 쪼개지 않으면 관리가 어려워진다.
패키지를 너무 잘게 쪼개도 마찬가지로 관리가 어려워진다.
대규모 패키지 변경은 팀원과의 합의를 이룬 시점에서 하자.
[코드 다듬기 - 패키지 나누기](https://github.com/iamminseongKim/readable-code/commit/8f650421839f7b68021dc1a983a2a1cf29a64b8f)
기능 유지보수하기 (1) - 버그 잡기
이전 코드에선 지뢰판에 깃발을 전부 꽂으면 승리했다. 이걸 수정해보는 과정인데 셀을 체크하는 과정의 핵심이 뭔지 파악하고 고치는 작업이었다.
[코드 다듬기 - 기능 유지보수하기 (1) - 버그 잡기 - 객체의 책임을 명확하게 했기 때문에 고치기 쉽다.](https://github.com/iamminseongKim/readable-code/commit/8654bb3d727a9bffe4f7b2b8ab06211446a3cae8)
객체의 책임을 명확하게 분리했기 때문에 고치기 매우 쉬웠다.
기능 유지보수하기 (2) - 알고리즘 교체하기
재귀함수를 Stack으로 변경해서 주변 셀을 여는 로직을 개선했다.
[코드 다듬기 - 기능 유지보수하기 (2) - 알고리즘 바꾸기. 재귀함수 -> stack](https://github.com/iamminseongKim/readable-code/commit/3bbcfe61b7f0dca6fcee51efc2f3adbfb298aa3d)
원리는 이해하겠는데, 솔직히 이걸 일상에서 떠올릴 수 있을지 내 실력이 부족하다는걸 또 한번 느끼는 시간이었다.
IDE의 도움 받기
코드 포맷 정렬 :
Option + Cmd + L
|Ctrl + Alt + L
코드 품질 :
Sonarlint
lint(linting) : 잠재적인 문제가 될 수 있는 오류, 버그, 스타일 등을 미리 알려주는 코드 품질 체크 도구
포맷 규칙 :
.editorconfig
(https://editorconfig.org/)여러 사람과 협업을 염두하면 IDE 기본 포맷팅에 익숙해지는 것이 좋다.
스타일은 혼자 결정하는 것이 아니라, 팀 내 합의도 도출되어야만 한다.
한번 정해지면 절대적인 것이 아니라, 사용하면서 계속 의견을 듣고 개선/반영하는 것이 좋다.
[코드 다듬기 - IDE의 도움 받기, 코드 정렬, Sonarlint, .editorconfig](https://github.com/iamminseongKim/readable-code/commit/11469a97f84c4a2d04e6a499fdb5f37b106ebbff)
리팩토링 연습
리팩토링 (1) - 추상화 레벨
중복 제거, 메서드 추출
객체에 메시지 보내기
[null대신 Optional넘기기](https://github.com/iamminseongKim/readable-code/commit/716b682d2ea7149448adc8a149dd89067a99b3fe)
[객체에게 정중히 묻기. getter로 무례하게 하지 말고!](https://github.com/iamminseongKim/readable-code/commit/fd468510e9d3c144d4e938494825105e02624232)
이 순서대로 리팩토링하면 좀 정리하기 쉽다.
리팩토링 (2) - 객체의 책임과 응집도
IO 통합
[Input, Output 하나로 통합](https://github.com/iamminseongKim/readable-code/commit/4e2f36eceed58ada1d22316fe6f4b54ffca989ce)
일급 컬랙션
[StudyCafePass, LockerPass에 대한 일급컬랙션 생성](https://github.com/iamminseongKim/readable-code/commit/82a05d1493c516a31e41cb5bc9dc9b7aef2f6e07)
display()의 책임
[display를 IOHandler에서 하도록 하고, 하다보니 pass 인터페이스로 추상화](https://github.com/iamminseongKim/readable-code/commit/ae2546b1b76a44a5186333ec3bb66c165bb8b4b0)
Order 객체
[계산 로직을 IO에서 할게 아니라 PassOrder라는 주문 객체를 만들어서 위임](https://github.com/iamminseongKim/readable-code/commit/b74355e67420c138a0ac4ca31ea913667e52eba6)
코드를 이해하고 도메인을 이해한다면 다음과 같이 로직을 수정할 수 있다. 이 기법들을 자유자재로 사용할 수 있다면 해당 도메인을 이해했다고 생각한다!
리팩토링 (3) - 관점의 차이로 달라지는 추상화
FileHandler를 바라보는 관점
provider 인터페이스를 만들었는데, 구현체는 io에 provider 패키지를 만들어서 따로 분리 보관했다.
io에 만드는 구현체는 파일을 읽는, 그런 내용이 중요한것이기 때문에 따로 보관했고,
나중에 파일이 아니라 DB에서 읽는다면, 또 다른 패키지에서 인터페이스를 구현해서 갈아 끼우기만 하면된다.
핵사고날 아키텍쳐 - 포트(인터페이스)와 어댑터(구현체)
[FileHandler를 Provider 개념으로 리팩토링](https://github.com/iamminseongKim/readable-code/commit/e33dadae25724a61cf52713cc1fcaea5ed81c40e)
해당 챕터에선 추가적으로 개선할 방향을 찾고, 클린 아키텍쳐를 위한 새로운 사고를 열어 볼 수 있었다.
기억하면 좋은 조언들
능동적 읽기
핵심 목표는 우리의
도메인 지식
을 늘리는 것. 그리고 이전 작성자의 의도를 파악하는 것.
오버 엔지니어링
필요한 적정 수준보다 더 높은 수준의 엔지니어링
은탄환은 없다
클린 코드도 은탄환이 아니다.
실무 : 2가지 사이의 줄다리기
지속 가능한 소프트웨어의 품질 vs 기술 부채를 안고 가는 빠른 결과물
도구라는 것은, 일단 그것을 한계까지 사용할 줄 아는 사람이 그것을 사용하지 말아야 할 때도 아는 법이다.
Day 7 미션
섹션 7에 나오는 StudyCafe
를 이전 까지 배운 내용으로 스스로 리팩토링 하는 과제였다.
아쉬운점은 내가 기한을 착각하고 마감일에 리팩토링을 시작했다는 점이고, 다행인건 그날이 한글날이라 그래도 시간이 좀 있었다.
내가 이 미션을 하면서 중점적(순서)으로 생각한 점은 다음과 같다.
의미단위로 공백 끊기
큰 단위로 메서드 추출하기
의존성 주입을 할 수 있는 부분을 찾기 (DIP)
객체 단위로 추상화(추출)할게 있는지 찾기
메서드의 책임이 올바른 위치에 있는지 판단하기
일급 컬랙션 적용하기
도메인을 이해해서 추가적인 개념을 도출하기
해당 순서를 중요하게 생각하면서 리팩토링을 진행했다.
그리고 다음날 깨끗한 정신으로 다시한번 보니깐 진짜 좀 별로였다 ㅋㅋ ㅠ
그래서 다음날 내가 추가한 부분은 다음과 같다.
기존에는 OutputHandler
문자를 출력하는 부분에서 계산을 해서 콘솔에 출력하였다.
그래서 나는 PassCost
라는 계산하는 객체를 만들어서 계산은 여기서 다 하고 출력할 때는 여기서 getter를 통해 값만 얻어 오도록 수정했다.
전체 회고
이번주를 끝으로 Readable Code 읽기 좋은 코드를 작성하는 사고법 강의가 끝났다.
솔직히 시간이 좀 있었는데 TestCode강의를 시작하지 못한게 조금 아쉽긴 하다.
그래도 이번 강의를 통해 진짜 좋은 코드가 무엇인지, 물론 이 강의에서 나오는 개념들이 정답은 아닐지 몰라도
해당 강의를 통해 사고를 넓일 수 있는 계기가 되어서 너무 좋았다.
메서드를 추상화 하는 행위가 이 강의를 듣기 전까지는 명확하게 인지하진 못했다. 그냥 남들이 하니깐, 조금 하고 솔직히 많이 안했다.
하지만 내가 추상화를 해보면서도 코드가 더 읽기 쉬워지고 도메인을 이해하고 있다는걸 느껴서 적극적으로 적용해보려고 한다.
다음주에는 공휴일이 없으니깐 출 퇴근 후에 빡세게 들어야한다 ㅠㅠ. 그래도 좀 열심히 들어서 여유로운 주말을 맞이하고 싶다~
댓글을 작성해보세요.