Chapter 2 입출금 내역 분석기(1) - KISS 원칙, SRP 원칙
1. 프로젝트 목표
입출금 내역을 분석하는 프로그램이 필요하다. 이 거래 내역은 텍스트 파일에 저장되어있으며 CSV 형식으로 모든 거래 내역이 기록되어있다.
거래 내역 예시
25-12-2020,-100000,OutPackSteakHouse
27-12-2020,-5000,Daitseo
30-12-2020,800000,Salary
2-1-2021,-10000,CotteCinema
이처럼 날짜, 거래금액, 거래 설명의 순서를 가지고 CSV 형식으로 되어있다.
이때 CSV 형식(Comma-seperated Values)이란 콤마로 데이터가 분리되어있는 데이터 형식을 의미한다. 프로그램에서는 쉼표를 토큰으로 하여 파일의 한 줄 한 줄을 파싱해서 데이터로 사용할 것이다.
2. KISS 원칙
KISS 원칙은 "Keep it short and simple" , "Keep it simple and stupid" , "Keep it small and simple" 등 많은 어원이 있는 것으로 보이지만 결국 의미하는 것은 상통한다. 단순하게 하는 것. 정확히 정의하자면,
KISS 원칙이란 디자인에서 불필요한 복잡성을 버리고 가능한 단순하고 알기 쉽게 만들야 한다는 원리
라 할 수 있겠다. 이 책에서는 이 원리를 프로젝트에 적용시켜 요구사항을 풀고자 한다. 즉, "입출금 내역의 총 거래 금액 구하기", "특정 월의 입출금 내역 분석하기" 등의 각 문제를 하나씩 클래스로 만들어 해결한다.
예를 들어, 모든 파일의 모든 거래 내역의 합을 구하는 문제의 경우 다음과 같은 클래스로 해결할 수 있을 것이다.
public class BankTransactionAnalyzerSimple {
private static final String RESOURCES = "src/main/resources/";
public static void main(final String... args) throws IOException {
final Path path = Paths.get(RESOURCES + args[0]);
final List<String> lines = Files.readAllLines(path);
double total = 0d;
for (final String line: lines) {
final String[] columns = line.split(",");
final double amount = Double.parseDouble(columns[1]);
total += amount;
}
System.out.println("The total for all transactions is + " + total);
}
}
지정된 장소에 있는 파일의 내용을 String의 List로 저장해 한 줄씩 쉼표로 파싱하여 금액 부분만 합을 구하는 간단한 코드이다. 이 때 Path는 파일의 경로를 다루는 클래스로, Path 변수를 이용해서 Files의 readAllLines() 정적 메소드를 통해 파일의 내용을 String의 List로 바꾼다.
그 다음 문제인 "특정 월의 입출금 내역 분석하기"는 위의 코드를 조금만 바꾸면 해결 할 수 있을 것이다. 다음과 같이 말이다.
public class BankTransactionAnalyzerSimple {
private static final String RESOURCES = "src/main/resources/";
public static void main(final String... args) throws IOException {
final Path path = Paths.get(RESOURCES + args[0]);
final List<String> lines = Files.readAllLines(path);
double total = 0d;
final DateTimeFormatter DATE_PATTERN = DateTimeFormatter.ofPattern("dd-MM-yyyy");
for (final String line: lines) {
final String[] columns = line.split(",");
final LocalDate date = LocalDate.parse(columns[0], DATE_PATTERN);
if (date.getMonth() == Month.JANUARY) {
final double amount = Double.parseDouble(columns[1]);
total += amount;
}
}
System.out.println("The total for all transactions in January is + " + total);
}
}
데이터를 파싱해 금액만 사용하던 이전 코드에서 조금 수정하여 날짜도 사용하여 날짜가 특정 월일때만 합에 포함시키도록 했다. 이때 날짜는 LocalDate의 변수로 만들어지는데, DateTimeFormatter 변수를 사용해서 텍스트 파일의 날짜 형식인 dd-MM-yyyy(예. 21-02-2021)로 된 스트링을 파싱하여 저장할 수 있도록 했다.
그러나 이런 식으로 계속해서 모든 문제를 복사, 붙여넣기와 조금의 수정을 통해 해결하려고 하면 문제가 생기게 된다.
3. 코드 유지보수성과 안티 패턴
코드 유지보수성(code maintainability)이란 말 그대로 코드가 유지,보수하기에 얼마나 편리한지를 나타내는 것이다. 문제 해결을 위한 포르젝트 개발 후 바로 제출하게 되는 과제나 시험과는 다르게 지속적으로 서비스를 제공하는 실제 개발 환경에서는 구현보다 이미 구현된 코드를 유지하고 보수해나가는 데에 더 많은 시간이 든다. 또한 그 과정에서 다른사람이 작성한 코드를 보수하는 순간도 올 것이다. 그런 상황에서 문제가 없으려면 코드가 다음과 같은 속성을 가져야한다
1. 특정 기능을 담당하는 코드를 쉽게 찾을 수 있어야 한다.
2. 코드가 어떤 일을 수행하는지 쉽게 이해할 수 있어야 한다.
3. 새로운 기능을 쉽게 추가하거나 기존 기능을 쉽게 제거할 수 있어야 한다.
4. 캡슐화가 잘 되어 있어야 한다.
그러나 현재 하고있는 것처럼 계속 문제를 복사, 붙여넣기를 통해 해결한다면 코드 유지성에 전혀 도움이 되지 않을 것이다. 실제로 이러한 디자인 방식은 안티패턴에 속하는데, 안티 패턴이란 프로그래밍 시에 자주 사용되지만 비효율적이거나 비생산적인 방식을 의미한다. 이 책은 현재 코드가 가지고 있는 두가지의 안티패턴을 소개한다.
3-1. 갓 클래스
지금처럼 한 파일에서 모든 코드를 구현하고, 추가적인 요구사항이 생길 때마다 그 파일에만 수정을 가하고 기능을 추가하다보면 모든 문제를 해결하는 하나의 거대한 클래스가 생길 것이다. 그렇게 되면 이 클래스가 정확히 무슨 일을 하는 지 이해하기 어려워지고, 수정을 할 일이 있다면, 긴 코드를 일일이 다 읽어가며 수정이 필요한 부분을 찾아야 한다. 이러한 문제를 갓 클래스 안티 패턴이라 하고, 이는 지양해야 하는 방식이다.
3-2. 코드 중복
현재 코드는 두 가지 문제 밖에 해결하지 않지만 이미 많은 부분이 중복되어 있다. 파일의 입력을 읽어 파싱하는 과정이 그 부분이다. 만약 파일의 형식이 바뀌게 된다면 여러 곳에 중복되어 존재하는 부분을 일일이 다 수정해야 하며, 수정이 많아지면 버그가 발생할 가능성도 커진다. 실제로 이 문제는 원칙으로도 설명되어 있다. DRY 원칙이 그것이다.
DRY 원칙이란 "Don't Repeat Yourself"의 약어로, 코드 작성에서 중복을 배재해야 한다는 원리이다.
결국 KISS 원칙을 지키기 위해 가장 간단한 방식으로 문제를 해결하려 했지만 이는 더 많은 문제를 야기하는 것이었다. 이 책 역시 KISS 원칙을 남용하지 말아야 한다고 말한다. "불필요한 복잡성"은 배제해야 하지만 문제를 너무 간단하게 생각하여 바로 코드 작성에 뛰어들어서는 안 될 것이다. 전체 문제를 분석하여 쪼개서 작은 개별 문제로 만들고 파악하는 과정이 필요하다.