책 <실용주의 프로그래머> 중 ‘일반 텍스트의 힘’과 ‘셸 가지고 놀기’를 가계부 관리에 적용해본 경험을 이야기한다. 글에 나온 스크립트의 전체 소스 코드는 여기서 확인할 수 있다.

 

배경

왜 시작했나

기술 서적을 자주 읽는다. 책을 읽고 나면 책에 나온 내용을 프로그램을 짤 때 써먹을 수 있겠다는 기대를 한다. 하지만 이해했다고 생각했던 내용을 적용해보려고 프로그램을 짜보면 잘 되지 않아 당황한 적이 자주 있었다.

거꾸로 생각하면 읽은 내용을 이해했는지는 실제로 적용해보면 확실히 알 수 있다. 또한 해보면서 겪는 시행착오는 읽은 내용을 더 깊이 이해하게 해주곤 한다. 때문에 책에 나온 내용을 실제로 적용해본다. 읽는 책마다 하기는 어렵겠지만, 가능하면 프로그램을 만들어보고, 그게 어렵다면 적어도 예제 코드를 따라 치고 실행해본다.

실용주의 프로그래머는 서로 독립적이거나 부분적으로 연관이 있는 다양한 주제의 글로 이루어진 개발 서적이다. 나는 가끔씩 생각날 때마다 관심 가는 주제를 읽고는 하는데, 읽은 내용 중 ‘일반 텍스트의 힘’과 ‘셸 가지고 놀기’를 실제로 적용해보고 싶었다.

주제에 대해 요약하자면,

  • ‘일반 텍스트의 힘’에서는 이진 데이터가 아니라 일반 텍스트로 데이터를 관리하면 좋은 점이 많다고 이야기한다. 데이터가 일반 텍스트 형식이면 데이터를 다루는 데 여러 도구(버전 관리, 에디터, 명령줄 도구 등)를 이용할 수 있기 때문이다.
  • ‘셸 가지고 놀기’에서는 IDE나 GUI 기반 도구를 쓰는 게 편할 때도 있지만, 셸을 쓰면 훨씬 간단하게 처리할 수 있는 경우도 있다고 이야기한다.

 

왜 가계부인가

읽은 내용을 어디에 적용해야 할지 고민했다. 무턱대고 생각나는 걸 프로그램으로 만들었다가는 잘 쓰지 않게 되어 디렉토리 한구석에 둔 채로 잊힐지도 몰랐기 때문이다. 자주 들르는 애자일 이야기 블로그에서 무엇을 프로그래밍 할 것인가프로그래머의 위기지학 두 글을 읽고 나서 1) 내가 자주 쓸 프로그램인지, 2) 나에게 가치 있는 프로그램인지 두 가지 기준을 가지고 적용해볼 주제를 생각해봤다.

고민 끝에 가계부를 주제로 정했다. 가계부 작성을 자주 하기 때문에(1년이 넘는 기간 동안 스프레드 시트 앱으로 가계부를 작성하고 있다) 적용하게 되면 자주 사용할 것 같았고, 현재 방식에 반복 작업이 많아 이를 개선할 수 있다면 나에게 가치가 있을 것 같았다. 기존 가계부 데이터는 스프레드 시트에서만 쓸 수 있는 이진 형식이기도 해서 실용주의 프로그래머에서 읽은 내용을 적용하기에도 적당했다.

 

 

일반 텍스트의 힘 적용 과정

이진 형식인 가계부 데이터를 어떤 고민을 거쳐 일반 텍스트 형식으로 바꿨는지, 그러고 나서 이전에 느꼈던 가계부 관리의 불편함이 어떻게 개선되었는지를 이야기한다.

 

가계부 포맷 변경

이전에 스프레드 시트 앱에 쓰던 가계부 형식을 참고해서 일반 텍스트로 바꿨다. 이전 형식은 다음과 같았다.

  • 지출 내역 데이터는 날짜, 항목, 금액, 분류, 상세 설명, 계좌로 구성
  • 날짜 형식은 mm.dd(1월 1일과 같이 문자 수가 하나일 경우도 01.01과 같이 입력)
  • 지출한 금액은 +, 벌어들인 금액은 -로 입력

문제가 된 점은 같은 행의 항목을 어떤 문자로 구분할지였다. 최종적으로 ' | ' 문자열로 결정했고, 결정하기까지의 과정에서 떠올린 선택지를 실제로 지출 내역 작성에 써보면서 어떤 게 더 편한지 비교했다.

  • 쉼표(,) 문자: 입력하기 편했다. 처음엔 가계부 분석할 때 데이터 정제를 간단히 할 수 있을 것 같다고 생각했다. 하지만 지출 내역 설명 항목이나 상세 설명 항목에 쉼표가 들어갈 때가 있어 생각했던 것만큼 간단하지 않을 것 같아 쓰지 않았다.
  • 탭 문자: 눈에 안 보이는 문자이다 보니 지출 내역 입력할 때 알아보기 힘들 뿐 아니라 입력 후 잘못 입력한 곳이 있는지 알아차리기 어려웠다.
  • 콜론(':') 문자: 유닉스의 일반 텍스트 포맷 데이터베이스에서 쓰는 문자여서 적용해봤지만, 각 셀이 서로 붙어있게 되어서 가독성이 떨어졌다.
  • ' | ' 문자열: 입력이 조금 번거롭긴 하지만 입력된 항목 양옆이 빈칸으로 구분되어 입력된 내용을 구분해서 보기가 비교적 편했다.

포맷 변경 후 이런 식으로 가계부를 입력한다.

Date | Description | Amount | Category | Details | Account
01.01 | 커피 | 8500 | coffee | 프릳츠 | kakao
01.01 | 햄버거 | 4700 | eat_out | 맥도날드 | kakao
01.03 | 커피 원두 | 14000 | coffee | 디벨로핑룸 | eum
01.04 | 버스카드 | 67500 | transport |  | shinhan
01.09 | 커피 | 5500 | coffee | 디벨로핑룸 | eum
01.13 | 햄버거 | 6900 | eat_out | 맥도날드 | eum
01.22 | 드립 커피 | 1500 | coffee | 맥도날드 | kakao
01.26 | 전화 요금 | 24750 | phone |  | shinhan
02.01 | 햄버거 | 4700 | eat_out | 맥도날드 | kakao
02.04 | 책 | 37890 | book | 교보문고 | kakao
02.04 | 햄버거 | 5200 | eat_out | 맥도날드 | kakao
02.07 | 햄버거 | 4700 | eat_out | 맥도날드 | kakao
02.11 | 책 | 10340 | book | 알라딘 | kakao

 

텍스트 에디터로 데이터 입력

스프레드 시트 앱으로 가계부를 관리할 때는 데이터 입력이 번거로웠다. 웬만하면 하루에 카드 결제를 두 번은 하니까 같은 날짜를 반복해서 입력해야 했기 때문이다. 자동 완성도 매끄럽지 않다 보니 데이터 입력에 시간이 오래 걸렸다.

일반 텍스트 파일로 가계부를 관리하니 문서 편집기를 데이터 입력에 쓸 수 있게 되었다. 나는 주로 vim을 쓰는데, 코드나 문서를 효율적으로 편집할 수 있게 해주는 vim의 기능을 가계부 편집할 때도 똑같이 쓸 수 있어 전보다 편하게 가계부를 작성할 수 있게 되었다.

 

깃으로 버전 관리

종종 지출 내역 일부를 적지 않거나, 포맷이 맞지 않게 내용을 작성하는 등의 실수를 하게 되는데, 이럴 때 어디서 문제가 생겼는지 확인하려면 이전에 기록한 내용을 하나씩 유심히 확인해야 했다. 고쳤더라도 잘 고친 건지 확실하지 않아서 난감한 적도 있었다.

깃으로 버전 관리를 했다면 이런 문제가 생겼을 때 커밋 단위로 바뀐 내용을 확인해서 한 번에 봐야 할 범위를 줄일 수 있다. 하지만 가계부가 이진 포맷 파일이기에 깃으로 버전 관리하는 것이 의미가 없다. 바뀐 내용을 사람이 알아보기 어렵기 때문이다. 하더라도 백업 파일을 만들어서 단순하게 버전 관리하는 정도였고, 앞선 문제에 큰 도움은 안 되었다.

지출 내역을 일반 텍스트 파일로 바꾸고 나서는 프로그램을 개발할 때처럼 깃으로 버전 관리할 수 있게 되었고, 전과 같은 문제를 만나도 전보다 편하다. 이 외에도 고칠 곳이 많을 때, 기존 내용을 커밋하고 새로 브랜치를 만들어서 고치고, 오류가 있다면 간단히 이전 상태로 되돌릴 수 있다.

 

 

셸 가지고 놀기 적용 과정

셸을 이용해 가계부 관리와 분석을 편하게 할 수 있는 스크립트를 만들고 활용한 경험을 이야기한다.

 

가계부 알림 시스템

지출 내역을 써야 한다는 걸 잊는 날이 자주 있다. 잊어버린 채로 더 오랜 시간이 지날수록 입력해야 할 지난 지출 내역이 늘어났다. 문제가 없을 때도 있었지만, 돈을 어디에 쓴 건지 기억이 안 나는 경우도 자주 있었다. 한꺼번에 여러 지출 내역을 입력할 때는 은행 앱의 지출 내역을 일일이 눈으로 보고 입력하다 보니 지출 내역 일부를 빼먹는 경우도 있었다. 더 문제였던 건 빼먹은 데이터가 있다는 걸 모른 채 지출 내역을 추가하다가 나중에 은행 앱과 가계부의 잔액이 서로 맞지 않는 것을 발견하면 어디가 문제인지 찾는 게 어려워지는 점이었다.

가계부를 일반 텍스트로 만드니 일반 텍스트를 다룰 수 있는 각종 도구를 가계부를 다루는 데도 쓸 수 있게 되었다. 그래서 파일 내용을 기준으로 가계부 작성이 필요하다고 알림을 보여주는 간단한 스크립트를 만들었다.

#!/usr/bin/env bash

ledger="/Users/sehun/personal/ledger/$(date -v -1d "+%Y").txt"
result=$(grep -e "^$(date -v -1d "+%m.%d")" ${ledger})
yesterday=$(date -v -1d "+%m.%d")

if [ -z ${result} ] ; then
	echo display notification \"어제 \(${yesterday}\) 지출 내역을\
	작성하지 않으셨네요. \" with title \"가계부\"  | osascript
fi

스크립트는 가계부 파일에서 전날 날짜를 검색한 결과가 없으면 전날 지출 내역이 작성되어 있지 않다는 뜻이므로 컴퓨터 화면에 알림 뱃지를 띄우는 일을 한다. 이 스크립트를 crontab에 등록해서 매일 특정 시간마다 스크립트를 자동으로 실행할 수 있도록 했다.

 

지출 내역 분석

가계부를 계속해서 썼던 건 내가 알고 싶은 정보를 가계부 분석을 통해 얻고 싶어서였다. 예를 들어 지출 중에 꼭 필요하지 않은 지출이 있었는지 확인하고 소비 습관을 개선하거나, 현재 남은 돈을 확인해서 소비를 줄여야 할지 결정하는 데 가계부 기록을 활용하고 싶었다.

이전에도 스프레드 시트 앱에서 제공하는 함수를 이용해 분석을 시도해본 적이 있었다. 하지만 생각했던 것만큼 잘되지 않아서 그만두게 되었다. 가계부 포맷이 일반 텍스트로 바뀌니까 지출 내역 분석 문제를 일반 텍스트를 어떻게 가공할지의 문제로 볼 수 있게 되었다. 나는 셸을 써서 구분자나 특정 날짜, 카테고리를 기준으로 가계부를 필터링해 지출 내역을 확인하거나 해당하는 지출액 총합을 계산할 방법을 생각해봤다.

여러 번의 시행착오를 거쳐 작은 스크립트 여러 개를 만들었다. 스크립트는 출력을 다음 명령의 입력으로 받을 수 있도록 해서 다른 스크립트나 명령과 조합해 쓸 수 있도록 했다.

예를 들어 기간(3월 13일부터 3월 19일까지) 내 지출 총합을 계산하는 스크립트 조합은 다음과 같다.

스크립트 실행 흐름

각 스크립트의 역할은 다음과 같다. (스크립트 이름을 클릭하면 소스를 확인할 수 있다)

  • ext_date_range 스크립트는 입력받은 두 날짜 사이 날짜 목록을 출력한다.
  • ext_pay 스크립트는 날짜 목록을 받아서 이 기간에 해당하는 지출 내역 목록을 출력한다.
  • comp_sum 스크립트는 지출 내역 목록을 받아서 목록 내 지출 금액의 총합을 계산해 출력한다.

입출력 형식만 일치한다면 내가 짠 스크립트를 다른 유닉스 명령(grep, sed, cut 등)과 조합할 수도 있다. 이 점은 알고자 하는 문제의 답을 찾는 것을 유연하게 해줬다. 이미 구현된 도구를 해결 과정에 활용할 수 있다면, 굳이 새로운 스크립트를 만들지 않아도 되었다. 몇 가지 예시를 들면 다음과 같다.

  • 지금 남아있는 돈은 얼마일까
    • ./ext_date_range.sh 01.01 03.19 | ./ext_pay.sh | ./comp_sum.sh
  • 특정 계좌에 남은 총액은 얼마일까
    • ./ext_date_range.sh 01.01 03.19 | ./ext_pay.sh | grep kakao |\ ./comp_sum.sh
    • 기간 내 지출 내역을 계좌 이름으로 다시 필터링(grep kakao)해서 기간 내 해당 계좌의 지출액 합을 계산한다.
  • travel 카테고리에 속하는 지출 내역에는 무엇이 있을까
    • cat ~/personal/finance/2023.txt | grep travel
    • 가계부를 카테고리 이름으로 필터링한다. 만든 스크립트를 쓰지 않고 해결할 수 있는 문제다.

 

 

결론

  • 책에서 읽은 내용을 실제로 적용해보는 건 읽기만 하는 것보다 훨씬 더 많은 노력이 필요한 일이었다.
  • 이런 노력을 가능한 자발적으로 하기 위해 적용할 주제 선정에 많은 고민을 했고, 결과적으로 잘한 일이었다고 생각한다.
  • 일반 텍스트로 지식을 관리할 때 얻는 장점이 많다.
  • 나만 쓸 프로그램을 만드니까 ‘필요한 기능’이 무엇인지 쉽게 판단할 수 있었다. 바뀐 가계부 형식과 스크립트에는 변경에 취약한 면이 있다. 하지만 혼자 쓸 프로그램이기에 당장 중요하지 않았고, 그보다 내가 불편한 점을 개선해줄 수 있는 기능을 우선 구현하려고 하게 되었다.
  • 전보다 가계부 쓰는 게 편해졌고, 필요한 기능을 어떻게 구현할지 고민하는 일도 즐거워졌다.