당신의 코드 품질을 개선하고 싶다면(좋은 코드, 나쁜 코드)

승톨·2024년 5월 2일
0

요즘 1달에 1권 이상 책 읽기 챌린지를 실천하고 있는데요.
지난 달에 읽은 책 중 ‘좋은 코드, 나쁜 코드’라는 책을 읽고 정리해보았습니다.

참고로, 저자가 책 서두에 얘기한 것처럼 이 책은 3년 이내의 주니어 개발자들이 읽으면 아주 좋은 책이라고 생각합니다. 책을 읽으면서 제가 했던 실수들과 좋은 코드에 대한 많은 통찰을 얻을 수 있었습니다.

각 챕터 별로 참고하면 좋을 내용을 정리해보았는데요. 읽으시는 분들에게 도움이 되길 바라며, 꼭 한번 책 전체 내용을 읽어보시면 성장에 도움이 되리라 확신합니다.

CHAPTER 1: 코드 품질

코드를 작성할 때, 달성할 4가지 상위 수준의 목표

  1. 작성한 코드가 작동해야 한다.
  2. 작동이 멈춰서는 안 된다.
  3. 변화하는 요구 사항에 적응해야 한다.
  4. 이미 존재하는 기능을 또다시 구현해서는 안 된다.

위의 4가지 목표는 우리가 근본적으로 성취하고자 하는 것에 초점을 맞추는 데는 도움이 되지만, 일상적으로 하는 업무에 대해서는 특별한 조언을 제공하지 않는다. 그 조언은 아래의 여섯가지 전략을 중심으로 진행된다.

  1. 코드는 읽기 쉬워야 한다.
  2. 코드는 예측 가능해야 한다.
  3. 코드를 오용하기 어렵게 만들라.
  4. 코드를 모듈화 하라.
  5. 코드를 재사용 가능하고, 일반화 할 수 있게 작성하라.
  6. 테스트가 용이한 코드를 작성하고, 제대로 테스트 하라.

1.코드는 읽기 쉬워야 한다.

코드의 가독성이 떨어진다면, 다른 개발자가 그 코드를 이해하는데 많은 시간을 들여야 한다. 또한 코드의 기능에 대해 잘못 이해하거나 몇 가지 중요한 세부 사항을 놓칠 가능성 역시 크다.

2.코드는 예측 가능해야 한다.

  • 우리의 코드를 사용하는 다른 개발자는 이름, 데이터 유형, 일반적인 관행과 같은 단서를 사용해 코드가 입력값으로 무엇을 예상하는지, 코드가 무슨 일을 하는지, 그리고 무엇을 반환하는지에 대한 정신 모델을 구축한다. 이 정신 모델과 어긋나는 어떤 일이 코드에서 일어나면, 이로 인해 버그가 아무도 모르게 코드 내로 유입되는 일이 너무도 많이 일어난다.
  • 코드가 예상을 벗어나는 일을 수행하지는 않는지 주의 깊게 살펴야 하고, 할 수 있다면 그런 코드를 작성하지 않도록 노력해야 한다.

3. 코드를 오용하기 어렵게 만들라.

  • 자신이 작성한 코드가 잘못된 형태로 사용되면, 모든 것이 폭발할 수 있다. 시스템은 작동을 멈추고, 데이터베이스가 영구적으로 손상되거나, 중요한 데이터가 손실 될 수 있다.
  • 코드를 오용하기 어렵거나 불가능하게 하면 코드가 작동할 뿐만 아니라 계속해서 잘 작동할 가능성을 극대화할 수 있다.

4.코드를 모듈화 하라.

  • 코드를 외부에 의존하지 않고 실행할 수 있는 모듈로 나누는 것이 이로울 때가 많다.
  • 두 개의 인접한 모듈 사이의 상호작용은 한 곳에서 일어나고 잘 정의된 인터페이스를 사용한다. 이렇게 하면 변화하는 요구사항에 더 쉽게 적응할 수 있는 코드를 작성하는데 도움이 된다. 왜냐하면 한 가지 기능을 변경한다고 해서 다른 부분까지 변경할 필요가 없기 때문이다.

5.코드를 재사용 가능하고, 일반화 할 수 있게 작성하라.

  • 코드는 만들어 내는 데 시간과 노력이 필요하며, 일단 만들어지면 유지보수에 지속적인 시간과 노력이 들어간다. 코드를 작성하는 일이 위험이 없는 것은 아니다. 아무리 조심하다고 해도 코드에는 버그가 포함될 수 있고, 코드를 더 많이 작성할 수록 더 많은 버그가 생길 것이다. 우리는 코드를 많이 작성한 것에 대한 대가를 받는 것이 아니라 문제를 해결한 것에 대한 대가를 받는 것이다. 코드는 문제 해결을 위한 수단일 뿐이다. 노력을 덜 기울이면서도 문제를 해결하고, 버그로 인해 의도치 않게 다른 문제를 일으킬 가능성을 줄일 수 있다면 그것은 대단한 것이다.
  • 코드가 재사용할 수 있고, 일반화 되어 있으면 우리는 그 코드를 코드 베이스의 여러 부분에서, 그리고 하나 이상의 상황에서 사용할 수 있고, 여러 가지 문제를 해결할 수 있다.

6.테스트가 용이한 코드를 작성하고, 제대로 테스트 하라.

  • 테스트는 실제 서비스 환경을 실행하는 프로세스에서 두 가지 핵심 사항에 대한 주된 방어 수단이 될때가 많다.
    • 버그나 제대로 동작하지 않는 기능을 갖는 코드가 코드베이스에 병합되지 않도록 방지
    • 버그나 제대로 동작하지 않는 기능을 갖는 코드가 배포되지 않도록 막고 서비스 환경에서 실행되지 않도록 보장
  • 코드의 테스트 용이성이 낮으면 제대로 테스트하는 것이 불가능할 수 있다. 현재 작성 중인 콛의 테스트 용이성을 확인하기 위해 코드를 작성하면서 ‘어떻게 테스트 할 것인가?’를 계속 자문하는 것이 좋다. 코드를 다 작성하고 나서 테스트에 대해 생각해서는 안 된다.

고품질 코드 작성은 일정을 지연시키는가?

  • 코드 품질을 고려하지 않고 먼저 떠오르는대로 코딩하면 처음에는 시간을 절약할 수 있다. 그러나 이런 코드는 머지않아 취약하고 복잡한 코드베이스로 귀결될 것이며, 점점 더 이해하기 어렵고 추론할 수 없는 코드가 된다.
  • 유지 관리가 용이한 코드는 버그를 수정하고 변경 사항을 구현하는 데 더 적은 시간이 걸린다.
  • 확장 가능한 코드는 새로운 요구 사항에 맞게 쉽게 확장할 수 있어 추가 개발 시간을 절약할 수 있다.

CHAPTER 2: 추상화 계층

코드를 잘 구성한다는 것은 간결한 추상화 계층을 만드는 것으로 귀결될 때가 많다.

코드 작성은 복잡한 문제를 계속해서 더 작은 하위 문제로 세분화하는 작업이다. 어떤 문제를 하위 문제로 계속해서 나누어 내려가면서 추상화 계층을 만든다면, 같은 층위 내에서는 쉽게 이해할 수 있는 몇 개의 개념만을 다루기 때문에 개별 코드는 특별히 복잡해 보이지 않을 것이다.

실제로 추상화 계층을 생성하는 방법은 코드를 서로 다른 단위로 분할하여 단위 간의 의존 관계를 보여주는 의존성 그래프를 생성하는 것이다. 코드를 나누기 위해 아래의 요소들을 사용할 수 있다.

  1. 함수
    1. 각 함수에 포함된 코드가 하나의 잘 써진 짧은 문장처럼 읽히면 이상적이다.
    2. 함수가 하는 일을 다음 중 하나로 제한하면 이해하기 쉽고 단순한 문장으로 표현되는 함수를 작성하기 위한 좋은 전략이 될 수 있다.
      1. 단일 업무 수행
      2. 잘 명명된 다른 함수를 호출해서 더 복잡한 동작 구성
  2. 클래스
    1. 300줄 보다 긴 클래스는 너무 많은 개념을 다루므로 분리해야 한다는 것은 항상은 아니지만 아주 많은 경우에 사실이다.
    2. 좋은 클래스는 매우 응집력이 강하다.
    3. 시스템이 각각 별개의 관심사를 다루는 개별 구성 요소로 분리되어야 한다.(관심사의 분리)
    4. 단일 클래스 내에 얼마나 많은 다른 개념이 들어가 있는지, 그리고 어떤 로직이 재사용이나 재구성에 적합한지에 대해 개발자가 신중하게 생각하지 않으면 클래스는 종종 너무 커진다.
    5. 코드를 적절한 크기의 클래스로 세분화하는 것은 추상화 계층을 잘 만들기 위한 가장 효과적인 도구이기 때문에 이를 위한 시간과 노력을 들일 만한 가치가 충분히 있다.
  3. 인터페이스
    1. 하나의 추상화 계층에 대해 두 가지 이상의 다른 방식으로 구현을 하거나 향후 다르게 구현할 것으로 예상되는 경우 인터페이스를 정의하는 것이 좋다.

    2. 주어진 추상화 계층에 대해 한 가지 구현만 있고, 향후에 다른 구현을 추가할 계획이 없더라도 인터페이스를 통해 추상화 계층을 표현하면 좋다.

      인터페이스를 사용하면,

      1. 이 계층에서 사용해야하는 기능과 사용하지 말아야하는 기능에 대해 혼동할 일이 없다.
      2. 한 두달 후에는 또 다른 구현이 필요하지 않다는 가정이 잘못된 것으로 판명될 수 있다.
      3. 테스트를 쉽게할 수 있다.
      4. 코드의 일반화 가능성을 크게 높일 수 있다.

CHAPTER 3: 다른 개발자와 코드 계약

코드를 작성할 때 다음 세 가지를 고려하는 것이 유용하다.

  1. 자신에게 명백하다고 해서 다른 사람에게도 명백한 것은 아니다.
    1. 다른 개발자가 여러분이 작성한 코드와 상호작용하거나, 여러분의 코드를 변경해야 할 수도 있다는 것을 기억해야 한다. 그들은 그 문제를 이해하고 어떻게 해결할지에 대해 생각할 수 있는 시간을 아직 충분히 갖지 못한 상태이다.
  2. 다른 개발자는 무의식 중에 여러분의 코드를 망가뜨릴 수 있다.
    1. 여러분이 작성한 코드는 다른 코드로부터 전혀 영향을 받지 않은 채 독립적으로 있는 것이 아니라, 끊임없이 변화하는 코드 위에 놓여있고, 여러분의 코드를 기반으로 계속해서 변화하는 코드 역시 끊임없이 작성된다.
  3. 시간이 지남에 따라 자신의 코드를 기억하지 못한다.
    1. 1~2년 전에 작성한 코드를 다시 들여다보는 일은 다른 사람이 작성한 코드를 보는 것과 크게 다르지 않다.

여러 분이 작성한 코드를 어떻게 사용해야 하는지 알아내기 위해 다른 개발자가 할 수 있는 일은 다음과 같다.

  1. 함수, 클래스, 열거형 등의 이름을 살펴본다.
  2. 함수와 생성자의 매개변수 유형 또는 반환 값의 유형 같은 데이터 유형을 살펴본다.
  3. 함수/클래스 수준의 문서나 주석문을 읽어본다.
  4. 직접 와서 묻거나 채팅/이메일을 통해 문의한다.
  5. 여러분이 작성한 함수와 클래스의 자세한 구현 코드를 읽는다.

이 중 처음 세 가지만이 실제로 사용할만 하고, 이름과 데이터 유형을 확인하는 것이 문서를 읽는 것보다 더 신뢰할 만 하다.

서로 다른 코드 간의 상호작용은 계약과 같다. 어떤 코드를 호출하는 코드는 특정 요건을 충족해야 하며, 호출되는 코드는 원하는 값을 반환하거나 일부 상태를 수정한다.

코드 계약을 다음과 같은 세 가지 범주로 나누면 유용하다.

  1. 선결 조건: 코드를 호출하기 전에 사실이어야 하는 것, 예를 들어 시스템이 어떤 상태에 있어야 하는지, 코드에 어떤 입력을 공급해야 하는지와 같은 사항
  2. 사후 조건 : 코드가 호출된 후에 사실이어야 하는 것, 예를 들어 시스템이 새로운 상태에 놓인다든지 반환되는 값과 같은 사항
  3. 불변 사항 : 코드가 호출되기 전과 후에 시스템 상태를 비교해서 변경되지 않아야 하는 사항

코드 계약의 세부 조항은 다른 개발자가 계약을 준수하도록 하기 위한 방법이지만 신뢰할만한 방법은 아니다. 보통 더 나은 접근법은 명백한 항목으로 계약의 내용을 전달하는 것이다.

CHAPTER 4: 오류

오류에 대해 생각할 때 소프트웨어가 작동을 계속할 수 있는 오류와 작동을 계속할 합리적인 방법이 없는 오류로 구분하는 것이 유용할 때가 많다.

복구 가능한 오류

  • 복구 가능한 오류의 예는 다음과 같다.
    1. 잘못된 사용자 입력
    2. 네트워크 오류
    3. 중요하지 않은 작업 오류
  • 낮은 층위의 코드에서 오류를 시도하고 복구하는 것은 장점이 별로 없고, 오류 처리 방법을 알고 있는 더 높은 층위의 코드로 오류를 전송해야 하는 경우가 많다.

복구할 수 없는 오류

  • 이 오류는 프로그래밍 오류 때문에 발생 할 때가 많은데, 이 경우는 개발자가 코드의 어느 부분에서 뭔가를 망쳐놓은 것이다.
  • 오류를 복구할 수 있는 방법이 없다면, 유일하게 코드가 할 수 있는 합리적인 방법은 피해를 최소화하고 개발자가 문제를 발견하고 해결할 가능성을 최대화 하는 것이다.
  1. 신속하게 실패하라
    1. 가능한 문제의 실제 발생 지점으로부터 가까운 곳에서 오류를 나타내는 것이다.
    2. 오류가 발생하자마자 바로 실패나 오류를 보여주지 않으면 문제가 발생할 때 디버그하기 어려울 뿐만 아니라, 코드가 제대로 작동하지 않거나 잠재적으로 문제를 일으킬 수 있다.
  2. 요란하게 실패해라
    1. 오류가 발생하는데도 불구하고 아무도 모르는 상황을 막고자 하는 것이다. 요란하게 오류를 나타내면 개발 도중이나 테스트하는 동안에 버그가 발견될 가능성이 크다. 상세 오류 정보를 기록하여 개발자가 발생한 일을 디버그할 수 있게 해주고, 오류 발생률이 너무 높아지면 개발자에게 알림 메세지를 보내는 것이 이에 해당한다.

오류 전달 방법

  1. 명시적 방법 : 코드를 직접 호출한 쪽에서 오류가 발생할 수 있음을 인지할 수 밖에 없도록 한다.
    • checked exception, Null 반환 유형, 옵셔널 반환 유형, 리절트 반환 유형 등이 있다.
  2. 암시적 방법 : 코드를 호출하는 쪽에 오류를 알리지만, 호출하는 쪽에서 그 오류를 신경 쓰지 않아도 된다.
    • unchecked exception, 매직값 반환, 프로미스/퓨처, assertion 등이 있다.

복구할 수 없는 오류를 전달 할 때는 unchecked exception을 발생시키거나, panic을 발생시키거나 assertion을 사용할 수 있다.

저자의 경우, 만약 호출하는 쪽에서 복구하기를 원할 가능성이 있는 오류에 대해서는 unchecked exception을 사용하지 않고, 명시적 전달 방법을 사용하는 것을 권하고 있다.

CHAPTER 5: 가독성 높은 코드를 작성하라

코드 이름을 지을 때 서술적인 이름을 짓기 위한 노력을 해야한다.

주석문은 서술적인 이름을 모두 대체할 수 없다.

코드 내에서 주석문을 사용할 때는, 아래의 목적으로 사용하면 된다.

  • 코드가 무엇을 하는지 설명하거나
  • 코드가 왜 그 일을 하는지 설명하거나
  • 사용 지침 등 기타 정보를 제공하거나

더 많은 줄이 필요하더라도 가독성 높은 코드를 작성하는게 좋다. 간결하지만 이해하기 어려운 코드는 피하자.

일관적이지 않은 코딩 스타일은 혼동을 일으킬 수 있다. 예를 들어 코드 작성 시 파스칼 케이스, 카멜 케이스 등을 혼용해서 쓰면 혼동을 줄 수 있다.

하드 코드 값을 사용해야하는 경우, 잘 명명된 상수를 사용하거나 잘 명명된 함수를 사용하는 것이 좋다.

CHAPTER 6: 예측 가능한 코드를 작성하라

예측 가능한 코드를 작성하는 것은 무언가를 분명하게 하는 것일 때가 많다.

함수에서 매직 값을 사용하는 경우, 버그를 유발할 수 있다. 호출하는 쪽에서 함수 계약의 세부 조항을 알아야 한다. 세부조항을 확인하지 않으면 예측을 벗어나는 일이 생길 수 있다.

이를 방지하기 위해 널이 가능한 유형, 옵셔널 값을 반환하거나 널 객체 패턴을 사용하는 것이 좋다.

부수 효과는 소프트웨어 작성 시 불가피한 부분이다. 예상치 못한 부수 효과를 일으키는 것을 피하는 좋은 방법 중 하나는 호출하는 쪽에서 이에 대해 확실하게 인지하도록 하는 것이다.

예를 들어 함수 이름을 displayErrorMesssage() 라고 명명하면 부수효과가 발생할 것이라는 것이 명백하다.

입력 매개변수를 수정하면 버그를 초래할 수 있다. 입력 매개변수 내의 값을 어쩔 수 없이 변경해야하는 경우에는 변경 전에 새 자료구조에 복사하는 것이 최상의 방법이다.

예를 들어 결과값을 복사해서 리턴하는 함수 등을 사용할 수 있다.

List<Invoice> getBillableInvoices(Map<User,Invoice> userInvoices, Set<User> usersWithFreeTrial) {
	return userInvoices.entries().filter(entry -> !usersWithFreeTrial.contains(entry.getKey())).map(entry -> entry.getValue());
}

CHAPTER 7: 코드를 오용하기 어렵게 만들라

비합리적이거나 애매한 가정에 기반해서 코드가 작성되거나 다른 개발자가 잘못된 일을 하는 것을 막지 못할 때 코드는 오용되기 쉽다. 코드를 잘못 사용할 수 있는 몇 가지 일반적인 경우는 다음과 같다.

  1. 호출하는 쪽에서 잘못된 입력을 제공
  2. 다른 코드의 부수 효과(입력 매개변수 수정 등)
  3. 정확한 시간이나 순서에 따라 함수를 호출하지 않음(3장 참조)
  4. 관련 코드에서 가정과 맞지 않게 수정이 이루어짐

코드를 오용하게 어렵게 만드는 기법들은 다음과 같다.

  1. 불변 객체로 만드는 것을 고려하라. 객체를 생성할 때만 값을 할당하면 클래스를 불변적으로 만들 수 있다. 빌더 패턴, 쓰기 시 복사 패턴을 활용하면 좋다.
  2. 객체를 깊은 수준까지 불변적으로 만드는 것을 고려하라. 클래스의 어떤 객체에 대한 참조를 클래스 외부에서도 가지고 있으면 깊은 가변성과 관련된 문제가 발생할 수 있다.
    1. 객체를 반환할 때 객체의 복사본을 만드는 방법이 있다. 다만 복사하는데 비용이 많이 들 수 있다.
    2. 혹은 불변적 자료구조를 사용해서 코드의 어디선가 동일한 참조를 하는게 막을 수 있다.
  3. 일반적인 데이터 유형(정수, 문자열, 리스트 등) 보다는 전용 유형을 사용한다.
    1. 예를 들어 위도를 나타낼 때 Double 을 사용하는 것이 아니라 LatLong 클래스를 사용한다.
  4. 시간을 다룰 때 코드를 잘못 사용하고 혼동을 일으킬 여지가 많다. 정수로 시간을 나타낼 경우, 시간의 절대 순간을 나타낼지 시간의 양을 나타낼지를 명확히 해야 한다.
  5. 논리에 대한 진실의 원천을 하나만 가져야 한다.

CHAPTER 8: 코드를 모듈화하라

모듈화의 주된 목적 중 하나는 코드가 향후에 어떻게 변경되거나 재구성될지 정확히 알지 못한 상태에서 변경과 재구성이 용이한 코드를 작성하는 것이다. 모듈화하는 것은 하위 문제에 대한 해결책의 자세한 세부 사항들이 독립적이고 서로 밀접하게 연관 되지 않도록 하는 것으로 귀결된다. 이렇게 하면 적응성이 뛰어난 코드가 될 뿐만 아니라 소프트웨어 시스템에 대한 추론을 쉽게 해준다.

  1. 의존성 주입의 사용을 고려해보라
    • 하드 코드화된 의존성은 문제가 될 수 있다. 의존성을 주입 받는다면 클래스는 훨씬 더 모듈화 되고 다용도로 쓰일 수 있다.
  2. 인터페이스에 의존하라
    1. 인터페이스에 의존하면, 어떤 구현 클래스라도 사용할 수 있으므로 코드가 훨씬 더 모듈화되고 적응성이 높아진다.
  3. 클래스 상속을 주의하라
    1. 한 클래스가 다른 클래스를 확장하면 슈퍼 클래스의 모든 기능을 상속 받는데, 이로 인해 추상화 계층이 복잡해지고 구현 세부 정보가 드러날 수 있다.
    2. 상속보다는 구성을 사용하는 것이 좋다.
  4. 관련 있는 데이터는 함께 캡슐화 하라. (객체 또는 클래스로 그룹화 하라)
  5. 반환 유형에 구현 세부 정보가 유출되지 않도록 주의하라
  6. 예외 처리 시 구현 세부 사항이 유출되지 않도록 주의하라

CHAPTER 9: 코드를 재사용하고 일반화할 수 있도록 하라

다른 개발자가 이미 내가 해결하려는 하위 문제를 해결했다면, 해당 문제에 대한 해결책을 재사용하는 것이 타당하다. 하지만 안타깝게도 하위 문제에 대한 해결책이 이미 존재한다고 해서 항상 재사용할 수 있는 것은 아니다. 다른 개발자가 구현한 해결책이 자신의 사례에 맞지 않는 가정을 하거나, 그 해결책이 자신에게는 필요 없는 다른 기능과 함께 구성된 경우 이러한 문제가 발생할 수 있다.

따라서 이 점을 적극적으로 고려하여 향후에 재사용이 가능하도록 의도적으로 코드를 작성하고 구조화하는 것이 바람직하다.

  1. 가정을 주의하라
    1. 가정은 코드 재사용시 버그를 초래할 수 있다. 예컨대 이미지 리스트 데이터 유형을 반환하지만 그 안에는 1개의 이미지만 들어가야 한다고 가정을 하고 반환을 하면, 다른 사람에 의해 버그를 초래할 가능성이 있다.
    2. 가정이 필요하면 가정이 깨지면 컴파일 되지 않는 방식으로 만들거나, 가정이 깨지면 오류를 감지해 신속하게 실패하도록 해라.
  2. 전역 상태를 주의하라
  3. 필요 이상으로 매개변수를 받는 함수는 재사용하기 어려울 수 있다
  4. 제네릭의 사용을 고려해라

CHAPTER 10: 단위 테스트의 원칙

코드를 잘 테스트 하고 이 작업을 유지 보수할 수 있는 방향으로 작업을 해야한다.

좋은 단위 테스트가 가져야 할 5가지 주요 기능은 아래와 같다.

  1. 코드 훼손의 정확한 감지
    1. 코드가 훼손되면 테스트가 실패해야 한다. 이것은 코드에 대한 초기 신뢰를 주고, 미래의 훼손을 막아준다.
  2. 세부 구현 사항에 독립적이어야 한다
    1. 리팩터링을 수행할때 테스트는 여전히 성공해야 한다. 테스트가 실패한다면, 리팩터링이 잘못됐다는 것이 분명하다.
  3. 코드가 잘못되면 테스트는 실패의 원인과 문제점을 명확하게 설명해야한다
    1. 테스트 실패가 무엇이 잘못됐는지 알려주지 않는다면 그것을 알아내기 위해 많은 시간을 낭비해야한다.
  4. 이해할 수 있는 테스트 코드
    1. 한번에 너무 많은 것을 테스트하거나, 너무 많은 공유 테스트 설정을 사용하면 이해하기 어렵고 추론하기 어려운 테스트로 이어질 수 있다.
  5. 테스트를 쉽고 빠르게 실행할 수 있어야 한다.
    1. 단위 테스트는 꽤 자주 실행해야하는데, 실행하는데 1시간이 걸린다면 모든 개발자의 속도가 느려진다.

퍼블릭 API에 집중하되, 중요한 동작을 테스트 할 때 무시해선 안 된다. 중요한 동작이 퍼블릭 API 외부에 있을 수 있다.

테스트 더블(Fake, Mock, Stub)을 이용해서 격리된 방식으로 코드 단위를 테스트할 때 도움을 받아라.

Mock이나 Stub은 최소한으로 사용하는 것이 최선이다. Mock과 Stub은 실제적이지 않은 테스트를 만들 수 있고, 테스트가 구현 세부 정보에 유착될 수 있다. 실제 의존성이나 Fake를 사용하는 것이 가능하다면 그게 더 바람직하다.

CHAPTER 11: 단위 테스트의 실제

개발자들이 가끔 저지르는 실수는 테스트할 목록에 함수 이름만 추가하는 것이다. 함수별로 테스트 케이스를 하나만 작성하면 중요한 동작을 놓칠 수 있다. 함수가 수행하는 모든 동작을 테스트 케이스로 만드는게 좋다.

테스트를 위해 코드가 보일 행동을 생각해내는 연습을 하는 것은 코드의 잠재적인 문제를 발견하는 좋은 방법이다.

코드가 제대로 테스트 되는지 여부를 측정하기 위한 한 가지 좋은 방법은 수정된 코드에 버그나 오류가 있음에도 여전히 테스트를 통과할 수 있는지에 대해 생각해보는 것이다.

코드가 오류 시나리오를 처리하고 알리는 방법을 간과하지 말고 테스트해라.

테스트만을 위해 함수를 퍼블릭으로 만들지 말라. 프라이빗 함수를 퍼블릭으로 만든 후에 테스트 할 때의 문제는 다음과 같다.

  1. 이 테스트는 실제로 우리가 신경 쓰는 행동을 테스트하는 것이 아니다.
  2. 테스트가 구현 세부사항에 독립적이지 못하게 된다.
  3. 좋은 단위 테스트는 궁극적으로 중요한 행동을 테스트해야 한다.

한번에 하나의 동작만 테스트해라. 테스트 실패의 이유를 더 잘 알 수 있고 테스트 코드를 이해하기가 더 쉽다.

테스트 설정 공유는 양날의 검이 될 수 있다. 코드 반복이나 비용이 큰 설정을 피할 수 있지만 부적절하게 사용할 경우 효과적이지 못하거나 신뢰할 수 없는 결과를 초래할 수 있다.

profile
소프트웨어 엔지니어링을 연마하고자 합니다.

0개의 댓글