Menu
Coddy logo textTech

센티넬 에러

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 55번째.

센티널 에러(sentinel error)는 특정한 에러 상태를 나타내는 미리 정의된 패키지 수준의 에러 변수입니다. errors.New를 사용하여 매번 새로운 에러를 생성하는 것과 달리, 센티널 에러는 한 번 선언되면 코드 전체에서 재사용됩니다.

센티널 에러(sentinel error)는 보통 Err로 시작하는 이름을 가진 패키지 레벨 변수로 정의합니다:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

함수는 특정 조건이 발생할 때 다음과 같은 미리 정의된 오류를 반환합니다:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

센티널 에러의 강력함은 호출자가 에러 값을 직접 비교하여 특정 에러 조건을 확인할 수 있다는 점에 있습니다:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Go의 표준 라이브러리는 센티넬 에러(sentinel errors)를 광범위하게 사용합니다. 예를 들어, io.EOF는 입력의 끝을 알리고, sql.ErrNoRows는 빈 쿼리 결과를 나타냅니다. 이 패턴은 패키지와 사용자 사이에 어떤 에러를 기대할 수 있는지에 대한 명확한 규약(contract)을 생성합니다.

하지만 에러가 래핑(wrapped)된 경우 단순한 동등성 비교는 제대로 작동하지 않습니다. 다음 레슨에서는 errors.Is()가 전체 에러 체인을 확인하여 이 문제를 어떻게 해결하는지 배우게 됩니다.

challenge icon

챌린지

쉬움

특정 실패 조건을 전달하기 위해 센티넬 에러(sentinel errors)를 사용하는 재고 관리 시스템을 구축해 보겠습니다. 호출자가 다양한 시나리오를 적절하게 처리할 수 있도록 패키지 수준의 에러 변수를 정의하게 됩니다.

코드는 두 개의 파일로 구성됩니다:

  • inventory.go: 일반적인 실패 사례에 대해 미리 정의된 센티넬 에러를 가진 재고 시스템을 생성합니다.

    패키지 수준에서 세 가지 센티넬 에러를 정의하세요:

    • ErrItemNotFound: 메시지 "item not found in inventory"
    • ErrOutOfStock: 메시지 "item is out of stock"
    • ErrInsufficientQuantity: 메시지 "insufficient quantity available"

    아이템 이름과 수량을 매핑하여 저장하는 Inventory 구조체를 생성하세요. 다음 메서드들을 구현하세요:

    • NewInventory() *Inventory - 빈 재고를 생성합니다.
    • AddItem(name string, quantity int) - 아이템의 수량을 추가하거나 업데이트합니다.
    • GetQuantity(name string) (int, error) - 아이템의 수량을 반환하거나, 아이템이 존재하지 않으면 ErrItemNotFound를 반환합니다.
    • Purchase(name string, quantity int) error - 아이템 구매를 시도합니다. 아이템이 존재하지 않으면 ErrItemNotFound를, 현재 수량이 0이면 ErrOutOfStock을, 요청된 수량이 가용 재고를 초과하면 ErrInsufficientQuantity를 반환합니다. 성공 시 재고를 줄이고 nil을 반환합니다.
  • main.go: 입력으로부터 재고 작업을 읽고, 호출자가 직접적인 비교 연산(equality comparison)을 사용하여 특정 센티넬 에러를 확인하는 방법을 보여줍니다.

    입력에서 아이템 이름과 구매 수량을 읽습니다. 재고를 생성하고, "laptop" 아이템을 수량 5로 추가한 다음 구매를 시도합니다. 반환된 에러에 따라 어떤 센티넬 에러가 발생했는지 식별하여 특정 메시지를 출력하세요.

다음 입력이 제공됩니다:

  • Line 1: 구매할 아이템 이름
  • Line 2: 구매할 수량

반환된 에러가 무엇인지 확인하여 결과를 처리하세요:

  • 만약 err == ErrItemNotFound이면: Error: Item '[name]' does not exist in our inventory를 출력합니다.
  • 만약 err == ErrOutOfStock이면: Error: Item '[name]' is currently out of stock을 출력합니다.
  • 만약 err == ErrInsufficientQuantity이면: Error: Cannot purchase [quantity] units of '[name]' - not enough in stock을 출력합니다.
  • 성공한 경우: Successfully purchased [quantity] units of '[name]'을 출력합니다.

예를 들어, laptop3이 주어지면 출력은 다음과 같아야 합니다:

Successfully purchased 3 units of 'laptop'

그리고 phone2가 주어지면 출력은 다음과 같아야 합니다:

Error: Item 'phone' does not exist in our inventory

그리고 laptop10이 주어지면 출력은 다음과 같아야 합니다:

Error: Cannot purchase 10 units of 'laptop' - not enough in stock

치트 시트

센티널 에러(sentinel error)는 특정 에러 상태를 나타내는 미리 정의된 패키지 수준의 에러 변수입니다. 센티널 에러는 한 번 선언되면 코드 전체에서 재사용됩니다.

센티널 에러는 패키지 수준의 변수로 정의하며, 일반적으로 Err로 시작하는 이름을 사용합니다:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

함수는 특정 조건이 발생했을 때 이러한 미리 정의된 에러를 반환합니다:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

호출자는 에러 값을 직접 비교하여 특정 에러 상태를 확인할 수 있습니다:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Go의 표준 라이브러리는 io.EOFsql.ErrNoRows와 같이 센티널 에러를 광범위하게 사용합니다.

직접 해보기

package main

import (
	"fmt"
)

func main() {
	// 입력 읽기
	var itemName string
	var quantity int
	fmt.Scanln(&itemName)
	fmt.Scanln(&quantity)

	// 인벤토리를 생성하고 수량이 5개인 "laptop" 추가
	inv := NewInventory()
	inv.AddItem("laptop", 5)

	// TODO: 아이템 구매 시도
	// inv.Purchase(itemName, quantity) 호출

	// TODO: 직접적인 동등 비교를 사용하여 어떤 특정 센티널 에러(sentinel error)가 반환되었는지 확인
	// if/else if 문을 사용하여 다음을 확인:
	// - err == ErrItemNotFound
	// - err == ErrOutOfStock
	// - err == ErrInsufficientQuantity
	// - err == nil (성공)

	// TODO: 에러에 따라 적절한 메시지 출력
	// ErrItemNotFound인 경우: fmt.Printf("Error: Item '%s' does not exist in our inventory\n", itemName)
	// ErrOutOfStock인 경우: fmt.Printf("Error: Item '%s' is currently out of stock\n", itemName)
	// ErrInsufficientQuantity인 경우: fmt.Printf("Error: Cannot purchase %d units of '%s' - not enough in stock\n", quantity, itemName)
	// 성공한 경우: fmt.Printf("Successfully purchased %d units of '%s'\n", quantity, itemName)
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨