본문 바로가기
컴퓨터

클린코드 - 클래스에 관하여

by dharana7723 2024. 2. 2.

 

그 이전까지에서는 깨끗한 표현력과 코드행, 블록을 작성하는 방법에 초점을 맞추었다면,

여기서는 더 높은 차원에 관련된 부분인 클래스에 대해 다루는 내용이다.

 

왜냐면 코드는 결국 행과 열로 구성되어 있지만 좀 더 높은 차원에서 연결과 응집성, 설계와 클린코드를 고려하지 않으면 깨끗한 코드를 얻기 어렵기 때문이다.

 

 

프로그램은 신문기사 처럼 읽히는 것이 좋다는 것은 앞에서 설명하였다.

클래스를 정의하는 표준 자바 관례에 따르면, 정적 상수, 정적 비공개 변수, 비공개 인스턴스 변수 등과 같은 변수목록에서 시작해 (공개 변수가 필요한 경우는 거의 없다.) 공개 함수, 비공개 함수(자신을 호출하는 공개 함수 직후에 넣는다.) 순으로 순차적으로 내려간다.

그래서 이와 같이 추상화 단계가 순차적으로 내려가기 때문에 프로그램은 신문 기사처럼 읽히게 된다.

 

 

변수와 유틸리티 함수는 가능한 공개하지 않는 것이 낫지만 반드시 숨겨야 한다는 법칙도 없다.

때로는 이들을 protected 로 선언해 테스트 코드에 접근을 허용하기도 하는데

같은 패키지 안에서 테스트 코드가 함수를 호출하거나 변수를 사용해야 한다면 그 함수나 변수를 protected로 선언하거나 패키지 전체로 공개한다.

그러나 그 전에 비공개 상태를 유지할 온갖 방법을 강구한다. 캡슐화를 풀어주는 결정은 언제나 최후의 수단이기 때문이다.

 

 

클래스를 만들때 가장 첫번째 규칙은 크기이다. 클래스는 함수와 마찬가지로, 작아야 한다.

설계할때는 함수던, 코드던, 클래스던, 작게가 기본 규칙인 것이다.

그렇다면 이는 얼마나 작아야 할까?

 

함수는 물리적인 행 수, 열수로 크기를 측정했으나 클래스는 클래스가 맡은 책임을 센다.

 

공개 메서드가 70개에 달하는 SuperDashBoardf라는 클래스가 있다고 가정하자.

이는 너무 많은 책임을 가지고 있다.

 

그러나 SuperDashBoard가 메서드 몇개만 포함한다면 좀더 바람직하지 않을까?

 

메서드 다섯개 정도이면 괜찮은 편일까?

그러나 변환된 SuperDashBoard조차 메서드 수가 작음에도 불구하고 책임이 너무 많다.

 

클래스 이름은 해당 클래스 책임을 기술해야하며 작명은 클래스 크기를 줄이는 첫번째 관문이다.

클래스 이름이 모호하거나 간결한 이름이 떠오르지 않는다면 클래스 크기가 너무 크거나 클래스의 책임이 너무 많아서이기 때문일 것이다.

 

예를 들어 클래스 이름에 Processor, Manager, Super와 같이 모호한 단어가 있다면 클래스에 여러 책임을 떠안겼다는 증거이다.

 

매니저, 프로세서는 어쩌다 본 것 같은데 가능하다면 나 또한 이를 분할 하거나 좀 더 줄여야 되겠다는 생각이 들었다.

 

이러한 클래스 설명은 만일(if), 그리고(and), 하며(or), 하지만(but)을 사용하지 않고서 25단어 내외로 가능해야 한다.

 

SuperDashBoard의 설명을 요약하자면 마지막으로 포커스를 얻었던 컴포넌트에 접근하는 방법을 제공하며, 버전과 필드 번호를 추적하는 메커니즘을 제공하는 것이다 인데, "하며" 라는 말이 들어간 순간  이미 책임이 너무 많다는 증거이다.

 

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

이는 책임이라는 개념을 정의하며 적절한 클래스 크기를 제시한다.

 

규모가 어느 정도에 이르는 시스템은 논리가 많고 복잡하며, 이런 복잡성을 다루려면 체계적인 정리가 필수이다.

그래야 개발자가 무엇이 어디에 있는지 쉽게 찾고 코드를 빠르게 이해할 수 있다.

그래야 변경과 유지보수가 쉬워지고 새로운 기능을 추가하기에도 용이해 질 수 있다.

 

이에 따라 큰 클래스 몇개가 아니라 응집도가 높은 작은 클래스 여럿으로 이루어진 시스템이 더 바람직하다고 말할 수 있을 것이다.

 

응집도가 높다는 것의 또다른 의미는 클래스는 인스턴스 변수 수가 작아야 한다는 것이다. 각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 하는데 일반적으로 메서드가 변수를 더 많이 사용할 수록 메서드와 클래스는 응집도가 더 높다.

모든 인스턴스 변수를 매서드마다 사용하는 클래스가 응집도가 가장 높겠으나 현실적으로 이것은 가능하지 않고 바람직하지도 않다.

 

그러나 응집도가 높은 코드일수록 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶이기 때문에 응집도가 높은 클래스는 바람직하다.

 

이를 이용해 함수는 작게, 매개변수 목록은 짧게라는 전략을 따르다 보면 때때로 몇몇 메서드 만이 사용하는 인스턴스 변수가 아주 많아지는데, 이는 대부분 이를 새로운 클래스로 쪼개야 한다는 신호이다.

그렇다면 이를 다시 응집도가 높은 변수와 메서드로 적절히 분리해 새로운 클래스 두세개로 적절하게 쪼개면 될 것이다.

 

응집도를 유지하고자 함수와 클래스를 리팩토링하다보면 작은 클래스 여럿이 나오게 하는 좋은 방법이 있다.

큰 함수를 작은 함수 여럿으로 나누고 싶은데 해당 함수가 큰 함수의 여러 변수를 사용할때, 인풋으로 굳이 넘기지 않고 클래스 인스턴스 변수로 승격해도 된다. 그런데 그러다보면 클래스가 응집력을 잃는다. 그러나 이런 것이 많다면, 클래스를 쪼개면된다!

클래스가 응집력을 잃는다면, 독자적인 클래스로 분리하면 된다.

 

 

또한 대다수 시스템은 변경이 지속적으로 발생하기 때문에 뭔가 변경할때마다 시스템을 수정하거나, 그에 따라 시스템에 의도적으로 동작하지 않을 위험이 뒤따른다.

깨끗한 시스템은 클래스를 체계적으로 정리해 이러한 변경에 수반되는 위험도를 낮춘다.

 

클린코드 책의 p.187의 sql 클래스를 수정하는 내용을 참조하자.

 

또한 상세한 구현에 의존하는 코드는 테스트가 어렵다 . 예를 들어 외부 거래소 api를 통해 값을 받아오고 계산하는 클래스가 있다 가정하자. 해당 테스트 코드는 시세 변화에 영향을 받는다. 5분 마다 값이 달라지는 API로 테스트 코드를 짜기란 쉽지 않다.

 

따라서 해당 클래스에서 해당 API를 직접 호출하는 대신 StockExchange라는 인터페이스를 생성하고 메서드 하나를 선언한다.

public interface StockExchange{

      Money currentPrice(String sysmbol);

}

 

이 인터페이스를 구현하는 TokyoStockExchange 클래스를 구현하고, Portfolio 클래스에서 생성자를 수정해 StockExchange 참조자를 인수로 받는다.

 

public Portfoli{

    private StockExchange exchange;

     public Portfolio(Stock Exchange exchange){

           this.exchange = exchange;

     }

}

 

이렇게 하여 StockExchange 인터페이스를 구현하며 TokyoStockExchange를 흉내내는 테스트용 클래스를 만들 수 있다.

해당 클래스는 고정된 주가를 반환한다.

 

위와 같은 테스트가 가능할 정도로 시스템의 결합도를 낮추면 유연성과 재사용성도 더 높아진다.

시스템 요소가 잘 격리되어 있으면 각 요소를 이해하기 더 쉬워진다.

 

이렇게 결합도를 최소로 줄이면 자연스럽게 또 다른 클래스 설계 원칙인 DIP (Dependency Inversion Principle)을 따르는 클래스가 나온다. 본질적으로 DIP는 클래스가 상세한 구현이 아닌 추상화에 의존해야 한다는 원칙이다.

 

 

'컴퓨터' 카테고리의 다른 글

클린코드 - 시스템  (0) 2024.02.02
클린 코드 - 단위테스트에 대하여  (0) 2024.02.02
프록시 의미  (0) 2021.11.16
docker entrypoint와 cmd 차이  (0) 2021.11.16
ngix 및 apache 사용 이유  (0) 2021.11.15