자바/클린 코드

클린코드 10장 - 클래스

끄적끄적 2022. 6. 7. 22:15

클래스 체계
클래스는 public static 변수 → private static 변수 → private 변수 순으로 배치. public 변수가 필요한 경우는 거의 없다.
변수 목록 다음에는 public 함수를 배치하며, private 함수는 자신을 호출하는 함수 직후에 넣는다.
즉, 추상화 단계가 순차적으로 내려가서, 신문 기사처럼 읽혀야 한다.

캡슐화
테스트 코드에서 private 변수나 함수에 접근하려고 하면 protected로 선언하는 방법도 있으나, 테스트 코드에서 private 변수나 함수에 접근해야 하는 경우는 별도 클래스로 빼는게 맞다.

클래스는 작아야 한다.
하나의 함수가 하나의 행동만을 해야 했다면, 하나의 클래스는 하나의 책임만을 맡아야 한다.
간결한 클래스 이름이 떠오르지 않는다면 필경 클래스 크기가 너무 커서 그렇다.

SRP(단일 책임 원칙, Single Responsibility Priciple)
클래스나 모듈을 변경할 이유는 단 하나뿐이어야 한다.
SRP원칙이 현장에서 잘 지켜지지 않는데, 그것은 '돌아가는 소프트웨어'에 초점을 맞추어 개발 한 이후에 '깨끗하고 체계적인 소프트웨어'로 관점을 전환하는 것을 빠트리기 때문이다. 동작을 구현한 이후에는 만능 클래스를 단일 책임 클래스로 분리해야 한다.
작은 클래스로 분리하면, 클래스를 이동하며 보기 번거롭다고 생각할 수 있지만 '도구상자를 큰 서럽에 모두 던져넣는 것 보다는 작은 서랍을 많이 두고 기능과 이름을 명확히 나누어서 넣는게 훨씬 낫다'
작은 클래스는 각자 맡은 책임이 하나이며, 변경할 이유도 하나다. 다른 작은 클래스와 협력해 시스템에 필요한 동작을 한다.

응집도
응집도가 높은 클래스는 클래스 메스드가 클래스 인스턴스 변수를 많이 사용한다.
모든 인스턴스 변수를 메서드마다 사용하는 클래스는 가장 응집도가 높지만 가능하지도 바람직하지도 않다.
그렇지만 응집도가 높은 클래스를 선호하며, 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다

응집도를 유지하면 작은 클래스 여럿이 나온다
큰 함수 일부를 작은 함수 하나로 빼내고 싶은데, 빼내려는 코드가 큰 함수에 정의된 변수 넷을 사용한다. 이때, 변수 네 개를 새 함수에 인수로 넘기기보다는 네 변수를 클래스 인스턴스 변수로 승격한다면 새 함수는 인수가 필요없다.
이렇게 하면 클래스가 응집력을 잃게 되는데, 몇몇 함수만 사용하는 인스턴스 변수가 늘어나면 독자적인 클래스로 분리하라. (클래스가 응집력을 잃는다면 별도 클래스로 쪼개라)

코드 개선 사례
정확한 동작을 검증하는 테스트 코드 작성
  한번에 하나씩 수 차례에 걸쳐 조금씩 코드 변경
→  코드를 변경할 때마다테스트수행해 동일하게 동작하는지 확인
→ 클래스를 체계적으로 정리해서 변경에 수반하는 위험을 낮추기


PrimePrinter : main함수로서 실행 환경을 책임진다. 호출방식이 달라지면 클래스도 바뀐다.

public class PrimePrinter {
	public static void main(String[] args) {
		final int NUMBER_OF_PRIMES = 1000;
		int[] primes = PrimeGenerator.generate(NUMBER_OF_PRIMES);
		
		final int ROWS_PER_PAGE = 50; 
		final int COLUMNS_PER_PAGE = 4; 
		RowColumnPagePrinter tablePrinter = 
			new RowColumnPagePrinter(ROWS_PER_PAGE, 
						COLUMNS_PER_PAGE, 
						"The First " + NUMBER_OF_PRIMES + " Prime Numbers");
		tablePrinter.print(primes); 
	}
}

RowColumnPagePrinter : 숫자 목록을 주어진 행과 열에 맞추어 페이지에 출력하는 책임. 출력 모양새를 바꾸려면 변경

public class RowColumnPagePrinter { 
	private int rowsPerPage;
	private int columnsPerPage; 
	private int numbersPerPage; 
	private String pageHeader; 
	private PrintStream printStream;
	
	public RowColumnPagePrinter(int rowsPerPage, int columnsPerPage, String pageHeader) { 
		this.rowsPerPage = rowsPerPage;
		this.columnsPerPage = columnsPerPage; 
		this.pageHeader = pageHeader;
		numbersPerPage = rowsPerPage * columnsPerPage; 
		printStream = System.out;
	}
	
	public void print(int data[]) { 
		int pageNumber = 1;
		for (int firstIndexOnPage = 0 ; 
			firstIndexOnPage < data.length ; 
			firstIndexOnPage += numbersPerPage) { 
			int lastIndexOnPage =  Math.min(firstIndexOnPage + numbersPerPage - 1, data.length - 1);
			printPageHeader(pageHeader, pageNumber); 
			printPage(firstIndexOnPage, lastIndexOnPage, data); 
			printStream.println("\f");
			pageNumber++;
		} 
	}
	
	private void printPage(int firstIndexOnPage, int lastIndexOnPage, int[] data) { 
		int firstIndexOfLastRowOnPage =
		firstIndexOnPage + rowsPerPage - 1;
		for (int firstIndexInRow = firstIndexOnPage ; 
			firstIndexInRow <= firstIndexOfLastRowOnPage ;
			firstIndexInRow++) { 
			printRow(firstIndexInRow, lastIndexOnPage, data); 
			printStream.println("");
		} 
	}
	
	private void printRow(int firstIndexInRow, int lastIndexOnPage, int[] data) {
		for (int column = 0; column < columnsPerPage; column++) {
			int index = firstIndexInRow + column * rowsPerPage; 
			if (index <= lastIndexOnPage)
				printStream.format("%10d", data[index]); 
		}
	}

	private void printPageHeader(String pageHeader, int pageNumber) {
		printStream.println(pageHeader + " --- Page " + pageNumber);
		printStream.println(""); 
	}
		
	public void setOutput(PrintStream printStream) { 
		this.printStream = printStream;
	} 
}

 

PrimeGenerator : 소수 목록을 생성하는 책임. 소수를 계산하는 알고리즘이 바뀌면 수정

public class PrimeGenerator {
	private static int[] primes;
	private static ArrayList<Integer> multiplesOfPrimeFactors;

	protected static int[] generate(int n) {
		primes = new int[n];
		multiplesOfPrimeFactors = new ArrayList<Integer>(); 
		set2AsFirstPrime(); 
		checkOddNumbersForSubsequentPrimes();
		return primes; 
	}

	private static void set2AsFirstPrime() { 
		primes[0] = 2; 
		multiplesOfPrimeFactors.add(2);
	}
	
	private static void checkOddNumbersForSubsequentPrimes() { 
		int primeIndex = 1;
		for (int candidate = 3 ; primeIndex < primes.length ; candidate += 2) { 
			if (isPrime(candidate))
				primes[primeIndex++] = candidate; 
		}
	}

	private static boolean isPrime(int candidate) {
		if (isLeastRelevantMultipleOfNextLargerPrimeFactor(candidate)) {
			multiplesOfPrimeFactors.add(candidate);
			return false; 
		}
		return isNotMultipleOfAnyPreviousPrimeFactor(candidate); 
	}

	private static boolean isLeastRelevantMultipleOfNextLargerPrimeFactor(int candidate) {
		int nextLargerPrimeFactor = primes[multiplesOfPrimeFactors.size()];
		int leastRelevantMultiple = nextLargerPrimeFactor * nextLargerPrimeFactor; 
		return candidate == leastRelevantMultiple;
	}
	
	private static boolean isNotMultipleOfAnyPreviousPrimeFactor(int candidate) {
		for (int n = 1; n < multiplesOfPrimeFactors.size(); n++) {
			if (isMultipleOfNthPrimeFactor(candidate, n)) 
				return false;
		}
		return true; 
	}
	
	private static boolean isMultipleOfNthPrimeFactor(int candidate, int n) {
		return candidate == smallestOddNthMultipleNotLessThanCandidate(candidate, n);
	}
	
	private static int smallestOddNthMultipleNotLessThanCandidate(int candidate, int n) {
		int multiple = multiplesOfPrimeFactors.get(n); 
		while (multiple < candidate)
			multiple += 2 * primes[n]; 
		multiplesOfPrimeFactors.set(n, multiple); 
		return multiple;
	} 
}

 

변경하기 쉬운 클래스
클래스 일부에서만 사용하는 private 메서드는 클래스를 분리할 신호가 된다. 공통으로 사용하는 private 매서드는 유틸리티성 클래스로 분리하고, 일부만 사용하는 private 메서드는 파생 클래스로 옮긴다.
새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직하다. 새 기능을 추가할 때 시스템을 확장(새로운 클래스 추가)할 뿐 기존 코드를 변경하지 않는 구조가 이상적이다.(Open-Closed Principle)

변경으로부터 격리
상세한 구현을 포함하는 구체적인 클래스는 테스트가 어렵고, 구현이 바뀌면 위험에 빠진다.
인터페이스와 추상클래스를 사용해 구현에 미치는 영향을 격리하고, Stub 클래스 등을 통해 Test Double형태로 테스트를 용이하게 해 준다.
이렇게 결합도를 낮추면 DIP(Depencency Inversion Principle)원칙을 만족해서 , 클래스가 상세한 구현이 아니라 추상화에 의존하게 된다.

반응형