과거에 [Java] byte[]을 String으로 바꾸기 글을 쓴 적이 있다.
요즘은 코틀린을 주로 사용을 해서 글을 다시 써보았다.
방법에 대해서는 https://www.baeldung.com/kotlin/byte-arrays-to-hex-strings 에 잘 나와 있기에 링크를 참고하는 것이 더 좋다.
최초 코드
원래 코드을 method extract 로 리팩토링을 해보니 아래와 같이 표현을 할 수 있는 코드였다. (원래코드는 아래에 나옵니다. ^^;;)
private fun bytesToHexString(bytes: ByteArray): String {
val hash = StringBuilder()
for (aByte in bytes) {
val hex = Integer.toHexString(0xFF and aByte.toInt())
if (hex.length == 1) {
hash.append('0')
}
}
return hash.toString()
}
최초 코드 리뷰 때 장호님이 왜 길이가 1이면 0을 붙이는지에 대해 문의를 주셨다.
생각해보니 byte[] 를 16진수 문자열로 바꾸는 코드라 할 수 있다.
일단 의문을 남기게 하는 코드는 냄새가 난다고 볼 수 있다.
사실 원래 코드는...
위의 코드는 기능을 분리를 해서 이해하기 쉬워졌다. 하지만 원래 아래처럼 해싱하는 부분과 같이 붙어있었다.
따라서 인지 부하를 높혀서 이해를 떨어뜨렸을 것이라 생각한다.
override fun hash(payload: String): String {
val sha256HMAC = Mac.getInstance(ALGORITHM)
sha256HMAC.init(secretKeySpec)
val hash = StringBuilder()
val bytes = sha256HMAC.doFinal(payload.toByteArray())
for (aByte in bytes) {
val hex = Integer.toHexString(0xFF and aByte.toInt())
if (hex.length == 1) {
hash.append('0')
}
hash.append(hex)
}
return hash.toString()
}
리팩토링 후 해싱을 하는 부분은 아래와 같이 단순해졌다.
override fun hash(payload: String): String {
val sha256HMAC = Mac.getInstance(ALGORITHM)
sha256HMAC.init(secretKeySpec)
val payloadInBytes = payload.toByteArray()
val bytes: ByteArray = sha256HMAC.doFinal(payloadInBytes)
return bytesToHexString(bytes)
}
장호 님의 추천 코드
두 번째 리뷰 때 장호님은 "%02X" 를 사용을 문의하셨다.
X 와 x 는 한글자 차이지만 값 자체가 달라지니 주의해야 한다.
다행히 테스트 코드가 잘 잡아주어서 "%02X" 가 아닌 "%02x" 로 적용을 했다. 16진수 값은 FF 나 ff 로 나타낼 수 있지만 문자열 상으로는 다른 값이다.
private fun bytesToHexString(bytes: ByteArray): String {
val hash = StringBuilder()
for (aByte in bytes) {
val hex = "%02x".format(aByte)
hash.append(hex)
}
return hash.toString()
}
혹시나 해서 코틀린의 확장 함수로 적용을 해보았다.
fun ByteArray.toHex(): String = joinToString(separator = "") {
eachByte -> "%02x".format(eachByte)
}
그런데 생각보다 성능이 떨어지는 것을 느껴졌다. 단위테스트의 시간이 세 자리로 바뀌었기 때문이었다.
15.4ms 정도 느려져서 사실 큰 차이는 아닐 수 있지만 레이턴시에 민감한 서비스라면 아쉬운 시간일 수 있기에 %02x 를 사용하기로 했다.
성능 차이에 대해 깊게 생각은 못했지만 아마도 joinToString 를 통해 합칠 때 객체를 계속 생성을 하게 되는 것이 성능을 떨어뜨리는 요소가 될 것으로 추측했다. StringBuilder 의 경우 내부적으로는 길이에 대해 재할당을 받기는 하지만 mutable 한 문자열이라 재할당에 대한 부담이 아무래도 적을 것이기 때문이다.
스트림 처리나 이벤트 프로세싱, 함수형 처리 등에서 불변에 대해 장점을 내새우지만 어떨 때는 가변 처리가 나은 경우가 있는데 이것이 그런 케이스라고 할 수 있겠다. "은총알은 없다"라는 말을 다시 새삼 느꼈다.
'Programing > JVM(Java, Kotlin)' 카테고리의 다른 글
실패하는 테스트부터 만들기 패턴 (0) | 2023.10.31 |
---|---|
Java 17 으로 업데이트할 이유가 하나 더 생겼다. (0) | 2023.08.07 |
@NotNull이 Needs Work가 필요한 수준인가요? (0) | 2021.08.09 |
System.out 의 성능? (0) | 2021.04.21 |
[Kotlin] Kotlin Koans (0) | 2020.12.25 |