도서 개발 공부/Real-World Software Development

Chapter 3 입출금 내역 분석기 확장판 - 다양한 형식, 예외 처리, 빌드 도구

캐티시 2021. 2. 27. 23:22

5. 다양한 형식으로 내보내기

이전 단계에서 개방/폐쇄 원칙에 따라 검색 기능을 구현했다. 그 다음 과제는 입출금 목록의 요약 통계를 텍스트, HTML, JSON 처럼 다양한 형식으로 내보내도록 하는 것이다. 우선 도메인 객체의 형식을 정해야 한다. 여기서 도메인 객체란 비즈니스 문제를 분석하여 나오는 객체를 뜻한다. 입출금 내역 분석기에서는 거래 내역인 BankTransaction이 도메인 객체였으며, 이번 문제에서는 요약 통계가 도메인 객체가 될 것이다. 이 책에서는 다음의 4가지 도메인 객체를 소개한다.

  • 숫자

거래 내역 금액의 합을 계산하는 것 처럼 어떤 연산의 반환 결과가 필요한 경우가 있다. BankStatementProcessor클래스의 calculateTotalAmount() 메소드 처럼 간단히 double을 반환하도록 할 수 있다. 그러나 이렇게 하면 요구사항이 바뀔 때 유연하게 대처할 수 없다. 만약 다양한 결과를 포함할 필요가 있거나 연산 결과를 받아 사용하는 기능이 다른 형식의 데이터를 받는다면 코드 구현의 많은 부분이 바뀌어야 한다. 

 

  • 컬렉션

findTransactions() 메소드 처럼 어떤 도메인 객체의 목록을 원하는 경우도 있을 것이다. 이때 Iterable을 반환하면 상황에 맞춰서 처리하기 떄문에 유연성이 높아진다. 그러나 Iterable은 오직 컬렉션만 반환해야 한다는 제약이 있다. 

 

  • 특별한 도메인 객체

요약통계 결과를 반환하기 위해 요약 정보를 대표하는 SummaryStatistics라는 새로운 개념을 만들 수 있다. 이 방법을 이용하면 결합을 깰 수 있다. 새로운 요구 사항이 생겨서 추가 정보를 내보내야 할때도 기존의 코드를 바꿀 필요 없이 새로운 클래스의 일부로 구현할 수 있기 때문이다.

 

  • 더 복잡한 도메인 객체

요약 정보를 대표하는 개념이 Report 처럼 더 일반적이며 거래 내역 컬랙션 등 다양한 결과를 저장하도록 만들 수도 있다. 사용자의 요구사항이 무엇이고 더 복잡한 정보를 내보내야 하는지 여부에 따라 도메인 객체가 달라질 것이다. 그러나 어떤 상황이든 Report 객체를 생산하는 부분과 이를 소비하는 부분이 서로 결합하지 않는다는 큰 장점이 있다.

 

어떤 것을 선택할지에 대한 정답은 없다. 상황에 따라 달라질 것이다. 만약 요약 정보가 double형태로 절대 바뀌지 않을 것이라면 KISS 원칙에 의해 간단히 만들어도 되겠지만, 요구사항에 따라 달라지거나 추가될 것이라면 더 복잡한 방식을 생각해야 할 것이다. 이 책에서는 SummaryStatistics 라는 다음과 같은 도메인 객체를 만들어서 사용하기로 했다. 

 

public class SummaryStatistics {
    private final double sum;
    private final double max;
    private final double min;
    private final double average;

    public SummaryStatistics(final double sum, final double max, final double min, final double average) {
        this.sum = sum;
        this.max = max;
        this.min = min;
        this.average = average;
    }

    // 필드들에 대한 getter method 부분 생략
}

도메인 객체를 정했으면 이를 구현할 API가 필요하다. Exporter라는 인터페이스를 정의해서 다양한 형식으로 결과를 내보내는 코드가 다른 코드와 결합하지 않도록 해야한다. 개방/폐쇄 원칙에 따라 수정에 대해 닫혀있으면서 개방에 열려 있도록 하기 위함이다. 또 다른 형식으로 결과를 내보내도록 기능을 추가하고 싶다면, 간단히 Exporter 인터페이스를 구현하도록 하기만 하면 다른 코드는 수정할 필요가 없다. 

Exporter 인터페이스는 SummaryStatistics객체를 받아 결과를 내보내는 export() 메소드 하나만을 가진 함수형 인터페이스이다. 이때 이 메소드에 대해서도 잘 생각해야 한다. 다음과 같은 방식은 좋지 않다.

public interface Exporter {
	void export(SummaryStatistics summaryStatistics);
}

void 반환형은 읽는 사람으로 하여금 아무런 도움도 주지 않는다. 메소드가 무엇을 반환하는지 알 수 없으므로 기능 파악이 어렵기 때문이다. 메소드 자체가 아무것도 반환하지 않기 때문에 다른 구현 메소드에서 어던 작업을 진행하고, 이를 기록하거나 화면에 출력할 가능성이 크다. 또한 void를 반환하면 결과를 테스트하기도 어렵다. 예상한 값과 실제 결괏값을 비교하기가 매우 어려워진다. 그렇기 때문에 다음과 같은 인터페이스가 더 나은 방식이다

public interface Exporter {
	String export(SummaryStatistics summaryStatistics);
}

인터페이스도 정의했으니 특정한 형식으로 내보내는 기능만 구현하면 될 것이다. 다음은 HTML형식으로 결과를 내보내는 구현 코드이다.

public class HtmlExporter implements Exporter {
    @Override
    public String export(final SummaryStatistics summaryStatistics) {
        String result = "<!doctype html>";
        result += "<html lang='en'>";
        result += "<head><title>Bank Transaction Report</title></head>";
        result += "<body>";
        result += "<ul>";
        result += "<li><strong>The sum is</strong>: " + summaryStatistics.getSum() + "</li>";
        result += "<li><strong>The average is</strong>: " + summaryStatistics.getAverage() + "</li>";
        result += "<li><strong>The max is</strong>: " + summaryStatistics.getMax() + "</li>";
        result += "<li><strong>The min is</strong>: " + summaryStatistics.getMin() + "</li>";
        result += "</ul>";
        result += "</body>";
        result += "</html>";
        return result;
    }
}

HTML 형식의 String 객체로 결과를 반환하는 코드이다. Exporter 인터페이스를 구현하기 때문에 다른 코드에서는 상세한 방식에 대해서는 알 수 없고, 알 필요도 없을 것이다.

 

6. 예외 처리

이번 장에서 필요한 검색기능과 다양한 형식으로 결과를 내보내는 기능을 구현했다. 하지만 아직 남은 부분이 하나 있다. 프로그램이 뭔가 잘못되었을 때 이를 어떻게 처리할지 생각하는 것이다 필요하다. 예를 들어 데이터를 적절하게 파싱하지 못 하거나, 데이터가 들어있는 CSV 파일을 읽을 수 없다면? 또는 응용프로그램을 실행하는 하드웨어의 램이나 저장 공간이 부족하다면 어떻게 해야할까? 과제나 실습을 할때 자주 보던 것처럼 어디서 문제가 발생했는지 알려주는 스택 트레이스와 함께 오류 메시지가 콘솔창에 나타날 것이다. 실제 개발 상황에서는 이러한 오류 상황을 적절하게 처리하도록 예외 처리가 필요하다. BankStatementCSVParser를 예로 들어보자, 만약 파일의 CSV행이 3개보다 적거나 많은 열을 포함하거나, 개수가 맞더라도 자료 형식이 잘못되었다면 문제가 생길 것이다. 고전적인 C 프로그래밍에서는 수많은 if 조건을 추가해서 오류코드를 반환했다. 그러나 이 방법은 여러 단점이 존재한다. 일단 전역으로 공유된 가변 상태에 의존해서 최근에 발생한 오류를 검색해야 한다. 이 떄문에 코드 부분이 따로 분리되어 이해하기가 어렵고 유지보수도 어려워진다. 그리고 어떤 값이 실제 값인지 아니면 오류를 가리키는 값인지 구분하기 어렵다. 또한 오류 제어 흐름이 비즈니스 로직과 섞이면서 코드를 유지보수하거나 프로그램의 일부를 따로 테스트하기 어렵게 된다. 이 때문에, 자바에서는 예외를 언어 기능으로 추가해서 다음과 같은 장점을 제공한다.

  • 문서화 : 메소드 시그니처 자체에 예외를 지원한다.
  • 형식 안정성 : 개발자가 예외 흐름을 처리하고 있는지를 형식 시스템이 파악한다.
  • 관심사 분리 : 비즈니스 로직과 예외 회복이 각각 try/catch 블록으로 구분된다.

예외에는 두가지 종류가 존재한다.

  • 확인된 예외 : 회복해야 하는 대상의 예외. 메소드가 던질 수 있는 확인된 예외 목록을 선언하거나, try/catch 블록으로 해당 예외를 처리해야 한다.
  • 미확인된 예외 : 프로그램을 실행하면서 언제든지 발생할 수 있는 종류의 예외이다. 메소드 시그니처에 명시적으로 오류를 선언하지 않으면, 호출자도 이를 꼭 처리할 필요는 없다.

자바의 예외 계층도

Error와 RuntimeException 클래스는 미확인 예외이고, 보통 반드시 잡을 필요는 없다. Exception클래스는 일반적으로 프로그램에서 잡아서 회복해야 하는 오류이다. 

 

6-1. 예외의 패턴과 안티패턴

그렇다면 어떤 상황에서 어떤 종류의 예외를 사용해야 할까? 이에 대한 답도 정해지지 않았다. 상황에 따라 다르다. 계속해서 BankStatementCSVParser클래스를 예로 들자면, CSV 파일 파싱은 정해진 문법 파싱 기능과 데이터 검증(날짜 확인, 금액 확인 등)의 두가지 기능이 필요하다. 만약 CSV 파일에 잘못된 문법(콤마 없음 등)이 포함될 수 있다. 이 문제를 무시하고 계속 진행할 경우 더 파악하기 어려운 오류가 발생할 수 있다. 예외를 지원하는 코드를 구현하면 API에 문제가 생겼을 때 문제를 더 명확하게 진단할 수 있다.  예를 들어 다음은 CSV파일 한 줄의 문법을 진단하는 코드이다.

final String[] columns = line.split(",");

if (columns.length < EXPECTED_ATTRIBUTES_LENGTH) {
	throw new CSVSyntaxException();
}

CSVSyntaxException은 확인된 예외와 미확인된 예외 중에 어떤 것으로 사용해야 할까. 이때는 구현하는 API에서 CSVSyntaxException이 발생했을 때 프로그램이 회복되도록 강제해야 할지를 생각해봐야 한다. 만약 일시적으로 발생하는 오류라면 동작을 다시시도하거나 화면에 메시지를 출력하는 방식을 채택할 수 있다. 그러나 보통 비즈니스 로직 검증 시 발생한 문제(잘못된 형식이나 연산 등)는 불필요한 try/catch 구문을 줄일 수 있도록 미확인 예외로 결정한다. 이런 상황에서는 API 사용자에게 오류를 복구하도록 강제할 필요가 없다. 또한 시스템 오류가 발생했을 때도 사용자가 할 수 있는 일이 없으므로 미확인 오류로 지정한다. 

즉, 대다수의 예외를 미확인 예외로 지정하고, 꼭 필요한 상황에서만 확인된 예외로 지정해서 불필요한 코드를 줄여야한다.

정해진 문법 파싱 기능을 갖추었다면 이번에는 데이터 검증 문제를 살펴보자. 검증 로직은 어디에 추가해야 할까? BankStatement 객체를 생서하는 곳에 검증 코드를 추가할 수도 있지만, 다음과 같은 이유로 전용 Validator 클래스를 만드는 것이 좋다.

  • 검증 로직을 재사용하기 때문에 코드를 중복하지 않는다.
  • 시스템의 다른 부분도 같은 방법으로 검증할 수 있다.
  • 로직을 독립적으로 유닛 테스트 할 수 있다.
  • 유지보수와 이해를 용이하게 만드는 단일 책임 원칙을 따른다.

검증 관련 예외에서는 흔히 발생하는 두가지 안티 패턴이 있다.

    1. 과도하게 세밀함

입력에서 발생할 수 있는 모든 경계 상황을 고려하고, 각각의 경계 상황을 별도의 확인된 예외로 변환하는 방법이 있다. 

public class OverlySpecificBankStatementValidator {
	private String description;
    private String date;
    private String amount;
    
    public OverlySpecificBankStatementValidator(final String description
    									, final String date, final String amount) {
		this.description = Objects.requireNonNull(description);
        this.date = Objects.requireNonNull(date);
        this.amount = Objects.requireNonNull(amount);
	}
    
    public boolean validate() throws DescriptionTooLongException, InvalidDateFormat, DateInTheFutureException, InvalidAmountException {
	if (this.description.length() > 100) {
        	throw new DescriptionTooLongException();
        }
        
        final LocalDate parsedDate;
        try {
        	parsedDate = LocalDate.parse(this.date);
        } catch (DateTImeParseException e) {
        	throw new InvalidDateFormat();
        }
        if (parsedDate.isAfter(LocalDate.now())) {
        	throw new DateInTheFutureException();
        }
        
        final double amount;
        try {
        	amount = Double.parseDouble(this.amount);
        } catch (NumberFormatException e) {
        	throw new InvalidAmountException();
        }
        return true;
    }
}

위의 DescriptionTooLongException, InvalidDateFormat, DateInTheFutureException, InvalidAmountException은 모두 사용자가 정의한 확인된 예외이다. 이 방법을 적용하면 각각의 예외에 적합하고 정확한 회복 기법을 구현할 수 있지만, 너무 많은 설정 작업이 필요하고, 여러 예외를 선언해야 하며, 사용자가 이 모든 예외를 처리해야하기 때문에 생산성이 떨어진다. 즉, 사용자가 API를 쉽게 사용할 수 없다. 게다가 여러 오류가 발생했을 때 발생한 모든 오류 목록을 모아 제공할 수 없다.

 

    2. 과도하게 덤덤함

1번의 방법과 정반대로 다음처럼 모든 예외를 IllegalArgumentException 등의 미확인 예외로 지정하는 방식도 있다. 

 public boolean validate() {
	if (this.description.length() > 100) {
        	throw new IllegalArgumentException("The description is too long.");
        }
        
        final LocalDate parsedDate;
        try {
        	parsedDate = LocalDate.parse(this.date);
        } catch (DateTImeParseException e) {
        	throw new IllegalArgumentException("Invalid format for date", e);
        }
        if (parsedDate.isAfter(LocalDate.now())) {
        	throw new IllegalArgumentException("Date cannot be in the future", e);
        }
        
        final double amount;
        try {
        	amount = Double.parseDouble(this.amount);
        } catch (NumberFormatException e) {
        	throw new IllegalArgumentException("Invalid format for amount", e);
        }
        return true;
    }

위의 코드처럼 모든 예외를 모두 동일한 예외로 지정하면 구체적인 회복 로직을 만들 수 없다. 게다가 이 방식도 여러 오류가 발생했을 때 발생한 모든 오류 목록을 모아 제공할 수 없다.

 

이 두가지 안티 패턴을 해결하기 위한 방식이 노티피케이션 패턴이다. 노티피케이션 패턴은 도메인 클래스로 오류를 수집한다. 우선 오류를 수집할 Notification 클래스를 만든다.

public class Notification {
	private final List<String> errors = new ArrayList<>();
    
    public void addError(final String message) {
    	errors.add(message);
    }
    
    public boolean hasErrors() {
    	return !errors.isEmpty();
    }
    
    public String errorMessage() {
    	return errors.toString();
    }
    
    public ListS<String> getErrors() {
    	return this.errors;
    }

이 클래스는 한번에 여러 오류를 수집할 수 있는 검증자의 역할을 한다. 이 클래스를 이용하여 예외를 던지지 않고 Notification 객체에 메시지를 추가한다. 

 public Notification validate() {
 	final Notification notification = new Notification();
	if (this.description.length() > 100) {
        	notification.addError("The description is too long.");
        }
        
        final LocalDate parsedDate;
        try {
        	parsedDate = LocalDate.parse(this.date);
        	if (parsedDate.isAfter(LocalDate.now())) {
        		notification.addError("Date cannot be in the future.");
        	}
        } catch (DateTImeParseException e) {
        	notification.addError("Invalid format for date.");
        }
        
        final double amount;
        try {
        	amount = Double.parseDouble(this.amount);
        } catch (NumberFormatException e) {
        	notification.addError("Invalid format for amount.");
        }
        return notification;
    }

 

6-2. 예외 사용 가이드라인

이 책은 응용프로그램에서 예외를 사용하는 일반적인 가이드라인으로 다음을 제시한다.

  • 예외를 무시하지 않음 : 문제의 근본 원인을 알 수 없다고 예외를 무시하면 안된다. 예외를 처리할 방법이 명확하지 않다면 미확인 예외를 던진다.
  • 일반적인 예외는 잡지 않음 : 가능한 구체적으로 예외를 잡아서 가독성을 높이고 더 세밀하게 예외를 처리할 수 있도록 해야한다. 
  • 예외 문서화 : API 수전에서 미확인 예외를 포함한 예외를 문서화하므로 API 사용자에게 문제 해결의 실마리를 제공한다. 
  • 특정 구현에 종속된 예외를 주의할 것 : 특정 구현에 종속된 예외를 던지면 API의 캡슐화가 깨지므로 주의해야 한다.
  • 예외 vs 제어흐름 : 예외로 흐름을 제어하지 않는다. 예외를 처리하느라 불필요한 try/catch 블록이 추가되어 가독성이 떨어지고, 코드의 의도도 이해하기 어려워진다. 즉, 예외를 정말 던져야 하는 상황이 아니라면 예외를 만들지 않는다.

6-3. 예외 대안 기능

이 책은 예외를 대체할 만한 다양한 기능을 제시한다.

    1. null 사용

만약 예외를 던지는 것 대신 null을 반환하면 어떨까? 다음과 같이 말이다.

final String[] columns = line.split(",");

if (columns.length < EXPECTED_ATTRIBUTES_LENGTH) {
	return null;
}

이 방법은 절대 사용하면 안된다. null은 호출자에게 아무 정보도 제공하지 않기 때문이다. 또한 API의 결과가 null인지 항상 확인해야 하기 때문에 오류가 쉽게 발생할 수 있다. 이 때문에 많은 NullPointerException이 발생할 수 있고, 불필요한 디버깅에 시간이 소비된다.

 

    2. null 객체 패턴

null 객체 패턴은 객체가 존재하지 않을 때 null 레퍼런스를 반환하는 대신에 필요한 인터페이스를 구현하는 객체를 반환하는 기법이다. 이 객체는 바디가 비어있어 동작을 예측하기 쉽고, 의도하지 않은 NullPointerException과 긴 null 확인 코드를 피할 수 있다. 그러나 이 패턴은 데이터에 문제가 있어도 빈 객체를 이용해 실제 문제를 무시할 수 있어서 나중에 문제를 해결하기가 더 어려워질 수 있다.

 

    3. Optional<T>

자바 8에서는 값이 없는 상태를 표현하는 java.util.Optional<T>를 제공한다. 이 형식은 값이 없는 상태를 명시적으로 처리하는 다양한 메소드 집합을 제공하기 때문에 버그의 범위를 줄이는 데 도움이 된다. 또한 다양한 Optional 객체를 조합할 수 있다. 

 

     4. Try<T>

성공하거나 실패할 수 있는 연산을 가리키는 Try<T> 데이터 형식도 있다. Optional<T>와는 값이 아니라 연산에 적용된다는 점에서 차이점을 가진다.

 

7. 빌드 도구

지금까지 응용 프로그램 구현에 있어서의 원칙과 이러한 원칙을 적용하는 방식을 배웠다. 그렇다면 응용 프로그램 자체를 구성하고 빌드하는 데에는 어떤 방식이 좋을까? 이 책은 빌드 도구를 사용하는 이유를 설명하고 빌드 도구 두가지를 간단히 설명한다.

 

7-1. 왜 빌드 도구를 사용할까

응용 프로그램을 실행하는 단계에서 여러 문제점이 생겨날 수 있다. 다음과 같이 말이다

  • 여러 파일, 혹은 여러 패키지를 컴파일 하려면 어떤 명령어를 사용해야 하는가
  • 다른 자바 라이브러리를 사용한다면 디펜던시는 어떻게 관리하는가
  • 프로젝트를 war이나 jar 처럼 특정한 형식으로 패키징하는 방법은 무엇인가

이외에도 다양한 문제가 있을 것이다. 이러한 문제는 스크립트를 만들어 모든 명령어를 자동화하면 어느정도 해결할 수 있다. 명령어를 매번 반복 실행하지 않아도 되기 때문이다. 그러나 새 스크립트를 만든다면 다른 개발자도 스크립트가 어떻게 구성되어 있으며 어떻게 유지보수 해야하는지 이해해야 한다. 또한 소프트웨어 개발 생명 주기도 고려해야 한다. 테스트나 배포의 문제도 있을 것이다.

 

빌드 도구를 이용하면 이러한 문제를 해결할 수 있다. 빌드 도구는 응용프로그램 빌드, 테스트, 배포 등 소프트웨어 개발 생명 주기를 자동화 하는 것을 도와준다. 이외에도 이 책은 다음의 장점을 제시한다.

  • 프로젝트에 적용되는 공통적인 구조를 제공하기 때문에 다른 개발자가 더 편안하게 프로젝트를 받아들인다.
  • 응용 프로그램을 빌드하고 실행하는 반복적이고, 표준적인 작업을 설정한다.
  • 저수준 설정과 초기화에 들이는 시간을 절약 해주기 때문에 개발에만 집중할 수 있다.
  • 잘못된 설정이나 일부 빌드 과정 생략 등으로 발생하는 오류의 범위를 줄여준다.
  • 공통 빌드 작업을 재사용해 이를 다시 구현할 필요가 없다.

 

7-2 메이븐

메이븐은 자바 커뮤니티에서 유명한 빌드 도구로 메이븐을 이용해 소프트웨어의 디벤던시와 빌드 과정을 작성한다.

프로젝트 구조는 다음과 같다.

  • src/main/java : 프로젝트에 필요한 모든 자바 클래스를 저장하는 폴더
  • src/test/java : 프로젝트의 테스트 코드를 저장하는 폴더
  • src/main/resources : 응용 프로그램에서 사용하는 텍스트 파일 등의 추가 자원을 포함하는 폴더
  • src/test/resources : 테스트에서 사용하는 추가 자원을 포함하는 폴더

이렇게 기본적으로 제공되는 디렉터리 구조를 사용하면, 메이븐을 잘 아는 사람이면 누구나 주요 파일의 위치를 빨리 파악할 수 있다. 또한 pom.xml파일을 만들어서 응용 프로그램 빌드에 필요한 과정을 다양한 XML 정의로 지정하여 빌드 프로세스를 정의한다. pom.xml을 설정하고 나면 메이븐으로 프로젝트를 만들고 패키징한다. 이에 필요한 주요 명령어는 다음과 같다. 

  • mvn clean :  빌드하기 전에 기존 빌드에서 생성된 부산물을 정리한다.
  • mvn compile : 프로젝트의 소스코드를 컴파일한다.
  • mvn test : 컴파일된 소스코드를 테스트한다.
  • mvn package : jar등의 적절한 형식으로 컴파일된 코드를 패키징한다.

 

7-3 그레이들

그레이들 역시 인기있는 빌드 도구이다. 메이븐이 XML을 사용하기 때문에 다음과 같은 단점이 존재한다.

  • XML 문법으로 파일 복사나 이동 등 시스템 명령어를 지정하는게 어려우며, 가독성이 떨어진다
  • XML은 장황한 언어에 속해 유지보수 부담을 증가시킨다.

이에 반해 그레이들은 프로젝트 구조 표준화 등 메이븐의 장점은 계승하면서도, 도메인 특화 언어(DSL)를 적용한다는 차이점이 있다. 이 덕분에 빌드 지정이 자연스러워지고, 커스터마이징이 쉬우며, 가독성도 높다는 장점이 있다. 그레이들 또한 여러 주요 명령어가 있다. 이 책에서는 다음의 4가지를 소개한다.

  • gradle clean :  이전 빌드에서 생성된 파일을 정리한다.
  • gradle build : 응용 프로그램을 패키징한다.
  • gradle test : 테스트를 실행한다.
  • gradle run : application 플러그인의 mainClassName으로 지정된 메인 클래스를 실행한다.

 

8. 총정리

3장을 통해 다음의 정보를 배웠다.

  • 개방/폐쇄 원칙을 이용하면 코드를 바꾸지 않고도 메소드나 클래스의 동작을 바꿀 수 있다.
  • 개방/폐쇄 원칙을 이용하면 기존 코드를 바꾸지 않기 때문에 코드가 망가질 가능성이 줄어들고, 기존 코드의 재사용성을 높이며, 결합도가 높아지므로 유지보수성이 개선된다.
  • 너무 많은 메소드를 포함하는 인터페이스는 복잡도와 결합도를 높인다.
  • 너무 세밀한 메소드를 포함하는 인터페이스는 응집도를 낮춘다.
  • 상황에 따라 명시적 API를 사용하거나 암묵적 API를 사용할 수 있다.
  • API의 가독성을 높이기 위해 메소드 이름을 서술적으로 만들어야 한다.
  • 상황에 따라 사용할 도메인 객체를 적절히 만들어야 한다.
  • 연산의 결과를 void를 반환하면 동작을 테스트하기 어렵다.
  • 자바의 예외는 문서화. 형식 안정성, 관심라 분리를 촉진한다.
  • 예외에는 확인된 예외와 미확인 예외가 있으며, 확인된 예외는 불필요한 코드를 추가해야 하므로 되도록 사용하지 않는다. 
  • 너무 세밀하게 또는 너무 덤덤하게 예외를 적용하면 개발의 생산성이 떨어진다.
  • 노티피케이션 패턴을 이용하여 도메인 클래스로 오류를 수집할 수 있다.
  • 예외 사용 가이드 라인을 통해 알맞은 방법으로 예외를 던지고 처리해야 한다.
  • 빌드 도구를 사용하면 응용 프로그램 빌드, 테스트, 배포 등의 소프트웨어 개발 생명 주기 작업을 자동화 할 수 있다.