안드로이드 코드 개선하기 (1) 문제 파악

Lee Sanghyun·2024년 4월 18일
1

SQLI

목록 보기
4/7
post-thumbnail

🛠️ Refactoring? WHY

플레이스토어 출시도 끝났겠다. 정리정돈을 좀 하자.

앞으로 다양한 기능을 추가하기 전에 코드를 좀 더 간결하게 하기 위해 리팩터링을 진행해보려 한다. 지난 프로젝트로 배운 것은 리팩터링 작업은 선택이 아니라 필수라는 점이다. 물론 처음부터 완벽한 코드를 작성하는 고수분들은 다르시겠지만, 나 같은 경우에는 한 번에 완벽한 코드를 작성하기 쉽지 않다. 그렇다고 기능 구현만 간신히 되는 코드를 방치하면 나중에는 얽히고 섥혀서 기능을 확장하는 게 어려워진다. 여유가 될 때마다 기능 구현에 필요한 시간을 너무 잡아먹지 않는 선에서 코드를 개선하는 것이 좋아보인다. 그럼 어떤 의식의 흐름으로 리팩터링하는지 알아보도록 하자.


// 이게 바로 문제의 코드이다.
class EditorViewModel(private val databaseRepository: DatabaseRepository) : ViewModel() {
    private val lineNumberModel = LineNumber("")
    var statementEditView = MutableLiveData<EditText>()
    val lineNumberView = MutableLiveData<String>()
	
    // Activity_editor.xml과 data binding #EditText의 afterTextChanged()
    fun onStatementChanged() {
        val editText = statementEditView.value
        val layout = editText?.layout
        layout?.let {
            lineNumberModel.content = generateLineNumber(it.lineCount)
            lineNumberView.value = lineNumberModel.content
        }
    }
	
    // Activity_editor.xml과 data binding #버튼 클릭시 실행
    fun execStatement(): Any? {
        return statementEditView.value?.let {
            if (isQueryStatement(statementEditView.value.toString())) {
                databaseRepository.execQuery(it.text.toString())
            } else {
                databaseRepository.execStatement(it.text.toString())
            }
        }
    }
	
    // SQL문이 쿼리인지 검증. 즉 SELECT로 시작하는지 확인
    private fun isQueryStatement(statement: String): Boolean {
        return statement.startsWith("SELECT") or statement.startsWith("select")
    }
	
    // Line Count를 입력받으면 Line Number을 생성.
    private fun generateLineNumber(count: Int): String {
        val stringBuilder = StringBuilder()
        for (i in 1..count) {
            stringBuilder.append("$i\n")
        }
        return stringBuilder.toString()
    }
}

🔍 어떻게 하는데? Step by Step

코드를 하나하나 뜯어보면서 어떤 점이 마음에 안 드는지 정리해보자. 내가 생각해는 좋은 코드에 대한 질문을 하나씩 던지면서 어떤 점이 문제인지 찾아보도록 하겠다.

접근지정자와 변수명

private val lineNumberModel = LineNumber("")
var statementEditView = MutableLiveData<EditText>()
val lineNumberView = MutableLiveData<String>()
  1. 적절한 위치에 있는가? YES
  2. 모든 코드가 필요한 코드인가? YES
  3. 접근지정자가 올바르게 사용되었는가? YES
  4. 변수명이 가독성이 좋은가? NO
  5. 데이터 흐름의 방향성이 안전한가? NO

EditText 변화 탐지 함수 : onStatementChanged()

	// Activity_editor.xml과 data binding #EditText의 afterTextChanged()
    fun onStatementChanged() {
        val editText = statementEditView.value
        val layout = editText?.layout
        layout?.let {
            lineNumberModel.content = generateLineNumber(it.lineCount)
            lineNumberView.value = lineNumberModel.content
        }
    }
  1. 적절한 위치에 있는가? YES
  2. 모든 코드가 필요한 코드인가? YES
  3. 가독성이 좋게 작성되었는가? YES
  4. 함수가 용도에 맞게 작동하는가? YES
  5. 함수와 변수의 명칭이 적합한가? YES
  6. 오류 처리가 되어 있는가? YES
  7. 테스트하기 쉬운 구조인가 YES
  8. 함수의 호출 빈도가 적당한가? NO

SQL문 실행 함수 : execStatement()

	// Activity_editor.xml과 view binding #버튼 클릭시 실행
    fun execStatement(): Any? {
        return statementEditView.value?.let {
            if (isQueryStatement(statementEditView.value.toString())) {
                databaseRepository.execQuery(it.text.toString())
            } else {
                databaseRepository.execStatement(it.text.toString())
            }
        }
    }
  1. 적절한 위치에 있는가? YES
  2. 모든 코드가 필요한 코드인가? YES
  3. 가독성이 좋게 작성되었는가? YES
  4. 함수가 용도에 맞게 작동하는가? YES
  5. 함수와 변수의 명칭이 적합한가? YES
  6. 오류 처리가 되어 있는가? YES
  7. 테스트하기 쉬운 구조인가 YES
  8. 함수의 호출 빈도가 적당한가? YES

쿼리문 검증 함수 : isQueryStatement()

	// SQL문이 쿼리인지 검증. 즉 SELECT로 시작하는지 확인
    private fun isQueryStatement(statement: String): Boolean {
        return statement.startsWith("SELECT") or statement.startsWith("select")
    }
  1. 적절한 위치에 있는가? YES
  2. 모든 코드가 필요한 코드인가? YES
  3. 가독성이 좋게 작성되었는가? YES
  4. 함수가 용도에 맞게 작동하는가? NO
  5. 함수와 변수의 명칭이 적합한가? NO
  6. 오류 처리가 되어 있는가? YES
  7. 테스트하기 쉬운 구조인가 YES
  8. 함수의 호출 빈도가 적당한가? YES

Line Number 생성 함수 : generateLineNumber()

	// Line Count를 입력받으면 Line Number을 생성.
    private fun generateLineNumber(count: Int): String {
        val stringBuilder = StringBuilder()
        for (i in 1..count) {
            stringBuilder.append("$i\n")
        }
        return stringBuilder.toString()
    }
  1. 적절한 위치에 있는가? NO
  2. 모든 코드가 필요한 코드인가? NO
  3. 가독성이 좋게 작성되었는가? YES
  4. 함수가 용도에 맞게 작동하는가? YES
  5. 함수와 변수의 명칭이 적합한가? YES
  6. 오류 처리가 되어 있는가? YES
  7. 테스트하기 쉬운 구조인가 YES
  8. 함수의 호출 빈도가 적당한가? NO

❓ 목표를 정하자 WHAT

  • 접근지정자와 변수명:
    • 변수명을 보고 의도를 파악하기 어렵다. 가독성
    • ViewModel의 상태가 캡슐화 되어 있지 않다. 객체지향
  • onStatementChanged(): generateLineNumber() 함수를 과하게 많이 호출한다. 자원 낭비
  • isQueryStatement():
    • "Select" "SELect" 같은 키워드는 제대로 확인하지 못한다. 의도 실패
    • 함수명이 의도를 명확하게 보여주지 못한다. 가독성
  • generateLineNumber():
    • StringBuilder 객체를 불필요하게 선언했다. 자원 낭비
    • ViewModel이 LineUpdate 과정을 알 필요가 없다. 관심사

기능 구현에 너무 화이팅이 넘친 나머지 상당히 엉망인 코드가 탄생했다. 그 중 상당 부분은 개발 당시에도 문제점을 알았지만 시간에 쫓기는 바람에 수정하지 못했다. 다음 포스팅을 통해서 문제를 하나 하나 수정해보도록 하자! Repository의 실제 Refactoring Issue가 궁금하다면 아래의 참고를 확인하는 걸 추천한다 :)


SQLI 프로젝트의 GitHub Repository가 궁금하다면? 클릭


📚 참고

profile
개미 같이 일하는 안드로이드 개발자💻

0개의 댓글

관련 채용 정보