Agile Java Lesson 6, 7

기본

레슨 6은 상속에 관한 내용입니다. 레슨 첫 장에 switch 문이 등장합니다. 어디선가 switch 문을 if-else로 바꿔서 컴파일 한다는 얘기를 들었었는데, JLS에는 딱히 명시된 얘기는 없었고 Sun 포럼에서 찾은 글을 보면 바이트코드가 if-else, switch 문에 따라 달라지며, switch문이 테이블스위치로 변환되는 경우 O(1), 룩업스위치로 바뀌는 경우 O(log(n)) 이기 때문에 O(n)인 if-else 보다는 어쨌든 더 퍼포먼스가 나오지만 그래봐야 microsecond 정도 아닐까 싶습니다.

개인적으로 숫자나 enum을 사용할 땐 switch 쪽이 가독성이 더 좋다고 생각하고, 적어도 자바에선 퍼포먼스보단 어느 코드가 더 읽기 좋은가에 포커스를 맞추는게 맞는 것 같습니다.

그런데 switch 문도 break를 빼먹으면 재앙이 일어날 수도 있고, 코드 변화에 민감하기 때문에 중복과 반복의 정도, 유지보수 용이성에 따라 아예 다형성을 이용하는 코드로 바꾸는게 좋다고 합니다. switch 문의 대안(다형성을 사용하는)으로 EnumMap을 소개하는데요. 이름에서 알 수 있듯이 이 맵은 키가 Enum이어야 합니다.

private Map messages = new EnumMap<Student.Grade, String>(Student.Grade.class);

public String getMessgae(Student.Grade grade) {
return getMessages().get(grade);
}

다음으로 늦은 초기화(lazy initialization)에 대해서 나오는데 역설이지만 OOP에서는 객체를 많이 만드는게 좋지많은 않습니다. 왜냐면 객체를 생성하고 유지하는데 비용이 적지 않기 때문입니다. 따라서 이런 부담을 줄이기 위해 꼭 필요할 때 객체를 생성하는 것을 늦은 초기화라고 합니다.

레슨 5에서 학점 계산을 하는 코드를 리팩토링 하면서 Strategy 패턴을 적용했었는데, 이때 중복되는 코드가 있었습니다. 이번엔 이 구조를 상속을 통해 개선합니다. GradingStrategy 인터페이스에 학점 계산 메소드를 하나 선언해 두고 BasicGradingStrategy에서 기본 구현을 한 다음, HonorsGradingStrategy, SenatorsSonGradingStrategy 등의 전략에서는 인터페이스가 아닌 BasicGradingStrategy 클래스를 상속 받아 메소드를 확장하는 형태입니다.

이제 본격적으로 Enum 클래스의 생성자와 메소드를 이용하기 시작하는 코드가 나오는데 역시 유용하단 생각이 듭니다. 토비님의 책에서도 아마 회원 등급 계산할 때 Enum 클래스를 사용하는 걸로 기억하는데 처음에 좀 이상해 보여서 그렇지 사용할수록 좋아 보입니다.

제가 TDD를 접하면서 들었던 의문 중에 하나는, 어떤 메소드를 만들고 그에 따른 테스트 코드를 만들지 않고 왜 테스트 코드를 먼저 만드는가? 하는 거였습니다. 그리고 테스트 코드를 먼저 만들면서 느낀점은 테스트 코드를 먼저 작성하면서 어떤 기능에 대한 템플릿 내지는 목표를 정확하게 지정해 삼천포로 빠지는 일을 막아주지 않았나 싶습니다. 책에서도 이런 내용이 나옵니다.

클라이언트는 어떤 객체에 메시지를 보낼 때 특정 방식으로 객체가 동작하기를 기대하는데, TDD에서 단위 테스트(unit test)가 바로 이 방식을 정의한다고 할 수 있다. 왜냐면, 단위 테스트는 인터페이스에서 정의한 기능에 대해 실제로 동작시켜보고 몇 가지 조건을 확인해 기능을 확인하기 때문이다.

레슨 끝 부분에 Abstract Test 패턴에 대해서 나옵니다. Abstract Test 패턴은 상위 테스트 클래스에서 어떤 공통적인 규칙을 만들어두고 이를 통과하면 하위 테스트 클래스에서 좀 더 강화된 테스트조건을 테스트 하는 것입니다. 왜 이러한 구조가 필요한가에 대한 설명은 이렇습니다.

하위 클래스는 extends의 의미대로 상위 클래스를 확장하기 때문에 결국 하위 클래스의 테스트에는 조건이 추가되어야 한다는 것을 의미합니다.  따라서 상위 -> 하위 클래스 순으로 테스트 하는게 이치에 맞아 보입니다.

그런데 이 패턴을 이클립스에서 지원해 주지 않는건지 상위 테스트 클래스에서 테스트를 할 경우 프로젝트 폴더 안에 있는 모든 테스트가 실행됩니다. 상위 테스트 클래스가 추상 클래스이기 때문에 그렇다고 생각되긴 하지만 type hierarchy 기능을 가지고 있는 IDE 툴로써 좀 아쉬운 부분이었습니다.

레슨 7은 C언어에서 파생된 요소들을 배우게 되는데 특별한 내용은 없었습니다. 단지 다른 입문서와 다른건 이런 내용을 담은 예제가 모두 단위 테스트로 나오기 때문에 이 예제가 뭘 확인하려는지 의도가 확실하다는 점입니다. 책에서 오타가 심심찮게 나오긴 하지만 자바 입문하시는 분들이 이 책으로 시작하면 어떨런지요.

ps. 책을 쭉 둘러보니 체스 예제가 여기서 사실상 마감이네요.

Advertisements

Agile Java Lesson 4, 5

기본

레슨 4 부터는 본격적으로 클래스에 대한 얘기가 나옵니다. 그러면서 메소드 디자인 하는 방법에 대해 말하는데, 객체의 상태를 바꾸거나 정보를 반환하는 것 중 한 가지만 하도록 디자인 해야 한다고 합니다. 그런데 가끔 위의 두 가지를 하지 않고, 매개변수에 대한 연산만 하고 반환하는 메소드가 있는데 이를 유틸리티 메소드(다른 언어에선 함수)라고 합니다. Java에서 대표적인 유틸리티 메소드를 가진 클래스로 Math가 있죠. 이런 유틸리티 메소드 같은 경우엔 객체를 생성하는게 의미가 없기 때문에 클래스 메소드(static)로 만드는게 좋습니다.

클래스 메소드나 변수를 사용할 때는 클래스명.멤버 식으로 호출해 주는게 좋습니다. 그렇지 않을 경우 불필요한 혼란을 줄 수 있습니다. 또한 static import 역시 무분별하게 사용할 경우 불필요한 혼란을 일으킬 수 있습니다. 책에선 만약 클래스 멤버 호출이 많아질 경우, 왜 이런 호출이 필요한지 생각해 보고 디자인을 바꿔보라고 합니다.

static import가 적절히 사용될 수 있는 부분은 어떤 클래스에 모여있는 관련있는 클래스 상수들을묶어서 하나의 클래스로 만든 후, static import를 사용해 편하게 사용하는 겁니다. 단, 여러 곳에서 자주 사용되는 요소를 클래스 멤버로 만드는게 좋습니다. 또한 클래스 멤버 호출로 코드가 너무 지저분해질 경우 정리하기 위해 사용될 수 있습니다.

static import는 어느 클래스 내부에 있는 클래스 멤버들을 가져오는 것으로, 다음과 같이 사용할 수는 없습니다.

import static java.lang.*;

저자가 말하는 클래스 멤버 사용 기준

클래스 멤버가 필요하질 때까지 사용하지 않는다.

오버라이딩된 생성자가 늘어나고 점차 객체 생성이 복잡해 짐에 따라 책에서 팩토리 메소드를 소개합니다. 팩토리 메소드는 보통 클래스 메소드로 만들고 객체 생성을 하는 역할을 맡습니다. 팩토리 메소드가 객체 생성을 전담하기 위해 생성자엔 private 접근자를 붙여 더이상 new로 객체 생성을 할 수 없습니다. 팩토리 메소드의 가장 큰 장점은 알기 쉬운 이름을 붙일 수 있다는 겁니다.

객체 지향 디자인의 중요한 부분 중 하나는 코드의 의미가 명확하고 의미상 알맞은 부분에 위치하도록 하는 것입니다. 물론 항상 처음부터 ‘알맞은 곳’에 위치시킬 순 없습니다. 하지만 좀 더 나은 위치를 알게 되면 리팩토링 해야합니다.

책에서 단순한 디자인에 관해 설명하는 부분이 있는데 아마 이 책에서 말하고자 하는 바가 담긴 핵심적인 내용 같습니다.

항상 코드를 깨끗하게 유지하라.

  • 테스트가 항상 100% 녹색인 것을 확인한다.
  • 중복을 없앤다.
  • 코드가 깨끗하고 명확한지 확인한다.
  • 클래스와 메소드 수를 최소화한다.

현재의 기능을 지원하기 위한 것보다 더 많은 설계를 포함하지 마라.

TDD에서 말하는 테스트에 대해서도 흥미로운 내용이 있습니다. 테스트는 단지 코드가 올바른지 검사하는 도구가 아니라 일정한 속도로 개발하는 방법을 제공하고 점진적으로 코드를 추가해 나가는 방법을 알려주며 올바르게 개발을 하고 있는지 신뢰감을 준다는 것입니다.

TDD는 시스템의 설계에 영향을 주기 위해 하는게 아니라 쉽게 테스트할 수 있는 시스템을 만드는데 목적이 있으며, 테스트 하기 쉬우려면 테스트할 부분이 시스템의 다른 부분과 분리돼 있어야 하기 때문에 OOD의 원칙과 맞닿아 있다고 할 수 있습니다.

그렇다고 TDD가 코드의 결점을 완전히 없애는 은총알은 아니며 TDD가 말하고자 하는 핵심 가치는 피드백의 중요성이 아닌가 합니다.

레슨 5는 인터페이스와 다형성을 다룹니다. 인터페이스의 단골 메뉴인 Comparable이 예제로 나옵니다. 저는 Comparable/Comparator 를 처음 봤을 때 어떤 행동을 주입시키는 광경을 보고 좀 놀랬던 기억이 있습니다. 인터페이스는 코드 상속이 안되는 이상한 녀석일 뿐이라는 생각을 송두리째 날려 버렸다고 할까요?

인터페이스는 상속 보다 한 단계 더 높은 추상화를 제공함으로써 인터페이스를 구현하는 구체 클래스나 이를 사용하려는 클라이언트 사이에 단 하나의 통로를 만들어 줍니다. 이 통로가 있는 한 구체 클래스와 클라이언트는 서로를 몰라도, 서로가 변화해도 소통할 수 있습니다. 문제는 클라이언트에서 인터페이스를 사용할 때 구체 클래스를 알아야 하는 아이러니한 점인데 이 문제는 DIP 원칙으로 우아하게 해결할 수 있습니다.

책에서 enum 클래스가 나오는데, enum 클래스는 static final 변수 대용으로 사용할 수도 있지만 그 태생이 static이란 점을 활용해 이펙티브 자바에선 최고의 싱글턴용 클래스로 소개 되며, 기초 자료형이 아닌 클래스 이기 때문에 enum 클래스 내부에 생성자만 만들어 줘도 정말 유용하게 써먹을 수 있습니다.

연습 문제로 나오는 체스가 점점 복잡해 지고 있습니다 (…)

Agile Java Lesson 2, 3

기본

여전히 흥미로운 챕터 구성입니다. 이제서야 int(이것 만;)가 나오는 반면에 갑작스런 ArrayList의 등장. 두둥. 하지만 이 구성이 오히려 괜찮아 보이는건 예제 프로그램에서 기능을 구현함에 있어 당장 필요한 것이기 때문입니다.

작년 C++ 공부할 때 Accelerated C++ 책을 좀 봤었는데, 이 책도 하나의 예제를 점점 완성해 나가면서 필요한 것을 배우는 구성이었습니다. 참 재미있게 봤었는데 보다가 한 번 놓치니까 붕 떠버리더라구요. 장/단점이 있는 것 같습니다.

레슨 2에서 데이터 저장 클래스만 다루는 것은 쓸모없는 시스템이며, 객체 지향 시스템은 특정 동작을 모델링 하는 것이라고 합니다. 대학 수업 때 클래스 만드는 첫 번째 방법으로 동사와 명사를 구분하는 작업을 하던게 기억나는데, 역시 객체 지향은 어떠한 동작이나 행동이 주가 되고 이런 동작을 하기 위해 필요한 데이터를 찾는 식이 더 좋은 접근 방법인 것 같습니다.

책에서 테스트 메소드를 작성하는 이유에 대해 저자가 이렇게 말합니다.

테스트 메소드를 작성하는 것은 부분적으로 개발자가 해당 클래스를 사용하기 위한 공용 인터페이스(public interface)를 디자인하는 과정이다.

그리고 클래스 디자인의 목표는 클래스를 사용하고자 하는 개발자가 가장 단순하게 사용할 수 있도록 하는 것이라고 하는군요. 이 책 여기저기에서 툭툭 던지는 말이 굉장히 뼈있는 말이 많습니다. 참.. 어려운 거죠. 그래도 리팩토링으로 꾸준히 바꿔나가다 보면 언젠간 좋은 설계에 다다를 것이라고 믿어야죠.

이번에 클래스가 대폭 추가됨에 따라 테스트 스위트(suites)를 사용합니다. 책과 다르게 JUnit 4를 사용하니 어노테이션이 길어지고 클래스는 비어있는 이상한 형태가 되어버리는군요.

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({StudentTest.class, CourseSessionTest.class})
public class AllTests {
}

ArrayList 다루면서 Generic도 살짝 나오네요. 만약 자바를 이 책으로 처음 시작한다면 아무래도 개념적인 이해를 돕기 위해 헤드 퍼스트를 같이 보는게 좋을 것 같습니다.

책에서 메소드 리팩토링의 기준을 말하는데 메소드는 한 줄에서 여섯 줄이어야 한다고 합니다. 그 이유는 메소드를 빠르게 이해하고 관리하기 위해서 라고 하구요. 더 길어지면 좀 더 작은 메소드로 분할하라는군요. 음.. 그러고 보면 봄싹 메소드들도 대략 4~5줄 정도 였던 것 같습니다.

그리고 주석은 잘못되거나 오해를 만들기 쉽기 때문에 코드 자체를 의미가 명확하게 짜라고 하는군요. 저자는 JavaDoc에도 이미 메소드나 필드 이름 자체가 나타내는 정도 말고 정말 모를만한 걸 적으라고 하는데 아마 애자일 사용하면 문서 작성이 줄어든다는게 이런 걸 염두해 두고 하는 말 같습니다. 그런데 비영어권에선 힘들지 않을런지..;

레슨 2 끝에 JavaDoc 생성하는 부분이 나오는데, 여기서 한글 깨짐 현상이 발생하더군요. 여기를 참고했는데 앞에 locale을 붙이면 에러나서 빼고 해봤는데 한글 깨짐 현상은 없어 졌습니다.

-encoding UTF-8 -charset UTF-8 -docencoding UTF-8

레슨 3에서 루프가 처음 등장하는데 바로 for-each입니다 ㄷㄷ 이 책은 Java 5를 추가한게 아니라 녹여넣었다는 느낌이 듭니다. 패키지가 복잡해 짐에 따라 패키지 별로 하나의 AllTests 클래스를 만들라고 합니다. 이렇게 만들다 보면 가장 최상위 패키지에 있는 AllTests 하나만 실행해 주면 모든 테스트를 한 번에 실행할 수 있습니다. 음.. 그런데 이런 경우 Maven에서 mvn test 할 경우 테스트가 중복되더군요. 테스트 할 때 특정 클래스를 지정할 수도 있지만 그냥 AllTests 클래스를 굳이 만들지 않아도 될 것 같습니다.

오늘 기선이형 블로그에 메이븐 포스트를 보다가 토비님 포스트도 보게 됐습니다. 그래서 책에 있는 Ant 버리고 Maven을 다시 써보려고 했죠. 이제 Phase, Goal이 뭔지 알 것 같습니다. 박재성님의 자바 프로젝트 필수 유틸리티 책도 큰 도움이 됐네요. (yes24는 배송의 끝인듯)

레슨 3 연습문제 화면 출력하는 부분에서 고민 좀 했습니다. 결국 List<List<Pawn>> 식으로 처리했는데 왠지 저자 의도는 이게 아닌거 같은데 말이죠;;

Agile Java Lesson 1

기본
지름길로 빠르게 배울 수 있는 자바 프로그래밍

Agile Java

자바와의 인연을 이 책으로 시작했으면 어땠을까.. 하고 생각해 봤습니다. 책 시작하자 마자 Hello World 찍고 그 다음이 JUnit 이라니.. ㄷㄷ 저자의 말대로 public static void main(String[] args) 이 한 줄을 이해하기 위해서 정말 많은 것들이 필요하긴 하죠. C 언어 배울 때도 그냥 ‘무시’ 하고 가라고 들었지만..

아예 이 책 접근 방식처럼 필요한 걸 먼저 보는게 더 좋아 보이네요. 책은 JUnit 3을 사용했지만 아마 JUnit 4를 사용했으면 더 좋지 않았을까 하는 생각이 들었습니다.

레슨 1을 쭉 보면서 역자 분이 왠지 OOP를 잘 모르시는 분 같다는 생각이 들긴 하지만 뭐.. 크게 신경 쓰이는 부분은 아닙니다.

전 보통 국내 입문서나 대학교 수업에서 배우는 ‘붕어빵’ 예제가 별로 와닿지 않았었습니다. 붕어빵 틀에서 붕어빵을 찍어 내는게 클래스와 객체 관계라는건 납득되지만 그 다음 객체가 클래스에서 정의해 둔 메소드를 호출하는건 어떻게 이해해야 할까요? 그래서 헤드 퍼스트에 있는 리모트 컨트롤러(…) 예제나 이 책에 나와있는 Door 예제가 더 낫다고 생각합니다.

책에 나와있는 몇 가지 개념을 정리해 보면..

  • 객체 지향 프로그래밍: 실세계의 물체, 혹은 객체에 대한 추상화를 하는 것
  • 객체: 어떤 개념에 대한 코드 단위의 추상화. 단, 실세계의 내용을 너무 깊이 코드에 넣지 않도록 조심해야 한다.
  • 추상화: 핵심적인 것을 강조하고, 불필요한 것을 제거한다.
  • 캡슐화: 어떤 경우에도, 메시지를 보내는 쪽은 추상적인 개념에만 관심이 있다.
  • 다형성: 어떤 구현을 대신해서 다른 구현을 넣어도 클라이언트 코드에서는 알지 못하고 관련이 없어야 하는 속성
  • 클래스: 어떤 객체 그룹의 공통성을 정의하는 방법
  • 상속: 시스템 내의 다른 클래스를 기본으로 특별한 동작을 추가하는 클래스 간의 관계
TDD 사이클

TDD 사이클

TDD를 통해 예제를 점진적으로 수정해 나가는 부분이 참 재미있습니다. 다음 레슨이 기대가 되네요.

끝으로 레슨 1에서 가장 기억에 남는 부분

가장 중요한 일은 실제로 작동되는 코드를 작성하는 것이며 이런 코드 작성을 위해 실제 코드를 작성하기 전에 테스트를 먼저 작성해야 한다. 두 번째 일은 코드를 깨끗하게 유지하는 것이다. 코드를 깨끗하게 유지하기 위해선 중복이 없어야 하고 코드의 목적을 명확히 해야한다.