golang 챌린지: 구조체x
를 받고 내부에서 찾을 수 있는 문자열 필드를 위한fn
을 호출하는 함수walk(x interface{}, fn func(string))
을 작성하라. 난이도: 재귀적
컴퓨팅에서 Reflection이란 자신의 구조, 특히 타입을 통해서 검토할 수있는 프로그램의 능력을 말한다. 이것은 메타프로그램의 한 형태이다. 이것은 또한 혼란의 큰 원인이 되기도 한다.
interface
란 무엇인가?string
, int
그리고 우리의 자료형인 BackAccount
와 같이 알려진 자료형으로 동작한다는 측면에서 type-safety의 편리성을 느껴왔다.interface{}
라는 자료형을 제공한다.walk(x interface{}, fn func(string))
은 x
로 어떠한 값도 받을 수 있다.interface
를 사용하고 정말 유연한 함수를 갖는 건 어떨까?interface
를 사용하는 함수의 사용자는 type-safery를 잃게 된다. 만약 string
형인 Foo.bar
를 함수에 전달하도록 의도했지만 int
형의 Foo.baz
가 전달됐다면? 컴파일러는 그 실수를 알려줄 수 없을 것이다. 또한 함수에 무엇이 잘되어야 하는지도 알 수 없다. 예시로 함수가 UserService
를 수용한다는 걸 아는 것은 매우 도움이 된다.interface
가 아님)를 중심으로 설계할 수 있는지 고려한다. 그러면 사용자는 그 함수가 동작하는데 필요한 방법을 구현 할 때 여러 자료형을 통해 함수를 사용할 수 있다.x
)를 갖는 구조체와 함께 함수를 호출할 것이다. 그러면 전달된 함수(fn
)에서 그것이 호출되는 지 확인할 수 있다.walk
를 통해 fn
에 들어오는 문자열을 담는 문자열 슬라이스를 저장하고자 한다. 이전 장에서는 함수/메소드의 호출부에 전용 자료형을 만들었지만, 이 경우에는 단지 got
에 접근하는 익명 함수 fn
을 전달한다.Name
을 갖는 익명 구조체
를 사용한다.x
와 함께 walk
를 호출하고 got
의 길이를 확인한다. 그리고 우리가 아주 기본적인 일을 하게 될 때, assertions에 대해 조금 더 자세히 알아본다.walk
에 대한 정의가 필요하다.fn
이 어떤 것과 호출될 것인지 조금 더 정확하게 선언하는 것이다.fn
에 전달된 문자열이 올바른지 확인하기 위해 다음의 코드를 추가한다.x
와 그 속성을 알아보기 위해 reflection을 이용한다.값
을 전달하는 ValueOf
함수를 갖는다. 이것은 우리에게 값을 알아볼 방법을 제공하고 우리가 그 다음 줄에 사용한 것처럼 그 값의 필드까지도 포함한다.String()
을 호출하고 만약 해당 필드가 문자열이 아닌 다른 값이라면 문제가 될 것임을 안다.fn
과 함께 호출되는 문자열 집합을 확인할 것이다.cases
에 추가한다.val
은 값 내부 필드의 수를 반환하는 NumField
메소드를 갖는다. 이것을 통해 필드를 순회하여 테스트에 통과하는 fn
을 호출할 수 있다.walk
의 다음 단점은 모든 필드를 string
으로 간주하는 것이다. 다음 시나리오에 대한 테스트를 작성해본다.string
인지 확인할 필요가 있다.struct
가 "flat" 하지 않은 경우이다. 다른 말로, 만약 struct
가 nested 필드를 갖는다면 어떻게 되는지이다.구조체
의 구조를 추론할 수 있어야 한다.Kind
를 통해 다시 한번 검사하고 만약 그것이 구조체
라면 우리는 단지 내부에서 walk
를 다시 호출하면 된다.switch
구문으로 변경하는 것이 가독성과 확장성을 높일 수 있다.값
에서는 NumField
를 사용할 수 없다. 우리는 그전에 드러나지 않은 값을 추출할 필요가 있고 그것은 Elem()
을 통해 할 수 있다.interface{}
로부터 refect.Value
를 추출하는 기능을 encapsulate 해보자.x
의 reflect.Value
를 얻고 검사할 수 있지만, 나는 그것이 어떻게 되는지 신경쓰지 않아도 된다.reflect.Value
에서 NumField
를 호출 하려하지만, 그것은 구조체가 아니기 때문에 값이 없다.walk
를 호출 하고 싶을 것이다.무언가
return
이 있는)인지를 처음에 검사하고 슬라이스가 아니라면 구조체라고 가정한다.walk
를 호출하며 그 값을 순회한다. 그렇지 않고 만약 relect.String
이라면 fn
을 호출하면 된다.walk
함수를 호출하는데, 이것은 개념적으로 모두 같은 부분이다.값
이 reflect.String
이라면 평소처럼 그냥 fn
을 호출한다.switch
를 통해 타입에 의존한 두 가지 것을 추출한다.값
(필드
또는 인덱스
)을 추출할 것인지numberOfValues
만큼 순회하며 getField
함수의 결과와 함께 walk
를 호출할 수 있다.map
이다.map
은 struct
와 굉장히 유사하다는 것을 알 수 있다. 단지 컴파일 과정에서 키들을 알 수 없다는 것 뿐이다.val
에서 reflect.Value
를 추출하기 위해 switch
에서 walk
를 호출하는(DRY) walkValue
를 도입했다.fn
이 호출되는 것으로 단언했기 때문에 테스트는 때때로 실패할 것이다.assertContains
의 정의는 아래와 같다.chan
이다.func
이다.reflect
패키지의 몇가지 개념을 설명했다.