Go 100가지 실수 패턴과 솔루션을 보고 정리한 글입니다.
nil 슬라이스와 빈 슬라이스
nil 과 빈 슬라이스 는 개념적으로 혼동하기 쉬우나 상황에 따라 적합한 방법으로 만들어야 할 수 있습니다.
Go 언어에서 슬라이스 초기화 하는 법
Go 에서 슬라이스 를 초기화 하는 방법에는 크게 네가지가 있습니다.
|
|
위 코드의 실행 결과는 다음과 같습니다.
|
|
네가지 방식 모두 길이가 0인 empty 슬라이스 입니다. 따라서 nil 인 슬라이스 도 empty 슬라이스 입니다.
무의미한 할당 피하기
nil 슬라이스와 빈 슬라이스 의 가장 큰 차이점은 할당 방식입니다. nil 슬라이스로 초기화하면 할당 할 필요가 있지만 빈 슬라이스 는 할당할 필요가 있습니다. 따라서 슬라이스 를 리턴하는 함수를 작성 할 경우 다른 언어에서 안전을 위해 빈 컬렉션을 리턴하는 것과 달리 nil 슬라이스 를 리턴하는 것이 좋습니다.
|
|
위와 같은 함수의 경우와 같이 foo 가 false 인 경우 무의미한 빈 할당이 발생할 수 있기 때문에 방법 1을 사용하는 것이 좋습니다.
생성 할 slice 의 크기를 아는 경우
|
|
다만 생성할 slice 의 크기를 아는 경우 방법 4를 사용하여 불필요한 할당과 복제를 피하는 것이 좋습니다.
편의 구문(syntex sugar)
방법 2의 경우 널리 사용되지는 않으나, append 와 같은 함수를 호출할 때 nil 슬라이스 를 한 줄로 전달할 수 있어 편의를 위해 사용할 수 있습니다. 반드시 가독성이 높아지는 방법은 아니지만 알아두면 좋습니다.
|
|
초기값을 넣을 경우
방법 3의 경우 초기 값을 넣을 경우 적합합니다.
|
|
하지만 생성할 때 초기값을 넣을 필요가 없는 경우 이 방법을 사용 해선 안 됩니다. 슬라이스가 nil 이 아니라는 점만 제외하면 방법 1과 차이가 없으며 무의미한 할당만이 발생합니다.
nil 과 빈 슬라이스 를 구분해야 하는 경우
어떤 라이브러리 에서는 nil 슬라이스 와 빈 슬라이스 를 구분하는 경우가 있습니다. 예를 들어 encoding/json 패키지가 그렇습니다.
|
|
이 예제를 실행시 두 구조체에 대한 마샬링 결과가 다르다는 점에 주목해야합니다.
|
|
여기서 nil 슬라이스는 null 로 마샬링 되었으나 nil 이 아닌 빈 슬라이스는 빈 배열로 마샬링 되었습니다. null 과 [] 를 엄격히 구분하는 JSON 클라이언트 를 처리할 때는 이러한 차이를 명확히 구분 해야합니다.
슬라이스가 비었는지 제대로 확인하는 방법
nil 슬라이스와 empty 슬라이스에는 차이가 있으므로 슬라이스가 비어있는지 확인하기 위한 코드를 명확하게 작성하지 않으면 미묘한 버그가 발생할 수 있습니다.
|
|
위 예제에서는 operations 슬라이스가 nil 인지 확인하는 방식으로 슬라이스에 원소가 있는지 확인했습니다.
하지만 getOperations 에서는 nil 슬라이스가 아닌 empty 슬라이스를 리턴하기 때문에 operations != nil 는 항상 true 가 되며 문제가 발생합니다.
|
|
해결하는 방법 중 하나로는 getOperations 함수에서 id 가 비어있을 시 nil 을 반환하는 것입니다. 이렇게 바꾸면 슬라이스가 nil 인지 확인하는 코드를 사용할 수 있습니다. 하지만 외부라이브러리를 사용하는 경우 와 같이 호출 대상을 수정할 수 없을 경우 문제가 발생할 수 있으므로 슬라이스가 empty 슬라이스 인지 nil 인지 확인할 필요가 있을수 있습니다. 그런 경우는 다음과 같이 길이를 검사하면 됩니다.
|
|
앞에서 확인한 것과 같이 빈 슬라이스는 본래 정의에 의해 길이가 0 이므로 nil 슬라이스도 항상 비어있습니다. 그래서 슬라이스의 길이를 검사하면 보든 경우를 골라낼 수 있습니다. ‘따라서 길이를 검사하는 방법이 가장 바람직합니다. 우리가 호출하는 함수를 항상 수정할 수 없기 때문입니다.
Go 언어의 공식 문서에서는 인터페이스를 디자인 할 때 nil 슬라이스와 empty 슬라이스를 구분하는 방식을 피하라고 권장합니다. 미묘한 에러를 발생시킬 수 있기 때문입니다. 리턴하는 슬라이스가 nil 인지 아니면 비어있는지에 따라 의미나 구문이 달라져선 안됩니다. 호출하는 입장에서는 두 경우 모두 의미가 같기 때문입니다.