Clean Code Day 8 8장 경계 ~ 9장 단위 테스트

Jeeho Park (aquashdw)·2024년 9월 6일
0

Clean Code

목록 보기
8/10

오늘도 한주가 끝났다. 어쩌다보니 전혀 예정에 없던 줄서는 식당에 가버려서 거진 15시간만에 집에 도착했다. 그래도 좀있다 애 씻기고 맥주한잔 하기로 했다. 내일은 즐거운 주말. 출근만 안해도 이리도 행복할 줄이야.

abstract

이번 범위는 총 2장이다. 사실 "9장 단위 테스트"라고 되어 있으니 9장만 이야기한것 같기도 하지만....이왕 읽은거 경계도 같이 작성해본다.

현재 세상에서 하나의 언어를 선택해서 처음부터 끝까지 자신이 직접 작업한 코드로만 프로그램을 만드는건 사실상 불가능할 것이다. 각 언어의 표준 라이브러리는 그렇다 쳐도, Java에는 Maven, Python에는 PyPI, Node.JS는 npm 등. 기본적으로 우리는 다른 개발자가 만들어놓은 다른 코드를 활용하여 내가 만들고자 하는 기능을 제공하게 될것이다.

우리가 아무리 팀으로 활동하고, 팀의 규칙을 정하며 활동을 한다고 하더라도, 개발자는 결국 사람이다. 사람은 성격과 개성을 가지고 있으니, 세상의 모든 사람은 특별하다고 할 수 있다. 개발자가 작성한 코드도 마찬가지. 10명의 개발자가 모이면 아무리 규칙을 정한다고 하더라도, 규칙의 테두리 내에서 어떻게든 자신의 개성이 드러나는 코드를 작성하게 될것이다. 규칙을 벗어나거나

하물며 남이 작성한 코드가 얼마나 나의 스타일을 따라서 만들어져 있을까? 조금이라도 비슷하면 다행일 것이다. 8장 경계는 이런 다른 사람이 만든 코드를 좀더 안정적으로 나의 코드에 포함시키는 방법에 대한 논의라고 볼 수 있다.

9장 단위 테스트는 나의 죄악이다. 나는 정말 테스트 작성을 잘 못한다. 좀 해보겠다고 TDD도 사두었는데, 읽을때는 재밌지만 솔직히 적용하는건 정말 어려운 일인것 같다. 하지만 프로젝트를 하다보면 필요성은 확실히 느낄 수 있다. 관리되지 않은 테스트 코드를 가지고 있다면 나의 변경사항이 어떤 영향을 미치게 될지 누가 알 수 있을까? 어찌보면 8장의 이야기와도 일맥상통 하는데, 우리가 사용하는 라이브러리, 프레임워크 등이 안정적으로 잘 돌아가는 것을 보장하는것도 저자들이 작성한 테스트 코드들이다. 이번 챌린지가 끝나고 지금 작업중인 프로젝트가 마무리되면 테스트 코드 작성부터 다시 해봐야겠다.

8장 경계

시스템에 들어가는 모든 소프트웨어를 직접 개발하는 경우는 드물다.

외부 코드 사용하기

패키지 제공자나 프레임워크 제공자는 적용성을 최대한 넓히려 애쓴다.

누군가가 자신이 만든 코드를 Maven이든 npm이든 올린다면 그건 보통 다른 개발자가 자신이 만든것을 사용하고 자신의 이름을 널리 펼치기 위해서일 것이다. 물론 순수하게 공익적 목적일 수도 있고. 어찌됬든 이런 코드의 생리는 어찌보면 장사의 생리와도 비슷한데, 결국 고객(개발자)를 얼마나 끌어들이느냐에 문제라는 이야기다.

그러니 우리 서비스의 특수한 구조, 우리만의 특별한 요구사항에 완벽히 대응되는 코드란 존재하기 어렵고, 마찬가지로 아주 특수한 기능을 제공하는 패키지는 많이 사용되기 어렵다. 그렇기 때문에 대부분 공개된 코드는 아주 일반적인 기능을 대부분 제한 없이 제공하기 마련이다.

가령 책에서 이야기한 Map 인터페이스 같은 것이다. MapHashTable등 Key - Value로 데이터를 저장하기 위한 객체들을 나타내는 인터페이스인데, Map을 사용하도록 유도하는 입장에서는 데이터를 저장하는 put만 제공하고 데이터를 회수하는 get같은 메서드를 제공하지 않는다면 당연히 Map을 사용하는 개발자는 없을것이다. 요즘이라면 ChatGPT한테 물어만 봐도 적당한 Map 구현체를 만들어줄텐데 말이다.

그래서 만약 Map의 기능을 일부만 사용하고 싶다면, 책에서 나온것처럼 자신의 목적을 묘사한 별도의 클래스로 한번 감싸주어 일부 기능만 제공하는 방법이 있을것이다.

public class Sensors {
  private Map sensors = new HashMap();
  
  public Sensor getById(String id) {
    return (Sensor) sensors.get(id);
  }
}

어찌보면 Spring 등지에서 많이 활용하는 DAO도 비슷한 목적을 가지고 있다고 볼 수 있지 않을까? DAO 내부에서라면 순수 JDBC를 사용하던, MyBatis를 사용하던, JPA를 사용하던 큰 문제가 되지 않을 것이다. 또한 DAO를 인터페이스로 만든다면 기술 발전에 따라 다른 기술을 사용하던 구현체를 좀더 쉽게 교체할 수 있을것이다. 그것이 실행중에 실제 알고리즘을 결정하는, Strategy Pattern의 모습 아니었나? (물론 이 형식은 DAO에만 국한된 이야기가 아니지만)

암튼 결론은 Map을 이용해 특정 클래스나 메서드 내부에서 사용하는 것은 괜찮지만, 데이터를 이리저리 주고받아야 하는 상황에는 Map을 직접적으로 넘기는 것을 지양해야 한다는 의미이다.

경계 살피고 익히기

그래서 외부 라이브러리를 사용하기 전에 단위 테스트 등으로 외부 코드가 어떻게 동작하는지 살펴보는 시간이 있으면 좋을것이다. 이를 학습 테스트라고 부른다.

외부 라이브러리를 사용하는 방법을 익히기 위한 학습 테스트는 결국 사용해야되는 외부 라이브러리의 사용법을 익히는 방법이다. 그리고 자연스럽게 자신의 목적에 맞는 동작을 하는지가 테스트 코드로 작성이 된다. 그러면 라이브러리의 업그레이드가 자신의 목적에 맞지 않을 경우 테스트 코드가 이를 감지할 수 있다.

아직 존재하지 않는 코드를 사용하기

아직 존재하지 않는 코드, 예를들어 다른 팀과 협업 중에 어떻게 코드를 사용할지 결정이 되어있지 않은 경우 추상화를 적극적으로 활용한다.

  • 만들어지지 않은 코드의 동작을 유추하여, 인터페이스 형태로 만들어준다.
    • 해당 인터페이스가 최종적으로 만들어진 코드와 다르다고 해도 괜찮다.
    • 오히려 그 인터페이스는 상대방으로부터 내가 필요한 내용을 명확히 정의하는 것이니 더 좋을 수 있다.
  • 이후 인터페이스를 사용하여 개발한다. 단위 테스트의 Mock 처럼 임의의 클래스를 만드는것도 괜찮을듯 하다.
  • 최종적으로 상대방의 실제 코드가 나오면, 해당 인터페이스의 구현체가 해당 코드를 사용하는 방향으로 개발을 한다.
    • 이는 Adapter Pattern이라 부르는 디자인 패턴의 일종이다.

GOF는 수많은 디자인 패턴을 만들어냈고, 객체지향 진영의 발전에 눈부신 기여를 했다고 생각한다. 어댑터 패턴은 코드로 만드는 여행용 어댑터 같은 개념으로 생각하면 된다. 꽂는 구멍과 꽂아야 되는 구멍의 크기 차이를 효율적으로 변환해주듯이, 구현 클래스의 바깥의 사용법을 유지한채 실제 사용해야하는 코드를 격리하여 관리할 수 있게 해준다.

Mock을 만드는 것과 비슷하므로, 결국 테스트에도 도움을 주는 방식이다.

9장 단위 테스트

우리 분야는 지금까지 눈부신 성장을 이뤘지만 앞으로 갈 길은 여전히 멀다.

TDD 법칙 세 가지

  1. 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
  2. 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
  3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

깨끗한 테스트 코드 유지하기

실제 코드가 진화하면 테스트 코드도 변해야 한다는 데 있다. 그런데 테스트 코드가 지저분할 수록 변경하기 어려워진다.

테스트 코드는 내 코드가 올바른 방향으로 유지되어 변경되었다는 지표라고 생각된다. 내가 기능을 만들고, 해당 기능이 정상적으로 동작하는지 확인하는 테스트 코드를 작성해두면, 추후 내가 코드를 변경했을 때 기능의 변경이 이전 동작을 해하는지 자동으로 확인해주는 것이 테스트 코드이다.

당연하지만 기능이 변하면 테스트 코드도 같이 변하기 마련이다. 하지만 대충 만든 테스트 코드는 새로운 기능이 추가될 때 더 많은 테스트를 작성하는데 걸림돌로 작용하게 된다. 그리고 개발자는 언젠가는 버려야할 레거시 코드마냥 테스트 코드를 폐기하게 될지도 모른다.

  1. 테스트 슈트가 없으면 수정된 코드가 제대로 도는지 확인할 방법이 없다.
  2. 한쪽의 수정이 다른쪽의 안전을 해치지 않는지 검증하기 어렵다.
  3. 결국 결함율이 높아지고,
  4. 결함율로 인해 변경이 두려워지고, 득보다 해가 더 커지게 되는 상황이 된다.

테스트 코드는 실제 코드 못지 않게 중요하다.

테스트는 유연성, 유지보수성, 재사용성을 제공한다.

테스트 케이스가 있으면 변경이 두렵지 않으니까!
...
테스트 케이스가 있으면 변경이 쉬워지기 때문이다.

이중 표준

실제 환경과 테슽 환경은 요구사항이 판이하게 다르다.

우리가 작성한 코드는 여러 환경에서 실행될 가능성이 있다. 예를 들어 대부분의 부트캠프를 비롯한 과정에서는 AWS를 비롯한 클라우드를 활용하고, 가능하면 free tier에서 구성된 실습 환경을 활용하는 경우가 많다. AWS free tier의 경우 Ubuntu t2였나 t3였나를 micro 사이즈로 제공한다. 인터넷 속도는 둘째치코, micro의 메모리는 1GB이다. minikube도 간신히 돌아가....지 않는다. 엄밀히 말하면 2GB를 요구하는 minikube기 때문에.

하지만 테스트 환경은 어떤 컴퓨터에서 하더라도 괜찮으니 자원을 상대적으로 더 활용할 수 있다. 일반적으로 알고리즘 문제풀이에는 사용해서는 안되는 "반복문 내부의 String concatenation"또한 큰 문제가 되지 않는다.

결론

아...단위 테스트 부분이 너무 부실해 보인다. 아무래도 혼자 개발할때는 재미 위주의 개발이고, 단발성 발전 적은 프로젝트라 단위 테스트를 덜 작성해서 그런지 모르겠다. 반성하고, 앞으로라도 테스트 코드 꼼꼼히 작성해주자.

최애틸 찾아보기

지금 현재 북클럽 챌린지를 함께하고 계신 분들 것을 살펴보았는데

https://mgcllee.github.io/ 이분이 블로그 열심히 작성하시면서 코드도 C 계열 코드로 재현하고 있어서 보는 재미가 있는것 같다.
https://nomadcoders.co/community/thread/10181 이분 글도 정성이 느껴져서 읽는 보람이 있다.
https://velog.io/@hjeong1200/posts 이분 TIL이 가볍게 읽기 좋은 느낌이다.

0개의 댓글

관련 채용 정보