-
객체지향 생활 체조Programming/Java 2018. 9. 13. 01:04
객체지향 생활 체조 객체지향 생활 체조
The ThoughtWorks Anthology 챕터중 객체지향생활체조부분
절차지향적인 코딩이 아닌 객체지향적인 코딩을 연습할 목적으로 만들어진 가이드 라인이라고 생각하면 된다. 때문에 매우 극단적이다. 책에서는 총 9가지 훈련법을 극단적으로 지켜서 1000줄짜리 코드를 짜는 연습을 하라고 되어있다. 그만큼 연습을 목적으로 이루어진 규칙이다.
때문에 처음에는 이해가 안되는 부분이 많았다. 굳이 이렇게 까지 해야하는 이유가 뭐지? 이런 규칙들은 차라리 객체지향방법에 어긋나는 것이 아닌가? 코드가 너무 낭비되는 것 아니가? 라는 생각이 들때도 많아서 읽으면서 물음표가 많았다. 쉽게 생각하면 드래곤볼의 손오공이 무거운 모래주머니 차고 움직이는 연습하는 것과 같다고 할 수 있다.
총9가지 규칙으로 이루어진 훈련방법. 내용은 다음과 같다. 자세한내용을 공부한수 아래 리스트를 띄어놓고 잊지않고 반복적으로 훈련해보자.
- 메서드당 들여쓰기는 한번
- else 예약어 사용금지
- 원시값과 문자열의 포장
- 한줄에 한점만 사용
- 줄여쓰지 않는다. 축약 금지
- 모든 엔티티는 작게
- 2개 이상의 인스턴스 변수를 가진 클래스 사용금지
- 일급 콜렉션 사용
- getter/setter/속성 사용 금지
1. 메서드당 들여쓰기 한 번
메소드는 한가지 일만 하는 것이 보기 좋다.
- 예를들어 중첩된 제어구조가 있다면(if안에 if가 있다면) 한가지 이상의 일을 하고 있다.
- 그렇다면 기본적으로 메소드를 만들때 필요한 작업들 때문에 타이핑을 많이 해야하니 메모리도 많이 차지하고 손도 아프지 않을까? 그정도는 무시해도 될정도로 컴퓨터 리소스들이 발달했다. 그리고 자동완성 기능이 좋아져서 타자 속도에도 그렇게 차이나지도 않는다.
- 들여쓰기 == indent(인덴트)
그래서 이점은?
- 코드에서 버그 판별이 쉬워진다.
- 재사용이 쉬워진다.
- 메소드의 응집력이 높아진다.
예제
- 기존 배열을 선택하는 2중포문을 열과 행을 선택하는 메소드로 분리하다.
xclass Board {
...
String board() {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++)
buf.append(data[i][j]);
buf.append("\n");
}
return buf.toString();
}
}
class Board {
...
String board() {
StringBuffer buf = new StringBuffer();
collectRows(buf);
return buf.toString();
}
void collectRows(StringBuffer buf) {
for (int i = 0; i < 10; i++)
collectRow(buf, i);
}
void collectRow(StringBuffer buf, int row) {
for (int i = 0; i < 10; i++)
buf.append(data[row][i]);
buf.append("\n");
}
}
참고 : https://developerfarm.wordpress.com/2012/02/03/object_calisthenics_summary/
더욱 극단적으로 하기위해서 최대한 모든 동적을 메소드로 빼버리는 훈련을 해보자. 윈도우 intelliJ에서 메소드 추출하는 단축키는
ctrl
+alt
+M
이다.2. else 예약어 금지
if/else는 언어를 처음 배울때 배운 기초인데 else를 쓰지말라니..목적
- 조건문 쉽기 때문에 조건이 생길때 마다 추가할 수 있는데, 그럴 수록 코드가 보기 불편해진다.
- switch도 마찬가지
방법
- 조건문안에서 return 하여, 조건을 만족하면 메소드를 벗어나게 만들어라.
xxxxxxxxxx
public static void ex(){
if(status == DONE) {
doSomething();
return;
}
// 다른 코드
}
아래처럼 바꿀수 있다.
xxxxxxxxxx
public static Object head() {
if (isAdvancing()) {return first;}
return second;
}
xxxxxxxxxx
public static Object head() {
return isAdvancing() ? first : last;
}
참고 : https://developerfarm.wordpress.com/2012/01/27/object_calisthenics_3/
3. 원시값과 문자열의 포장
목적
- 매개변수로 원시값(int)를 사용한다면 해당 매개변수의 의미를 알 수 없다.
- 컴파일 자체에는 문제가 없으나 다른사람
(혹은 미래의 나)이 이해하기 어렵다.
방법
- 만약 시간을 처리하는 메소드가 있다면, 매개변수로 int나 float값을 전달 할 수 있다. 하지만, 그 값이 시간인지, 분인지 아는 방법은 없다. 그렇게 하려면 주석을 달아주거나, 이름을 맛깔나게 만들어야한다(사실상 불가능)
- 만약 시간이라는 매개변수가 필요하다면, 클래스를 정의하여 객체를 전달하게 해라.
장점
- 코드보기가 수월해 진다.
- 다양한 정보를 넣을 수 있다.( 시간이라는 객체안에 시, 분, 초 등으로 된 정보를 넣을 수 있다.)
- 추가적으로 시간으라는 정보는 24를 넘으면 안된다. 원시값으로 사용할 경우 메소드에 매개변수를 넣기 전에 항상 24가 넘지 않는지 check해 주어야 한다. 하지만 객체화 한다면, 시간이라는 객체를 만들때 미리 처리할 수 있다. 예외처리가 쉬워지고 코드의 반복을 줄여준다.
4. 한 줄에 한 점만 사용
코드 한 줄에 점이 하나 이상 있으면, 그 곳에서 다른 동작이 일어나고 있다는 뜻이다.
객체를 점으로 호출한다는 것은 캡슐화가 잘못되어 있다는 것이다.
xxxxxxxxxx
class Board {
...
class Piece {
...
String representation;
}
class Location {
...
Piece current;
public Lication() {
current = new Piece();
}
}
String boardRepresentation() {
StringBuffer buf = new StringBuffer();
for (Location l : squares())
buf.append(l.current.representation.substring(0, 1));
/**
* Board 객체안에 내부 클래스가 있는데
* 이를 .으로 곧장 참조하고 있다.
*/
return buf.toString();
}
}
class Board {
...
class Piece {
...
private String representation;
String character() {
return representation.substring(0, 1);
}
void addTo(StringBuffer buf) {
buf.append(character());
}
}
class Location {
...
private Piece current;
void addTo(StringBuffer buf) {
current.addTo(buf);
}
}
String boardRepresentation() {
StringBuffer buf = new StringBuffer();
for (Location l : squares())
l.addTo(buf);
return buf.toString();
}
}
5. 줄여쓰지 않는다(축약금지!)
목적
- 컴퓨팅 파워와 자동완성 기능이 좋아졌으니, 좋은 컨벤션을 만들고 읽기 좋은 코드를 만들자.
방법
- 클래스나, 메소드의 이름은 2개의 단어가 가장 적당하나, 1의 조건은 무조건 이해하기 편한것
- 문맥을 중복하는 이름을 자제해야한다. game이라는 객체에 게임을 시작하는 메소드가 있을수 있다. 만약 게임을 시작하는 메소드를 gameStart() 라고 짓게 된다면, 이 클래스를 사용할 때, game.gameStart()가 된다. 따라서 Start()라는 이름을 사용하면 더 좋다.
- 메소드는 문장형도 괜찮다. true()보다는 isTure()가 더 익숙 할 것이다.
6. 모든 엔티티를 작게 유지
50줄 이상 되는 클래스와 파일이 10개 이상인 패키지는 없어야 한다.
- 50줄이상의 클래스는 보통 한가지 이상의 일을 하는 것이다.
클래스와 패키지의 크기를 줄여 응집력을 높인다.
- 응집력 : 연관된 내용만 모인 것을 의미함.
7. 2개 이상의 인스턴스 변수를 가진 클래스 사용 금지
- 새로운 인스턴스 변수를 하나 더 기존 클래스에 추가하면 클래스의 응집도가 떨어진다.
- 대부분의 클래스는 하나의 상태변수를 처리하는 일을 맡는 것이 마땅하다.
이름에 대하여 다음과 같은 인스턴스 변수를 가지고 있는 클래스가 있을 수 있다.
xxxxxxxxxx
{
...
String first;
String middle;
String last;
}
다음과 같이 분해할 수 있다.
xxxxxxxxxx
class Name {
Surname family;
GivenNames given;
}
class Surname {
String family;
}
class GivenNames {
List<String> names;
}
GivenName을 통해서 성과 이름 이외의 규칙이 적용된 이름을 흡수 할 수 있다.
(Surname '성', Givenname '성이외의 이름')
8. 일급 콜렉션 사용
콜렉션을 다루는 클래스라면 콜렉션 하나만을 멤버 번수로 사용해라.
즉 하나의 클래스가 콜렉션을 다루는 클래스라면, 하나의 콜렉션과 그를 다루는 메소드들로 이루어지게 설계하라는 뜻이다.
장점
- 콜렉션에는 다양한 메소드들이 있다. 콜렉션을 객체화 하지 않으면, 설계자의 의도와 다른 메소드들을 써버릴 수 있다. 따라서 해당 콜렉션에 필요한 메소드들만 정의하는 역할을 하는 것이다.
예제
- 자동차 경주게임을 구현한 Game이라는 클래스가 있다. 이 클래스는 자동차 게임에 필요한 Car객체들이 필요할 것이다. 이 car객체들을 다루기 위해서 배열을 쓸수도, ArrayList를 쓸 수도 있다. ArrayList에는 다양한 메소드들이 있는데, 그 중에 필요한 것들만 빼서 정의할 수 있다. 그러면 Game 객체를 사용하는 사람의 정의된 메소드들만을 사용할 수있다. 예를 들면 자동차 ArrayList를 정렬하는 기능이 실다면, sort 기능을 빼버리는 역할을 할 수 있는 것이다.
9. getter/setter/속성 사용 금지
getter와 setter는 캡슐화의 기본인데 쓰지 말라는 이유는 무엇인가?
-> getter와 setter를 절대 쓰지 말라는 것은 아닌 것 같다. 굳이 필요 없는 곳에 남용하지 말라는 뜻이겠지..
예시
- 바둑돌을 움직이는 Game 클래스를 생각해보자. 아마 바둑돌이라는 클래스에는 point x, y가 있을 것이다. 바둑돌들을 관리하는 Game이라는 클래스에서 바둑돌을 움직이기 위해서 어떻게 하면 좋을까? Game의 클래스에 move()라는 메소들를 만들어 바둑알 개체에서 getX, getY를 해온 point x,y값을 다시변형해 set해 줄 것이다.
- 하지만 이렇게 하는 것보다. 바둑알의 자표를 움직이는 것은 바둘알이 당하거나 해야하는, 즉 자신의 속성을 사용해야하는 일이다. 그렇다면, getter와 setter를 사용하는데 신 바둑알 클래스에 움직임을 담당하는 메소들를 만들어 Game에서 그 메소드를 사용하게 하자.
그럼 getter와 setter를 사용해야 할 때는 언제인가?
- MVC 모델에서 View가 화면에 객체의 위치를 포현새 주어야 할때는 바둑알의 Point를 get, set하는 것이 맞겠죠?
아마 그럴 것이라 생각한다..
한마디로 하자면
- 속성값을(좌표)를 가지고 있는 객체에, 해당 속성값을 조정할 수 있는 메소드를 만들자.
맺음말
객체 지향 생활체조 내용자체는 무조건해야하는 것은 아니라, 객체지향적인 코드를 짜기위해 극단적으로 코드를 연습하는 하나의 방법이다. 이 개념을 모르고 읽었을 때, 원칙들이 이해되지 않았다. 코드를 꼭 저렇게 짜라고 하는 건 줄 알았기 때문이다. 하지만 단순히 극단적으로 연습하기 위한 하나의 방법이다. 책에서(
사실 책은 읽지 않았지만) 위의 규칙을 극단적으로 지켜가며 1000줄의 코드를 짜라고 했으니, 그렇게 한번 해보자.참고사이트
'Programming > Java' 카테고리의 다른 글
BufferedReader 사용하기 (0) 2018.11.09 BufferedReader와 StringTokenizer를 사용해보자 (1) 2018.05.18