프로그래밍을 하다보면 자원을 할당해서 사용하고, 그것을 명시적으로 해제를 해주어야하는 경우가 존재한다.
하지만 사람인지라 그것을 까먹을 때가 있고, 그것이 나중에는 OOM 같은 문제를 일으키기도 한다.
그렇기에 여러 언어에서 자원의 할당과 해제를 쉽게 할 수 있도록 도와주는 여러 방식이 있는데, 그것을 살펴보자.
자바에서는 많이 사용하는 Connection 객체를 예시로 들 것이다.
보통 아래와 같이 try-catch-finally 구문을 많이 사용한다.
public class SomeClass {
public void doSomething() throws Exception {
BufferedReader br = new BufferedReader(new FileReader("some/path"));
try {
System.out.println(br);
} catch (Exception e) {
logger.error(e);
} finally {
br.close();
}
}
}
하지만, finally를 빼먹는 경우가 많기도 하고 finally 구문 안에서 Exception이 발생하면 정말 난감해진다.
이러한 문제를 해결하기위해, JDK 1.7 부터는 AutoCloseable
이라는 인터페이스가 등장했고 close() 를 지원하는 클래스들은 위 인터페이스를 구현토록하였다.
AutoCloseable
인터페이스를 구현하는 클래스에서는 try-with-resource라는 방법을 사용할 수 있고, 이 방법은 아래와 같다.
public class SomeClass {
public void doSomething() throws Exception {
try (BufferedReader br = new BufferedReader(new FileReader("some/path"));){
System.out.println(br.readline());
} catch (Exception e) {
logger.error(e);
}
}
}
위의 try-catch-finally 를 사용했을 때와 달리 직접 자원을 해제하지 않아도 되도록 변경되었고, 코드가 간결해졌다.
이번엔 Kotlin의 경우를 보자.
Kotlin은 Java와 호환성을 이야기하는 언어라 그런지, try-catch-finally를 사용할 수 있다.
class SomeClass {
fun doSomething() {
val br = BufferedReader(FileReader("some/path"))
try {
println(br.readline());
} catch (e: Exception) {
logger.error(e)
} finally {
br.close()
}
}
}
runCatching은 kotlin에서 제공하는 일종의 try-catch를 캡슐화한 블록이다.
(자원 할당 및 해제를 제외한) try-catch를 사용해야하는 일반적인 경우에는 try-catch보다 선호되는 방식이다.
왜 앞에 "자원 할당 및 해제를 제외한" 을 명시했는지는 다음 단락에 설명되어 있다.
class SomeClass {
fun doSomething() {
val br = BufferedReader(FileReader("some/path"))
runCatching {
println(br.readLine)
}.onFailure {
logger.error(it)
}.also {
br.close()
}
}
}
try-catch-finally 를 사용하는 것 보다는 kotlin스럽게 바뀌었다. block을 계속 chaining하여, runCatching에서 제공하는 여러가지기능을 사용할 수 있게 되었다.
하지만 이것도 별로다. 결국 직접 자원을 해제해야한다.
use
Extension 함수를 사용하는 방식public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R
use는 Closeable 인터페이스를 구현하는 클래스의 확장함수이며, java의 try-with-resources와 같이 자원의 해제를 자동으로 해주는 장점이 있다.
하지만, use 자체가 안전하지는 않기 때문에 try-catch를 함께 사용해줘야하긴한다.
그렇기에 다음과 같이 쓸 수 있다.
class SomeClass {
fun doSomething() {
try {
BufferedReader(FileReader("some/path")).use {
println(it.readline())
}
} catch (e: Exception) {
logger.error(e)
}
}
}
하지만, 우리는 try-catch 대신 위에서 소개한 runCatching이라는 kotlin스러운 방법을 사용할 수 있다.
그것을 적용해보면 다음과 같다.
class SomeClass {
fun doSomething() {
runCatching {
BufferedReader(FileReader("some/path")).use {
println(it.readline())
}
}.onFailure {
logger.error(it)
}
}
}
깔끔해졌다.
이제는 파이썬의 경우를 살펴보자.
위에서 실컷 JVM 언어들을 살펴보다, 갑자기 Python이라니 "엥?"이라는 말이나올수도 있지만 Python에서도 비슷한 문제가 있었는지 with라는 방식을 제공한다.
위에서 소개한, try-catch-finally의 Python 버전이라 생각하면 된다.
file = None
try:
file = open("some/path", "r")
print(file.readline())
except IOError:
print("error")
finally:
file.close()
JVM 언어들의 try-catch-finally와 똑같이 여러문제가 발생한다.
직접 자원을 해제해야하고, Exception 처리하기가 까다롭다.
이러한 문제를 해결하기 위해, Python에서는 with라는 함수를 제공한다.
Python의 with도 직접 자원을 해제해야하는 경우를 제거하기 위한 방법이다.
try:
with open("some/path", "r") as file:
print(file.readline())
except IOError:
print("error")
위와 같이 사용하면, finally 구문을 사용하지 않고 자원의 해제를 할 수 있다.
여러 언어들에서 자원을 할당하고, 직접해제하는 과정이 프로그래머의 입장에서 꽤나 잊어버리기 쉬운 과정이라 생각했는지 여러 방법을 지원하는 듯 하다.
다들 비슷비슷한 방식으로 하나의 방법을 지원하는 것 보면, 역시 모두가 생각하는 불편한 점은 비슷한거 아닐까..?