책 읽기

[Clean Code/클린 코드] - 함수

지팡구 2023. 5. 29. 19:13

3장 함수 chapter에서 가장 중요한 내용은

 

작게 만들어라, 그리고 한 가지만 해라" 입니다

 

1. 작게 만들어라, 그리고 한 가지만 해라

public static String renderPageWithSetupsAndTearDowns(
PageData pageData, boolean isSuite) throws Excetpion{

boolean isTestPage = pageData.hasAttribute("test");
	if(isTestPage){
		WikiPAge testPage = pageData.getWikiPage();
		StringBuffer newPageContent = new StringBuffer();
		includeSetupPages(testPage, newPageContent, isSuite);
		newPageCOntent.append(PageData.getContent());
		includeTeardownPages(testPage, newPageContent, isSuite);
		pageData.setContent(newPageContent.toString());
	}
	return pageData.getHtml();
}

 

책에 기입된 리팩토링 v1된 코드입니다. ( 기존에 코드가 너무 길어서 ... 리팩토링 버전만 가지고 왔습니다 )

간략하게 함수를 설명하면 설정(setUp)페이지와 해제(tearDown)페이지를 테스트 페이지에 넣은 후 해당 테스트 페이지를 HTML로 렌더링 한다는 사실을 짐작할 수 있습니다.

 

책에서 예시로 제시해준 코드를 살펴보면 이 전 코드에 비해 조금 리팩토링 한 버전이지만 매우 지저분하게 느집니다.

 

아직 if 문에서 많은 작업을 진행하고 있으며, 비슷한 작업을 진행하고 있습니다.

 

그래서 if 문을 다시 리팩토링을 진행할 수 있는데 리팩토링을 진행하면 다음과 같습니다.

 

if(isTestPage(pageData))
	includeSetUpAndTeardownPages(PageData, isSuite);
return pageData.getHtml();

책에서 말하길 최대한 작게 만들고, if, else, while 등에 들어가는블록은 1~2 줄이어야 한다고 말하고 있습니다.그러나 이를 지키기 어려울 수 있음을 알고 있습니다.

 

하지만 좋은 코드를 위해선  최소한 단위를 작게 나눠서 작게 만드는 것이 중요하고, 한 함수는 하나의 역할만 하도록 만들어야 한다라고 말하고 있습니다.( 해당 chapter를 읽으면서 단일 책임 원칙 (Single Responsibility Principle )이 생각났다. )

 

무턱대고 함수를 쪼개는 것이 아닌, 함수 내부의 추상화 수준은 같아야 합니다. 그 이유는 한 함수 내에 추상화 수준이 섞여 있으면 코드를 읽는 사람에게 혼동을 줄 수 있기 때문입니다. 이를 해결하기 위해 내려가기 규칙 을 적용함을 권장하고 있습니다.

 

**여기서 내려가기 규칙이란?**

코드가 위에서 아래로 읽히는 것을 의미 하며, 한 함수 다음 추상화 수준이 한 단계 낮은 함수가 와야 함을 이야기 합니다.
( 한줄 요약 : 짧으면서도 한 가지 일만 하는 함수가 일 잘러! )

 

 

여기서 추상화 수준이란?

책을 읽으며 문듯 궁금했다. 여기서 말하는 추상화 수준은 무엇을 의미하는 것일까? ” 해당 코드를 읽으면서 파악할 수 있는 정보의 수준 ”

3장 함수에서 처음 소개한 코드의 if문 을 추상화하여 한 줄의 코드로 변경했다.

큰 개념을 분해해서 N개의 기능으로 만든 것이고, 이 N개의 기능을 수행하는 한 줄의 코드로 만들었다.

결국, 여기서 N개가 하나의 추상화 수준이 되는 것이다.

 

예시 ( 책 )

높은 추상화 수준 : PageData.getHtml() 에서 getHtml() 함수는 Html을 가지고 오는 것은 알겠지만,
어떤 것이랑 연관되어 있는지 알 수 없기에 추상화 수준이 높다고 할 수 있음.

( 추상화 수준이 높다? -> 더 쪼갤 수 있거나 endpoint가 아니다 ?)

보통 추상화 수준 : String pagePathName - PathParser.render(pagepath); 에서  PathParser 객체에서
render 함수를 이용해 String type의 객체인(pagePathName)을 가지고 올 수있다. 

낮은 추상화 수준 : .append("\n")는 바로 어떤 의미인지 알 수 있기에 추상화 수준이 낮다.

 

하지만 추상화 수준을 낮추거나, 함수, 구문을 작게 만드는 것에 예외는 존재합니다. ( 예시 : Switch 문)

Switch 문은 본질적으로 여러 분기 처리가 들어가기 위해 탄생했기에 처리에 대한 문제를 해결할 순 없다. 그러나 여러 개념을 적용해서 조금은 간극을 극복할 수 있습니다. ( 다형성 이용 )

다형성(Polymorphism)이란?

“ 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질 “

 

<  예시 >
public Money calculatePay(Employee employee)
	throws InvalidEmployeeType {
		switch(employee.type){
			case COMMISSIONED:
				return calculateCommissionedPay(employee);
			case HOURLY:
				return calculateHourlyPay(employee);
			case SALARIED:
				return calculateSalariedPay(employee);
			default:
				throw new InvalidEmployeeType(employee.type);  
	}
}

 

< 문제점 >

1. 긴 함수
2. 추가되는 직원 유형에 따라 추가되는 코드 ( 한 가지 작업만 수행하지 않음 )
3. 단일 책임 원칙( SRP ) 위반 -> 코드를 변경할 이유가 어렷이기에 -> 단일 책임 원칙은 말 그대로 하나의 책임(기능) 만 하도록 해야함 
4. 개방 폐쇄 원칙( OPC ) 위반 -> 추가되는 직원 유형에 따라 변경되는 코드 -> 개방 폐쇄 원칙은 확장에는 열려있고, 변경에는 닫혀 있어야 한다는 의미

 

< 해결 >  추상 팩토리 패턴을 이용한 문제 해결 

public abstract class Employee {
	public abstract boolean isPayday();
	public abstract Money calculatePay();
	public abstract void deliverPay(Money pay);
}

//

public interface EmployeeFactory{
	public Employee makeEmployee(EmployeeRecord r) throws InvaludEmployeeType;
}

//

public class EmployeeFactoryImpl implements EmployeeFactory {
	public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
		switch (r.type) {
			case COMISSIONED:
				return new ComissionedEmployee(r);
			case HOURLY:
				return new HourlyEmployee(r);
			case SALARIED:
				return SalariedEmployee(r);
			default:
				throw new InvalidEmployeeType(e.type);}

 

추상 팩토리 패턴이란?

“ 관련있는 여러 개의 인스턴스를 만들어주는 인터페이스로 분리하여 생성하는 패턴 “

 

< 참고 레퍼런스 >

https://github.com/Nelmm/DesignPattern/blob/main/%EA%B0%9D%EC%B2%B4%EC%83%9D%EC%84%B1/2%EC%A3%BC%EC%B0%A8-%EC%B6%94%EC%83%81%ED%8C%A9%ED%86%A0%EB%A6%AC/README.md

 

GitHub - Nelmm/DesignPattern: DesignPattern들을 할짝

DesignPattern들을 할짝. Contribute to Nelmm/DesignPattern development by creating an account on GitHub.

github.com

2. 서술적인 이름을 사용해라, 그리고 부수 효과(Side Effect)를 일으키지 마라

  • 서술적인 이름을 애플리케이션 전체에 일관성 있게 사용해서 메서드가 의미하는 바를 분명하게 나타내는 것은 중요하며, 하나의 함수가 여러 기능을 하게 되면 Side Effect를 일으킬 가능성이 커진다.