파싱 기능을 클래스로 분리

2024. 7. 2. 22:08WebBack/Software 코드 분석

입출금 내역 분석기 요구 사항  경로
CSV + 입출금 내역 분석기 요구 사항 https://ycraah.tistory.com/25
모든 거래 내역의 합 계산하기 https://ycraah.tistory.com/26
1월 입출금 내역 합계 계산하기 https://ycraah.tistory.com/32
public class BankTransactionAnalyzerJanuary{
  private static final String RESOURCE = "src/main/resources/";
  public static void main(String... args) throws IOException {
    final Path path = Paths.get(RESOURCE + args[0]);
    final List<String> lines = Files.readAllLines(path);
    final DateTimeFormatter formatted = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    double total = 0d;
    for(String line : lines){
      String[] columns = line.split(","); //2024-01-30, -10, 교통비
      LocalDate date = LocalDate.parse(columns[0], formatted);
      if(date.getMonthValue() == 1){
        double amount = Double.parseDouble(columns[1]);
        total += amount;
      }
    }
    System.out.println("1월달의 은행 입출금 내역의 총합은 " + total +"(만원)입니다.");
  } //end of main
}//end of class

 

이 책에서는 위 코드의 다음과 같은 문제점을 지적한다. 

  • 특정 기능을 담당하는 코드를 쉽게 찾을 수 없다. 
  • 코드가 어떤 일을 수행하는지 쉽게 이해할 수 없다. 
  • 새로운 기능을 추가하거나 제거하기 어렵다. 
  • 캡슐화가 되어 있지 않다. 

그래서 이 책의 저자는 관리와 유지보수를 위해서 '단일 책임 원칙'을 제안한다. 즉, 한 클래스는 한 기능만 책임진다는 것이다. 위 코드는 다음과 같은 기능을 포함하고 있다. 

 

1. 입력 읽기

2. 주어진 형식의 입력 파싱

3. 결과 처리 

4. 결과 요약 리포트

 

2번을 따로 추출한 코드가 다음과 같다. 

public class BankStatementCSVParser {
  private static final DateTimeFormatter DATE_PATTERN = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  
  private BankTransaction parseFromCSV(final String line){
    final String[] columns = line.split(",");
    
    final LocalDate date = LocalDate.parse(columns[0], DATE_PATTERN);
    final double amount = Double.parseDouble(columns[1]);
    final String description = columns[2];
    
    return new BankTranscation(date, amount, description);
  }
  
  public List<BankTransaction> parseLinesFromCSV(final List<String> lines){
    final List<BankTransaction> bankTransactions = new ArrayList<>();
    for(final String line: lines){
      bankTransactions.add(parseFromCSV(line));
    }
    return bankTransactions;
  }
}

 

위 코드를 하나씩 해석해보자. 

 

private static final DateTimeFormatter DATE_PATTERN = DateTimeFormatter.ofPattern("yyyy-MM-dd");

첫째줄의 코드는 기존의 코드와 똑같다. 연도-월-일 형식으로 지정된 정보를 읽을 수 있도록 돕는다. 클래스가 분리되면서 외부에서의 접근을 막아야 하는 필요성이 생겨서 private를 추가하였다. 

 

private BankTransaction parseFromCSV(final String line)

클래스 내에 있는 parseFromCSV 메서드이다. 반환타입은 BankTransaction인데 아직 코드가 없어서 어떤 의미인지는 알 수 없다. 하지만 매개 변수가 line인 것을 보면 경로에 있는 txt 파일을 한 줄씩 String으로 저장한 내용으로 보인다. 아마 line에 입력되는 내용은 '2024-01-30, -10, 교통비'같은 형식의 내용일 것이다. 

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

이 부분은 기존의 코드에서 사용된 것과 똑같다. ','을 분리자로 삼아서 분리한다. 

columns에는 '2024-01-30' '-10' '교통비' 이렇게 나뉘어서 배열로 저장된다. 

 

final LocalDate date = LocalDate.parse(columns[0], DATE_PATTERN);
final double amount = Double.parseDouble(columns[1]);
final String description = columns[2];
return new BankTransaction(date, amount, description);

 

이렇게 배열로 저장된 값을 각각 저장한다. 그리고 이 값은 서로 관련성이 깊으니 묶어서 관리하는 것이 좋다. 그래서 BankTransaction 객체에 담는다. 아직 BankTransaction 클래스가 등장하지 않았지만 각 값을 저장하기 위한 생성자가 있다는 것을 유추할 수 있다. 

 

그런데 이 코드를 실행할 line은 어디서 가져오는 것일까? 이를 알기 위해서는 아래 코드를 봐야한다. 

public List<BankTransaction> parseLinesFromCSV(final List<String> lines){
  final List<BankTransaction> bankTransactions = new ArrayList<>();
  for(final String line: lines){
    bankTransactions.add(parseFromCSV(line));
  }
  return bankTransactions;
}

 

아직 무슨 코드인지 모르겠지만 CVS.txt를 한 줄씩 읽어낸 값을 매개변수로 삼고 있는 것을 알 수 있다. 

이전에 썼던 코드를 값으로 받았을 것이다. 

final Path path = Paths.get(RESOURCES + args[0]);
final List<String> lines = Files.readAllLines(path);

 

public List<BankTransaction> parseLinesFromCSV(final List<String> lines)

반환 타입이 List<BankTransaction>이라는 것을 통해 parseFromCVS 메서드가 여기서 실행될 것이라는 것을 알 수 있다. 

final List<BankTransaction> bankTransactions = new ArrayList<>();

 

그러면 bankTransactions는 parseFromCVS에서 반환한 bankTransaction을 반환하는 객체를 저장하는 List가 된다. 

List에 저장하기 위한 반복문이 아래의 내용이다. 

for(final String line: lines){
  bankTransactions.add(parseFromCSV(line));
}

향상된 for문을 사용하였다. 그리고 List를 반환한다. 

return bankTransactions;