Home 객체지향 생활체조 원칙
Post
Cancel

객체지향 생활체조 원칙

개발자는 의도한대로 잘 동작하는 코드를 짜는 것도 중요하지만 대부분이 협업으로 이루어지기 때문에 자신이 짠 코드를 남들도 잘 이해할 수 있어야 한다.

그리고 기능 개발보다 유지 보수하는 것이 대부분을 차지하기 때문에 유지 보수의 편리함을 위해서라도 처음부터 클린 코드를 실천하는 것이 좋다. 개발 생산성을 높여준다.

유지보수성을 위한 코드작성을 위해 지키면 좋은 원칙에 대해 설명한다. 물론 아래 요약한 내용말고도 더 있다. 개인마다 생각하는 것이 다를 수 있고 그만큼 클린 코드를 실천하는 방법은 방대하기 때문이다. 좋은 코드를 작성하기 위한 객체지향 생활 체조 원칙에 대해 요약해본다.

객체지향 생활 체조 원칙

모든 원시값과 문자열을 포장하자.

1
2
int age = 23; //원시 타입의 변수
Age age = new Age(23); //원시 타입을 객체로 포장

이렇게 포장하면 자신의 상태를 스스로 관리할 수 있다. 예를 들면 age값에 0이 들어오면 안되는데 이를 원시 변수 상태로 제한하려면 바깥에 조건을 걸어주어야 한다. 그러나 Age 객체로 묶는다면 Age 객체안에 필요한 로직을 넣고 관리할 수 있는 것이다.

1
2
3
4
5
6
7
8
9
10
class Age {
	private int age;
	
	public Age(int inputValue) {
		int inputAge = Integer.parseInt(inputValue);
		if (inputAge <= 0) {
			throw new IllegalArgumentException();
		}
	}
}

그리고 이에 따라 유지 보수도 도움이 된다. inputVale의 자료형에도 구애받지 않을 수 있다. 상황에 따라 알맞게 메서드를 작성해주면 되기 때문이다.

아래에 설명할 일급 컬렉션도 이와 일맥상통한다.

매직 리터럴 / 매직 넘버 사용을 자제하고 상수를 사용해라.

1
return height * 3.14

위와 같이 코드를 처음보는 사람이 3.14의 의미를 알 수 없게 숫자나 메시지가 직접 사용되는 것, 소스코드에 여러 번 등장하는 일반적인 리터럴 값을 매직 리터럴이라고 한다.

이를 상수로 대체하면?

1
2
3
private static final double PI = 3.14;

return height * PI;

처음보는 사람은 PI라는 것만 보고 어떤 의미인지 알 수 있다.

메서드가 한 가지 일만 담당하도록 구현해라.

하나의 메서드가 여러 책임을 담당하면 코드의 길이가 늘어나고 그에 따라 다른 개발자가 해당 메서드의 명확한 역할을 이해하기 어려워진다. 메서드의 이름을 작성하기도 어렵다.

메서드의 이름을 생각할 때 적당한 이름이 생각이 나지 않고 길어진다 싶으면 그 메서드가 담당하고 있는 역할이 많지 않은지 의심해봐야 한다.

메서드의 인자 수를 3개 이하로 제한해라.

메서드에 많은 인자가 있다는 것은 그 메서드의 역할이 한 가지 이상일 수 있다. 인자 수를 줄여보도록 한다.

엔티티(클래스, 패키지)를 작게 유지하도록 노력해라.

50줄 이하의 클래스, 10개 이하의 파일을 갖는 패키지를 가지도록 노력하자. 메서드 당 line을 10까지만 허용하며 길이가 길어진다면 메서드로 분리하는 것을 고려해야 한다.

SOLID 원칙 중 단일 책임 원칙과 상통하는 부분이다.

물론 패키지같은 부분은 처음 설계단계부터 완벽하게 할 수 없다. 리팩토링을 거치면서 효율적인 패키지, 관리체계를 만들면 된다.

getter, setter 사용을 하지 않고 구현해라. 단 DTO는 허용한다.

객체의 상태가 변경되는 것은 객체 스스로의 행동에 의해야 한다. getter, setter 메서드를 사용한다면 private로 선언한 필드임에도 외부의 영향을 받아 변할 수 있다. 따라서 getter, setter가 있다면 제거하자.

DTO는 데이터 전달을 목적으로 설계되는 객체이다. 데이터 전달을 위해서는 속성을 꺼내고 수정할 수 있는 getter와 setter가 필요하기 때문에 허용한다.

한 줄에 점을 하나만 찍어라.

1
member.getAccounts().get(accountNumber).getStatements().add();

점을 찍는 것은 필드나 메서드를 통해 인스턴스에 접근하는 것과 같다. 그런데 점의 개수가 많다는 것은 대상 객체의 내부에 깊이 접근하겠다는 것이고 이렇게 설계한다면 문제가 발생한다.

member가 가지고 있는 account의 상태를 알고 싶다는 것이다.

Member와 Account가 결합되어 있고, Account와 Statement가 결합되어 있다. 이렇게 설계되어 있다면 연계된 클래스의 레이아웃이 변경되는 순간 모든 코드에 영향을 미칠 수 있다.

점을 하나만 찍으라는 것은 지역 변수를 활용해 쪼개라는 의미보다는 위와 같이 결합된 클래스들의 구조를 다시 생각해보라는 의미이다.

지금의 설계는 각 클래스가 독립적인 역할을 할 수 없도록 되어 있다는 것이고, 이를 해결하려면 위의 경우에는 아래와 같이 해결할 수 있을 것이다.

1
account.addStatement(statement);

계좌의 상태를 알기 위해 member에서부터 접근할 필요가 없는 것이다.

그리고 이렇게 설계를 나누다보면 클래스의 필드에 접근할 때 getter로 불러와 처리하는 경우가 많아지는데 이는 생활체조 원칙을 위반하는 것이다. 이런 경우 일급컬렉션같은 도메인 오브젝트 설계를 활용하는 것이 좋다.

클래스가 3개 이상의 인스턴스 변수를 갖지 않도록 하자.

대부분의 클래스들이 하나의 상태 변수를 처리하는 일을 맡아야 마땅하다. 몇몇 경우 더 필요할 수 있지만 인스턴스 변수가 많아질수록 클래스의 응집도가 낮아진다.

아래의 예시를 보고 이해하면 된다.

1
2
3
4
5
6
7
8
9
10
11
class Customer {
	Name name;
	CustomerId id;
} // O

class Customer {
	String FirstName;
	String LastName;
	int id;
	/...
} // X

일급 컬렉션(First Class Collection)을 사용하자.

우선 일급 컬렉션을 간단히 설명하자면 Collection을 Wrapping하는 것이다.

컬렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다.

1
2
3
4
public void createNumber() {
	List<Integer> number = createNonDuplicateNumber();
	validate(number);
}

필요한 번호를 생성한다고 가정하면 그 로직에는 생성한 숫자가 제약조건에 맞는지 검증을 해야한다. 그런데 위와 같이 설계하면 숫자를 생성하는 모든 부분에 검증 로직이 함께 들어가야 한다.

코드를 처음 보는 사람이 이를 본다면 위와 같은 List 타입은 모두 validate() 해야 한다고 생각할 수 있다. 어떤 상황에 왜 validate 해야하는지 모르는 것이다.

따라서 이러한 구조를 validate된 조건으로만 생성할 수 있는 자료구조를 만들어 해결하는 것이 일급 컬렉션이다. 꼭 validate이 아니더라도 값의 계산도 컬렉션을 묶은 클래스 내부에서 해결하고 관리할 수 있도록 할 수 있다는 장점이 있다.

1
Number number = new Number(createNonDuplicateNumber());

일급 컬렉션의 자세한 내용 참고

else 예약어를 사용하지 말자.

if와 else if는 사용해도 좋다. 하지만 else와 같이 조건없이 모든 경우를 열어주는 코드는 예상하지 못한 버그를 초래할 수 있다.

간단한 if - else문은 쉽게 파악할 수 있지만 if, else-if 조건이 많아지면 나머지 조건인 else의 파악도 어려워진다.

한 메서드에 최소한의 들여쓰기만 허용하자. (최대 2까지 허용)

1
2
3
4
while {
	if (a < b) {
	}
}

위가 들여쓰기가 2인 경우이다. 이러한 들여쓰기를 줄이는 방법은 메서드로 분리하는 것이다.

This post is licensed under CC BY 4.0 by the author.