📖 해당 글은 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 |