이동 방법: 값 수신기 유형에 대한 포인터

Avoid Using Value Receiver Type in Golang Structs Methods



이 기사에 대해



이 기사에서는 독자가 Go 프로그래밍 언어에서 struct , methodsgoroutine 에 대한 기본 지식을 이미 알고 있다고 가정합니다.

이 문서의 주요 목적은 구조체 메서드(Go 프로그래밍 언어)에서 값/포인터 수신기 유형의 사용에 대해 스스로 상기시키는 것입니다.
나는 이것에 대해 쓰기로 결정했습니다. 이것은 매우 간단한 일이지만,
구조체 메서드에서 값 수신기 유형을 사용하여 발생하는 문제를 디버깅하는 데 1시간 이상을 보낸 후.

아래 예에서는 값 수신자를 사용하여 발생한 문제를 경고 없이 보여줍니다.

구조체 메서드의 수신자 유형



Golang에서는 객체 지향 프로그래밍 언어의 메서드와 유사한 구조체에 대한 메서드를 정의합니다.
OO 언어와의 차이점은 Golang에는 valuepointer 수신기 유형의 두 가지 유형의 수신기가 있다는 것입니다.

포인터 리시버 유형은 '객체/구조체'의 포인터를 메모리에 복사합니다. 이는 메서드에서 변경된 사항이 메모리의 실제 '객체/구조체'를 변경하는 OO 프로그래밍 언어의 메서드와 동일합니다.
포인터 수신기 유형의 예는 다음과 같습니다.

// Define a structtype Book struct {
    Title string
}

    // A pointer receiver type method. 
// Marked with the asterisk (*) sign.
func(b *Book) ChangeTitle(title string) {
  b.Title = title
}


값 수신자 유형은 '객체/구조체' 데이터를 복사합니다. 메서드에서 객체/구조체에 대한 변경 사항은 메모리의 실제 '객체/구조체'를 변경하지 않습니다.
값 수신자 유형의 예는 다음과 같습니다.

// Value receiver type. 
// Marked with no asterisk (*) sign, in the struct receiver. 
func (b Book) ChangeTitle(title string) {
    b.Title = title
}


Golang에서는 메서드가 구조체 데이터를 변경할 필요가 없는 경우에만 값 수신자를 사용합니다.
그러나 모든 경우에 해당되는 것은 아닙니다. 이 기사에서는 '읽기 전용' 방식으로 포인터 수신기를 사용해야 하는 예를 보여 드리겠습니다.

값 포인터 유형


비효율 할당의 예 경고



package main

import (
    "fmt"
)

type book struct {
    title string
}

// Change the title of a book
func (b book) changeTitle(title string) {
    b.title = title
}

type library struct {
    books []book
}

func (lib library) addABook(book book) {
    lib.books = append(lib.books, book)
}

func main() {
    lib := library{}

    fmt.Println(lib)

    b := book{title: "Three little pigs"}
    b.changeTitle("Blue Birds")
    lib.addABook(b)

    fmt.Println(lib)
}

Go 언어 서버(vscode 확장), goplsineffective-assignmentchangeTitle 메서드의 할당에 대해 addABook 경고를 표시합니다.
이는 포인터 수신기 유형을 사용하는 것을 잊은 경우 버그를 방지하는 데 매우 유용합니다.

고루틴 구현의 예



포인터 수신기가 필요하지만 값 수신기가 사용되지만 경고가 표시되지 않는 다른 조건도 있습니다.
이 상황은 아래 코드와 같이 goroutine 를 사용하면 발생합니다.

import (
    "fmt"
    "time"
)

type book struct {
    title string
}

type library struct {
    books []book
}

func (lib *library) addABook(book book) {
    fmt.Printf("Add book '%s'\n", book.title)
    lib.books = append(lib.books, book)
}

// Method below is mimicking time consuming operation.
// Method below is a Value receiver type.
func (lib library) timeConsumingOperation() {
    // `Sleep` to mimick ` time-consuming operation.
    time.Sleep(5 * time.Millisecond)

    // Check the number of books
    fmt.Printf("Value receiver type. Found %d books.\n", len(lib.books))
}
func main() {
    lib := library{}

    // Goroutine mimicking a time consuming operation.
    go lib.timeConsumingOperation()

    // Add 2 books
    go lib.addABook(book{title: "Rain Rain Go Away"})
    go lib.addABook(book{title: "Rainbow After Rain"})

    // `Sleep` to make sure all goroutines are completed
    time.Sleep(2 * time.Second)

    fmt.Printf("Actual number of books : %d books.\n", len(lib.books))
}


위의 코드를 실행하면 잘못된 결과가 생성됩니다.LibrarytimeConsumingOperation에는 오래된 버전이 있습니다.
(방법이 goroutine로 실행되었기 때문에).

Add book 'Rainbow After Rain'
Add book 'Rain Rain Go Away'
Value receiver type. Found 0 books.
Actual number of books : 2 books.


포인터 수신기 유형



데이터 무결성을 원하는 경우 구조체 메서드에서 포인터 수신기 유형을 사용하는 것이 필수입니다.
물론 값 수신자 유형을 사용해야 하는 경우는 적습니다.timeConsumingOperation 에 포인터 수신기 유형이 있는 이전 예제의 수정은 아래에서 볼 수 있습니다(별표(*) 추가).

func (lib *library) timeConsumingOperation() {

timeConsumingOperation에서 포인터 유형을 사용하면lib가 메모리의 동일한 데이터를 가리키도록 하여 데이터 무결성을 보장합니다.
수정된 코드를 실행하면 다음이 생성됩니다.

Add book 'Rainbow After Rain'
Add book 'Rain Rain Go Away'
Value receiver type. Found 2 books.
Actual number of books : 2 books.


결론



값 수신기 유형 대신 메서드에서 포인터 수신기를 사용하면 데이터 무결성이 보장됩니다.
몇 가지 경우를 제외하고 항상 포인터 수신기 유형을 사용하여 잠재적인 버그를 방지해야 한다고 제안합니다.
위의 경우와 같은 도구로는 잡을 수 없습니다.

다음은 Code xample repository in Github.com의 링크입니다.

이 기사가 유용할 수 있기를 바라며 읽어 주셔서 감사합니다.

좋은 웹페이지 즐겨찾기