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);}
추상 팩토리 패턴이란?
“ 관련있는 여러 개의 인스턴스를 만들어주는 인터페이스로 분리하여 생성하는 패턴 “
< 참고 레퍼런스 >
2. 서술적인 이름을 사용해라, 그리고 부수 효과(Side Effect)를 일으키지 마라
- 서술적인 이름을 애플리케이션 전체에 일관성 있게 사용해서 메서드가 의미하는 바를 분명하게 나타내는 것은 중요하며, 하나의 함수가 여러 기능을 하게 되면 Side Effect를 일으킬 가능성이 커진다.
'책 읽기' 카테고리의 다른 글
[Clean Code/클린 코드] - 객체와 자료구조 (0) | 2023.06.18 |
---|---|
[Clean Code/클린 코드] - 형식 맞추기 (1) | 2023.06.04 |
[Clean Code/클린 코드] - 주석 (0) | 2023.06.04 |
[Clean Code/클린 코드] - 의미있는 이름 (0) | 2023.05.29 |
[자바 웹 프로그래밍 - Next Step] - 테스트와 리팩토링 (1) | 2022.12.27 |