티스토리 뷰

Go/문서 읽기

[Effective Go] Names

piatoss 2023. 3. 29. 16:30

Package names

패키지를 임포트하면, 패키지 이름은 해당 패키지에 포함된 컨텐츠에 대한 접근자로 사용됩니다.

import "bytes"

func main() {
    _ = bytes.NewBuffer([]byte{})
}

좋은 패키지 이름은 짧고 간결하며 관련된 컨텐츠들을 연상시킬 수 있어야 합니다.
네이밍 컨벤션에 따라 패키지 이름은 소문자, 단일 단어여야 하며 _언더스코어나 대문자를 혼합할 필요는 없습니다.

 

패키지 이름은 유니크할 필요는 없으며, 만약 동일한 이름의 패키지를 사용할 경우 별칭(alias)을 붙여줍니다.
패키지를 임포트할 때 붙여준 이름이 사용중인 패키지를 결정하므로 충돌은 거의 발생하지 않습니다.

임포트한 패키지의 경로가 hello/world일지라도, 패키지 이름은 원본 디렉토리의 기본 이름인 world를 사용합니다.

import (
    "hello/world"
    hw "hello/world" // add alias
    bw "bye/world" // add alias
    . "fmt" // avoid this way to import
)

func main() {
    world.Print()
    hw.Print()
    bw.Print()
    Println("Hello World")
}

패키지를 임포트한 사용자는 패키지를 사용하기 위해 패키지이름.컨텐츠 형식으로 접근해야 하므로 해당 패키지로부터 외부로 노출된(exposed) 컨텐츠의 이름은 패키지이름과 반복을 피하는 것이 좋습니다.

 

예를 들어, repository 패키지의 Repository 인스턴스를 생성하는 NewRepository()라는 생성자 함수가 있다고 봅시다. 생성자 함수를 사용하기 위해서는 repository.NewRepository()로 접근을 해야 합니다.

package main

import "repository"

func main() {
    _ = repository.NewRepository()
}

여기서 패키지이름을 통해 사용자가 repository와 관련된 인스턴스를 생성하는 것이 명백하므로
NewRepository보다는 반복을 줄여서 repository.New()로 접근하는 것이 더 간결하고 명백해 집니다.

package main

import "repository"

func main() {
    _ = repository.New()
}

물론 하나의 패키지 안에 여러 개의 타입이 있고 대응되는 생성자가 여러 개인 경우처럼 예외상황이 있을 수 있으므로, 패키지 구조를 잘 살펴보고 상황에 따라 좋은 이름을 선택하도록 합시다.

Getters

Go에서는 getter와 setter를 자동으로 지원하지 않습니다. 필요에 따라 정의해서 사용하면 되지만, getter의 이름에 Get을 붙이는 것은 불필요해 보이는군요.

package contract

type Contract struct {
    owner string // lower case, not exported field
}

Contract 구조체의 owner필드는 소문자로 시작하여 해당 패키지 내부에서만 직접 접근하여 사용할 수 있는 private 필드입니다. 따라서 외부 패키지에서 임포트하여 사용할 경우 값을 직접 초기화할 수도 없을뿐더러 읽어올 수도 없습니다.

package main

import "contract"

func main() {
  c := contract.Contract{owner: "Lee"} // unknown field owner in struct literal 컴파일 에러!
  println(c.owner) // c.owner undefined (type contract.Contract has no field or method owner) 컴파일 에러!
}

private 필드의 값을 설정하고 가져오기 위한 getter와 setter 메서드를 아래와 같이 정의했습니다.

package contract

type Contract struct {
    owner string
}

func (c Contract) Owner() string {
    return c.owner
}

func (c *Contract) SetOwner(newOwner string) {
    c.owner = newOwner
}

getter의 이름은 Get으로 시작하지 않으면서 필드명과 동일하지만, 외부에서도 접근할 수 있도록 대문자로 시작합니다. setter의 이름은 Set과 필드명을 더하여 파스칼 케이스로 적어주었습니다.

package main

import "contract"

func main() {
    c := contract.Contract{}
    c.SetOwner("Lee")
    println(c.Owner())
    c.SetOwner("Park")
    println(c.Owner())
}
Lee
Park

만약 Contract 구조체의 필드가 아래와 같이 대문자로 시작한다면 외부 패키지에서도 접근하여 사용할 수 있으므로  getter와 setter는 굳이 필요하지 않을 것입니다.

package contract

type Contract struct {
    Owner string
}
package main

import "contract"

func main() {
    c := contract.Contract{Owner: "Lee"}
    println(c.Owner)
    c.Owner = "Park"
    println(c.Owner)
}
Lee
Park

Interface names

관례적으로, 단일 메서드를 가진 인터페이스의 이름은 해당 메서드 이름뒤에 er을 붙이거나 메서드 이름과 유사한 행위자 명사로 짓습니다.

type Stringer interface {
    String() string
}

이 외에도 Write - Writer, Read - Reader, Format - Formatter, Close - Closer 등이 있습니다.

 

혼란을 방지하기 위해 이러한 단일 메서드 인터페이스가 가진 메서드 이름과 시그니처가 동일하지 않다면 해당 메서드 이름을 사용하는 것을 지양해야 합니다.
반대로 해당 기능이 필요하거나 구현해야 한다면 동일한 메서드 이름과 시그니처를 사용할 것을 권장합니다.

package main

import (
	"fmt"
)

type Stringer interface {
	String() string
}

type MyString string

func (s MyString) String() string {
	return "MyString implements Stringer interface!"
}

// func (s MyString) String(x string) string {
//   return "Signature doesn't match with method of Stringer interface"
// }

// func (s MyString) ToString() string {
//     return "You'd better name this method as String..."
// }

func main() {
	s := MyString("")
	fmt.Println(s)
}
MyString implements Stringer interface!

MixedCaps

Go에서는 _언더스코어(스네이크 케이스)보다는 MixedCaps, mixedCaps와 같이 대문자로 단어들을 연결(파스칼, 카멜 케이스)합니다

Reference

Effective Go

최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday
글 보관함