[2주차 발자국] Readable Code 적용기
인프런 ‘Readable Code: 읽기 좋은 코드를 작성하는 사고법’을 수강한 후, 작성한 내용입니다.
📌 2주차 강의 (Readable Code)
코드 다듬기
주석의 양면성
주석이 많다 ⇒ 비즈니스 요구사항을 코드에 잘 녹이지 못했나 의심해보자.
추상화로 설명이 덜 된 것은 아닐까?
주석에 의존하면 적절하지 않은 추상화 레벨을 가지게 된다.
좋은 주석
우리가 가진 모든 표현 방법을 총동원해 코드에 의도를 녹여내고, 그럼에도 불구하고 전달해야 할 정보가 남았을 때 사용하는 주석
의사 결정의 히스토리를 도저히 코드로 표현할 수 없을 때, 주석으로 상세하게 설명!
번수와 메서드의 나열 순서
변수는 사용하는 순서대로 나열하자.
인지적 경제성
메서드의 순서는 객체의 입장에서 생각해보자.
객체는 외부 세계와 어떻게 소통할 것인지가 중요
공개 메서드의 스펙을 통해 외부 세계와 소통
객체는 협력을 위한 존재
외부 세계에 내가 어떤 기능을 제공할 수 있는지를 드러낸다.
공개 메서드끼리도 기준을 가져보자.
상태 변경 > 판별 > 조회 메서드
중요한 것은, 나열 순서로도 의도와 정보를 전달할 수 있다는 것!
패키지 나누기
패키지는 문맥으로서의 정보를 제공할 수 있다.
패키지를 쪼개지 않으면 관리가 어렵다.
너무 잘게 쪼개도 안됨
대규모 패키지 변경은 팀원과의 합의를 이룬 시점에!
기능 유지보수하기
객체 지향적으로 책임이 잘 분리되어 있다면,
문제가 되는 위치를 발견하여 수정하기 쉽다!
알고리즘 교체와 같은 작업이 수월하다!
IDE 도움 받기
코드 포맷팅
sonarlint
editconfig
리팩토링 연습
스스로 리팩토링을 진행해보고 비교해보는 시간
이유를 가지고 리팩토링을 진행하자.
변경 포인트는 그 이유를 명확하게 설명할 수 있어야 한다!!
감으로 하는 리팩토링은 설득할 수 없는 코드가 될 확률이 높다.
객체에 메시지를 보내자.
객체의 책임과 응집도를 고려하자.
기억하면 좋은 조언들
능동적 읽기
눈으로 복잡하게 보고 이해하려 애쓰는 것보다 직접 경험해보며 읽자.
복잡하거나 엉망인 코드를 읽고 이해하려 할 때, 리팩토링하면서 읽기
공백으로 구분
메서드와 객체 추상화
주석으로 이해한 내용 표기하며 읽기
핵심 목표는 도메인 지식을 늘리는 것.
그리고 이전 작성자의 의도를 파악하는 것.
오버 엔지니어링
필요한 적정 수준보다 더 높은 수준의 엔지니어링
구현체가 하나인 인터페이스
아키텍처 이해에 도움을 주거나, 근시일 내에 구현체가 추가될 가능성이 높다면 괜찮음
구현체 수정 시, 인터페이스도 수정
코드 탐색에 영향을 준다.
너무 이른 추상화
정보가 숨겨지기 때문에 복잡도가 높아짐
은탄환은 없다
만능 해결사 같은 기술은 없다.
객체 지향적인 체스 프로그램
but, 체스는 500년 동안 변하지 않았다.
실무 : 2가지 사이의 줄다리기
지속 가능한 소프트웨어의 품질 VS 기술 부채를 안고 가는 빠른 결과물
모든 기술과 방법론은 적정 기술의 범위 내에서 사용되어야 한다.
도구라는 것은, 한계까지 사용할 줄 아는 사람이 그것을 사용하지 말아야 할 때도 아는 법이다.
📌 2주차 강의 (Practical Testing)
Intro
강의 소개
무엇을 학습하는가?
테스트 코드가 필요한 이유
좋은 테스트 코드란 무엇일까?
실무에서 진행하는 방식 그대로 테스트를 작성해가면서 API를 설계하고 개발하는 방법
구체적인 이유에 근거한 상세한 테스트 작성 팁
어떻게 학습하면 좋을까?
무엇을 모르는지 아는 것 = 찾아볼 수 있게 된다는 것
선택과 집중을 통해 아는 영역을 넓혀가고, 익숙하지 않은 것들을 익숙하게 하고, 들어보지 못했던 것을 들으면서 나에게 노출시키자.
인덱스를 통해 영역을 넓혀가자.
테스트는 왜 필요할까?
테스트 코드를 작성하지 않는다면,
변화가 생기는 순간마다 발생할 수 있는 모든 Case 고려해야 한다.
변화가 생기는 순간마다 모든 팀원이 동일한 고민을 해야 한다.
빠르게 변화하는 소프트웨어의 안정성 보장 X
테스트 코드가 병목이 된다면,
프로덕션 코드의 안정성 보장 X
테스트 코드 자체가 유지보수하기 어려운 짐
잘못된 검증이 이루어질 가능성 O
테스트를 통해 얻고자 하는 것 ⇒ 빠른 피드백, 자동화, 안정감
따라서, 올바른 테스트 코드는
자동화 테스트로 빠른 시간에 버그를 발견하고 비용을 절약한다.
소프트웨어의 빠른 변화를 지원한다.
팀원들의 집단 지성을 팀 차원의 이익으로 승격시킨다.
단위 테스트
수동 테스트 vs 자동화된 테스트
콘솔에 찍힌 내용을 보고 판단하는 테스트
최종 단계에서 사람이 개입해야 함
다른 사람이 봤을 때, 무엇을 검증하고 어떤 것이 맞는 상황인지 알 수 없다.
과연 이것이 자동화된 테스트일까?
JUnit5로 테스트하기
단위 테스트
작은 코드 단위를 독립적으로 검증하는 테스트
작은 코드 = 클래스, 메서드
독립적 = 외부 상황에 의존적이지 않아야 한다.
검증 속도가 빠르고, 안정적이다.
JUnit 5
단위 테스트를 위한 테스트 프레임워크
AssertJ
테스트 코드 작성을 원활하게 돕는 테스트 라이브러리
풍부한 API, 메서드 체이닝 지원
리스트 사이즈 검증할 때 다음을 이용하자.
.hasSize()
.isEmpty()
테스트 케이스 세분화하기
해피 케이스
예외 케이스
assertThatThrownBy()
경계값 테스트
범위, 구간, 날짜 등
테스트하기 어려운 영역 분리하기
생성 시간 검증에 대한 테스트에서 생성 시간을 메서드 내부에서 측정한다면?
테스트하는 시간에 따라 테스트 결과가 달라진다.
외부로 분리하자!
외부로 분리할수록 테스트 가능한 코드는 많아진다.
테스트하기 어려운 영역
관측할 때마다 다른 값에 의존하는 코드
현재 시간에 의존하는 코드
외부 세계에 영향을 주는 코드
테스트 하기 좋은 영역 (순수함수)
같은 입력에는 항상 같은 결과
외부 세상과 단절된 형태
TDD : Test Driven Development
프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론
RED → GREEN → REFACTOR
RED : 프로덕션 코드 없이 테스트를 먼저 작성
GREEN : 테스트가 통과할 수 있는 최소한의 코딩
REFACTOR : 테스트 통과를 유지하면서 구현 코드 개선
기능 구현 후, 테스트를 작성하면,
테스트 자체의 누락 가능성
특정 테스트 케이스만 검증할 가능성
잘못된 구현을 다소 늦게 발견할 가능성
테스트를 먼저 작성하면,
복잡도가 낮은, 테스트 가능한 코드로 구현
쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.
구현에 대한 빠른 피드백
과감한 리팩토링
테스트를 구현부 검증을 위한 보조 수단이 아니라, 테스트와 상호 작용하며 발전하는 프로덕션 코드을 만든다고 바라본다.
즉, 객체 사용자 관점에서의 피드백을 준다!
추가 인덱스
애자일 방법론
일정한 주기를 가지고 빠르게 제품을 출시하여 고객의 요구사항, 변화된 환경에 맞게 요구를 더하고 수정해나가는 방법
익스트림 프로그래밍
빠른 개발 속도를 유지하며 고객이 원하는 요구들을 지속적으로 피드백하는 방법
의사소통, 단순성, 용기, 피드백, 존중
하나의 방법론 : TDD
스크럼, 칸반
스크럼 : 스프린트라는 일정기간 안에 완료할 수 있는 작업으로 업무를 분할한다.
칸반 : 동시에 개발이 진행될 수 있는 아이템의 수를 제한하여, 업무의 병목 현상과 리소스 낭비를 처리할 수 있도록 한다.
테스트는 [ ]다.
테스트는 [문서]다.
프로덕션 기능을 설명하는 테스트 코드 문서
다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점 보완
과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜, 모두의 자산으로 공유
DisplayName을 섬세하게
비교
음료 1개 추가 테스트
음료 1개 추가하면 주문 목록에 담긴다.
명사의 나열보다 문장으로
테스트 행위에 대한 결과까지 기술하기
비교
특정 시간 이전에 주문을 생성하면 실패한다.
영업 시작 시간 이전에는 주문을 생성할 수 없다.
도메인 용어를 사용하여 한층 추상화된 내용을 담기
메서드 자체의 관점보다 도메인 정책 관점으로!
테스트의 현상을 중점으로 기술하지 말 것
BDD 스타일로 작성하기
함수 단위의 테스트에 집중하기보다, 시나리오에 기반한 테스트케이스(TC) 자체에 집중하여 테스트
개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 레벨 권장
Given / When / Then
Given : 시나리오 진행에 필요한 모든 준비 과정
When : 시나리오 행동 진행
Then : 시나리오 진행에 대한 결과 명시, 검증
명확하게 표현하지 못한 테스트는 나중에 우리의 사고를 제한하는 허들이 될 수 있다!
추가 인덱스
JUnit
전통적인 Java 기반의 테스트 프레임워크
어노테이션 사용
Spock
Groovy 기반의 BDD(Behavior-Driven Development) 프레임워크
BDD 스타일 테스트로 가독성이 더 좋다.
Groovy 언어로 작성해야하므로 작성이 어려울 것 같다.
📌 Day 7 미션 & 중간 점검 라이브
Day 7 미션 : 변경 포인트는 그 이유를 명확하게 설명할 수 있어야 한다!
Day 7 미션은 ‘스스로 스터디 카페 이용권 선택 시스템 리팩토링해보기’였다.
미션 수행 전 강의를 들으며 내용들에 대해 거부감 없이 받아들여졌고, 빨리 코드를 짜보고 싶다는 생각이 들도록 하였다. 하지만 실제로 코드를 작성하면서 고민되는 지점들도 꽤 많았고, 근거를 가지고 리팩토링을 하는 과정이 쉽지는 않았다. ‘이게 더 나은거 같은데…?’하며 감으로 진행한 부분도 꽤 있었던 것 같다. 미션을 진행하고 강의를 수강하면서 ‘변경 포인트는 그 이유를 명확하게 설명할 수 있어야 한다.’라는 말이 내가 유의해야 할 말로 느껴졌다.
중간 점검 라이브
중간 점검 라이브는 Day 4 미션 공통 피드백, Q&A, 코드 리뷰 순서로 진행되었다.
Day 4 미션 공통 피드백
Day 4 미션 공통 피드백 다음과 같다.
추출한 메서드에 static이 있는 경우
boolean을 return 하는 메서드에 예외 throw
오버 엔지니어링
나도 메서드를 추상화하기 위해 추출하다보면, IDE에서 static 키워드를 붙여서 따로 지워준 적이 있다. 추출하여 메서드명만 적지 말고, 전체적으로 확인해보자.
상태를 체크하고 boolean을 반환하는 메서드가 있을 때 예외를 던지는 것 보다 상황에 맞게 리팩토링 하는게 좋은 것 같다. validateOrder
에서 false를 반환해서 유효하지 않은 주문을 나타내면 되지 않을까?
미션의 안내 사항을 잘못 이해한 것 같다. Order의 내부 구현 없이 단순한 리팩토링을 제안하는 것이 요구사항이다. 그런데 나는 ‘Order의 메서드를 추가하더라도 구현 내용은 안적어도 된다.’라고 이해했다. 미션에서 요구했던 내용은 Order에 추가적으로 구현되는 사항 없이 리팩토링을 진행하라고 했던 것 같다.
Q&A
Q&A의 많은 내용들이 나에게 도움이 되었다. 다른 분들께서 질문해주신 내용들이 생각 외로 많은 인사이트를 얻을 수 있었다.
질문하고자 했던 내용들이 다른 분 질문에 있기도 했고 개발 외 질문을 하고 싶어서 우빈님께 개인 취향의 저가 커피 브랜드 1순위와 선호하는 취향에 대해 여쭤보았다.
우빈님께서는 저가 커피는 거의 안 드신다고 한다… 그래도 재밌는 영상을 추천해주셨다.
항상 카페가면 필터 커피가 있으면 필터 커피를 먹어보거나 그 카페의 스페셜한 원두를 맛을 보려 노력하는데, 항상 먹고 나면 기억이 나지 않는다… ‘이게 어떤 맛이었더라..?’ 하게 된다. 이런 소소한 것에서 취미를 가지는 것도 재밌는 것 같다.
코드 리뷰
기간 내에 신청해서 우빈님께 코드 리뷰를 받을 수 있었다! 라이브 동안 다른 분 코드 리뷰 해주시는 것도 같이 경청하였는데, 생각보다 내가 놓친 부분들을 여기서 얻을 수도 있었다. 물론 내가 작성한 코드를 리뷰 해주신 것이 직접적으로 바로 와닿았지만, 다른 분 코드 리뷰에서도 배울 점이 있었다.
다른 분 코드 리뷰를 해주시면서 퀴즈로 내신 부분이 있다. 퀴즈 내용은 간략하게 설명하면 ‘try-catch에서 예외를 throw했는데 왜 잡히지 않을까?’이다. 코드를 보여주셨을 때 문제가 없어보였다. 그래서 나는 ‘import가 잘못된거 아니야?’라고 생각을 해서 정답을 맞췄다!
사실 미션 진행하면서 정말 유사한 문제를 겪은 적이 있었다. 분명 메서드 파라미터가 똑같은데 파라미터가 다르다고 컴파일 에러가 났었다. 미션 특성 상, 맨 처음 리팩토링 이전 패키지와 이후 패키지의 클래스는 똑같다. 그래서 리팩토링 패키지에서 import를 잘못하면, 다른 패키지의 클래스를 가져온다. 그래서 문제가 발생했었는데 이와 똑같은 문제라 운이 좋게도 맞출 수 있었다.
영광의 흔적!!
코드 리뷰 해주시는 것을 보며 내가 배운 점들은 다음과 같다.
Enum의 valueOf 메서드를 통한 생성
valueOf 대신 String을 받아서 객체를 생성하는 정적 메서드를 구성
사물함 이용 가능 여부를 StudyCafePassType에 저장하는 부분도 좋음
get/set은 관용적인 어구로 필드에 대한 getter/setter가 아니더라도, 메서드에 get~~, set~~ 으로 네이밍하는 건 지양하자.
early return 보다 else-if가 더 배타적인 내용을 나타내지 않을까?
if 절 내에 return을 봐야한다.
구조화보다 적절한 책임의 분배가 더 중요하다.
위 내용은 다른 분들의 코드 리뷰에서 내가 배운 부분들이다. 다음은 내가 질문하거나 리뷰해주신 내용이다.
있을 수도, 없을 수도 있는 필드에 대한 관리를 어떻게 해야할까요?
사물함 이용권을 별도 필드로 관리하고 EMPTY 객체를 넣는다? (내용이 정확히 기억이 나지 않는다….)
PassReader - FilePassReader로 PassReader의 책임을 부여하고, File에 저장된 Pass를 읽는 구현체로 분리해도 괜찮을까요?
괜찮은 접근인 것 같다.
Order order = Order.create()
후,order.add(pass)
와 같은 형식으로 주문이후에 주문의 내용이 바뀔 위험성이 존재하므로, 만들어놓고 추가하는 것보다 마지막에 한 번에 Order 객체를 만드는 것이 더 안정적이다. 위험성을 제거하자. 가변보다 불변이 더 좋다!
우빈님꼐서 약 1시간 45분동안 라이브를 진행해주셨다. 긴 시간동안 라이브로 진행한다는 것이 쉽지 않으실텐데, 덕분에 나는 유익한 시간을 보낼 수 있었던 것 같다! 직접적으로 우빈님과 소통할 수도 있고 내가 얻을 수 있는 인사이트도 챙겨 소중한 시간이었다. 처음에 말씀하셨던 ‘함께 자라기’를 위해 더 노력해야겠다.
댓글을 작성해보세요.