[개발 도서] Clean Code :: 10장 - 클래스

Jihyoung·2021년 7월 26일
0

Clean Code

목록 보기
5/11
post-thumbnail

📕 클래스 체계

자바 표준 클래스 정의에 따른 변수 순서
: 추상화 단계가 순차적으로 내려가게 짜여진다

  1. 정적 공개 상수 // public static
  2. 정적 비공개 변수 // static private
  3. 비공개 인스턴스 변수 (공개 변수 : 필요한 경우 거의 x)
  4. 공개 함수
  5. 비공개 함수 (자신을 호출하는 공개 함수 직후에)

📍 캡슐화

  • 외부에서 특정 속성이나 메서드를 시용자가 사용할 수 없도록하여 객체의 손상을 막는다.
  • 객체의 필드, 메소드를 하나로 묶고 실제 구현 내용을 외부에 감춘다.
  • 외부 객체는 객체 내부의 구조를 얻지 못하며 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있다.
  • 자바 언어는 캡슐화된 멤버를 노출시킬 것인지 숨길 것인지를 결정하기 위해 접근 제한자(Access Modifier : public, private, protected)를 사용한다.

변수와 유틸리티 함수는 가능한 공개하지 않는 편(protected로 선언)이 낫지만 반드시 숨겨야 한다는 법칙도 없다. 때때로 필요하다면 캡슐화를 풀기도 한다. 하지만 그 전에 비공개 상태를 유지할수 있는 방법을 찾고, 캡슐화를 푸는 것은 최후의 수단으로 한다.


📗 클래스는 작아야 한다

클래스를 만들 때 첫 번째 규칙은 '크기' 다. 클래스는 작아야 한다.

클래스의 크기는 '맡은 책임의 수' 를 기준으로 측정한다. 메서드의 개수와는 관계가 없다.

💻 너무 많은 책임을 가진 클래스

모든 메서드를 담아 만능 클래스라고 할 수 있지만, 이렇게 한 클래스에 책임이 많을 경우 유지보수에 어려움을 겪는다.

public class SuperDashboard extends JFrame implements MetaDataUser {
    public String getCustomizerLanguagePath()
    public void setSystemConfigPath(String systemConfigPath)
    public String getSystemConfigDocument()
    public Properties getProps()
    public String getUserHome()
    public String getBaseDir()
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber()
    // 외 70개의 메서드
}
💻 5개의 메서드를 가진 클래스

이렇게 적은 양의 메서드만 포함하더라도, 여러개의 책임을 갖고 있기 때문에 좋지 못하다.
메서드의 수와 책임은 관계가 없다.

public class SuperDashboard extends JFrame implements MetaDataUser {
    public Component getLastFocusedComponent()
    public void setLastFocused(Component lastFocused)
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber() 
}
  • 클래스 이름은 해당 클래스 책임을 기술해야 한다.
    • 간결한 이름이 없다면 클래스가 너무 큼을 의미한다.
    • 클래스 이름에 모호한 단어 Manager, Super등이 들어간다면 클래스가 너무 많은 책임을 가지고 있음을 의미한다.
  • 클래스 설명은 만일, 그리고, -하며, 하지만을 사용하지 않고서 35단어 내외로 가능해야 한다.

📍 단일 책임 원칙

  • 클래스나 모듈을 변경할 이유가 단 하나뿐이어야 한다는 원칙이다.
  • 클래스는 단 하나의 책임만을 가져야 한다.
  • 책임 == 변경할 이유

위의 5개의 메소드를 가진 클래스를 보면 Super라는 단어를 클래스 이름으로 하여 어떤 책임을 맡고 있는지 알기 어렵다. 메소드를 보면 하나는 프로그램의 소프트웨어 버전 정보를 추적하고, 또 하나는 JFrame을 상속받아 스윙 컴포넌트를 관리한다.

-> 프로그램의 코드가 변경될 때마다 소프트웨어 버전이 바뀐다고 했을 때, 이 버전정보는 SuperDashboard에 국한된 동작은 아니라는 것이다.

💻 단일 책임 클래스

버전 정보를 다루는 메서드 추출 : 재사용성 증가

public class Version {
    public int getMajorVersionNumber()
    public int getMinorVersionNumber()
    public int getBuildNumber()
}

이렇게 변경할 이유를 파악하게 되면 추상화가 더 쉬워진다.

큰 클래스 몇 개 보다 작은 클래스 여럿인 시스템이 더 바람직하다.
작은 클래스는 맡은 책임이 하나며, 변경할 이유가 하나며, 다른 작은 클래스들과 협력해 시스템을 동작한다.


📍 응집도

* 응집도 : 하나의 프로그램을 구성하는 각각의 모듈이 그 고유의 기능을 잘 처리할 수 있는지를 나타내는 정도
  • 클래스는 인스턴스 변수 수가 작아야 한다.
  • 메서드가 변수를 더 많이 사용할수록 메서드와 클래스는 응집도가 더 높다.
  • 응집도가 높다 : 클래스에 속한 메소드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미이다.

응집도가 높아질수 있도록 변수와 메소드를 적절하게 분리하여 새로운 클래스로 쪼개준다.

💻 응집도가 높은 클래스
  • 클래스의 메소드들이 클래스의 인스턴스 변수를 하나 이상 사용하고 있는지 확인
  • 응집도를 더 높이기 위해서 일부의 메소드에서만 사용되는 인스턴스 변수가 있는지 확인
    • 응집도가 있는 클래스를 응집도가 더 높은 2개이상의 다른 클래스들로 쪼갤 수 있다.
public class Stack {
    private int topOfStack = 0; // 인스턴스 변수
    List<Integer> elements = new LinkedList<Integer>();
    public int size() { 
        return topOfStack; // 인스턴스 변수 사용
    }
    public void push(int element) { 
        topOfStack++; // 인스턴스 변수 사용
        elements.add(element);
    }
    public int pop() throws PoppedWhenEmpty { 
        if (topOfStack == 0) // 인스턴스 변수 사용
            throw new PoppedWhenEmpty();
        int element = elements.get(--topOfStack); 
        elements.remove(topOfStack);
        return element;
    }
}

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

  • 큰 함수를 작은 함수로 쪼갤 때 필요한 변수들을 클래스 인스턴스로 바꾸면 함수를 쪼개기 쉬워진다. 하지만 그만큼 한 함수에 몇몇 변수들만 사용하기 떄문에 클래스가 응집도가 낮아진다.

  • 응집도가 낮아지면 한 클래스가 아니라 여러 클래스로 쪼갠다.

클래스를 쪼개게 되면, 같은 동작을 하더라도 아래와 같은 이유로 프로그램의 길이가 길어질 수 있다.

  • 더 길고 서술적인 변수 이름
  • 함수 선언과 클래스 선언으로 코드를 설명
  • 가독성 증가를 위한 공백 추가, 형식 맞춤

📙 변경하기 쉬운 클래스

  • 클래스를 체계적으로 정리하여 변경에 따르는 위험을 낮춘다.
    -> 단일 책임 원칙과, 개방-폐쇄의 원칙을 만족할 수 있도록 한다.
  • 새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 좋다.

📍 변경으로부터 격리

요구사항이 변하면 코드가 변하기 쉽다. 그래서 인터페이스추상 클래스를 사용해 구현이 미치는 영향을 격리한다. 결합도를 낮춰서 유연성과 재사용성을 높일 수 있다.

Porfolio 클래스
: 외부 TokyoStockExchange API를 사용해 값을 계산, 5분마다 변하는 값 때문에 test 코드 짜는데 어려움
public insterface StockExchange {
	Money currentPrice(String symbol);
}
public Portfolio {
	private StockExchange exchange;
	public Portfolio(StockExchange exchange) {
		this.exchange = exchange;
	}
	// ...
}
public class PortfolioTest {
	private FixedStockExchangeStub exchange;
	private Portfolio portfolio;
	@Before
	protected void setUp() throws Exception {
		exchange = new FixedStockExchangeStub();
		exchange.fix("MSFT", 100);
		portfolio = new Portfolio(exchange);
	}
	@Test
	public void GivenFiveMSFTTotalShouldBe500() throws Exception {
		portfolio.add(5, "MSFT");
		Assert.assertEquals(500, portfolio.value());
	}
}

낮은 시스템 결합도

  • 유연성, 재사용성 증가
  • 시스템 요소가 다른 시스템 요소로부터, 변경으로부터 잘 격리되어있다는 의미
  • 요소를 이해하기 더 쉬움
  • DIP 원칙에 부합하는 클래스 생성 가능

추상 클래스와 인터페이스를 잘 활용하여 변경하기 쉬운 클래스를 만들어라.


* 의존 역전 원칙(DIP) : 의존 관계를 맺을 때, 변화하기 쉬운것 보단 변화하기 어려운 것에 의존해야 한다는 원칙
* 개방-폐쇄의 원칙(OCP) : 확장에는 개방적이고 수정에 폐쇄적이어야 한다는 원칙

🧩 더 공부할 부분

📚 Reference

profile
로그를 생활화

0개의 댓글