[Disco Very Go] Function

함수 인자로 일반 (혹은 포인터) 슬라이스 받는경우

func ReadFrom(r io.Reader, lines *[]string) error {
  • []string이 아니라 *[]string을 인자로 받는 이유

ReadFrom 함수가 변수의 값을 변경하고자 하기 때문이다.

슬라이스는 배열에 대한 포인터,길이,용량 세 값으로 구성되있다.

만일 *[]string을 쓰지 않고 []string을 인자로 넘긴다면 함수내에서 line 변수가 품고있는 값을 변경하더라도 함수 바깥에서는 무관한일이다.

만약에 lins []string으로 받았지만 거기에 있는 배열 포인터를 따라서 값을 변경한다면?

슬라이스가 담고 있어서 넘어간 세값은(포인터,길이,용량) 그저 값이지만 그중 하나인 포인터를 타고 가면 함수 바깥에서 쓰던것과 같은 배열을 가리키고 있기 때문에 변경이 일어나면 영향을 받게 된다.

func AddOne(nums []int) { // 단순하게 받는 인자들 ++ 하는 함수
for i := range nums {
nums[i]++
}
}
func ExampleAddOne() {
n := []int{1,2,3,4}
AddOne(n)
fmt.Println(n)
}
//결과 값
//[2 3 4 5]

함수 바깥에서도 반영된 이유는

앞서 설명한것 처럼 슬라이스가 포인터,길이,용량으로 구성되있는데 그 세가지 값이 넘어갔기 때문이다.

단순하게 정수 1,2,3,4 가 넘어간것이 아니다.

func ReadFrom(r io.Reader, lines *[]string) error {

함수에 *[]string 포인터 변수를 전달하는 이유는?

이 함수가 외부에서 넘겨준 슬라이스의 값을 변경해야 하기 때문

함수에서 슬라이스에 새로운 값을 추가할 경우 슬라이스의 길이를 변경해야한다.

용량이 부족한 경우에는 크기가 큰 새로운 배열을 만들어야 하기 때문에 배열 포인터와 용량도 변경이 될수있다. 따라서 포인터가 필요하다.

포인터로 넘어온 값은 *을 변수 앞에 붙여서 값을 참조할수 있다.

변수앞에 &을 붙이면 해당 변수에 담겨있는 값의 포인터 값을 얻을수있다.

Multiple Return

  • 리턴 값이 1개 인 경우
func WriteTo(w io.Writter, lines []string) error {
  • 리턴값이 2개 인 경우
func WriteTo(w io.Writter, lines []string) (int64, error) {

괄호로 둘러 쌀 필요가 있다.

  • return 도 아래 처럼 하면된다.
return n, err

에러 값 처리

리턴값이 한개 일때 에러값을 받을때 처리 방식

정상적이지 않는 경우 결과값을 돌려주는 방식으로 개수를 돌려주는 함수에서 음수를 돌려준다면 에러를 의미하는 규칙을 정하는것

Go에서는 이런 경우 에러가 아니라 정상으로 처리할수있도록 활용한다.

예로 strings.index*는 문자열에서 원하는 문자열이 나타나면 위치를 돌려주는 함수로 위치는 0에서 부터 시작하므로 음수가 발생할수 없다.

따라서 문자열이 발견되지 않으면 -1을 돌려주게 되있다.

그러나 이 경우는 에러 상황으로 취급하기 보다는 정상적인 상황의 하나로 취급하는것이 좋기 때문에 에러 값을 돌려주지 않고 특수 값을 돌려주는 방법을 사용한것이다.

이 함수는 에러를 돌려주지 않기 때문에 어떤 입력에 대해서도 성공하는 함수로 만들어 진것이다.

또다른 방법은 호출하는 쪽에서 에러 값을 받고 싶은 변수의 포인터나 레퍼런스를 함수로 넘겨줘서 받는 방법이다.

이 방법은 넘겨주는 쪽에서 자료형을 알수가 없기 때문에 에러를 받을 자료형을 알아야한다.

Go는 관례로 에러는 리턴값의 마지막 순서가 에러이다.

func WriteTo(w io.Writter, lines []string) (int64, error) {

물론 패닉이 있어서 다른 언어들의 exception 같은 처리를 제공한다.

보통 예외 가 생기면 호출스택을 따라 호출 역순으로 되짚어 가면서 예외를 처리해줄 코드를 찾는다.

그러나 GO의 패닉은 일반적인 에러 가 아니라 심각한 에러 상황에 쓰인다.

패닉없이 일반적인 흐름에서 에러를 처리하는 경우가 대부분이고 에러값을 돌려주는 방식에 익숙해져야한다.

에러를 돌려 받아서 처리하는 방식의 코딩은 반복적인 코드량이 매우 많아지는 문제가 있다.

그나마 다행히 go의 경우 if문을 아래 처럼 쓸수있다.

if err := MyFunc(); err !=nil {
}

위 경우가 Exception 방식보다 좋은 점은 Exception이 나고 해당 내용을 처리해줄 부분을 역순으로 찾다보면 해당 위치를 찾기가 힘든데 비해

Go는 해당 문제 발생시점에서 에러그대로 반환할수 있어서 좋다.

if err := MyFunc(); err != nil {
   return nil, err
}

예를 들어 포인터 하나와 에러 하나를 반환하는 함수 내에서 다음과 같이 에러를 호출자에게 넘길수있다.

새로운 에러를 생성해야 하는 경우 가장 간단한 방법으로 errors.New와 fmt.Errorf를가 있다.

물론, 더 자세한 구조와 정보를 담아서 호출한 곳으로 돌려줄 수 있지만 가장 단순한 것은 문자열 메시지를 주는 것입니다.

많은 경우 문자열 메시지는 로그로 출력되는 경우가 많은데 메시지만 보고 어디가 잘못됬는지 알기어려우니 알기쉽게 써줘야한다.

return errors.New(“stringList.ReadFrom: line is to long”)

무언가 부가적인 정보나 가변적 정보를 보내고 싶다면

return fmt.Errorf(“stringList: too long line at %d”, count)

이렇게 해도 된다.

Show your support

Clapping shows how much you appreciated jihoon’s story.