클린코드 핵심 정리 (SOLID, 3장 함수)
들어가기
SOLID 란?
클린코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 제시한 내용을 포스팅하도록 하겠다.
단일 책임 원칙 SRP
SRP 란?
한 클래스는 하나의 책임만 가져야 한다
- 클래스는 하나의 기능만 가지며, 어떤 변화에 의해 클래스를 변경
해야 하는 이유는 오직 하나뿐이어야 한다.
- SRP 책임이 분명해지기 때문에, 변경에 의한 연쇄작용에서 자유
로워 질 수 있다.
- 가독성 향상과 유지 보수가 용이해진다.
- 실전에서는 쉽지 않지만 늘 상기 해야 한다!
예시
SOLID 란?
클린코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 제시한 내용을 포스팅하도록 하겠다.
SRP 란?
한 클래스는 하나의 책임만 가져야 한다
- 클래스는 하나의 기능만 가지며, 어떤 변화에 의해 클래스를 변경
해야 하는 이유는 오직 하나뿐이어야 한다.- SRP 책임이 분명해지기 때문에, 변경에 의한 연쇄작용에서 자유
로워 질 수 있다.- 가독성 향상과 유지 보수가 용이해진다.
- 실전에서는 쉽지 않지만 늘 상기 해야 한다!
예시
설명
왼쪽의 로봇을 보면 4개의 기능을 수행한다. 로봇을 클래스로 보면 하나의 클래스에서 여러 책임을 가지고 있다는 의미이다. 이를 해결하기 위해 오른쪽의 그림처럼 4개의 책임 하나당 하나의 클래스로 분리를 한다.
개방-폐쇄 원칙 OCP
OCP 란?
소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야한다.
- 변경을 위한 비용은 가능한 줄이고, 확장을 위한 비용은 가능한
극대화해야 한다.
- 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소
에는 수정이 일어나지 않고, 기존 구성요소를 쉽게 확장해서 재
사용한다.
- 객체지향의 추상화와 다형성을 활용한다.
예시
소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야한다.
- 변경을 위한 비용은 가능한 줄이고, 확장을 위한 비용은 가능한
극대화해야 한다. - 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소
에는 수정이 일어나지 않고, 기존 구성요소를 쉽게 확장해서 재
사용한다. - 객체지향의 추상화와 다형성을 활용한다.
설명
왼쪽 그림처럼 OCP 가 적용되지 않을 경우, Paint 기능을 추가하였는데 기존 Cut 기능이 동작하지 않는 예시이다.
즉, 클래스의 현재 동작을 변경하면 해당 클래스를 사용하는 모든 시스템에 영향을 미친다는 뜻이다.
OCP 의 목표는 해당 클래스의 기존 동작을 변경하지 않고 클래스의 확장하는 것을 목표로 한다. 클래스가 더 많은 기능을 수행하기를 원한다면 이상적인 접근 방식은 이미 존재하는 기능을 변경하지 않고 추가하는 것이다.
리스코프 치환 원칙 LSP
리스코프 치환 원칙 이란?
서브타입은 언제나 기반 타입으로 교체할 수 있어야 한다.
- 서브타입은 기반 타입이 약속한 규약(접근 제한 자, 예외 포함)을
지켜야 한다.
- 클래스 상속, 인터페이스 상속을 이용해 확장성을 획득한다.
- 다형성과 확장성을 극대화하기 위해 인터페이스를 사용하는 것
이 더 좋다.
- 합성(composition)을 이용할 수도 있다
예시
서브타입은 언제나 기반 타입으로 교체할 수 있어야 한다.
- 서브타입은 기반 타입이 약속한 규약(접근 제한 자, 예외 포함)을
지켜야 한다. - 클래스 상속, 인터페이스 상속을 이용해 확장성을 획득한다.
- 다형성과 확장성을 극대화하기 위해 인터페이스를 사용하는 것
이 더 좋다. - 합성(composition)을 이용할 수도 있다
설명
위 그림은, 부모 로봇이 Coffee 종류를 배달하는 그림이다. 카푸치노, 에스프레소는 커피의 종류이기 때문에 자식 로봇이 배달하는 것을 허용하지만 물을 배달하는 것을 허용하지 않는다는 예시이다.
즉, 자식 클래스가 부모 클래스와 동일한 작업을 수행할 수 없는 경우 버그가 발생할 수 있다라는 의미
자식 클래스는 부모 클래스가 할 수 있는 모든 것을 할 수 있어야 하며 이 프로세스를 상속이라고 한다.
목표
이 원칙은 부모 클래스 또는 자식 클래스가 오류 없이 동일한 방식으로 사용될 수 있도록 일관성을 유지하는 것이다.
인터페이스 분리 원칙 ISP
인터페이스 분리 원칙이란?
자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
- 가능한 최소한의 인터페이스만 구현한다.
- 만약 어떤 클래스를 이용하는 클라이언트가 여러 개고, 이들이 클래스의 특정 부분만 이용한다면, 여러 인터페이스로 분류하여 클라이언트가 필요한 기능만 전달한다.
- SRP가 클래스의 단일 책임이라면, ISP는 인터페이스의 단일 책임
예시
자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다.
- 가능한 최소한의 인터페이스만 구현한다.
- 만약 어떤 클래스를 이용하는 클라이언트가 여러 개고, 이들이 클래스의 특정 부분만 이용한다면, 여러 인터페이스로 분류하여 클라이언트가 필요한 기능만 전달한다.
- SRP가 클래스의 단일 책임이라면, ISP는 인터페이스의 단일 책임
설명
왼쪽의 그림은 로봇의 용도가 다르지만 필요하지 않은 기능까지 모두 구현을 한 예시 이며, 오른쪽 그림은 로봇의 용도에 맞게 기능이 분리되어있는 것을 의미한다.
클래스는 필요하지 않은 기능을 수행해야 하는 경우 버그가 발생할 수 있다. 따라서 인터페이스를 분리하여 클래스가 필요한 기능만 상속 받을 수 있게 하는 것이 이 규칙이다.
이 원칙은 작업 집합을 더 작은 집합으로 분할하여 클래스가 필요한 작업 집합만 실행하도록 하는 것이 목표이다.
의존성 역전 원칙 DIP
의존성 역전 원칙 이란?
상위 모델은 하위 모델에 의존하면 안된다. 둘 다 추상화에 의존해야 한다.
추상화는 세부 사항에 의존해서는 안된다. 세부 사항은 추상화에 따라 달라진다.
- 하위 모델의 변경이 상위 모듈의 변경을 요구하는 위계관계를 끊는다.
- 실제 사용관계는 그대로이지만, 추상화를 매개로 메시지를 주고 받으면서 관계를 느슨하게 만든
예시
상위 모델은 하위 모델에 의존하면 안된다. 둘 다 추상화에 의존해야 한다.
추상화는 세부 사항에 의존해서는 안된다. 세부 사항은 추상화에 따라 달라진다.
- 하위 모델의 변경이 상위 모듈의 변경을 요구하는 위계관계를 끊는다.
- 실제 사용관계는 그대로이지만, 추상화를 매개로 메시지를 주고 받으면서 관계를 느슨하게 만든
설명
왼쪽 그림은 로봇의 팔에 피자를 자르는 기능만 할 수 있는 것을 의미하며, 오른쪽의 로봇은 언제든 팔을 갈아 끼워 새로운 도구를 사용할 수 있다는 것을 의미한다.
즉, 클래스가 작업을 실행하는 데 구현체 클래스에 의존하는 것이 아닌 추상화에 의존하여 다양한 구체 클래스를 사용할 수 있다는 의미이다.
새로운 카드사가 추가된다면?
@RequestMapping(value = "/api/payment", method = RequestMethod.POST)
public void pay(@RequestBody CardPaymentDto.PaymentRequest req){
if(req.getType() == CardType.SHINHAN) {
shinhanCardPaymentService.pay(req);
} else if(req.getType() == CardType.WOORI){
wooriCardPaymentService.pay(req);
}
}
확장에 유연하지 않다..
-> 둘다 추상화된 인터페이스에 의존하도록 한다.
class PaymentController {
@RequestMapping(value = "/payment", method = RequestMethod.POST)
public void pay(@RequestBody CardPaymentDto.PaymentRequest req) {
final CardPaymentService cardPaymentService = cardPaymentFactory.getType(req.getType());
cardPaymentService.pay(req);
}
}
*public interface CardPaymentService {
void pay(CardPaymentDto.PaymentRequest req);
}*
public class ShinhanCardPaymentService implements CardPaymentService {
@Override
public void pay(CardPaymentDto.PaymentRequest req) {
shinhanCardApi.pay(req);
}
}
간결한 함수 작성하기
public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) throws Exception {
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();
}
public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) throws Exception {
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();
}
'함수가 길고, 여러가지 기능이 섞여있다..'
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}
작게 쪼갠다.
함수 내 추상화 수준을 동일하게 맞춘다.
한 가지만 하기(SRP), 변경에 닫게 만들기(OCP)
한 가지만 해라!
함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
-
그럼 '한 가지'가 무엇인지 어떻게 알지?
- 지정된 함수 이름 아래에서, '추상화 수준'이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 하는 것이다.(사소한 요소를 제거하고 핵심만 뽑아내는 것)
-
하지만, 의미 있는 이름으로 다른 함수를 추출할 수 있다면, 그 함수는 여러 작업을 하는 것이다.
'추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 하는 것이다.' 라는 말이 어려울 수 있다. 하지만 예시를 보면 그렇게 어려운 것은 아니다. 1. 쓰레기를 판단한다. 2. 쓰레기를 줍는다. 3. 쓰레기를 휴지통에 넣는다.
쓰레기를 휴지통에 버리는 함수 아래에서 3가지 함수를 호출해야 한다는 것을 알 수 있다. 따라서 함수 이름 아래에서 추상화 수준은 하나라고 알 수 있다. 즉 한 가지 작업만 하는 함수는 함수 안에서 모든 문장의 추상화 수준이(판단한다, 줍는다, 넣는다 등..) 동일해야 한다는 것을 알 수 있다.
NOT
판단, 줍기, 넣기 등 세가지 함수로 구분할 수 있는데. 판단 함수에서 줍기까지 더해 판단하여 줍기 등과 같은 추상화를 하면 안된다는 의미이다.
추상화 수준이란?
말 그대로 구체적으로 풀어 쓰기보다는 추상적으로 표현되어 있다면 추상화 수준이 높은 것이고, 추상화 되어 있지 않고 직접적인 코드는 추상화 수준이 낮다고 한다.
예시
public Money calculatePay(Employee e) throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
'계산도 하고, Money도 생성한다.. 두 가지 기능이 보인다.'
'새로운 직원 타입이 추가된다면?'
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 InvalidEmployeeType;
}
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
switch (r.type) {
case COMMISSIONED:
return new CommissionedEmployee(r);
case HOURLY:
return new SalariedEmployee(r);
case SALARIED:
return new SalariedEmployee(r);
default:
return new InvalidEmployeeType(r.type);
}
}
}
계산과 타입관리를 분리
타입에 대한 처리는 최대한 Factory에서만
함수 인수
인수의 갯수는 0~2개가 적당하다.
3개 이상인 경우에는?
// 객체를 인자로 넘기기
Circle makeCircle(double x, double y, double radius); // X
Circle makeCircle(Point center, double radius); // O
// 가변 인자를 넘기기 ==> 특별한 경우가 아니면 잘 안쓴다.
String.format(String format, Object... args);
안전한 함수 작성하기
부수 효과(Side Effect) 없는 함수
부수 효과? 값을 반환하는 함수가 외부 상태를 변경하는 경우
public class UserValidator {
private Cryptographer cryptographer;
public boolean checkPassword(String userName, String password) {
User user = UserGateway.findByName(userName);
if (user != User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword();
String phrase = cryptographer.decrypt(codedPhrase, password);
if ("Valid Password".equals(phrase)) {
Session.initialize();
return true;
}
}
return false;
}
}
참고
해당 포스팅은 제로 베이스 클린코드 한달한권을 수강 후 정리한 내용입니다.
Author And Source
이 문제에 관하여(클린코드 핵심 정리 (SOLID, 3장 함수)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@coconenne/클린코드-핵심-정리-SOLID-3장-함수
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
해당 포스팅은 제로 베이스 클린코드 한달한권을 수강 후 정리한 내용입니다.
Author And Source
이 문제에 관하여(클린코드 핵심 정리 (SOLID, 3장 함수)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@coconenne/클린코드-핵심-정리-SOLID-3장-함수저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)