리팩터링 | 마틴 파울러 | 한빛미디어- 교보ebook

코드 구조를 체계적으로 개선하여 효율적인 리팩터링 구현하기, 20여 년 만에 다시 돌아온 마틴 파울러의 리팩터링 2판 리팩터링 1판은 1999년 출간되었으며, 한국어판은 2002년 한국에 소개되었다

ebook-product.kyobobook.co.kr

개요

가변 데이터는 모두 캡슐화하는 것이 좋다. 그러면 데이터 구조가 언제 수정되는지 파악하기 쉽다. 파악이 쉽다는 것은 데이터 구조를 변경하기도 쉬워진다는 말과 같다.

 

컬렉션을 캡슐화할 흔히하는 실수는 컬렉션을 게터로 그대로 반환하는 것이다. 개발자가 캡슐화를 했다고 착각하지만 컬렉션에 저장된 정보를 클라이언트가 바꿀 수가 있어 캡슐화가 깨진다.

위와 같은 문제를 방지하려면, 컬렉션 요소 추가/삭제 가지 기능을 직접 구현해야 한다.

 

컬렉션을 수정하지 않는 다는 규칙을 정해두고, 사용하는 경우도 있는데 그런 관례같은 것에 의존하는 것보다 컬렉션 복사본을 리턴해 실수로 컬렉션을 수정해도 영향을 여지를 없애는 것이 중요하다.

 

내부 컬렉션을 수정하지 못하게 하는 방법 하나는 컬렉션을 리턴하지 말고, 컬렉션을 가진 클래스가 제공한 컬렉션 제어용 메서드 사용이 있다.

방법의 단점은 표준화된 인터페이스가 아닌 별도 메서드를 쓴다는 점에서 기존 라이브러리와 호환성이 크게 떨어진다.

 

다른 방법으로 읽기 전용 컬렉션을 만들어 리턴하는 것이다.

 

가장 흔한 방식은 컬렉션 복사본을 리턴하는 것이다.

 

여기서 중요한 것은 표준화된 방식에 있다. 가지 방식만 적용해서 사용해야 한다.

 

예시

class Person{
    constructor(name){
        this._name = name;
        this._courses = [];
    }
    get name(){return this._name;}
    get courses(){return this._courses;}
    set courses(arg){this.courses = arg;}
}
class Course {
    constructor(name, isAdvanced){
        this._name = name;
        this._isAdvanced = isAdvanced;
    }
    get name(){return this._name;}
    get isAdvanced(){return this._isAdvanced;}
}
//클라이언트 Person이 제공하는 수업 컬렉션에서 수업 정보 얻음
let numAdvancedCourses;
numAdvancedCourses = aPerson.courses
    .filter(c=>c.isAdvanced)
    .length;


//클라이언트
const basicCourseNames = readBasicCourseNames(filename);
aPerson.courses = basicCourseNames.map(name => new Course(name, false));
//Person에서 게터로 수업컬렉션을 못가져오지만
//내가 수업 컬렉션을 세터로 설정하면, 수업 컬렉션을 수정해 캡슐화가 깨질 수 있다.


class Person{
    constructor(name){
        this._name = name;
        this._courses = [];
    }
    get name(){return this._name;}
    get courses(){return this._courses;}
    set courses(arg){this.courses = arg;}
    //제대로된 캡슐화를 위해 수업추가/제거 메서드 추가
    addCourse(aCourse){
        this._courses.push(aCourse);
    }
    removeCourse(aCourse, fnIfAbsent= () => {throw new RangeError();}){
        const index = this._courses.indexOf(aCourse);
        if(index=== -1) fnIfAbsent();
        else this._courses.splice(index,1);
    }
}


//만들어둔 메서드를 사용해야해서, 이제 이방식으론 저장 못한다.
// aPerson.courses = basicCourseNames.map(name => new Course(name, false));
for(const name of basicCourseNames){
    aPerson.courses.addCourse(new Course(name,false));
}
    //세터를 제거한다. 만약 제공해야한다면 복사본을 저장한다. 권장하는 것은 세터제거
    // set courses(aList){this.courses = _.cloneDeep(aList);}
    //사본을 제공한다.
    get courses(){return _.cloneDeep(this._courses);}
class Person{
    constructor(name){
        this._name = name;
        this._courses = [];
    }
    get name(){return this._name;}
    get courses(){return _.cloneDeep(this._courses);}
    addCourse(aCourse){
        this._courses.push(aCourse);
    }
    removeCourse(aCourse, fnIfAbsent= () => {throw new RangeError();}){
        const index = this._courses.indexOf(aCourse);
        if(index=== -1) fnIfAbsent();
        else this._courses.splice(index,1);
    }
}
class Course {
    constructor(name, isAdvanced){
        this._name = name;
        this._isAdvanced = isAdvanced;
    }
    get name(){return this._name;}
    get isAdvanced(){return this._isAdvanced;}
}
let numAdvancedCourses;
numAdvancedCourses = aPerson.courses
    .filter(c=>c.isAdvanced)
    .length;
const basicCourseNames = readBasicCourseNames(filename);
for(const name of basicCourseNames){
    aPerson.courses.addCourse(new Course(name,false));
}

 

컬렉션은 무조건 복제본을 만드는 것이 예상치 못한 수정으로 인한 버그를 막을 있다.

+ Recent posts