클린 코드 9장-17장

sanghee·2022년 5월 2일
0

🍀인턴 스터디

목록 보기
2/12
post-thumbnail

9. 단위 테스트

TDD(Test Driven Development) 세가지 법칙

  1. 단위 테스트를 작성한 후에 실제 코드를 작성한다.
  2. 실행이 실패하는 정도로만 작성한다.
  3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

테스트 코드의 중요성

  • 테스트 코드가 복잡하게 되면 실제 코드보다 테스트 케이스를 추가하는 시간이 오래 걸리고, 관리하기가 어려워진다.
  • 결국 결함율이 높아진다. 결함율이 높아지면 개발자는 변경을 주저한다.

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

FIRST

  • Fast
    • 테스트는 빨라야 한다.
    • 자주 돌려서 초반에 결함을 빨리 찾아야 한다.
  • Independent
    • 각 테스트는 독립적으로 실행되어야 한다.
  • Repeatable
    • 어떤 환경에서도 반복 가능해야 한다.
    • 환경때문에 테스트 코드를 못돌리는 경우는 없어야 한다.
  • Self-Validating
    • 성공 아니면 실패를 반환한다.
    • 통과 여부를 확인하기 위해 로그 파일을 읽어서는 안된다.
  • Timely
    • 테스트 코드를 작성한 이후에 실제 코드를 작성한다.
    • 반대로 작성하게 되면, 실제 코드가 테스트하기 어려워질 수 있다.



10. 클래스

클래스 체계

  • 변수
    • 상수
    • 인스턴스 변수
  • 함수
  • 각각의 변수와 함수는 공개에서 비공개 순으로 나열한다.

캡슐화

  • 가능한 숨겨야 한다.
  • 그렇지만 반드시 숨겨야 한다는 법칙도 없다.
  • 최대한 숨겨보고, 안될 때에는 캡슐화를 푼다.

클래스 크기

  • 클래스는 무조건 작아야 한다.
  • 클래스가 맡은 책임을 센다.
  • 책임이 많다면? 클래스를 나눠야 한다.

단일 책임 원칙(Single Responsibility Principle)

  • 클래스는 하나의 책임만 가진다.

응집도

  • 응집도가 높다는 말은,
  • 클래스에 속한 변수와 메서드가 서로 의존하며 논리적인 단위로 묶인다는 뜻이다.
  • 응집도가 높을 수록 좋다.

클래스 분리

  • 클래스를 작성한 후, 반드시 분리한다.
  • 극도로 단순하게 쪼개고 쪼갠다.

구체적인 클래스, 추상 클래스

  • 구체적인 클래스: 상세한 구현(코드)를 포함한다.
  • 추상 클래스: 개념만 포함한다.



11. 시스템

복잡성

복잡성은 죽음이다. 개발자에게서 생기를 앗아가며, 제품을 계획하고 제작하고 테스트하기 어렵게 만든다.

도시

  • 도시는 혼자서 관리하지 못한다.
  • 각 분야마다 관리하는 팀이 있다.
  • 적절한 추상화와 모듈화가 필요하다.

시스템의 제작과 사용을 분리해라.

  • 제작과 사용은 다르다.
  • 시스템의 제작: 애플리케이션 객체를 제작하고 의존성을 서로 연결하는 준비 과정
  • 시스템의 사용: 런타임 로직

Lazy

  • lazy하게 선언하는 것은, 작게나마 단일 책임 원칙을 깬다.
  • 테스트도 문제다. 테스트를 하기 전에 테스트 전용 객체를 할당해야만 하기 때문이다.

의존성 주입

  • 새로운 객체는 넘겨받은 책임만 지므로, 단일 책임 원칙을 지키게 된다.
  • 클래스를 완전히 수동적으로 설계한다.
  • 필요한 객체를 만든 후에 클래스 내부의 setter를 통해 주입한다.

확장

  • 초기의 작은 클래스에 이것 저것 붙으면서 점점 커졌다.
  • 성장에는 고통이 따르고, 안되는 부분도 생긴다.
  • 처음에 잘못한게 아니고, 당연한 것이다.
  • 반복적이고 점진적인 애자일 방법으로, 계속 수정해간면 된다.

결론

  • 시스템은 깨끗해야 한다.
  • 실제로 돌아가는 가장 단순한 수단을 사용해야 한다.



12. 창발성

단순한 설계 4가지 법칙

  • 중요한 순서대로이다.
  1. 모든 테스트를 실행한다.
  2. 중복을 없앤다.
  3. 프로그래머 의도를 표현한다.
  4. 클래스와 메서드 수를 최소로 줄인다.

1. 모든 테스트를 실행한다.

  • 테스트를 철저히 거친 시스템은 검증이 가능한 시스템이다.
  • 설계 품질은 당연하게 올라간다.
  • 크기가 작고 책임이 하나인 클래스가 나온다.
  • 결합도가 높으면 테스트 케이스를 작성하기 어려우므로 의존성 주입, 인터페이스 등과 같은 방법으로 결합도를 낮춘다.

2~4. 리팩터링

  • 테스트 케이스를 작성한 후에, 코드와 클래스를 정리한다.
  • 코드를 작성하다가, 설계를 보며 확인한다.
  • 테스트 케이스가 있으므로, 기존 시스템이 깨질지 걱정하지 않아도 된다.

중복을 없애라

  • 중복 == 추가 작업, 추가 위험, 불필요한 복잡도
  • 시스템의 복잡도를 낮춘다.
  • 소규모 재사용을 하게 되며, 이후 대규모 재사용이 가능하게 한다.

표현하라

  • 자신이 이해하는 코드를 짜기는 쉽다.
  • 하지만 남이 이해할 코드를 짜기는 쉽지 않다.
  • 그러기 위해서는, 우선 의도를 분명히 해야 한다.
  • 명백하게 짜야 한다.
  • 네이밍을 고민한다.
  • 함수와 클래스의 크기를 줄인다.
  • 표준 명칭을 사용한다.
  • 단위 테스트 케이스를 꼼꼼하게 작성한다.



13. 동시성

동시성

  • 동시성은 결합(coupling)을 없애는 전략이다.
  • 무엇과 언제를 분리하는 전략이다.

동시성 방어 원칙

  • 단일 책임 원칙
    • 동시성 관련 코드는 다른 코드와 분리한다.
  • 따름 정리
    • 자료 범위를 제한한다.
    • 코드 내 임계영역을 보호한다.
    • 임계영역의 수를 줄이는 것도 중요하다.
  • 자료 사본
    • 공유 자료를 줄이기 위해 애초부터 공유를 하지 않는 방법도 있다.
    • 복사해 사용한다.
  • 스레드는 가능한 독립적으로 구현한다.
  • 다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자



14. 점진적인 개선

깨끗한 코드 짜기

  • 일단 돌아만 가는, 지저분한 코드를 작성한다.
  • 이후 깨끗하게 정리한다.
  • 네이밍, 인수의 수, 분기의 수 등을 체크하며 개선한다.
  • 개선한 후 모든 테스트를 통과하는지 살핀다.

나쁜 코드

  • 나쁜 코드를 깨끗한 코드로 개선하기는 어렵다.
  • 오래된 의존성을 찾아내 깨려면 상당한 시간과 인내심이 필요하다.
  • 점점 무게가 늘어나 팀의 발목을 잡는다.

깨끗한 코드

  • 반면 처음부터 깨끗한 코드를 작성하고 유지하는 것은 상대적으로 쉽다.



15. JUnit 들여다보기

JUnit

  • iOS에서 UnitTest와 비슷한 개념이다.
  • 작은 모듈단위로 테스트한다.
  • 모듈에 필요한 기능이 좀 더 드러난다.

보이스카우트 규칙

  • 처음 왔을 때보다 더 깨끗하게 해놓고 떠나야 한다.
  • 코드를 추가하거나 수정한 경우, 이전 코드보다 더 깨끗하게 해놓는다.

모듈화 팁

  • 한 메서드가 여러 기능을 수행하고 있지는 않은지 체크한다.
  • 조건문이 너무 길다면, 캡슐화한다.
  • 조건문을 메서드로 뽑아내 적절한 이름을 붙인다.
  • 인수를 고려해 네이밍을 다듬는다



16. SerialDate 리팩터링

개요

  • SerialDate를 리팩토링해보자.

첫째, 돌려보자

  • SerialDateTests라는 클래스에서 모든 경우를 테스트하지 않았다.
  • 클래스를 철저히 이해하고 리팩토링하기 위해 단위 테스트를 구현한다.

둘째, 고쳐보자

Serial 네이밍

  • Serial이라는 네이밍을 고민한다.
  • Serial은 구현할 때 일련번호(serial number)를 사용하기에 나온 단어이다.
  • SerialDate가 아닌, Date가 더 바람직하지만, Date는 너무 많이 쓰인다.
  • OrdinalDate나 DayDate로 변경했다.

변수 선언

  • 접근 제어자를 설정한다.
  • 가장 제한적인 접근제어자를 사용할 수 있다면 사용해 선언한다.

함수

  • 함수 하나가 여러 기능을 하면 쪼갠다.

추상화

  • 클래스를 추상화한다.



17. 냄새와 휴리스틱

코드의 가장 나쁜 냄새

  • 부적절한 주석
    • 오히려 혼란만 야기한다.
  • 쓸모 없는 주석
    • 가독성만 떨어지게 만든다.
    • 쓸모 없어지면 바로 삭제한다.
    • 애초에 쓸모 없어질 주석은 추가하지 않는다.
  • 중복된 주석
    • 코드만으로 충분하다면 작성하지 않는다.
  • 성의 없는 주석
    • 주석을 달 것이라면, 신중하게 작성한다.
  • 주석 처리된 코드
    • 주석 처리된 코드는 바로 삭제한다.
    • 어차피 이전 버전에서 해당 코드를 기억하고 있다.
  • 여러 단계로 빌드한다?
    • 빌드는 한 단계로 끝나야 한다.
    • 빌드를 위해 여기저기 뒤적일 필요가 없어야 한다.
    • 과정이 많게 되면, 작성 중에 지속적인 빌드가 귀찮게 된다.
  • 여러 단계로 테스트한다?
    • 모든 단위 테스트는 한 명령으로 돌려야 한다.
    • 작성 중에 지속적으로 테스트할 수 있도록 한다.
  • 너무 많은 인수
    • 함수 인수는 작을 수록 좋고, 없으면 더 좋다.
    • 인수 개수가 넷 이상은 무조건 피한다.
  • 출력 인수
  • 플래그 인수
    • Bool 타입 인수는 함수가 여러 기능을 수행한다는 명백한 근거다.
    • 플래그 인수는 피하고, 함수를 여러 개로 쪼갠다.
  • 죽은 함수
    • 아무도 호출하지 않은 함수는 제거한다.
  • 한 소스 파일에 여러 언어를 사용한다?
    • 혼란스럽고 조잡하다.
    • 소스 파일에서 언어 수와 범위를 최대한 줄인다.
  • 당연한 동작을 구현하지 않는다?
    • 최소 놀람의 법칙에 의거해, 함수느 클래스는 다른 프로그래머가 당연하게 여길 만한 동작이나 기능을 제공해야 한다.
  • 경계를 올바로 처리하지 않는다.
    • 모둔 경계 조건을 테스트하는 테스트 코드를 작성한다.
  • 중복
    • 중복을 피한다.
    • 중복을 발견하면 없애려고 노력한다.
  • 이름과 기능이 일치하는 함수
    • date에 5일을 더하는 건지, 5개월을 더하는 건지 알 수가 없다.

      let newDate = date.add(5)
    • date에 5일을 더하는 거라면 다음과 같이 수정할 수 있다.

      let newDate = date.daysLater(5)
  • 경계 조건을 캡슐화하라
    • 경계 조건은 한 곳에서 별도로 처리한다.

      if (level + 1 < tags.count) {
          nextLevel = level + 1
      }
      let nextLevel = level + 1
      if (nextLevel < tags.count) {
          self.nextLevel = nextLevel
      }
profile
👩‍💻

0개의 댓글