리팩터링을 하는 것은 중요하다고 다들 이야기한다. 나도 리팩터링은 중요하다고 생각한다. 다만 리팩터링에 대한 중요성을 인지하고 있다고 하더라도 실무에서는 리팩터링 활동이 활발한지는 잘 모르겠다. 짧은 내 경력기간동안에서 겪은바로는 개발자들은 프로젝트 일정으로 인해 혹은 인력의 부족으로 인해 리팩터링이 필요하다고 생각하지만 잘 수행하지 못했던 것 같다.

또 하나의 문제는 바로 리펙터링 시 기능의 변경이 없음을 보장하지 못하는 경우가 많았다는 것이다. 프로젝트 진행으로 기능의 변경을 위해 어쩔 수 없이 코드를 변경하는 것이 아니라면 개발자들은 잘 동작하고 있는 코드를 수정하는 것을 꺼려한다. 테스트 코드가 없는 경우에는 더욱더 그렇다. 설령 테스트 코드가 있더라도 테스트 코드를 믿고 코드를 수정해보았는데 여기저기서 테스트가 실패한다면 지레 겁먹고 수정했던 코드를 원상복구할 것이다.

코드 수정을 꺼려하는 이유중 다른 하나는 동료 개발자가 작성한 코드를 자신이 생각하기에 더 나은 방향이라고 해서 수정해도 될지에 대한 두려움에 있다. 더 좋은 코드라는 것은 상당히 주관적이다. 개발자마다 다를 수 있고 그렇기 때문에 기능에 오류가 있어서 고치는 것이 아닌 리팩터링인 경우 동료의 코드를 수정하는게 부담스러울 수 있다.

현재 우리 백엔드 챕터에서는 리팩터링을 적극 권장하고 있다. 단순한 변수명을 변경하는 것에서부터 의존성에 대한 버전 업그레이드, 챕터원끼리 수립한 코드 정책에 맞는 구조 변경에 이르기까지 모두가 적극적으로 참가하고 있고 테스트코드가 있기에 코드 수정에 겁내지 않을 수 있었다. 다만 최근 QA 진행시 리펙터링으로 인한 버그가 몇건 발생하게 되었고 QA팀에서 리펙터링 작업을 특정 기간마다 수행하는 것이 어떤가 하는 의견을 받게 되었다.

나는 작은 범위로 자주 종종 리팩터링을 하는 것이 좋다고 생각했기 때문에 QA팀에서 준 의견을 따르기에는 어려움이 있다고 생각했고 조율을 통해서 프로젝트를 진행하면서 리팩터링은 기존과 동일하게 수행하고 대신 QA입고 시 리펙터링 작업에 대한 영향범위를 함께 공유해 주는 것으로 협의를 할 수 있었다.

이와같이 리펙터링 후 이슈가 발생하면 위축될 수 밖에 없다고 생각한다. 혹시나 챕터원들이 이번 이슈로 인해 리펙터링 활동이 위축될까 우려가 되기도 한다. 앞서 이야기한 리팩터링이 잘 되지 않는 상황이 반복되지 않도록 하기 위해서는 리팩터링에 대한 긍정적인 면을 지속적으로 보여주는 것이 중요한 것 같다.


바쁜 프로젝트도 끝났고 책 원고 작성도 어느정도 여유가 있어서 이번 연휴기간에 휴가를 추가로 내어서 자전거로 국토종주를 다녀왔다. 취미로 자전거를 타기 시작한지 거의 2년이 넘어가는것 같다. 자전거를 타기 시작하면서 서울-부산 국토종주는 반드시 해보아야지 다짐했었는데 이번에 기회가 생겨서 다녀올 수 있게 되어 너무 좋았다.

국토종주는 혼자갈지 지인과 함께 갈지 고민을 많이하였는데 아무래도 목적지인 부산에서 친구들도 만나고 내 페이스에 맞게 자전거를 타려면 혼자서 가는게 좋을 것 같아 가족들의 우려에도 불구하고 혼자서 종주를 진행하기로 하였다.

4박 5일 코스로 계획하고 출발하였는데 일기예보를 보니 2일째 되는날부터 서울에서부터 남쪽으로 비구름이 내려와서 일정을 하루씩 앞당기기 위해 첫날부터 200KM를 넘게 달리게 되었다. 덕분에 비는 거의 맞지 않았지만 여유롭게 자전거길도 구경하고 지역탐방도 하는 등의 여유는 부릴 수 없어 아쉬움은 있었다. 결국 부지런히 달린 덕분에 3일째 되는날 부모님댁에 도착하는 일정을 2일째 되는날 도착하는 일정으로 당길 수 있었고 집에서 하루 쉰다음 부산에 도착하는 코스로 총 3박 4일의 일정으로 국토종주를 마칠 수 있게 되었다.

생각보다 국토종주가 재밌어서 다음에는 다른 종주 코스들을 달려서 인증수첩에 도장을 모두 찍어보아야 겠다. 내년 봄에도 달려봐야지. 그나저나 새 자전거 사고싶다.

아래는 10월동안 정리한 이슈 내용들이다.

Terraform RDS duration

RDS 백업 설정시 기본 30분 duration을 설정해주어야 한다. 만약 데이터 용량이 많아진다면 duration 설정을 바꿔주는게 좋다. 기본 설정은 중요하진 않지만 Terraform 설정 시 기본값을 설정해주지 않으면 오류가 발생하므로 챙겨서 작성해주자.

https://stackoverflow.com/questions/22148001/amazon-rds-instance-backup-window-duration

Kotlin Sequence

Kotlin의 Sequence은 Java의 Stream과 유사하게 요소에 대한 지연 평가를 지원해준다.

그래서 아래와 같이 사용하는 경우 지연 평가로 인해 연산에 대한 효율성을 가져갈 수 있다.

listOf("apple", "banana", "kiwi", "mango", "strawberry").asSequence()
    .filter {
        println("filter: $it")
        it.length > 5
    }
    .map {
        println("map: $it")
    }
    .take(1)
    .toList()

// filter: apple
// filter: banana
// map: banana

Collection의 경우는 요소에 대한 평가를 즉시하므로 연산에 대한 효율성을 가져가기에는 힘들 수 있다.

listOf("apple", "banana", "kiwi", "mango", "strawberry")
    .filter {
        println("filter: $it")
        it.length > 5
    }
    .map {
        println("map: $it")
    }
    .take(1)
    .toList()

// filter: apple
// filter: banana
// filter: kiwi
// filter: mango
// filter: strawberry
// map: banana
// map: strawberry

그렇다고 무조건 Sequence가 좋다고 할 순 없다. 작은 크기의 Collection을 처리하는 경우나 간단한 연산을 수행하는 경우에는 지연평가에 필요한 오버헤드로 인해 성능적으로 더 좋지 않을 수 있으니 사용 사례에 맞게 잘 사용해야하겠다.

추가로 Java에서 Stream을 사용해오던 개발자라면 Kotlin의 Collection의 filter, map을 사용할 때 지연평가를 하지 않을까 하는 오해를 할 수 있으니 주의해야겠다.

Heap dump 방법들

jmap

jmap -dump:format=b, file=<file-path> <pid>

Gradle HeapDumpOnOutOfMemoryError

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file-path>

jcmd

jcmd <pid> GC.heap_dump <file-path>

OOM 이슈 대응

최근 서버에서 OOM 발생 사례가 있어 모니터링을 위한 설정들을 모색하고 있다. 메모리 누수가 있을만한 코드가 있는지 살펴보기는 했지만 특정하기에는 힘들었고 OOM으로 Pod이 죽는 경우 Kubernetes 특성상 바로 되살아나므로 어떤 API로 인해 OOM이 발생하는지 특정하기도 어려웠다.

그래서 일단 OOM 발생시점을 인지하기 위해 Pod이 재시작하는 경우 알림을 받을 수 있도록 조치를 하였다. Prometheus Rule이나 Elastic APM의 Rule설정을 통해 Pod 재시작에 대한 알림 설정을 시도하려 했으나 kubernetes의 helm 중에 Pod 재시작을 알려주는 Collector가 있어서 해당 Collector를 설치해주는 것으로 설정하였다.

https://github.com/airwallex/k8s-pod-restart-info-collector

그런다음 OOM으로 인해 Pod이 다운되는 경우 Heap dump를 수행하도록 설정을 하려고 하였으나 자칫 Heap dump를 수행하는 도중에 node의 메모리가 부족해지는 상황이 발생하면 node까지도 다운될 수 있으므로 해당 설정은 위험성이 있어 설정하지 않기로 하였다.

현재까지 한번식 Heap dump를 실행해보면서 일반적이지 않은 메모리 할당이 있는지 모니터링 중인데 특이점은 보이지 않고 있다. 그래서 특정 API의 문제인지 확인해봐야 겠다. Pod이 재시작할 때 요청된 API들도 모니터링 중인데 특정한 API가 보이지 않아 원인을 파악하는데 어려움을 겪고 있다. API 요청건들을 좀 더 분석하면서 APM의 다른 설정을 할 수 있을지 살펴봐야겠다.

행동이 상태를 결정한다

요즘 객체지향의 사실과 오해를 읽으면서 상태에 대한 나의 견해와 유사한 내용이 있어 적어본다.

상태를 결정하고 행동을 결정하면 나쁜 점들

  • 상태를 먼저 결정할 경우 캡슐화가 저해된다.
  • 객체를 협력자가 아닌 고립된 섬으로 만든다.
  • 객체의 재사용성이 저하된다.

요즘에도 OOP 언어로 서버를 개발하고 있지만 Database에 종속적인 설계를 하거나 클라이언트의 상태머신처럼 만들어지는 경우를 많이 보았다. 이로 인해 서버에서 제공해주는 기능이 어색하거나 재사용성이 떨어지는 경우가 많다. 그러다보니 코드가 불필요하게 복잡해지고 클라이언트에 혹은 데이터베이스에 종속적인 서버스들이 나오는 경우도 있는 것 같다. 특히 가장 많이 발견되는 사례가 Domain Model이 데이터베이스의 상태를 전달해주는 역할만 수행하고 서비스 객체가 모든 비지니스 로직을 수행하게 되면서 서비스 객체가 지나치게 비대해지고 책임을 많이 가지고 있는 것이다.

클라이언트든 서버든 제공해야 할 행동에 의해서 상태를 결정하는 어플리케이션을 만들면 좋을것 같다. 그러기 위해서는 데이터베이스 설계를 먼저하고 API를 설계하는 관습에서 벗어나 사용자 또는 클라이언트에서 필요로하는 API먼저 설계하고 그에 대한 결과로 상태가 결정되면 데이터베이스의 스키마를 설계하는 Outsicde in devlopment 개발방법을 실천해보면 좋을 것 같다.