Intro
2021년 새해를 맞이한 첫 포스트다. 요 며칠 계속 클린코드에 대한 포스팅을 미뤘는데, 솔직히 말하면 12월 한달동안 조금 게으르고 노는 것에 좀 더 치중했던 것 같다. (*자기반성..) 그래서 스터디를 위해 책은 읽어도 블로그에 읽은 내용을 정리하는 시간을 계속 미뤄왔다.
다시 마음을 잡을 겸, 이번 주차에 대한 내용을 정리할 겸 다시 클린코드와 관련된 포스트를 진행하고자 한다. 10장 이전의 아직 올리지 못한 포스트는 최대한 빨리 정리해 올려야지... (게으른 자의 삶이란..)
Overview
10장 이전까지는 코드 행과 코드 블록, 함수에 초점을 맞춰 클린코드에 대한 설명이 되어있었다. 코드의 표현력과 함수에 대해서 깨끗한 코드를 유지하는 것도 중요하지만, 그것보다 조금 더 고차원인 클래스 레벨에서도 깨끗한 코드를 만들기 위해 신경쓰는 것은 중요하다. 본 챕터에서는 깨끗한 클래스를 다루는 방법에 대해서 설명한다. 본 챕터에서는 기본적인 OOP와 객체지향 5원칙(SOLID)에 대해 조금이라도 알고 있어야 이해할 수 있다. 또한, 블로그 주인의 생각은 기울어진 글씨체로 작성하였다.
클래스 체계
깨끗한 클래스를 작성하기에 앞서 클래스 체계에 대해서 알아보자.
표준 자바 관례에 따르면, 클래스 안에서의 순서는 아래와 같다.
- public static 상수
- static private 변수
- private 인스턴스 변수 (참고로, public 변수가 필요한 경우는 거의 없음)
- public 메소드
- private 메소드는 자신을 호출하는 public 메소드 직후에 위치
- 5장. 형식맞추기의 "신문기사 처럼 작성하라"에서 설명
public class Example {
public final static int MONDAY = 0; //public static 상수
private static int privateStaticNum = 0; //static private 변수
private int privateNum = 0; //private instance 변수
public void printEx() //public 메소드
private void makePrintForm() //private 메소드
}
변수와 유틸리티 함수는 가능한 private으로 선언하는 것이 좋지만, 무조건 숨겨야한다는 법칙이 있는 것도 아니다. 가끔 테스트를 위해 protected로 선언해 테스트 코드에 접근을 허용하기도 한다. 하지만 그 전에 공개하지 않을 방법을 충분히 생각하고, 답이 안나올 경우에만 캡슐화를 해제하자.
캡슐화
출처: https://webclub.tistory.com/156 [Web Club]
- 객체의 필드(속성), 메소드를 하나로 묶고, 실제 구현 내용을 외부에 감추는 것을 말한다.
- 외부 객체는 객체 내부의 구조를 얻지 못하며 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있다.
- 필드와 메소드를 캡슐화하여 보호하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하는데 있다.
- 자바 언어는 캡슐화된 멤버를 노출시킬 것인지 숨길 것인지를 결정하기 위해 접근 제한자(Access Modifier)를 사용한다.
클래스는 작아야 한다!
클래스를 만들 때 첫 번째 규칙은 "클래스는 작아야 한다"는 것이다.
두 번째 규칙도 "클래스는 작아야 한다"는 것이다.
그만큼 '작게'만드는 것이 가장 기본 규칙인데, 그렇다면 얼마나 작아야하는 것일까?
사실 정답은 없지만, 클래스의 크기의 척도를 본 책에서는 "클래스가 맡은 책임"를 척도로 잡았다.
//책에서 소개된 예제로, 70개 정도의 메소드를 가지고 있다.
public class SuperDashboard extends JFrame implements MetaDataUser {
public String getCustomizerLanguagePath()
public void setSystemConfigPath(String systemConfigPath)
public String getSystemConfigDocument()
public void setSystemConfigDocument(String systemConfigDocument)
...
}
위의 예제에서는 클래스가 70개 정도의 엄청난 양의 메소드를 가지고 있는데, 그 만큼 클래스가 맡은 책임의 크기도 굉장히 크다. 어찌보면 '만능 클래스'라고 부를 수 있지만 이런 책임이 많은 클래스는 유지보수 하기도 힘들고, 객체지향적인 관점에서 부합하지 않다고 생각한다.
그렇다면 아래와 같이 몇개의 메서드만 포함한 것은 괜찮을까?
public class SuperDashboard extends JFrame implements MetaDataUser {
public Component getLastFocusedComponent()
public void setLastFocused(Component lastFocused)
public int getMajorVersionNumber()
public int getMinorVersionNumber()
public int getBuildNumber()
}
메소드가 5개정도로 적고,, 괜찮아 보일 수 있지만 좋은 클래스가 아니다.
아래의 작고 깨끗한 클래스를 만들기 위한 조건들을 보며 얘기해보자.
1. 클래스의 이름은 해당 클래스의 책임을 기술해야한다.
여기서 또 네이밍의 중요성이 등장한다! 두둥
- 네이밍은 클래스 크기를 줄이는 첫번째 관문으로, 이름을 모호하지 않게 짓는 것이 중요하다.
- 이름이 모호하다면 클래스의 책임이 명확하지 않거나 너무 많다는 것
- Processor, Manager, Super 등과 같이 모호한 단어가 있다면 클래스가 여러 책임을 갖고 있다는 증거
- 클래스의 설명은 if, and, or, but을 사용하지 않고 25단어 내외로 가능해야한다.
위의 예제는 SuperDashboard로, 클래스가 정확히 어떤 책임을 갖고있는지 알기 어렵다.
먼저 Super라는 모호한 단어를 사용함으로써 정확히 어떤 책임을 맡고있는지 기술하지 않았다.
본 책에서는 이 클래스가 다음과 같은 역할을 한다고 한다.
"마지막으로 focus를 얻었던 컴포넌트에 접근하는 방법을 제공하고, 소프트웨어 버전과 빌드 번호를 추적하는 메커니즘을 제공"
언뜻 보기에도 SuperDashboard라는 이름을 통해 어떤 행동을 하는 클래스인지 알기는 굉장히 어렵다.
2. 클래스는 단일 책임 원칙(Single Responsibility Principle, SRP)를 따라야 한다.
단일책임의 원칙은 객체지향의 5원칙(SOLID)에서 S를 맡고있는 중요한 원칙이다.
"어떤 클래스를 변경하는 이유는 오직 하나뿐이어야 한다"라는 것이다. 단일 책임 원칙에 대한 이해가 부족하다면 이 포스트를 참고하길 바란다.
(스프링 입문을 위한 자바 객체지향 원리와 이해라는 책에서 나온 예제인데, 굉장히 이해하기 쉽게 잘 설명되어있다.)
SRP는 굉장히 객체지향 관점에서 중요한 개념이지만, 지켜지지 않는 개념이기도 하다.
책에서는 그 이유를 다음과 같이 설명한다.
- 대다수가 프로그램이 돌아가면 일이 끝났다고 여기는 것에 있다. 즉, '깨끗하고 체계적인 소프트웨어'라는 다음관신사로 전환하지 않는다.
- 많은 개발자는 자잘한 단일 책임 클래스가 많아지면 큰 그림을 이해하기 어렵다고 우려한다.
- 필자는 프로그램의 규모가 어느정도 커지면 논리도 많아지고 복잡해지지만, 이런 복잡한 형태를 다루기 위해서는 체계적인 정리가 중요하다고 말한다.
위에서 언급한 SuperDashboard의 경우 2가지 책임을 가지고 있다.
하나는 프로그램의 소프트웨어 버전 정보를 추적하고, 또 하나는 JFrame을 상속받아 스윙 컴포넌트를 관리한다.
언뜻봐도 이상한 것이 있다. 프로그램의 코드가 변경될 때마다 소프트웨어 버전이 바뀐다고 했을 때, 이 버전정보는 SuperDashboard에 국한된 동작은 아니라는 것이다. 이럴경우에 버전 정보를 다루는 메서드를 따로 빼서 Version이라는 독자적인 클래스를 만든다면, 프로그램을 관리하기 쉬워질 것이다. 이렇게 된다면, 기존의 2가지 책임을 갖고 있던 클래스가 1가지의 책임을 갖고 있는 클래스가 되고, 책임에 맞는 적절한 클래스 명을 지어주면 된다.
3. 클래스 인스턴스 변수의 개수가 작아야 한다. (응집도-Cohesion 관점)
"클래스 인스턴스 변수의 개수가 작아야한다"라는 의미는, "클래스의 응집도를 높혀라!"라는 것과 일맥상통하다. 객체지향 설계를 하다보면 좋은 프로그램의 조건으로 "응집도는 높이고, 결합도는 낮춘다!" 라는 말을 듣는 경우가 많다.
응집도
하나의 프로그램을 구성하는 각각의 모듈이 그 고유의 기능을 잘 처리할 수 있는지를 나타내는 정도 (한국정보통신기술협회 IT용어사전)
어렵게 느껴질 수 있는데, 본 책의 저자에 따르면 응집도가 높다는 것은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 것을 의미한다. "함수를 작게, 매개변수 목록은 적게"라는 전략을 따르다보면 가끔 일부만 사용하는 인스턴스 변수가 많아진다. 이럴경우 응집도를 높이기 위해 클래스를 쪼갤 수 있다.
쉽게 설명해 보자면, 클래스의 메소드들이 클래스의 인스턴스 변수를 하나 이상 사용하고 있는지 확인한다. 응집도를 더 높이기 위해서 일부의 메소드에서만 사용되는 인스턴스 변수가 있는지 확인하고, 만약 있다면 응집도가 있는 클래스를 응집도가 더 높은 2개이상의 다른 클래스들로 쪼갤 수 있다.
클래스가 응집력을 잃는다는 판단이 들면, 쪼개라!!!
변경하기 쉬운 클래스
대다수의 프로그램은 지속적으로 변경될 수 있다. 그 때마다 프로그램이 의도한대로 동작하지 않을 수 있다는 위험이 있다. 클래스를 체계적으로 정리해 변경할 때 동반되는 위험을 낮출 수 있도록 깨끗한 프로그램을 설계하는 것은 중요하다.
- 새 기능을 수정하거나 기존 기능을 변경할 때 코드를 최소한으로 건드리는 구조를 설계
- 이상적인 프로그램이라면 새 기능을 추가할 때 시스템을 확장할 뿐 기존 코드를 변경하지 않는다
책에서 언급한 예시와 내용을 보면, 결국 객체지향 5원칙을 잘 지키는 코드를 설계하면 변경하기 쉬운 클래스가 된다는 얘기를 담고 있는 것 같다. 그리고 아래의 변경으로 부터의 격리 파트를 보자면, 추상클래스/인터페이스를 잘 활용함으로써 변경하기 쉬운 클래스를 만들자는 내용인 것 같다.
변경으로부터 격리 (결합도 관점)
요구사항이 변하면, 코드도 변하기 쉽다.
인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 최대한 격리하자.
- 결합도를 낮추면 유연성과 재사용성을 높일 수 있다.
- 결합도가 낮다는 의미는 각 시스템 요소가 다른 요소와 변경으로부터 잘 격리되어 있다는 뜻
- 결합도를 낮추면 의존 역전의 원칙(Dependency Inversion Principle, DIP)를 따를 수 있다
- DIP에 대한 설명은 이 포스트를 참고하자. (위의 SRP와 동일하게 스프링 입문을 위한 자바 객체지향 원리와 이해라는 책에서 나온 예제인데, 굉장히 이해하기 쉽게 잘 설명되어있다.)
Outro
전체적으로 객체지향 5원칙 관점에서 깨끗한 클래스를 설계하는 법에 대한 내용이 서술되어있었다. OOP에 대한 공부를 입사하고 자바를 공부하며 처음 이해했는데, 당시 조금 이해가 안갔던 철학들이 이 책을 보며 조금 이해가 되었다. 이번달 중순까지 코드프리징 기간인데, 최근에 진행한 프로젝트에서 리팩토링할 수 있는 부분들을 조금씩 개선해봐야겠다.
'개발 > Clean Code' 카테고리의 다른 글
[오브젝트] 9장 유연한 설계 (0) | 2021.06.03 |
---|---|
[Clean Code] 15장. JUnit 들여다보기 (0) | 2021.03.08 |
[Clean Code] 5장. 형식맞추기 (0) | 2020.12.01 |
[Clean Code] 4장. 주석(comments) (0) | 2020.11.24 |
Clean Code에 대하여 (0) | 2020.11.23 |
댓글