객체지향 5대 원칙 SOLID
- SRP : 단일 책임 원칙
- OCP : 개방-폐쇄 원칙
- LSP : 리스코프 치환 원칙
- ISP : 인터페이스 분리 원칙
- DIP : 의존성 역전 원칙
이 책을 읽다 보면 위의 객체지향 5대 원칙이 많이 등장한다.
3장 함수를 포함하여 객체지향 프로그래밍을 할 때 위의 원칙을 항상 상기하며 개발을 한다면 클린코드에 한걸음 가까워지게 될 것이다.
SOLID의 자세한 내용은 아래 링크를 확인해 주세요.
2023.04.20 - [JAVA/JAVA 이론] - [JAVA] 객체지향 4대 특성(캡슐화, 상속, 다형성, 추상화), 5 원칙(SOLID)
간결한 함수 만들기
함수를 만들 때는 작게(간결하게) 만들어야 한다.
함수를 만드는 첫째 규칙은 "작게!"이고, 둘째 규칙은 "더 작게!"이다.
함수는 100줄을 넘어가서는 안된다. 아니, 20줄도 길다.
기능이 여러 개라면 하나의 함수에서 그 기능을 다 하지 말고, 함수를 쪼개서 기능을 분담하게 하고 그 함수를 합쳐야 한다.
블록 들여 쓰기
if / else / while 문에 들어가는 불록은 한 줄이어야 한다. 대개 그 안에서 여러 함수들을 호출한다.
그렇게 되면 바깥을 감싸는 함수가 작아지고, 안에 들어가는 함수들의 이름을 적절히 짓는다면 가독성도 좋아진다.
이 말은 중첩구조가 생길 만큼 함수가 커져서는 안 된다는 듯이다. 함수에서 들여 쓰기 수준은 1단이나 2단을 넘어서면 안 된다.
" 함수는 한 가지를 해야 한다. 그 한 가지를 잘해야 한다. 그 한 가지만을 해야 한다. "
함수 당 추상화 수준은 하나로!
함수가 확실히 '한 가지' 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
하나의 함수에 여러 가지 조건이 있고, 그 조건에 따라 여러가지 방법으로 일을 처리한다면,
그 여러가지 방법에 따라 함수를 작게 쪼개야 한다. 함수 내 추상화 수준을 동일하게 맞춘다.
기능의 분리를 잘게 나누어 하나의 함수는 한 가지 작업만 하도록 한다.
이렇게 하면 한 가지만 하기(SRP), 확장이 쉬워지고 변경에 닿게(OCP) 만들 수 있다.
함수 인수(매개변수)의 개수는 적을수록 좋다.
함수에서 이상적인 인수 개수는 0개(무항)이다. 그다음은 1개(단항)이고, 그 다음은 2개(이항)이다.
3개(삼항)는 가능한 피하는 편이 좋다. 4개 이상(다항)은 특별한 이유가 필요하다.
함수에서 인수 개수가 많아질수록 이해하기 어려워진다.
인수 개수가 많아야만 한다면 최소한 메서드 이름을 신경 써서 지어서 이해하기 쉽게 만들어 줘야 한다.
// (1) 변경 전
boolean assertEquals(expected, actual);
// (2) 변경 후
boolean assertExpectedEqualsActual(expected, actual);
(1)을 (2)로 변경한 것처럼 인수 순서를 기억하기 쉽도록 메서드 이름을 변경해 주는 것도 좋은 방법이다.
함수 인수 개수를 줄이는 방법
아래와 같이 x좌표, y좌표를 Point라는 클래스를 만들어 합칠 수 있다.
이렇게 하면 가독성이 좋아진다.
원을 만들 때는 중심좌표와 반지름이 필요한것을 아는 사람이라면,
Point center 인자를 보고 바로 무엇을 의미하는지 알아차릴 것이다.
// (1) 변경 전
Circle makeCircle(double x, double y, double radius);
// (2) 변경 후
Circle makeCircle(Point center, double radius);
class Point{
double x;
double y;
}
안전한 함수 만들기
함수를 만들때는 해당함수의 권한과 범위를 넘어서지 말아야 한다.
부수 효과(Side Effect)를 일으키지 마라!
public class UserValidator {
private Cryptographer cryptographer;
public boolean checkPassword(String userName, String password){
User user = UserGateway.findByName(userName);
if(user != null){
String codedPhrase = user.getPhraseEncodeByPassword();
String phrase = cryptographer.decrypt(codedPhrase, password);
if("Valid Password".equals(phrase)){
Session.initialize();
return true;
}
}
}
return false;
}
위의 코드를 보자. 다른 건 중요하지 않지만, 이 코드는 userName과 password을 확인하는 코드이다.
아래 부분을 보면 비밀번호가 틀렸을 때의 로직이다.
if("Valid Password".equals(phrase)){
Session.initialize();
}
이름과 비밀번호를 체크하는 메서드에서 세션을 종료시키고 있다.
여기서 이 함수가 일으키는 부수효과는 Session.initialize();이다.
함수의 이름만 봐서는 check만 할 것 같지만, 실제로는 check 후 틀리면 세션을 종료시키기까지 한다.
"checkPassword"만 봐서는 세션을 초기화한다는 사실이 드러나지 않는다.
그래서 함수 이름만 보고 함수를 호출하는 사용자는 사용자를 인증하면서 기존 세션 정보를 지워버릴 위험에 처한다.
이렇게 함수와 관계없는 외부 상태를 변경시켜서는 안 된다.
이럴 때는 함수 이름을 checkPasswordAndInitializedSession()이라는 이름으로 변경하는 것이 좋다.
물론, 이 함수에서 세션을 종료시키지 않도록 변경하고 해당 기능은 다른 곳에서 처리하는 것이 더 좋다.
오류 대신 예외(Exception)를 사용해라.
함수는 한 가지 작업만 해야 하고, 오류처리도 '한 가지' 작업에 속한다. 오류를 처리하는 함수는 오류만 처리해야 마땅하다.
public enum Error {
OK,
INVALID,
NO_SUCH,
LOCKED,
OUT_OF_RESOURCES,
WAITING_FOR_EVENT;
}
오류코드를 반환한다는 말은, 위의 enum 클래스처럼 어디선가 오류 코드를 정의한다는 뜻이다.위와 같은 클래스를 의존성 자석이다. 다른 클래스에서 Error enum을 import 해서 사용해야 하므로 Error enum이 변경된다면 Error enum을 사용하는 클래스 전부를 다시 컴파일하고 다시 배치해야 할 것이다.
오류코드를 사용하지 말고, Exception 클래스를 사용하거나 상속받아 예외를 발생시켜라.
함수 리팩터링
함수는 어떻게 짜야할까?
소프트웨어를 짜는 행위는 글짓기와 비슷하다고 한다. 논문이나 기사를 작성할 때는 먼저 생각을 기록한 후에 읽기 좋게 다듬는다. 소프트웨어도 마찬가지이다.
1. 기능을 구현하는 지저분한 코드를 작성한다.
좋은 개발자도 처음 작성할 때는 길고 복잡하게 코드를 작성할 수밖에 없다.
중복된 루프도 많고, 인수 개수도 많고, 이름은 즉흥적이고, 코드는 중복된다.
2. 테스트코드를 빼먹지 않고 작성한다.
위에서 기능구현을 위한 지저분한 코드를 작성하는 와중에도 그 지저분한 코드를 빠짐없이 테스트하는 단위 테스트 케이스도 만든다. 이후에 코드를 리팩터링 하는 과정에서도 해당 단위 테스트는 변경하지 않지만 항상 단위 테스트를 통과한다.
3. 리팩터링
코드를 다듬고, 함수를 쪼개고, 이름을 바꾸고, 중복을 제거한다.
때로는 전체 클래스를 쪼개기도 한다.
이 모든 과정에서도 항상 단위테스트를 통과한다.
최종적으로 이 장에서 설명한 규칙을 따르는 함수가 얻어지게 된다.
'기타 > Book Review' 카테고리의 다른 글
[CleanCode] 클린코드 리뷰_6장 : 객체와 자료구조 (0) | 2023.06.21 |
---|---|
[Clean Code] 클린코드 리뷰_ 5장 : 형식 맞추기 (1) | 2023.06.17 |
[CleanCode] 클린코드 리뷰_ 4장 : 주석 (0) | 2023.06.16 |
[Clean Code] 클린코드 리뷰_ 2장 : 의미있는 이름 (1) | 2023.06.13 |
[Clean Code] 클린코드 리뷰_ 1장 : 깨끗한 코드 (0) | 2023.06.13 |