The Ultimate Go Study Guide의 내용을 참고하여 작성했습니다.
사용자 정보를 조회하여 변경하고 이를 다시 저장하는 간단한 예제 프로그램을 통해서 함수의 사용 방법에 대해서 알아봅니다.
먼저 user
, queryStats
라는 두개의 구조체를 정의합니다. user
는 사용자 정보를 표현하고, queryStats
는 DB에서 쿼리 후 쿼리 결과를 표현합니다. 도큐먼트 기반 DB에 데이터를 저장한다고 가정하고 슬라이스를 이용해 documentDatabase
라는 더미 데이터를 정의했습니다.
newQueryStats()
는 queryStats
구조체의 객체를 생성하는 함수입니다. 함수 정의는 func
키워드를 이용하며, func 함수명(인자1 타입, 인자2 타입, ...) 반환타입 {}
의 형태로 정의합니다.
특별히 객체를 생성하여 반환하는 목적의 함수를 만들때는 함수명의 시작을 new, New
로 시작하는 관례가 많이 사용됩니다. newQueryStats()
함수에서 반환하는 queryStats
객체는 참조를 반환하므로 힙에 할당됩니다.
type user struct { ID int Name string } type queryStats struct { Query string Success bool Error error User *user } var documentDatabase = []string{ `{"ID": 1, "Name": "Gump"}`, `{"ID": 2, "Name": "Johnny"}`, `{"ID": 3, "Name": "Chris"}`, `{"ID": 4, "Name": "Edgar"}`, `{"ID": 5, "Name": "Binary"}`, } func newQueryStats(query string, success bool, err error, u *user) *queryStats { // 힙에 할당 return &queryStats{ Query: query, Success: success, Error: err, User: u, } }
findUser()
DB에서 이름을 이용하여 조회하는 함수입니다. documentDatabase
슬라이스에서 각 요소를 순회하면서 이름이 일치하는 데이터를 조회합니다. convertToUser()
함수는 JSON 문자열을 user 구조체 객체로 만들어 반환합니다.
<1>
에 _
가 사용됐습니다. _
는 공백 식별자라고 부르며, 어떤 값/타입에 대해서도 할당될 수 있습니다. 주로 다중 할당 상황에서 사용하지 않을 값이 존재할 경우 공백 식별자를 이용하여 값을 무시합니다. 간혹 에러를 무시하기 위해서 공백 식별자를 사용하는데, 좋지 못한 습관이므로 반드시 에러를 확인하여 적절한 에러 처리를 하는 것이 바람직합니다.
공백 식별자는 또 다른 유용한 용도가 존재하는데요, 여기에 잘 정리가 되어있으니 참고해주세요.
<2>
에서는 JSON 값을 언마샬링하여 저장할 user
타입의 객체 u
를 생성합니다. json.Unmarshal()
함수에 객체u
를 전달하면, doc
에서 JSON을 읽은 후 객체 u
에 할당해 줍니다. 객체 u
는 참조를 반환하므로 힙에 할당됩니다.
JSON 디코딩에 실행할 경우 <3>
에서 함수가 반환됩니다. 최종 조회 결과는 <4>
에서 반환됩니다.
func findUser(name string) *queryStats { var user *user var success bool // <1> for _, d := range documentDatabase { u, err := convertToUser(d) if err != nil { // <3> return newQueryStats("find", success, err, nil) } if u.Name == name { user = u success = true break } } // <4> return newQueryStats("find", success, nil, user) } func convertToUser(doc string) (*user, error) { // <2> var u user // 변수 u를 json.Unmarshal 함수에 전달하면, 함수는 r로부터 JSON 을 읽어서 변수 u에 넣어준다. err := json.Unmarshal([]byte(doc), &u) return &u, err }
updateUser()
함수는 사용자 정보를 DB에 저장하는 함수입니다. 조회 결과 일치하는 문서가 존재할 경우 인자로 받은 u
객체의 정보로 업데이트합니다.
<1>
에서 for
문 이용하여 배열이나 슬라이스를 순회할 때 range
키워드를 사용했습니다. range
키워드를 이용한 반환 타입은 요소의 인덱스 번호와 값이 반환됩니다. 앞선 예제에서는 인덱스를 사용하지 않기 때문에 공백 식별자로 무시했으나, updateUser()
함수에서는 요소를 업데이트하기 위해서 사용합니다.
convertToDocument()
함수에서는 인자로 받은 u
객체를 JSON 문자열로 마샬링 후 반환합니다.
func updateUser(u *user) *queryStats { var success bool // <1> for idx, d := range documentDatabase { user, err := convertToUser(d) if err != nil { return newQueryStats("find", success, err, nil) } if user.ID != u.ID { continue } doc, err := convertToDocument(u) if err != nil { return newQueryStats("update", success, err, u) } documentDatabase[idx] = doc success = true break } return newQueryStats("update", success, nil, u) } func convertToDocument(u *user) (string, error) { b, err := json.Marshal(u) if err != nil { return "", err } return string(b), nil }
이제 결과를 확인해볼 차례입니다. 먼저 이름이 Gump
인 사용자를 조회한 후 이름을 Foo
로 변경합니다. 그리고 변경한 데이터를 DB에 저장하고 결과를 확인합니다.
<1>
에서는 조회 결과를 출력하기 위해서 *
연산자를 사용합니다. *
는 역참조 연산자라고 부르며, 포인터가 가르키는 값에 접근하기 위해서 사용합니다.
func main() { qs := findUser("Gump") if !qs.Success { if qs.Error != nil { fmt.Println(qs.Error) } return } // <1> fmt.Printf("%+v %+v\n", *qs, *qs.User) qs.User.Name = "Foo" qs = updateUser(qs.User) if qs.Error != nil { fmt.Println(qs.Error) return } fmt.Printf("%+v %+v\n", *qs, *qs.User) fmt.Println(fmt.Sprintf("Current database : %s", documentDatabase)) }
<출력결과> {Query:find Success:true Error: User:0xc0000ae018} {ID:1 Name:Gump} {Query:update Success:true Error: User:0xc0000ae018} {ID:1 Name:Foo} Current database : [{"ID":1,"Name":"Foo"} {"ID": 2, "Name": "Johnny"} {"ID": 3, "Name": "Chris"} {"ID": 4, "Name": "Edgar"} {"ID": 5, "Name": "Binary"}]
출력 결과를 보시면 ID
가 1인 문서의 Name
이 Foo
로 변경된 것이 확인됩니다.