[책 리뷰] 개발 고전들 읽어보기1

뜨뜨미지근한물
11 min readJan 7, 2023

--

리팩터링 2판 & 테스트 주도개발을 읽고 기록하기

약 1년전 유튜브 채널에서 이력서 첨삭 이벤트에 당첨되었다. 방송에서 내 이력서를 다루는 대신 몇권의 책을 받았는데. 소위 개발자 필독서 또는 고전으로 언급되는 책들이었다. 책장에 고이 모셔두다가, 사내 스터디를 통해 책들을 읽게 되었다.

마틴 파울러가 직접 말하는 리팩토링 (ft. TDD)

결과적으로 이 책들을 통해 유지보수에 좋은 & 협업에 도움이 되는 코드를 어떻게 구성할 수 있을지 좀 더 깊게 생각할 수 있게 되었다.

또한 (주로 OOP를 기반으로하는) 아키텍처에서 언급되는 용어들에 대한 이해와. 활용되는 패턴들이 어떤 의도를, 무슨 목적을 지향하는지에 대해서도 좀 더 이해하게 되는 경험도 할 수 있었다. (Aggregate, VO, CQRS, DI ..)

해당 글은 각 책을 1회독씩 하고 정리한 내용중 가장 와닿았던 부분들과 스스로가 느꼈던 것들을 내 언어로 정리한 것이다. (왜곡된 해석이 보인다면 말씀 부탁드리겠습니다!)

리팩토링 2판

요컨대 리팩토링한다는 것은 코드를 작성하고 난 뒤에, 차츰차츰 설계를 개선하는 일이다.
작성 후 유지보수가 필요없는 코드라면 리팩토링은 의미가 없다. 그러나 시간의 흐름에 따라 의도가 변경되고, 기능이 추가되어야 한다면, 리팩토링이 필요하다. (to 장기적인 생산성)

정의 및 목적

리팩토링: 겉으로 드러나는 코드의 기능(겉보기 동작)은 바꾸지 않으면서, 코드를 이해하고 수정하기 쉽도록 내부구조를 변경하는 기법.
단순히 코드를 정리하는 게 아니라, 동작을 보존하는 작은 단계들을 거쳐 코드를 수정하고, 이러한 단계들을 순차적으로 연결해 큰 변화를 만들어내는 일이다.

리팩토링을 지속함으로써,
- 소프트웨어가 썩지 않게 설계를 재구성하게끔 하며.
- 기능 추가를 쉽게 만든다. (구조적)
- 소프트웨어를 이해하기 쉽게 만든다.

개발을 끝이 있는 작업으로 보면 안 된다. 새 기능이 필요할 때마다 이를 반영하기 위해 수정되어야 한다. … 보기 싫은 코드는 물론이고, 잘 작성된 코드도 오늘의 기준에 맞게 리팩토링 해야 한다.

각 장에서는 이런 리팩토링 방법을 자세하게 알려준다. 때때로 정반대의 기법들을 제시해주곤 하는데, 이러한 구체적인 방법들은 내 상황에 따라서 취사선택해서 도입할 수 있어야 한다. (훈련이 필요하겠지..!)

이 글에서는 책의 내용중에서 인상깊었던 부분들을 적어보았다. 주로 방법론 사이사이의 원론적 이야기들을 추려서 짜깁기해보았다.

견고한 테스트 코드는 리팩토링의 근간!

리팩토링의 필수 전제!

  • 리팩토링은 작은 단계로 수정을 나눠 진행한다. 그래서 중간에 실수하더라도 버그를 쉽게 찾을 수 있다.
    잦은 컴파일 ~ 테스트 ~ 커밋 루프를 둠으로써 실천한다.
  • 자주 테스트하며, 체크포인트를 만들면 생산성에도 좋다.
  • 기능을 구현하기에 앞서 테스트를 먼저 작성하자. 구현보다 인터페이스에 집중하게 되며, 테스트를 작성하며 구현 사항에 대해 더 깊은 이해를 갖게 된다.
  • (이 부분들을 읽고, 다음 책으로 TDD를 선정했다.)

코드의 깊이와 구조를 명확히 하자 : 객체 중심으로 설계 구조 잡기

  • 모듈화로 코드량은 늘어날 수 있지만, 각 요소가 뚜렷이 부각되고, 본질적인 기능이 아닌 부분들(계산 부분과 출력형식을 다루는 부분 등)이 분리됨
  • 캡슐화: 중복 코드를 일원화하고, 응집도 높게(데이터+기능 묶음) 기능들을 작성한다. 값을 변경시키는 내부 동작을 간접적으로 노출시키고 데이터에 대한 간접적인 접근을 지향하자. ~ ex) 엔티티, 값객체 ..

적합한 데이터 구조를 활용하면 동작코드는 자연스럽게 단순하고 직관적으로 짜여진다.
기술과 경험에도 초기 설계에서의 실수는 빈번했다. 프로젝트를 진행할수록 우리는 문제 도메인과 데이터 구조에 대해 더 많은 것을 배우게 된다. 그래서 오늘까지는 합리적이고 올바랐던 설계가 다음주가 되면 잘못된 것으로 판명나곤 한다.

  • 상속보다는 위임을 고려 하기
    - 위임은 상속의 대안, 단일 상속과 강한 결합 등의 문제를 풀어줄 수 있다.
    - 위임으로 독립적인 기능을 가져다 쓰고, 상속으로 좀 더 본질적인 값과 기능 받아오기
  • 클린코드는 짧은 코드가 아닙니다. 찾고싶은 로직을 빠르게 찾을 수 있는 코드죠!
출처: 토스 프론트 클린코드
  • (이 외에도 여러 OOP적 설계 및 리팩토링 방법들을 조언해준다.)

가독성을 개선하는 코드를 작성하자.

- 조건부 로직, 거대한 메서드 ..

의도를 코드에 담아내어야 한다.
이런 마음가짐이 매직 넘버/리터럴 등을 의식적으로 줄이게 하고, 함수 시그니처에 대한 고민도 하게 만든다. What을 인터페이스에서 표현하고, How(구현)은 그 다음에 고민하자
참고: https://www.youtube.com/watch?v=4xg4OeGzGIw

추상화 레벨을 맞춘다.
함수를 읽어내려갈 때, 각 부분이 비슷한 역량의 일을 처리하는 목적(what)을 지칭하고 있어야 훨씬 읽기 편하다.
갑자기 훨씬 구체적인 혹은 기능에 집중된 함수가 문맥에 끼어있다면(how), 코드를 읽어가며 이해하는 흐름을 깬다.
또한 추상화 레벨이 잘 나눠져있으면, 디버깅 시에도 트리를 파고들 듯이 용이한 추적을 가능케 한다.

데이터가 어떻게 수정되는지를 추적하게 하기
:
코드를 파악하는데 가장 까다로운 부분은 데이터의 흐름
-
데이터가 변경(추가/수정/제거..)된다면 그 사실을 명확히 하고, 어느 함수가 무슨일을 하는지 쉽게 알게해야 함.
- 로직을 캡슐화하고 데이터를 callByValue로 다루기! (참조를 통한 데이터 변조 사전 방지!)
- 이뮤터블한 데이터 관리를 지향하자!

리팩토링과 성능

  • 때때로 리팩토링과 성능은 반비례일 수 있다. (ex 목적별로 코드를 구분)
    그러나 잘 다듬어진 코드여야 성능 개선 작업이 더 쉬워진다. 이 후 성능 개선작업이 추가되더라도, 결과적으로 깔끔하고 빠른 코드를 얻게 된다!
  • 시스템에 대해 잘 알더라도 섣불리 추측하지 말고, 성능을 측정해봐야 한다. 그러면 새로운 사실을 배우게 되는데, 십중팔구 내가 잘 못 알고(or 추측하고) 있었음을 깨닫게 된다.

리팩토링을 위한 팁 / 자세

  • 클래스의 균형의 적절함은 시스템이 변함에 따라 바뀌기 마련이다. 리팩토링은 결코 미안하다고 말하지 않는다. 즉시 고칠 뿐이다.
  • 계획된 리팩토링은 최소한으로 줄이고, 조금 긴 호흡을 갖고 깨지지 않는 수준의 리팩토링을 지속적으로 진행해가는게 좋다.
  • 코드 리뷰는 지식 공유와 이이디어 수집에 좋다.
    새로운 사람의 시각은 개선사항과 새로운 방향성 (아이디어)를 제시
    ex) 페어 프로그래밍, 새로운 사람의 온보딩
  • 지저분한 코드 있어도, 내부동작을 이해할 수 있는 시점에 리팩토링 진행하기

테스트 주도 개발 (TDD)

테스트 주도 개발, 단순 테스트 기술이 아닌, 코드 분석기술이며 설계 기술로 테스트를 제안한다. … TDD의 궁극적인 목표는 작동하는 깔끔한 코드를 매우 단순한 패턴으로 작성하게 하는 것이다.

테스트가 도움이 별로 안된다고 말하는 개발자들이 얼마나 될까? 다들 테스트의 중요성과 이것이 제공해주는 가치들을 부정하진 않을 것 같다. 개인적으로, 나는 테스트 작성이 기능을 작성하는 생산성?!과는 좀 떨어져 있는 가치라고 생각했다. (표현하자면 코스트가 크달까?!..)

비슷하게 책을 읽기전 TDD라는 이름만 보고 단편적으로 느낀 감정도 “그럼에도 불구하고 테스트하면 좋다 정도겠지..” 였던 것 같다.

TDD의 구성요소인 테스트 조각들이 선사할 용기

그런데 책을 읽으면서 테스트에 대한 내가 가진 선입견을 깨고, 저자가 테스트 하는 나를 격려하는 듯한 느낌을 받았다. 이런 느낌을 받게한 책의 흐름을 주관적으로 정리하면 아래와 같다.

  • 우선 책은 내가 어렴풋이 아는 테스트의 중요성을 구체화 해준다.
  • 그리고 테스트 작성에 대한 리소스 / 스트레스를 충분히 공감해 준다.
    - 한정된 자원에서의 효율적인 테스트를 위한 테스트 선정기준과 (챕터3 - 26)
    - 단순한 패턴들로 TDD를 구성하고. 이를 습관화 할 것 (챕터1)
    등을 그렇기에 제시한다.
  • 사실 테스트 툴의 본질은 단순하며, (2장)
  • TDD의 단순한 패턴을 따라, 테스트의 효과는 물론 TDD의 부가적인 가치들을 얻으라고 말한다. (서론, 3장, 마치는 글)
TDD로 얻을 수 있는 가치 중 하나

단순한 테스트 작성에서 나아가, 어떻게 설계에 영향을 주는 개발방식으로 테스트를 활용할수 있을 까? 책은 크게 3개의 챕터(+a)로 이 방법론을 제시한다. 책의 내용을 각 챕터마다 정리해 보았다.

챕터 1:

  • TDD의 단순한 단계들을, 작은 루프로 돌리는 것을 체화하기
  • 정말 구체적인 개발 흐름을 보여주면서 어떻게 TDD로 개발, 나아가 설계를 해가는지를 보여준다.

단순한 2개의 규칙을 지키기:
코드 작성전 실패하는 자동화 테스트를 작성하라 & 중복을 제거하라

바쁘다고 갈증을 참고 참았다가 다 끝나고 물을 왕창 마시는 것보다, 매 시간마다 틈틈이 조금씩 마셔두는게 건강에 좋다. 테스트를 이렇게 쉽고 잦게 (+ 일의 첫 단추로) 진행해야 한다.

위의 규칙을 조금 구체적인 단계들로 나열해보자면

  1. 요구사항들을 테스트 목록으로 만들어 두기
  2. 구현할 기능에 대한 테스트 생성하기
    - 때때로 우리의 추론이 맞지 않아서 결함이 손가락 사이로 빠져나갈 수 있다. 그럴 때면 테스트를 어떻게 작성해야 했는지 교훈을 얻고 앞으로 나아간다.
  3. 해당 테스트에 맞춰 기능 구현
    - 객체를 만들면서 시작하는게 아니라 테스트를 먼저 만들어야 한다. (난 항상 이 사실을 되뇌는데, 여러분도..)
    - 당장의 목표는 완벽한 해법을 구하는 것이 아니라 테스트를 통과하는 것일 뿐이다.
    - 여기까지는 빨리 진행해야한다. 도달하기 위해 어떤 죄든 저지를 수 있다. 속도가 설계보다 더 높은 패이기 때문이다.
  4. 가까스로 테스트를 통과하게 만든 기능을 올바르게 만든다.
    - 중복제거
    - 여러 케이스로 갈피가 잡히는 방향성 (로직)을 추상화
    - 뻔뻔스럽게 중복을 만들고 조금 고쳐서 테스트를 작성했다. 그리고 적절한 시기에 적절한 설계를. 돌아가게 만든 후(만들고) 올바르게 만들어라.

이 외에도, 구체적인 리팩토링 방법이나 테스트 기법들이 챕터 내 곳곳에 팁처럼 적혀있다.

챕터 2:

  • 테스트를 하는데 필요한 기본적인 것들은 무엇일까?

테스트 프레임워크

테스트 셋업과 본 테스트들 그리고 테스트 셋업을 정리하는 일련의 과정이 테스트 프레임워크의 기본이다.

이에 부가적으로 다음과 같은 기능들이 필요하다.
- 테스트를 빠르게 돌릴 수 있는 지원 기능
- 그리고 전체 테스트 결과를 한눈에 볼 수 있는 기능

테스트

이 테스트 프레임워크 안의 테스트들은 테스트 셋업 → 특정 자극 또는 행동(Act) → 이에 따른 결과 확인 (Assert) 테스트 패턴을 가지며. 매번 손쉽게 실행할 수 있도록 내부환경을 구성해야 한다. 이를테면,

  • mock 라이브러리 또는 데이터로 테스트 시간을 단축.
  • 테스트들이 각각 독립적으로 운영시켜, 테스트 커플링 스트레스 줄이기.

등 이다. (이에 대한 구체적인 패턴들은 챕터3에서 계속 조언해준다.)

챕터 3 ( + 마치는 글 ):

  • 정말 피가되고 살이되는 이야기들!
  • TDD를 왜 해야하는지 더욱 타당하게 & 어떻게 하는지 더 구체적으로!

자동화된 테스트, 픽스처(+mock), 디자인 패턴, 리팩토링 등 여러 조언들이 많지만, 그 중 지금의 내게 와닿았던 부분들을 짧게 정리했다.

A) 격리된 테스트의 중요성 ~ 25장

A-a) 테스트는 충분히 빨라서 내가 자주, 직접 실행할 수 있게끔 하자!
~ 작은 스케일의 테스트는 빠른 환경 셋업후 테스트 가능!
~ 테스트 격리를 위해 결과적으로 시스템이 응집도 높고 결합도 낮은 객체 모음으로 구성되도록 함
일상적인 격리된 테스트를 습관들이기 전까지, 응집도와 결합도를 어떻게 높이고 낮출지 정확히 이해할 수 없었다. ~ 잘 설계된 OOP적인 아키텍처의 효과이자 목적

A-b) 많은 에러(실패)가 많은 양의 문제를 의미하진 않는다.
~ 문제의 중복 또는 복합으로 인한
!
~ 문제가 하나면 하나만 영향받고, 둘이면 테스트도 2개만 실패해야 한다.
~ 테스트 일부만 실행하고 싶을 때, 선행 테스트 실행여부 고려하지 말아야!
테스트 실행 순서도 독립적이게 된다!

B) 테스트케이스가 기능에 대한 설명서가 될 수 있다. ~ 26장
- 테스트의 목적이 곧 기능의 요구사항을 담아내도록!
- 기능 구현중, 해당 테스트 목적과 무관한 아이디어가 떠오르면 이에 대한 테스트 만들기를 할일 목록에 적어놓고 다시 주제로 돌아올 것
- 산만함은 아이디어 원천 및 충전일 수 있다. 이를 적어두고 다시 일로 돌아가자.

C) 테스트의 양: ~ 32장
- 당신은 자신의 경험과 숙고를 통해, 얼마나 많은 테스트를 작성할지 결정해야할 것이다.
-
TDD의 테스트는 수단 ~ 우리가 깊이 신뢰할 수 있는 코드를 만드는 목적!
그렇기에 신뢰할만 하다면 더이상 테스트 작성은 무의미!

D-1) TDD의 가치: ~ 32장
TDD는 더 깔끔한 설계를 할 수 있도록, 더 많은 것을 배워감에 따라 설계를 더 개선할 수 있도록, 적절한 때 적절한 문제에 집중할 수 있게끔 도와준다.

D-2) TDD를 하며돌입하게 되는 정신상태: ~ 마치는 글
- 복잡한 조건을 여러 TC로 쪼개고, 이것을 잘 작동하도록 하나씩 접근
- TDD는 한번에 하나의 일에 충분히 집중하게끔 만듦
- 기능 작성 때 어떤 설계가 좋을지 고민하지 않고, 가장 쉽게 테스트 통과시키려고 노력
- 리팩토링 모드에는 기능 추가 신경쓰지 않고, 올바른 설계 얻는데만 집중

프로그래밍 행위를 여러 기본적 모드로 나누되, 그런 모드를 재빨리 전환해가며 단조로움을 피하는 것
단일사고적 모드들과 전환의 조합을 통해 집중의 이득과 단조로움 없애고 스트레스 낮춤

--

--