이 글은 우아한 테크 세미나 [테크 리더 3인이 말하는 "개발자 원칙"]의 인프랩 테크리더 이동욱 님의 세미나를 듣고 작성한 글입니다.
일정은 지키지만 버그가 많은 것 vs 일정은 못 지키지만 버그가 없는 것
보통은 이 고민에 대해서 정답을 내리지 못하는 경우가 많다.
프로그래머에게 요구되는 것은 100점이 아닌 80~90점짜리 프로그램을 기한 내에 완성하는 일이다.
- 나카지마 사토시
프로덕트의 엔지니어의 일은 고객이 원하는 기능을 고객이 원하는 시점에 전달하는 것이다.
그럼 일정이 퀄리티 보다 중요하다는 걸까? 그건 아니다.
결국 프로덕트 엔지니어가 쌓아야할 역량 중에 가장 중요한 것은 아무리 급해도 항상 80~90점짜리 소프트웨어를 일정 내 개발할 수 있는 방법이다.
일정을 항상 잘 지키는 분들의 공통점
같은 코드를 작성하더라도 함수를 작성하는 방법이라던가 class를 작성하는 방법과 같은 규칙들을 고민하게 된다.
이럴 때 보통 그때그때 본인의 환경 내에서 고민 끝에 더 좋은 코드를 선택하게 된다.
하지만 일정을 잘 지키시는 분들의 공통점은 본인만의 기준과 원칙으로 이러한 고민을 빠르게 결정한다는 것이다.
이동욱님이 가지신 이러한 원칙들 중 가장 빈번하게 사용하고 있는 원칙은 "제어할 수 없는 것에 의존하지 않기"이다.
현실 세계의 변화와 설계 사이의 결합도를 줄여야 한다. 전화번호를 식별자로 사용하는가?
자신의 힘으로 제어할 수 없는 속성에 의존하지 말라
- 실용주의 프로그래머 중
제어할 수 없는 것에 의존하지 않기
주민등록번호를 데이터베이스의 PK로 사용하게 되면 어떻게 될까?
예전 기업들 중에는 DB에 PK를 주민등록번호로 해놓는 경우가 있었다.
그러던 중 갑자기 2014년 중 주민번호를 무분별하게 수집하지 말라는 정부 지침이 내려왔다.
이렇게 주민번호를 PK로 정해 놓은 기업들은 DB의 PK를 새로 발급하고 기존에 설계까지 바꿔야 하는 일이 일어난다.
따라서 외부에서 전달 받은 값을 절대 주요 키로 선택하지 않아야 한다.
이렇게 주민등록번호같이 제어할 수 없는 것에 의존할 수록 변화에 쉽게 흔들리는 소프트웨어가 만들어진다.
그럼 제어할 수 없는 코드는 어떤 코드일까?
위의 코드는 현재 시간을 받아서 현재 요일이 일요일이면 10% 할인을 해주는 코드이다.
기능상으로는 정상적으로 작동할 것이다.
하지만 테스트 코드를 작성하면 어떻게 될까?
이렇게 일요일에만 성공을 하는 테스트를 작성할 수 밖에 없게 될 것이다.
왤까? 이유는 현재시간을 나타내는 LocalDateTime.now()
는 제어할 수 없는 것이기 때문이다.
그래서 Mocking을 통해서 테스트 코드를 작성하게 된다.
Mocking을 통해서 2023년 3월 26일로 설정하여 일요일이 아니더라도 테스트에 성공할 수 있다.
그럼 이렇게 작성된 코드는 문제가 없는 것일까?
- 날짜 라이브러리(js.joda)가 교체된다면 어떻게 해야할까?
- Mocking을 사용하고 있는 모든 코드들을 바꿔야 하는 문제가 있다.
- 테스트 프레임워크(jest)가 교체된다면 어떻게 해야 할까?
- jest를 사용하고 있는 모든 테스트 코드를 바꿔야 하는 문제가 있다.
쓰고 있는 라이브러리를 바꾸려면 모든 코드를 바꿔야하는 문제가 생겨 변화에 쉽게 흔들리는 소프트웨어가 되어버린다.
제어할 수 있는 코드로 바꿔보자.
기존에 제어할 수 없었던 코드인 LocalDateTime.now()
를 함수의 인자로 밀어내보자.
discount()
를 호출할 때 매개 변수를 받으면 받은 매개 변수로 실행이 되고 인자를 받지 않으면 LocalDateTime.now()
로 실행이 되는 코드로 바뀌었다.
테스트 코드가 외부 라이브러리에 전혀 의존하지 않아도 되도록 작성할 수 있다.
전염되는 제어할 수 없는 코드
위의 코드는 여러 강의들 중 결제 금액 계산 결과가 100원이 넘는 건들 만 골라서 결제하는 코드이다.
그럼 클린 코드를 따라서 함수는 하나의 기능을 하기 위해 리팩토링을 해보자.
위와 같이 payAll()
은 모든 강의들을 돌면서 결제하는 함수가 되고, pay()
는 하나의 강의만 결제하는 함수가 될 수 있을 것이다.
하지만 async, await이라는 외부 API를 호출하는 키워드가 모든 함수에 적용되어 있는 것을 볼 수 있다.
모든 함수가 하나의 책임을 가지도록 리팩토링했는데 왜 전부다 외부 API를 모킹 해야만 할까?
그 이유는 requestPg()
라는 외부 API를 호출하는 제어할 수 없는 함수에 전염이 되어서 모두 제어할수 없는 코드가 되어버렸기 때문이다.
그럼 제어할 수 있는 것과 제어할 수 없는 부분에는 어떤 것이 있을까?
제어할 수 없는 부분에는 외부 서비스, Modal과 같은 것이 있고, 제어 할 수 있는 부분은 결제 금액 계산 100원 이상 걸러내기 같은 부분이 있을 것이다.
그럼 코드 상에서 이 두 부분을 분리해 보자.
payAll()
함수에서만 제어할 수 없는 코드를 작성하고, 제어할 수 있는 코드를 getCourseAmounts()
와 getCourseAmount()
를 분리했다.
이렇게 작성된 코드는 외부 API를 통해 Mocking 된 payAll()
을 제외한 함수들은 단위테스트로 테스트할 수 있게 될 것이다.
결국, 제어할 수 없는 코드란 순수하지 않은 함수 혹은 객체라는 것을 알 수가 있다.
리팩토링이나 코드를 고민할 때도 가능하다면 제어 가능한 코드영역을 최대한 늘리고, 제어 불가능한 부분을 최대한 격리시키자.
느낀 점
코드를 작성할 때 항상 고민하는 부분들이 있었다.
- 클린 코드를 작성하기 위해 어떻게 함수를 분리해야 할까?
- 어떤 디자인 패턴을 사용하면 좀 더 객체지향적으로 설계할 수 있을까?
사실 Spring으로 개발하면서 Spring이 AOP, DI와 같이 책임을 분리해서 비즈니스로직에만 집중할 수 있도록 도와준다.
하지만 이 비즈니스 로직 안에서도 사실 어떻게 구현할지에 대한 고민이 비즈니스로직을 생각하고 구현하는 것보다 더 고민스러운 경우가 있었다.
이럴 때 나만의 원칙대로 분리를 해나갔지만 '제어할 수 없는 것에 의존하지 않기'라는 원칙이 나에게는 없었다.
이 원칙을 통해 좀 더 유지보수와 단위테스트에 유리한 코드를 작성할 수 있는 사고방식을 배울 수 있었다.
강의를 해주신 인프랩 테크리더 이동욱 님께 감사의 인사를 전하고 싶다.
참고 자료 :
https://www.youtube.com/watch?v=DJCmvzhFVOI
'백엔드 > etc' 카테고리의 다른 글
AMQP(Advanced Message Queing Protocol) (0) | 2023.05.19 |
---|---|
Cross-Origin Resource Sharing (CORS) (0) | 2023.05.15 |
Blocking, Non-blocking, Sync, Async의 차이 (0) | 2023.05.13 |