-
이기적인 총무 리팩토링 - Database 관리 부분사이드 프로젝트/이기적인 총무 2018. 1. 21. 15:50
구조설계서에 시스템에서 사용할 클래스 다이어그램을 그리다 보니 작성한 내용 그대로 실제로 시스템이 적용 할 수 있을지 의문이었다. 문서의 내용은 그럴듯 한데 여기에 안드로이드 시스템 특유의 feature들을 넣다보면 중간에 엉키게 되는 부분이 상당수 있을 것 같은 느낌이 드었다. 나중에 반복해서 고치는것 보단 직접 먼저 코드를 수정한 후 현실 가능성을 검증하고 옮기는 것이 나을것 같아 예전부터 가장 먼저 손대기로 했었던 DatabaseHelper 클래스를 리팩토링 했다.
수정전 DatabaseHelper.java 클래스는 이랬다.
그림 1. 초기 DatabaseHelper 클래스 다이어그램
DatabaseHelper 클래스는 이름 그래도 이기적인 총무의 데이터베이스를 관리할 수 있는 기능을 제공하는 클래스다. 그런데 여기에 Party, Pay, Person, Pay-Participation 테이블에 대해서 각각 CRUD 명령을 모두 넣다보니 위 클래스가 갖는 함수가 너무 많다. 클래스는 하나의 Responsibility만 가져야 하는데 위 경우에는 4개의 Responsibility를 가지게 됐으며 이런 경우 한 영역의 수정이 다른 영역의 수정에 영향을 미치게 된다. 객체지향적 관점에서 부적합한 설계 방법론이다. 위 클래스가 갖고 있는 Responsibility를 최소화하기 위해 총 4개의 subclass로 분리했다.
1. 역할에 맞춰서 클래스를 분리한다
그림 2. 1차 수정 버전
DatabaseHelper 클래스를 abstract 클래스로 두고 Table 별로 서브클래스를 두었다. 이렇게 하니 각각이 기능성으로 분류돼 각 클래스 별로 하나의 기능만 존재하게 됐다. 그런데 위 디자인을 적용해서 설치해보니 실행중 에러가 발생한다. 테이블이 생성되지 않았다는 에러다.
테이블을 생성하는 작업은 onCreate() 함수 안에서 담당한다. 그림 1에선 onCreate 함수 내에 모든 테이블을 생성하도록 했고, 그림 2에서는 각 클래스내에 존재하는 onCreate 함수에 테이블을 생성하도록 했다. 그런데 이렇게하니 문제가 발생했다. onCreate 함수는 애플리케이션내에서 데이터 베이스 함수를 열 때(getWritableDatabase 함수를 실행할 때) 딱 한번만 실행 된다. 각각이 현재 분리돼 있지만 시스템 내에서 실행되는 경우는 딱 한번 뿐이다. 그래서 PartyDatabseHelper에서 먼저 onCreate 함수를 실행 했다면 나머지 클래스에선 모두 생략된다.
위 문제는 시스템을 시작할 때 테이블을 생성하는 작업을 실행하도록 해 간단히 해결 할 수 있다. 그런데 테이블을 업그레이드 해야하는 경우는 문제가 생긴다. 업그레이드 명령인 경우 현재 데이터베이스 버전 번호와 최신 데이터베이스 버전 정보가 필요한데 이런 경우에는 기존 onUpgrade 루틴을 벗어나서 버전 정보를 읽어오는 것이 불가능하며 설사 가능하더라도 기존에 있는 기능을 중복해서 만드는것도 바람직한 코딩이 아니다.
마침 다른 블로그에 여러개의 테이블을 관리하는 코드가 있어 그 코드를 참조해서 리팩토링 했다.
2. 기존 create/upgrade 루틴과 호환되도록 한다.
그림 3. 2차 수정 버전
Party 데이터 베이스 관리 클래스가 DatabaseHelper 클래스로의 서브 클래스였는데 이제는 모두 별도의 클래스가 됐고 onCreate/onUpgrade 함수 대신 createTable(), upgradeTable()이 생겼다. 이 함수들은 static으로 선언해 외부에서도 불러줄 수 있도록 했다. DatabaseHelper에서는 기존대로 onCreate 루틴을 타는데 이때 각 서브클래스에서 선언된 createTable를 불러주도록 했다. onUpgrade 일때는 각 서브클래스에서 선언된 upgradeTable을 부른다.
@Override public void onCreate(SQLiteDatabase db) { PartyDatabaseManager.createTable(db); PayDatabaseManager.createTable(db); PersonDatabaseManager.createTable(db); PayParticipateDatabaseManager.createTable(db); Log.d(LOGTAG, "Create table is called"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { PartyDatabaseManager.upgradeTable(db, oldVersion, newVersion); PayDatabaseManager.upgradeTable(db, oldVersion, newVersion); PersonDatabaseManager.upgradeTable(db, oldVersion, newVersion); PayParticipateDatabaseManager.upgradeTable(db, oldVersion,newVersion); onCreate(db); }
기존 생성/업데이트 루틴을 타게 됐으니 앞서 말한 문제점이 깔끔하게 해결 된 것 같지만 이 코드도 문제점이 없는건 아니다. 만약 시스템에서 관리하는 테이블이 추가된다면 DatabaseHelper 클래스에 함수의 테이블 생성 작업과 업그레이드 작업을 추가해야 한다. 그런데 DatabaseHelper가 시스템 테이블의 구체적인 정보까지 알고 있는 것은 바람직하지 않다. DatabaseHelper는 각 테이블에게 추가 또는 업데이트를 명령할 뿐이지 구체적으로 어떤 테이블이 있는지는 알 필요가 없다.
차라리 외부에서 DatabaseHelper가 관리 해야하는 테이블을 등록하고 생성/업데이트 할 때 등록된 테이블만 관리하는 방법이 깔끔한 것 같다. 그래서 다음과 같이 변경했다.
3. DatabaseHelper에는 세부적인 내용을 넣지 않는다.
그림 4. 3차 수정 버전
// startActivity.java 내에 관리할 테이블 정보를 등록하는 코드. DatabaseHelper databaseHelper = new DatabaseHelper(this); databaseHelper.registerTableManager(new PartyTableManager()); databaseHelper.registerTableManager(new PayJoinTableManager()); databaseHelper.registerTableManager(new PayTableManager()); databaseHelper.registerTableManager(new PersonTableManager());
// DatabaseHelper 클래스는 등록된 테이블 별로 create/update 함수를 부른다 @Override public void onCreate(SQLiteDatabase db) { for (TableManager tableManager : tableManagerList) tableManager.createTable(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { for (TableManager tableManager : tableManagerList) tableManager.upgradeTable(db, oldVersion, newVersion); onCreate(db); }
TableManager라는 인터페이스를 둬 DatabaseHelper가 각 클래스의 세부 내용을 모르더라도 함수를 호출 할 수 있도록 했고 각각의 Table을 관리하는 클래스는 TableManager 인터페이스를 구현하도록 했다. DatabaeHelper 생성시 관리할 테이블을 등록해 안에 세부적으로 구현하지 않고 생성/업그레이드 작업을 수행할 수 있도록 했다. 관리할 테이블이 추가되면 startActivity에 테이블을 추가로 등록하면 되고 테이블을 삭제할 경우에는 등록하는 부분의 테이블을 지우면 된다. 이전보다 훨씬 쉽게 변경이 가능한 코드가 됐다.
'사이드 프로젝트 > 이기적인 총무' 카테고리의 다른 글
UI 문서 - 구조설계서 작성(2/4) (0) 2018.02.08 기능적/비기능적 요구사항 - 구조설계서 작성 (1/4) (0) 2018.02.04 이기적인 총무 - 리팩토링 계획 (1) 2018.01.07 총무앱 - 이기적인 총무 런칭 그리고 업데이트 (0) 2017.07.15 총무앱 - 디자인 (0) 2017.07.02