객체 간 이동이 가능한 상황 및 상황별 리팩토링 기법

  1. 기능을 넣을 적절한 위치를 찾는 경우
  2. 메서드 이동(Move Method)
  3. 필드 이동(Move Field)

  4. 방대해진 클래스의 정리 또는 클래스 기능이 너무 적은 경우
  5. 클래스 추출(Extract Class) - 기능이 너무 많아 방대해진 클래스
  6. 클래스 직접 삽입(Inline Class) - 리팩토링 결과로 클래스 내에 기능이 너무 작아진 경우 다른 클래스로 합침

  7. 다른 클래스(대리 클래스)를 이용할 경우
  8. 대리 객체 은폐(Hide Delegate) - 대리 클래스가 사용 중이라는 것을 외부에 감춤
  9. 과잉 중개 메서드 제거(Remove Middle Man) - 대리 객체 은폐시 대리 클래스를 사용 중인 클래스의 인터페이스가 변경될 때

  10. 클래스의 원본 코드에 접근할 수 없는 상황에서 수정 불가능한 클래스의 기능을 이동해야 할 때
  11. 외래 클래스에 메서드 추가(Introduce Foreign Method) - 기능을 이동할 메서드가 한두개 뿐일 때
  12. 국소적 상속확장 클래스 사용(Introduce Local Extension) - 기능을 이동할 메서드가 세개 이상일 때

메서드 이동 (Move Method)

메서드가 자신이 속한 클래스보다 다른 클래스의 기능을 더 많이 사용할 때
그 메서드가 가장 많이 이용하는 클래스 안에서 비슷한 내용의 메서드를 작성
기존 메서드는 간단한 대리 메서드로 전환하던지 아예 삭제

동기

언제 적용하는게 좋을까?

  • 클래스에 기능이 너무 많을 경우

  • 클래스가 다른 클래스와 과하게 연동되어 의존성이 지나칠 때
    자신이 속한 객체보다 다른 객체를를 더 많이 참조하는 메서드

어떤 이점을 가져올 수 있을까?

  • 클래스가 간결해짐

  • 기능의 구현이 명확해짐

방법

  1. 원본 클래스에 정의되어 있는 원본 메서드에 사용된 모든기능을 검사하고 그 기능들도 옮겨야할지를 판단한다.
    • 옮길 메서드에서만 사용되는 기능은 함께 옮겨야함
    • 옮길 메서드와 함께 옮길 기능이 다른 메서드에서도 사용된다면 그 메서드도 함께 옮기는 것을 고려함
  2. 원본 클래스의 하위클래스와 상위클래스에서 그 메서드에 대한 다른 선언이 있는지 검사
    • 다른 선언이 있다면 대상 클래스에도 재정의를 넣을 수 있을 때만 옮길 수 있을지도 모름
  3. 원본 메서드에 대응되는 대상 메서드를 대상 클래스에 선언
    • 대상 클래스 안에 있을 때 더욱 어울리는 다른 이름으로 메서드를 정의해도 됨
  4. 원본 메서드의 코드를 대상 메서드에 복사한 후, 대상 클래스 안에서 잘 돌아가게끔 대상 메서드를 수정
    • 대상 메서드가 원본 객체를 사용한다면 대상 메서드 안에서 원본 객체를 참조할 방법을 정한다.
    • 대상 클래스에 원본 클래스를 참조
    • 원본 객체를 매개변수로 전달 * 대상 메서드에 예외처리 코드가 있다면 예외를 논리적으로 어느 클래스가 처리할지 결정
  5. 대상 클래스를 컴파일

  6. 원본 객체에서 대상 객체를 참조할 방법의 결정
    • 대상 클래스를 참조하는 속성이나 메서드가 있는지 확인하고 없으면 만든다.
  7. 원본 메서드를 위임 메서드로 전환

  8. 컴파일과 테스트

  9. 위임 메서드로 변경된 원본 메서드를 삭제하던지 위임 메서드로 계속 사용하도록 함
    • 참조가 많을 때는 원본 메서드를 위임 메서드로 내버려두는 방법이 편함
  10. 원본 메서드를 삭제할 때는 기존의 참조를 전부 대상 메서드 참조로 수정
    • 찾아바꾸기 기능을 이용해 한 번 실행으로 일괄적으로 바꿔도 됨
  11. 컴파일과 테스트

예제

public class Account {
    private AccountType _type;
    private int _daysOverdrawn;

    ...

    double overdraftCharge() {
        if (_type.isPremium()) {
            double result = 10;
            if (_daysOverdrawn > 1) {
                result += (_daysOverdrawn - 7) * 0.85;
            }
            return result;
        } else {
            return _daysOverdrawn * 1.75;
        }
    }

    double bankCharge() {
        double result = 4.5;
        if (_daysOverdrawn > 0) {
            result += overdraftCharge();
        }
        return result;
    }

    ...
}

public class AccountType {
    ...
}

리팩토링 목표

  • 계좌 유형(AccountType)이 추가적으로 생길 수 있는 상황에서 각 계좌 유형에 따라 다를 수 있는 overdraftCharge 메서드를 AccountType 클래스로 이동
public class Account {
    ...
    // 삭제
    //double overdraftCharge() {
    //    return _type.overdraftCharge(_daysOverdrawn);
    //}
    
    double bankCharge() {
        double result = 4.5;
        if (_daysOverdrawn > 0) {
            result += _type.overdraftCharge(_daysOverdrawn);
        }
        return result;
    }
    ...
}

public class AccountType {
    ...
    double overdraftCharge(int daysOverdrawn) {
        if (isPremium()) {
            double result = 10;
            if (daysOverdrawn > 7) {
                result += (daysOverdrawn - 7) * 0.85;
            }
            return result;
        } else {
            return daysOverdrawn * 1.75;
        }
    }
    ...
}


필드 이동 (Move Field)

어떤 필드가 자신이 속한 클래스보다 다른 클래스에서 더 많이 사용될 때는
대상 클래스 안에 새 필드를 선언하고 그 필드 참조 부분을 전부 새 필드 참조로 수정

동기

언제 적용하는게 좋을까?

  • 어떤 필드가 자신이 속한 클래스보다 다른 클래스에 있는 메서드들이 많이 참조하여 사용하는 경우
    인터페이스에 따라 메서드를 옮길 수 도 있겠지만, 메서드의 위치가 올바르다고 판단되는 경우에는 필드를 옮긴다.

  • 클래스 추출(Extract Class) 를 실시하는 경우
    필드의 이동이 메서드의 이동보다 우선한다.

방법

  1. 필드가 public이면 필드 캡슐화(Encapsulate Field) 기법을 실시
    • 필드에 자주 접근하는 메서드를 옮기게 될 가능성이 높거나 그 필드에 많은 메서드가 접근할 때는 필드 자체 캡슐화(Self Encapsulate Field)를 실시
  2. 컴파일과 테스트

  3. 대상 클래스 안에 읽기/쓰기 메서드와 함께 필드를 작성

  4. 대상 클래스를 컴파일

  5. 원본 객체에서 대상 객체를 참조할 방법을 정함
    • 원본 클래스에서 필드가 옮겨진 대상 클래스에 대한 객체를 참조할 수 있도록 방법을 마련
  6. 원본 클래스에서 필드를 삭제

  7. 원본 필드를 참조하는 모든 부분을 대상 클래스에 있는 적절한 메서드를 참조하게 수정
    • 필드에 대한 읽기: getter 를 이용
    • 핃들에 대한 쓰기: setter 를 이용
  8. 컴파일과 테스트

예제

public class Account {
    ...
    private AccountType _type;
    private double _interestRate;
    
    double interestForAmount_days(double amount, int days) {
        return _interestRate * amount * days / 365;
    }
    ...
}

public class AccountType {
    ...
}

리팩토링 목표

  • 이자율을 나타내는 _interestRate 필드를 계좌 유형에 따라 이자율이 변경될 수 있다는 생각하에 AccountType 클래스로 옮기려 함

필드 캡슐화 (Encapsulate Field)

public class AccountType {
    ...
    private double _interestRate;

    public double getInterestRate() {
        return _interestRate;
    }

    public void setInterestRate(double interestRate) {
        _interestRate = interestRate;
    }
    ...
}

public class Account {
    private AccountType _type;
    //private double _interestRate; 삭제

    ...
    public double interestForAmount_days(double amount, int days) {
        return _type.getInterestRate() * amount * days / 365;
    }
    ...
}

필드 자체 캡슐화 (Self Encapsulate Field)

많은 메서드가 interestRate 필드를 사용한다면 내부적인 변화에 대응하기 쉽도록 필드에 대한 캡슐화(getter, setter)를 생성하여 사용한다.

public class Account {
    private AccountType _type;
    ...
    public double interestForAmount_days(double amount, int days) {
        return getInterestRate() * amount * days / 365;
    }
    
    private void setInterestRate(double arg) {
        _type.setInterestRate(arg);
    }
    
    private double getInterestRate() {
        return _type.getInterestRate();
    }
    ...
}

클래스 추출 (Extract Class)

두 클래스가 처리해야 할 기능이 하나의 클래스에 들어 있을 경우
새 클래스를 만들고 기존 클래스의 관련 필드와 메서드를 새 클래스로 옮김

동기

클래스는 확실하게 추상화 되어야하며, 두세 가지의 명확한 기능을 담당해야 함

방대한 클래스

  • 개발자는 클래스에 점증적으로 기능이나 데이터를 추가 → 클래스의 방대화
  • 별도의 클래스로 만들기에는 사소한 기능의 추가가 계속되면 클래스의 복잡도가 증가

어느 부분에 적용해야 하나?

  • 데이터의 일부분과 메서드의 일부분이 한 덩어리인 경우
  • 함께 변화된 코드들
  • 유난히 의존적인 데이터의 일부분

판단 방법
데이터나 메서드를 하나 제거하면 어떻게 될지, 다른 필드와 메서드를 추가하는 건 합리적이지 않은지 자문해 본다.

방법

  1. 클래스의 기능 분리 방법을 결정

  2. 분리한 기능을 넣을 새 클래스를 작성

  3. 원본 클래스에서 새 클래스로의 링크를 생성

  4. 옮길 필드마다 필드 이동(Move Field)을 적용

  5. 필드를 하나씩 옮길 때마다 컴파일과 테스트를 실시

  6. 메서드 이동(Move Method)을 실시해서 원본 클래스의 메서드를 새 클래스로 이동
    하급메서드(피 호출 메서드)부터 시작해서 상급 메서드(호출 메서드)에 적용

  7. 메서드 이동을 실시할 때마다 테스트를 실시

  8. 각 클래스를 다시 검사해서 인터페이스를 줄임
    • 양방향 링크가 있다면 가능하다면 단방향으로 바꿈
  9. 여러 곳에서 클래스에 접근할 수 있게 할지 결정
    • 여러 곳에서 접근할 수 있게 할 경우 새 클래스를 참조 객체나 변경불가 값 객체로서 공개할지 여부를 결정

예제

public class Person {
    private String _name;
    private String _officeAreaCode;
    private String _officeNumber;

    ...

    public String getTelephoneNumber() {
        return ("(" + _officeAreaCode + ")" + _officeNumber);
    }

    String getOfficeAreaCode() {
        return _officeAreaCode;
    }

    void setOfficeAreaCode(String officeAreaCode) {
        _officeAreaCode = officeAreaCode;
    }

    String getOfficeNumber() {
        return _officeNumber;
    }

    void setOfficeNumber(String officeNumber) {
        _officeNumber = officeNumber;
    }

    ...
}

리팩토링 목표
Person 클래스에서 전화번호 기능을 따로 하나의 클래스로 분리

public class Person {
    private String _name;
    private TelephoneNumber _officeTelephone = new TelephoneNumber();
    
    ...

    public String getName() {
        return _name;
    }
    
    public String getTelephoneNumber() {
        return _officeTelephone.getTelephoneNumber();
    }

    // 위임메서드
    TelephoneNumber getOfficeTelephone() {
        return _officeTelephone;
    }

    ...

    class TelephoneNumber {
        private String _areaCode;
        private String _number;

        public String getTelephoneNumber() {
            return ("(" + _areaCode + ")" + _number);
        }

        String getAreaCode() {
            return _areaCode;
        }

        void setAreaCode(String areaCode) {
            _areaCode = areaCode;
        }

        String getNumber() {
            return _number;
        }

        void setNumber(String number) {
            _number = number;
        }
    }
}

TelephoneNumber클래스의 공개 정도

getOfficeTelephone 위임 메서드를 이용하며 패키지 내에서만 공개

클래스 공개시 객체 변경에 대처 방식

  • 어디에서든 공개된 클래스의 객체가 변경될 수 있음을 받아들임
  • 어느 주체든 원본 클래스를 거치지 않고는 클래스의 값을 변경하지 못하도록 함
  • 클래스를 외부로 전달하기 전에 객체를 복제하여 원 객체가 변경되지 않도록 함
    코드를 보는 이들은 값의 변경이 가능한 것으로 착각할 수 있으므로 클라이언트 간의 왜곡 문제가 발생할 수 있음

클래스 내용 직접 삽입 (Inline Class)

클래스에 기능이 너무 적을 경우
그 클래스의 모든 기능을 다른 클래스로 합치고 원본 클래스는 삭제

동기

클래스 내용 직접(Inline Class) 와 클래스 추출(Extract Class)는 반대 개념

적용 대상

클래스의 대부분의 기능이 리팩토링을 통해서 다른 곳으로 옮겨져 클래스가 더이상 존재할 이유가 없어진 클래스

방법

  1. 원본 클래스의 public 메서드를 합칠 클래스에 선언하고 전부 원본 클래스에 위임
    • 원본 클래스의 메서드 대신 별도의 인터페이스가 알맞다고 판단되면 클래스 내용 직접 삽입을 실시하기 전에 인터페이스 추출(Extract Interface) 기법을 실시
  2. 원본 클래스를 참조하고 있는 부분들을 합칠 클래스를 참조하도록 수정
    • 원본 클래스를 private로 선언하고 패키지 밖에서 참조하고 있는 부분들을 삭제
    • 컴파일러가 껍데기만 남은 원본 클래스를 참조하고 있는 부분들을 찾아낼 수 있게 원본 클래스명을 변경
  3. 컴파일과 테스트를 실시

  4. 메서드 이동(Move Method)과 필드 이동(Move Field)을 실시해 기능들을 합칠 클래스로 이전

  5. 원본 클래스를 삭제

예제

public class Person {
    private String _name;
    private TelephoneNumber _officeTelephone = new TelephoneNumber();
    ...
    public String getName() {
        return _name;
    }

    public String getTelephoneNumber() {
        return _officeTelephone.getTelephoneNumber();
    }
    
    TelephoneNumber getOfficeTelephone() {
        return _officeTelephone;
    }
    ...
}

public class TelephoneNumber {
    private String _areaCode;
    private String _number;
    ...
    public String getTelephoneNumber() {
        return ("(" + _areaCode + ")" + _number);
    }

    String getAreaCode() {
        return _areaCode;
    }

    void setAreaCode(String arg) {
        _areaCode = arg;
    }

    String getNumber() {
        return _number;
    }

    void setNumber(String arg) {
        _number = arg;
    }
    ...
}

public class Client {
    public static void main() {
        Person martin = new Person();
        martin.getOfficeTelephone().setAreaCode("781");
        ...
    }
}

리팩토링 목표
존재할 의미가 없는 TelephoneNumber 클래스를 Person 클래스로 병합

public class Person {
    ...
    String getAreaCode() {
        return _officeTelephone.getAreaCode();
    }

    void setAreaCode(String arg) {
        _officeTelephone.setAreaCode(arg);
    }

    String getNumber() {
        return _officeTelephone.getNumber();
    }

    void setNumber(String arg) {
        _officeTelephone.setNumber(arg);
    }
    ...
}

public class Client {
    public static void main() {
        Person martin = new Person();
        martin.setAreaCode("781");
        ...
    }
}

대리 객체 은폐 (Hide Delegate)

클라이언트가 객체의 대리 클래스를 호출할 경우
대리 클래스를 감추는 메서드를 서버에 작성하자

동기

캡슐화란 객체가 시스템의 다른 부분에 대한 정보를 일부분만 알 수 있도록 함
→ _변경이 일어날 때 전달해야 하는 객체가 줄어들어 변경이 용이_해짐

  • 클라이언트 객체에서 서버 객체의 필드 중 하나에 정의된 메서드를 호출할 때 그 클라이언트는 이 대리 객체에 관하여 알아야 함
  • 이 대리 객체의 변경이 발생하면 클라이언트도 변경해야 할 가능성이 높아짐
  • 서버 객체에 대리 객체를 감추는 위임 메서드를 두면 클라이언트 객체에 대리 객체를 숨길 수 있음 → 대리 객체의 변경이 클라이언트에 영향을 미치지 않음

방법

  1. 대리 객체에 들어 있는 각 메서드를 대상으로 서버에 간단한 위임 메서드를 작성

  2. 클라이언트를 수정해서 서버를 호출하도록 수정
    • 클라이언트 클래스가 서버 클래스와 같은 패키지에 들어 있지 않다면 대리 클래스의 메서드에 대한 접근을 같은 패키지에 든 클래스만 접근할 수 있게 수정하는 것을 고려
  3. 각 메서드를 수정할 때마다 컴파일과 테스트를 실시

  4. 대리 객체를 읽고 써야 할 클라이언트가 하나도 남지 않게 되면, 서버에서 대리 객체를 제공하는 읽기/쓰기 메서드를 삭제

  5. 컴파일과 테스트를 실시

예제

public class Person {
    Department _department;
    ...
    public Department getDepartment() {
        return _department;
    }

    public void setDepartment(Department arg) {
        _department = arg;
    }
    ...
}

public class Department {
    private String _chargeCode;
    private Person _manager;
    ...
    public Department(Person manager) {
        _manager = manager;
    }

    public Person getManager() {
        return _manager;
    }
    ...
}

// In client
Person manager = john.getDepartment().getManager();

리팩토링 목표
클라이언트에서 대리 객체인 Department 클래스를 은닉함

public class Person {
    ...
    // 삭제
    //public Department getDepartment() {
    //    return _department;
    //}

    // 삭제
    //public void setDepartment(Department arg) {
    //    _department = arg;
    //}
    ...

    public Person getManager() {
        return _department.getManager();
    }
}

// In client
Person manager = john.getManager();

과잉 중개 메서드 제거 (Remove Middle Man)

클래스에 자잘한 위임이 너무 많을 경우
대리 객체를 클라이언트가 직접 호출하도록 함

동기

대리 객체 은폐(Hide Delegate) 와 과잉 중개 메서드 제거(Remove Middle Man) 은 반대 개념

대리 객체의 캡슐화는 많은 장점을 가지지만,
클라이언트가 대리 객체의 기능을 사용할 때마다 서버에 위임 메서드를 만들어야 하는 단점도 존재

적절한 은폐의 정도

  • 시간을 두고 조금씩 수정해 나감
  • 기존에는 적절한 것으로 보이던 캡슐화가 현재는 불필요할 수 있을 수 있으므로 필요할 때마다 보수

방법

  1. 대리 객체에 대한 접근 메서드를 작성

  2. 대리 메서드를 클라이언트가 사용할 때마다 서버에서 메서드를 제거하고 클라이언트에서 호출을 대리 객체에서의 메서드 호출로 교체

  3. 메서드를 수정할 때마다 테스트를 실시

예제

public class Person {
    ...
    public Person getManager() {
        return _department.getManager();
    }
    ...
}

// In client
Person manager = john.getManager();

리팩토링 목표
클라이언트에서 대리 객체인 Department 클래스를 직접 사용할 수 있도록 함

public class Person {
    ...
    public Department getDepartment() {
        return _department;
    }
    ...
}

// In client
Person manager = john.getDepartment().getManager();

일부 클라이언트에서는 위임 메서드를 그대로 사용하고 일부 클라이언트에서는 대리 객체를 사용할 경우도 있을 수 있으므로
위임 메서드를 그대로 둬야할 경우도 있음


외래 클래스에 메서드 추가 (Introduce Foreign Method)

사용 중인 서버 클래스에 메서드를 추가해야 하는데 그 클래스를 수정할 수 없을 경우
클라이언트 클래스 안에 서버 클래스의 인스턴스를 첫 번째 인자로 받는 메서드를 작성

동기

  • 원본 클래스의 수정이 불가한 경우에 중복적으로 작성해야 코드
  • 리팩토링을 실시할 때 새로 만드는 메서드를 외래 메서드로 만들어서 원본 메서드인 서버 메서드에 있어야 하는 것을 드러낼 수 있음
  • 임시 방편으로 원래 있어야 할 위치로 보내는 것을 목표로 해야 함

방법

  1. 필요한 기능의 메서드를 클라이언트 클래스 안에 작성
    • 메서드 내에서는 클라이언트 클래스의 어떠한 기능에도 접근해서는 안됨
      → 값이 필요할 때는 매개변수로 전달
  2. 서버 클래스의 인스턴스를 첫 번째 매개변수로 만듦

  3. 그 메서드에 ‘서버 클래스에 있을 외래 메서드’ 같은 주석을 작성
    • 외래 메서드를 옮길 일이 생겼을 경우 문자열 검색 기능의 활용이 가능

예제

Calendar newStart1 = (Calendar)end1.clone();
newStart1.add(Calendar.DAY_OF_MONTH, 1);

Date newStart2 = new Date(end2.getYear(), end2.getMonth(), end2.getDate() + 1);

리팩토링 목표
중복적으로 발생할 수 있는 날짜 관련 처리를 확장하는 메서드를 생성

Calendar newStart1 = getNextDay(end1);
Date newStart2 = getNextDay(end2);
...
private static Calendar getNextDay(Calendar arg) {
    Calendar newStart = (Calendar)arg.clone();
    newStart.add(Calendar.DAY_OF_MONTH, 1);
    return newStart;
}

private static Date getNextDay(Date arg) {
    Date newStart = new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
    return newStart;
}

국소적 상속확장 클래스 사용 (Introduce Local Extension)

사용 중인 서버 클래스에 여러 개의 메서드를 추가해야 하는데 클래스를 수정할 수 없을 땐
새 클래스를 작성하고 그 안에 필요한 여러개의 메서드를 작성
이 상속확장 클래스를 원본 클래스의 하위클래스나 래퍼클래스로 만듦

동기

원본 클래스의 수정이 불가능할 때 필요한 메서드가 한 두개라면 외래 클래스에 메서드 추가(Introduce Foreign Method) 기법을 실시
하지만 그 수가 세 개 이상이라면 메서드를 적당한 곳에 모아 두어야 함

해결을 위한 방법: 하위클래스화(subclassing) + 래퍼화(wrapping) → 국소적 상속확장 클래스(Local Extension)

국소적 상속확장 클래스는 별도의 클래스이지만 원본 클래스의 하위타입이므로, 원본 클래스의 모든 기능을 사용할 수 있으면서 추가 기능도 사용할 수 있게 됨 → 원본 클래스를 사용하는 게 아니라 국소 상속확장 클래스를 사용하게 됨

사용하면 얻을 수 있는 이점

메서드와 데이터가 체계적으로 묶여야 한다는 원칙이 지켜짐

하위클래스(subclass)

  • 작업량 줄어듬
  • 객체 생성 시점에서 하위클래스의 인스턴스로 만들어야 함

래퍼클래스(wrapper)

  • 원본클래스를 래핑하고 있음
  • 래퍼클래스의 변경이 원본클래스에 반영되고, 그 반대의 경우도 반영됨

방법

  1. 상속확장 클래스를 작성한 후 원본 클래스의 하위클래스나 래퍼클래스로 만듦

  2. 상속확장 클래스에 변환 생성자를 만듦
    • 생성자에서 원본클래스를 인자로 받음
    • 하위클래스: 적절한 상위클래스 생성자를 호출
    • 래퍼클래스: 대리 필드에 그 인자를 할당
  3. 상속확장 클래스에 새 기능을 추가

  4. 필요한 위치마다 원본 클래스를 상속확장 클래스로 수정

  5. 원본 클래스 용으로 정의된 외래 메서드를 전부 상속확장 클래스로 옮김

예제

// 하위 클래스
class MfDateSub extends Date {
    public MfDateSub getNextDay()...
    public int dayOfYear()...
}

// 래퍼 클래스
class MfDateWrap {
    private Date _original;
    ...
}

C# 의 확장 메서드

C# 의 확장 메서드는 원본 클래스의 파생(확장,하위)클래스를 만들지 않고 메서드를 추가하는 방법
확장 메서드는 특수한 정적 메서드 이지만 원본 클래스의 인스턴스 메서드인 것처럼 호출됨

  • 정적 클래스 내에 정의해야 함
  • 첫 번째 매개변수는 this 키워드를 포함한 확장하고자 하는 원본 형식을 이용
public static class DateTimeExtensions
{
    public static DateTime NextDay(this DateTime date)
    {
        return date.AddDays(1);
    }

    public static DateTime PreviousDay(this DateTime date)
    {
        return date.AddDays(-1);
    }
}

[TestClass]
public class DateTimeExtensionsUnitTest
{
    [TestMethod]
    public void NextDayTest()
    {
        // Arrange
        DateTime date1 = new DateTime(2015, 2, 28);
        DateTime date2 = new DateTime(2016, 2, 28);

        // Act
        DateTime next1 = date1.NextDay();
        DateTime next2 = date2.NextDay();

        // Assert
        Assert.AreEqual<int>(1, next1.Day);
        Assert.AreEqual<int>(29, next2.Day);
    }

    [TestMethod]
    public void PreviousTest()
    {
        // Arrange
        DateTime date1 = new DateTime(2015, 3, 1);
        DateTime date2 = new DateTime(2016, 3, 1);

        // Act
        DateTime prev1 = date1.PreviousDay();
        DateTime prev2 = date2.PreviousDay();

        // Assert
        Assert.AreEqual<int>(28, prev1.Day);
        Assert.AreEqual<int>(29, prev2.Day);
    }
}