# Reading/--- 개발서적

[Clean Code] 10장. 클래스

jiniz.ll 2022. 3. 9. 15:31

📖 해당 글은 Clean Code(클린 코드) 책을 읽고 정리한 글입니다.

 

클래스 체계

  • 클래스를 정의하는 표준 자바 관례에 따르면, 가장 먼저 변수 목록이 나온다.
  • 정적 공개(static public) 상수 정적 비공개(static private) 변수 비공개 인스턴스 변수
  • 공개 변수가 필요한 경우는 거의 없다.

 

  • 변수 목록 다음에는 공개 함수가 나온다.
  • 비공개 함수는 자신을 호출하는 공개 함수 직후에 넣는다.
  • 즉, 추상화 단계가 순차적으로 내려가며 작성된다.

 

캡슐화

  • 변수나 유틸리티 함수는 가능한 공개하지 않는 것이 좋지만 때로는 protected 로 선언해서 테스트 코드에 접근을 허용하기도 한다.
  • 하지만 캡슐화를 풀어주기 전에 가능한 비공개 상태를 유지할 방법을 찾아봐야 한다.

 

클래스는 작아야 한다

  • 클래스를 만들 때 가장 중요한 것은 함수와 마찬가지로 ‘작게’ 만들어야 한다는 것이다.
  • 그렇다면 클래스의 ‘작게’의 기준이란 무엇인가. 바로 클래스가 맡은 책임의 개수이다.

 

  • 클래스에 메서드 수가 단순히 많거나 적은 것이 중요한 것이 아니다. 메서드 수가 적더라도 책임이 많으면 안 된다.
  • 클래스의 크기를 줄이는 첫 번째 방법은 클래스 이름을 짓는 것인데, 클래스 이름에는 클래스의 책임을 기술해야 한다.
  • 만약 이름이 간결하지 않거나 이름이 모호하다면 클래스의 책임이 많기 때문이다.
  • 예를 들어, 클래스 이름에 Processor, Manager, Super 등과 같은 모호한 단어를 넣어서는 안 된다.
  • 또한 클래스 설명은 만일("if"), 그리고("and"), -(하)며("or"), 하지만("but") 을 사용하지 않고서 25단어 내외로 가능해야 한다.

 

🙂 지금 내가 만드는 프로젝트 중 하나의 클래스 명에 벌써 Manager 가 들어있다 하하하하

 

단일 책임 원칙 Single Responsibility Principle, SRP

: 클래스나 모듈을 변경할 이유(책임)가 하나, 단 하나뿐이어야 한다는 원칙이다.

책임, 즉 변경할 이유를 파악하다 보면 코드를 추상화하기 쉬워진다.

 

큰 클래스 몇 개가 아니라 작은 클래스 여럿으로 이뤄진 시스템이 더 바람직하다. 작은 클래스는 각자 맡은 책임이 하나며, 변경할 이유가 하나며, 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행한다.

 

사실 아직 이 ‘책임’ 이라던지 ‘변경할 이유’ 라는 것이 나에겐 조금 추상적이다. 정확히 어떤 포인트를 의미하는 것인지 잘 모르겠다. 일단은 내가 만든 클래스를 설명해보는 것을 시도해봐야겠다.

 

응집도 Cohesion

  • 클래스는 인스턴스 변수 수가 적어야 하고, 각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 한다.
  • 일반적으로 메서드가 변수를 더 많이 사용할 수록 메서드와 클래스의 응집도가 더 높다고 한다.
  • 모든 인스턴스 변수를 메서드마다 사용하는 클래스는 응집도가 가장 높다고 할 수 있다.

 

  • 응집도가 높다는 것은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미이므로 응집도가 높은 클래스를 선호한다.

 

  • 하지만 ‘함수를 작게, 매개변수 목록을 짧게’ 라는 전략을 따르다 보면 일부 메서드만 사용하는 인스턴수 변수가 생긴다.

해당 메서드와 인스턴스 변수의 응집도가 높다는 것이므로 새로운 클래스로 쪼개야 한다.

이런 변수와 메서드를 적절히 분리하면 쪼개진 클래스들의 응집도가 높아질 수 있다.

 

응집도를 유지하면 작은 클래스 여럿이 나온다

  • 큰 함수를 작은 함수로 나누기만 해도 클래스 수가 많아진다.
  • 예로, 변수가 많은 큰 함수가 있는데 일부를 작은 함수 하나로 빼내려고 한다. 그런데 빼내려는 코드가 큰 함수에 정의된 변수 4개를 사용한다면 이 변수들을 새 함수에 인수로 넘겨야 할까?

네 변수를 클래스 인스턴스 변수로 만든다면 새 함수는 인수를 넘겨 받을 필요가 없이 쉽게 함수를 쪼갤 수 있다.

하지만, 이렇게 하면 몇몇 함수만 사용하는 인스턴스 변수가 늘어나서 클래스가 응집력을 잃는다.

아, 몇몇 함수가 몇몇 변수만 사용한다면! 독자적인 클래스로 분리하면 된다.

 

  • 목록 10-5(p. 179) 는 함수가 하나뿐인 프로그램인데 사실 무슨 말인지 이해도 안간다,,
  • 이 프로그램을 목록 10-6 ~ 10-7(p. 181~184) 에 걸쳐 작은 함수와 클래스로 나누고 리팩터링하였는데 대충 훑어만 봐도 무슨 일을 하는 프로그램인지 이해하기 쉽다.
  • 이렇게 프로그램을 리팩터링하면서 1. 더 길고 서술적인 변수명을 사용했으며, 2. 코드에 주석을 추가하는 수단으로 함수 선언과 클래스 선언을 활용하였고, 3. 공백을 추가하고 형식을 맞추어 가독성을 높이면 프로그램이 길어졌다.

 

  • 리팩터링된 코드를 살펴보면 이해도 쉽고 변경이 잘 되었다고 느끼지만 아마도 막상 직접 변경하려고 시도한다면 한 번에 이렇게 바꿀수는 없을 것이다. 저자는 먼저 원래 프로그램의 정확한 동작을 검증하기 위한 테스트 슈트를 작성하고, 한 번에 하나씩 수 차례에 걸쳐 코드를 변경했다고 한다.
  • 이 코드를 리팩터링하기 위해서 원래 코드 각각이 하는 역할, 책임이 무엇인지 파악하여 코드를 분리하고 분리된 함수를 살펴보며 응집도를 체크한 뒤 각각의 클래스로 분리하지 않았을까 싶다. 

 

변경하기 쉬운 클래스

  • 대다수의 시스템은 지속적인 변경이 발생할 수 밖에 없다.
  • 앞선 내용들과 마찬가지로 변경이 가해질 때마다 코드가 망가질 위험이 존재한다.
  • 역시나 깨끗한 클래스, 잘 정리된 클래스는 변경에 수반되는 위험을 낮춘다.

 

  • 목록 10-9(p. 186)은 언젠가 변경이 필요할 수 있는 잠재적인 위험을 갖고 있는 코드이다.
  • 코드를 변경할 이유 또한 두 가지가 있다. — SRP 를 위반한다.

(1. 새로운 SQL 문을 지원해야할 때, 2. 기존 SQL 문을 수정해야 할 때)

  • 또한 클래스 일부에서만 사용되는 비공개 메서드가 존재한다.

— 코드를 개선해야 할 잠재적 여지 또한 갖고 있다.

 

  • 목록 10-10(p. 187)은 목록 10-9에 있는 공개 인터페이스를 각각 Sql 클래스에서 파생하는 클래스로 만들었으며, 특정 파생 클래스와 관련된 비공개 메서드는 해당 파생 클래스로 이동하였다. 모든 파생 클래스가 공통으로 사용하는 비공개 메서드는 유틸리티 클래스를 만들어 넣었다.
  • 이 코드는 클래스가 작게 분리되어 이전 코드에 비해 매우 단순해졌다.
  • 새로운 sql 문을 추가할 때는 기존 클래스를 변경할 필요없이 다른 sql 문과 마찬가지로 파생 클래스를 만들면 되기 때문에 다른 코드가 망가질 위험이 없어졌다.
  • 기존 sql 문도 마찬가지로 자신의 클래스만 수정하면 되기 때문에 다른 코드의 변경의 위험이 줄어들었다.
  • 목록 10-10은 SRP를 지원하며, OCP(Open-Closed Principle) 또한 지원한다.
OCP. Open-Closed Principle
클래스는 확장에 개방적이고 수정에 폐쇄적이어야 한다는 원칙

새 기능(새 sql 문)을 추가할 때 파생 클래스를 생성하는 방식을 사용하므로 확장에 개방적이며, 다른 클래스는 건드릴 필요가 없게 되므로 수정에 폐쇄적이다. 단지 새로운 sql 문 클래스를 추가하기만 하면 된다.

 

새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직하다. 이상적인 시스템이라면 새 기능을 추가할 때 시스템을 확장할 뿐 기존 코드를 변경하지는 않는다.

 

변경으로부터 격리

  • 상세한 구현에 의존하는 코드는 테스트가 어렵다.
  • 여기서 예시로 Portfolio 클래스는 외부 TokyoStockExchangeAPI 를 사용해 포트폴리오 값을 계산한다고 한다. 그런데 시세는 계속 변화하기 때문에 테스트를 하기 어렵다.
  • 따라서 이 때 테스트용 클래스를 만들어 StockExchange 인터페이스를 구현하게 한다. 이 테스트 클래스는 고정된 값을 반환한다.

 

시스템의 결합도를 낮추면 유연성과 재사용성도 더욱 높아진다. 결합도가 낮다는 소리는 각 시스템 요소가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다는 의미다. 시스템 요소가 서로 잘 격리되어 있으면 각 요소를 이해하기도 더 쉬워진다.

 

이렇게 결합도를 줄이면 자연스럽게 DIP(Dependency Inversion Principle)를 따르는 클래스가 나온다.

 

DIP. Dependency Inversion Principle
클래스는 상세한 구현이 아니라 추상화에 의존해야 한다는 원칙

 

 

이전 프로젝트를 할 때 이와 같은 방식의 테스트를 진행해본 적이 있다. 우리는 네트워킹 자체나 실제 데이터베이스를 테스트 할 수 없기(혹은 힘들기) 때문에 관련 프로토콜(자바에서 인터페이스와 유사?동일?한 개념인 것 같다)을 만들고 테스트용 클래스를 만들어 테스트를 진행했었다.  

이와 관련해서 면접 질문도 받아본적이 있는데 이런 형태의 테스트를 뭐라고 하는지 아냐고 물어보셨었다. 하지만 그와 관련된 용어는 여전히 모르겠다 😅 뭔가 지칭하는 용어가 있는건가…

 


► 이전 글 : Clean Code 9장. 단위 테스

► 다음 글 :

'# Reading > --- 개발서적' 카테고리의 다른 글

[Clean Code] 9장. 단위 테스트  (0) 2022.03.05
[Clean Code] 6장. 객체와 자료 구조  (0) 2022.03.01
[Clean Code] 5장. 형식 맞추기  (0) 2022.02.28
[Clean Code] 4장. 주석  (0) 2022.02.25
[Clean Code] 3장. 함수  (0) 2022.02.23