Updated:

16 minute read

개요

Go 1.21부터 추가된 minmax 내장 함수는 비교 가능한 값들 중 최소값과 최대값을 반환합니다.

주요 특징:

  • 내장 함수: import 없이 사용 가능
  • 가변 인자: 2개 이상의 값 비교
  • 타입 안전: 컴파일 타임 타입 체크
  • Ordered 타입: 정수, 실수, 문자열 지원
  • 간결성: if 문이나 반복문 불필요
  • 성능: 최적화된 구현
  • NaN 처리: 특수 케이스 지원

기본 사용법

1. 정수 타입

package main

import "fmt"

func main() {
    // int
    fmt.Println(min(1, 2))                    // 1
    fmt.Println(max(1, 2))                    // 2
    
    // 여러 값 비교
    fmt.Println(min(5, 3, 8, 1, 9))          // 1
    fmt.Println(max(5, 3, 8, 1, 9))          // 9
    
    // int64
    var a, b int64 = 100, 200
    fmt.Println(min(a, b))                    // 100
    fmt.Println(max(a, b))                    // 200
    
    // uint
    var x, y uint = 10, 20
    fmt.Println(min(x, y))                    // 10
    fmt.Println(max(x, y))                    // 20
    
    // 음수
    fmt.Println(min(-5, -2, -10))            // -10
    fmt.Println(max(-5, -2, -10))            // -2
}

2. 실수 타입

func main() {
    // float64
    fmt.Println(min(3.14, 2.71))             // 2.71
    fmt.Println(max(3.14, 2.71))             // 3.14
    
    // 여러 실수
    fmt.Println(min(1.1, 2.2, 3.3, 0.5))    // 0.5
    fmt.Println(max(1.1, 2.2, 3.3, 0.5))    // 3.3
    
    // float32
    var f1, f2 float32 = 1.5, 2.5
    fmt.Println(min(f1, f2))                 // 1.5
    fmt.Println(max(f1, f2))                 // 2.5
    
    // 0과 음수
    fmt.Println(min(0.0, -1.0, 1.0))        // -1
    fmt.Println(max(0.0, -1.0, 1.0))        // 1
}

3. 문자열 타입

func main() {
    // 문자열 비교 (사전순)
    fmt.Println(min("apple", "banana"))      // apple
    fmt.Println(max("apple", "banana"))      // banana
    
    // 여러 문자열
    fmt.Println(min("go", "rust", "python", "c"))   // c
    fmt.Println(max("go", "rust", "python", "c"))   // rust
    
    // 대소문자 구분
    fmt.Println(min("Apple", "apple"))       // Apple (대문자가 작음)
    fmt.Println(max("Apple", "apple"))       // apple
    
    // 길이와 무관
    fmt.Println(min("a", "aaa"))             // a
    fmt.Println(min("b", "aaa"))             // aaa (b > a)
}

4. 단일 값

func main() {
    // 단일 값도 허용
    fmt.Println(min(42))                     // 42
    fmt.Println(max(42))                     // 42
    
    // 문자열도 동일
    fmt.Println(min("hello"))                // hello
    fmt.Println(max("hello"))                // hello
}

타입 제약

1. cmp.Ordered

import "cmp"

// min과 max가 지원하는 타입들
type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    ~float32 | ~float64 |
    ~string
}

// 사용자 정의 타입도 가능
type MyInt int

func main() {
    var a, b MyInt = 10, 20
    fmt.Println(min(a, b))  // 10
    fmt.Println(max(a, b))  // 20
}

2. 컴파일 에러

func main() {
    // bool은 Ordered가 아님
    // min(true, false)  // 컴파일 에러
    
    // 구조체는 Ordered가 아님
    type Point struct{ X, Y int }
    // min(Point{1, 2}, Point{3, 4})  // 컴파일 에러
    
    // 슬라이스는 비교 불가
    // min([]int{1}, []int{2})  // 컴파일 에러
    
    // 다른 타입 혼용 불가
    // min(1, 1.5)  // 컴파일 에러 (int와 float64)
}

3. 타입 일관성

func main() {
    // 모든 인자는 같은 타입이어야 함
    var a int = 1
    var b int = 2
    fmt.Println(min(a, b))  // OK
    
    // 타입 변환 필요
    var x int = 1
    var y int64 = 2
    // min(x, y)  // 에러
    fmt.Println(min(int64(x), y))  // OK
    
    // 리터럴은 타입 추론
    fmt.Println(min(1, 2, 3))      // OK (모두 int)
    fmt.Println(min(1.0, 2.0, 3.0)) // OK (모두 float64)
}

이전 방법과 비교

1. if 문 사용

// Go 1.20 이전
func minOld(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func maxOld(a, b int) int {
    if a > b {
        return a
    }
    return b
}

// Go 1.21+
func main() {
    // 간결하고 명확
    result := min(10, 20)  // 10
    result = max(10, 20)   // 20
}

2. 삼항 연산자 대체

// Go에는 삼항 연산자가 없음
// 다른 언어: result = (a < b) ? a : b

// Go 1.20 이전
func getMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}

// Go 1.21+
func getMin(a, b int) int {
    return min(a, b)  // 훨씬 간결
}

3. 여러 값 비교

// Go 1.20 이전 - 반복문
func minSlice(values []int) int {
    if len(values) == 0 {
        panic("empty slice")
    }
    
    minimum := values[0]
    for _, v := range values[1:] {
        if v < minimum {
            minimum = v
        }
    }
    return minimum
}

// Go 1.21+ - 훨씬 간단
func main() {
    values := []int{5, 3, 8, 1, 9}
    
    // 슬라이스를 직접 전달할 수는 없지만
    // min(values...)  // 에러: 슬라이스는 전개 불가
    
    // 명시적 전달
    result := min(5, 3, 8, 1, 9)  // 1
    
    // 또는 슬라이스 순회 함수 사용
    result = minSlice(values)
}

4. math 패키지

import "math"

func main() {
    // math 패키지는 float64만 지원
    x := math.Min(1.5, 2.5)  // 1.5
    y := math.Max(1.5, 2.5)  // 2.5
    
    // int는 변환 필요
    a, b := 10, 20
    minInt := int(math.Min(float64(a), float64(b)))
    
    // 내장 함수는 타입 변환 불필요
    minInt = min(a, b)  // 훨씬 간단
    
    // math는 2개 값만 비교
    result := math.Min(math.Min(1.0, 2.0), 3.0)
    
    // 내장 함수는 가변 인자
    result = min(1.0, 2.0, 3.0)  // 간결
}

특수 케이스

1. NaN 처리

import "math"

func main() {
    nan := math.NaN()
    
    // NaN과 비교 시 NaN 반환
    fmt.Println(min(nan, 1.0))           // NaN
    fmt.Println(max(nan, 1.0))           // NaN
    
    // 여러 값 중 하나라도 NaN이면 NaN
    fmt.Println(min(1.0, nan, 3.0))      // NaN
    fmt.Println(max(1.0, 2.0, nan))      // NaN
    
    // NaN 확인
    result := min(nan, 1.0)
    if math.IsNaN(result) {
        fmt.Println("Result is NaN")
    }
}

2. 무한대 처리

import "math"

func main() {
    inf := math.Inf(1)   // +Inf
    ninf := math.Inf(-1) // -Inf
    
    // 무한대와 비교
    fmt.Println(min(inf, 100.0))         // 100
    fmt.Println(max(inf, 100.0))         // +Inf
    
    fmt.Println(min(ninf, 100.0))        // -Inf
    fmt.Println(max(ninf, 100.0))        // 100
    
    // 무한대끼리 비교
    fmt.Println(min(inf, ninf))          // -Inf
    fmt.Println(max(inf, ninf))          // +Inf
}

3. 제로 값

func main() {
    // 양수 제로와 음수 제로
    posZero := 0.0
    negZero := math.Copysign(0, -1)
    
    // Go의 min/max는 +0과 -0를 구분하지 않음
    fmt.Println(min(posZero, negZero))   // 0 또는 -0
    fmt.Println(max(posZero, negZero))   // 0 또는 -0
    
    // 정수는 제로만 존재
    fmt.Println(min(0, 0))               // 0
}

4. 같은 값들

func main() {
    // 모든 값이 같을 때
    fmt.Println(min(5, 5, 5, 5))        // 5
    fmt.Println(max(5, 5, 5, 5))        // 5
    
    // 문자열도 동일
    fmt.Println(min("a", "a", "a"))     // a
    fmt.Println(max("a", "a", "a"))     // a
}

실전 예제

1. 범위 제한 (Clamp)

func clamp(value, minVal, maxVal int) int {
    return max(minVal, min(value, maxVal))
}

func main() {
    // 값을 범위 내로 제한
    fmt.Println(clamp(5, 0, 10))   // 5 (범위 내)
    fmt.Println(clamp(-5, 0, 10))  // 0 (최소값으로)
    fmt.Println(clamp(15, 0, 10))  // 10 (최대값으로)
    
    // 실수에도 적용
    clampFloat := func(value, minVal, maxVal float64) float64 {
        return max(minVal, min(value, maxVal))
    }
    
    fmt.Println(clampFloat(3.14, 0.0, 5.0))   // 3.14
    fmt.Println(clampFloat(-1.0, 0.0, 5.0))   // 0.0
    fmt.Println(clampFloat(10.0, 0.0, 5.0))   // 5.0
}

2. 슬라이스 최소/최대값

func minSlice(values []int) (int, bool) {
    if len(values) == 0 {
        return 0, false
    }
    
    result := values[0]
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result, true
}

func maxSlice(values []int) (int, bool) {
    if len(values) == 0 {
        return 0, false
    }
    
    result := values[0]
    for _, v := range values[1:] {
        result = max(result, v)
    }
    return result, true
}

func main() {
    numbers := []int{5, 3, 8, 1, 9, 2}
    
    if minVal, ok := minSlice(numbers); ok {
        fmt.Println("Min:", minVal)  // Min: 1
    }
    
    if maxVal, ok := maxSlice(numbers); ok {
        fmt.Println("Max:", maxVal)  // Max: 9
    }
}

3. 제네릭 Min/Max

import "cmp"

func Min[T cmp.Ordered](values []T) (T, bool) {
    if len(values) == 0 {
        var zero T
        return zero, false
    }
    
    result := values[0]
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result, true
}

func Max[T cmp.Ordered](values []T) (T, bool) {
    if len(values) == 0 {
        var zero T
        return zero, false
    }
    
    result := values[0]
    for _, v := range values[1:] {
        result = max(result, v)
    }
    return result, true
}

func main() {
    // int 슬라이스
    ints := []int{5, 3, 8, 1, 9}
    minInt, _ := Min(ints)
    maxInt, _ := Max(ints)
    fmt.Printf("Int: min=%d, max=%d\n", minInt, maxInt)
    
    // float64 슬라이스
    floats := []float64{1.1, 2.2, 0.5, 3.3}
    minFloat, _ := Min(floats)
    maxFloat, _ := Max(floats)
    fmt.Printf("Float: min=%.1f, max=%.1f\n", minFloat, maxFloat)
    
    // string 슬라이스
    strings := []string{"go", "rust", "python", "c"}
    minStr, _ := Min(strings)
    maxStr, _ := Max(strings)
    fmt.Printf("String: min=%s, max=%s\n", minStr, maxStr)
}

출력:

Int: min=1, max=9
Float: min=0.5, max=3.3
String: min=c, max=rust

4. 좌표 범위

type Point struct {
    X, Y int
}

func boundingBox(points []Point) (minX, minY, maxX, maxY int, ok bool) {
    if len(points) == 0 {
        return 0, 0, 0, 0, false
    }
    
    minX, maxX = points[0].X, points[0].X
    minY, maxY = points[0].Y, points[0].Y
    
    for _, p := range points[1:] {
        minX = min(minX, p.X)
        maxX = max(maxX, p.X)
        minY = min(minY, p.Y)
        maxY = max(maxY, p.Y)
    }
    
    return minX, minY, maxX, maxY, true
}

func main() {
    points := []Point{
        {X: 1, Y: 2},
        {X: 5, Y: 8},
        {X: 3, Y: 1},
        {X: 7, Y: 4},
    }
    
    minX, minY, maxX, maxY, ok := boundingBox(points)
    if ok {
        fmt.Printf("Bounding box: (%d,%d) to (%d,%d)\n", 
            minX, minY, maxX, maxY)
    }
    // Bounding box: (1,1) to (7,8)
}

5. 윈도우 크기 조정

type Window struct {
    Width  int
    Height int
}

const (
    MinWidth  = 400
    MaxWidth  = 1920
    MinHeight = 300
    MaxHeight = 1080
)

func (w *Window) Resize(width, height int) {
    // 범위 제한
    w.Width = max(MinWidth, min(width, MaxWidth))
    w.Height = max(MinHeight, min(height, MaxHeight))
}

func main() {
    window := &Window{Width: 800, Height: 600}
    
    fmt.Printf("Initial: %dx%d\n", window.Width, window.Height)
    
    // 정상 범위
    window.Resize(1024, 768)
    fmt.Printf("After resize: %dx%d\n", window.Width, window.Height)
    
    // 최소값보다 작게
    window.Resize(100, 100)
    fmt.Printf("Too small: %dx%d\n", window.Width, window.Height)
    
    // 최대값보다 크게
    window.Resize(3000, 2000)
    fmt.Printf("Too large: %dx%d\n", window.Width, window.Height)
}

출력:

Initial: 800x600
After resize: 1024x768
Too small: 400x300
Too large: 1920x1080

6. 점수 정규화

func normalizeScores(scores []float64) []float64 {
    if len(scores) == 0 {
        return scores
    }
    
    // 최소/최대값 찾기
    minScore := scores[0]
    maxScore := scores[0]
    
    for _, score := range scores {
        minScore = min(minScore, score)
        maxScore = max(maxScore, score)
    }
    
    // 0-100 범위로 정규화
    normalized := make([]float64, len(scores))
    scoreRange := maxScore - minScore
    
    if scoreRange == 0 {
        // 모든 점수가 같으면 50으로
        for i := range normalized {
            normalized[i] = 50.0
        }
        return normalized
    }
    
    for i, score := range scores {
        normalized[i] = (score - minScore) / scoreRange * 100
    }
    
    return normalized
}

func main() {
    scores := []float64{45.0, 67.0, 89.0, 23.0, 78.0}
    
    fmt.Println("Original:", scores)
    normalized := normalizeScores(scores)
    fmt.Println("Normalized:", normalized)
}

출력:

Original: [45 67 89 23 78]
Normalized: [33.33 66.67 100 0 83.33]

7. 날짜 범위

import "time"

type DateRange struct {
    Start time.Time
    End   time.Time
}

func (dr DateRange) Contains(t time.Time) bool {
    return !t.Before(dr.Start) && !t.After(dr.End)
}

func mergeDateRanges(ranges []DateRange) DateRange {
    if len(ranges) == 0 {
        return DateRange{}
    }
    
    result := ranges[0]
    
    for _, r := range ranges[1:] {
        // 가장 이른 시작일
        if r.Start.Before(result.Start) {
            result.Start = r.Start
        }
        
        // 가장 늦은 종료일
        if r.End.After(result.End) {
            result.End = r.End
        }
    }
    
    return result
}

// Go 1.21+ string 비교로 간단히
func earliestDate(dates ...string) string {
    if len(dates) == 0 {
        return ""
    }
    result := dates[0]
    for _, d := range dates[1:] {
        result = min(result, d)
    }
    return result
}

func latestDate(dates ...string) string {
    if len(dates) == 0 {
        return ""
    }
    result := dates[0]
    for _, d := range dates[1:] {
        result = max(result, d)
    }
    return result
}

func main() {
    // ISO 8601 형식 (사전순 = 시간순)
    dates := []string{
        "2024-03-15",
        "2024-01-20",
        "2024-12-01",
        "2024-06-10",
    }
    
    fmt.Println("Earliest:", earliestDate(dates...))
    fmt.Println("Latest:", latestDate(dates...))
}

8. 가격 비교

type Product struct {
    Name  string
    Price float64
}

func findCheapest(products []Product) (Product, bool) {
    if len(products) == 0 {
        return Product{}, false
    }
    
    cheapest := products[0]
    for _, p := range products[1:] {
        if p.Price < cheapest.Price {
            cheapest = p
        }
    }
    
    return cheapest, true
}

func findMostExpensive(products []Product) (Product, bool) {
    if len(products) == 0 {
        return Product{}, false
    }
    
    expensive := products[0]
    for _, p := range products[1:] {
        if p.Price > expensive.Price {
            expensive = p
        }
    }
    
    return expensive, true
}

func priceRange(products []Product) (min, max float64, ok bool) {
    if len(products) == 0 {
        return 0, 0, false
    }
    
    min, max = products[0].Price, products[0].Price
    
    for _, p := range products[1:] {
        min = min(min, p.Price)  // 내장 함수 사용
        max = max(max, p.Price)
    }
    
    return min, max, true
}

func main() {
    products := []Product{
        {Name: "Laptop", Price: 1200.00},
        {Name: "Mouse", Price: 25.00},
        {Name: "Keyboard", Price: 75.00},
        {Name: "Monitor", Price: 300.00},
    }
    
    if cheapest, ok := findCheapest(products); ok {
        fmt.Printf("Cheapest: %s ($%.2f)\n", cheapest.Name, cheapest.Price)
    }
    
    if expensive, ok := findMostExpensive(products); ok {
        fmt.Printf("Most expensive: %s ($%.2f)\n", expensive.Name, expensive.Price)
    }
    
    if minPrice, maxPrice, ok := priceRange(products); ok {
        fmt.Printf("Price range: $%.2f - $%.2f\n", minPrice, maxPrice)
    }
}

출력:

Cheapest: Mouse ($25.00)
Most expensive: Laptop ($1200.00)
Price range: $25.00 - $1200.00

성능 고려사항

1. 벤치마크

package main

import "testing"

func BenchmarkMinBuiltin(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = min(10, 20)
    }
}

func BenchmarkMinIfElse(b *testing.B) {
    for i := 0; i < b.N; i++ {
        a, b := 10, 20
        var result int
        if a < b {
            result = a
        } else {
            result = b
        }
        _ = result
    }
}

func BenchmarkMinMultiple(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = min(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    }
}

func BenchmarkMinLoop(b *testing.B) {
    values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    for i := 0; i < b.N; i++ {
        result := values[0]
        for _, v := range values[1:] {
            if v < result {
                result = v
            }
        }
        _ = result
    }
}

예상 결과:

BenchmarkMinBuiltin      1000000000    0.25 ns/op
BenchmarkMinIfElse       1000000000    0.25 ns/op
BenchmarkMinMultiple      500000000    2.50 ns/op
BenchmarkMinLoop          100000000   10.00 ns/op

2. 인라인 최적화

// 컴파일러가 min/max를 인라인 최적화
func clampInlined(value, minVal, maxVal int) int {
    return max(minVal, min(value, maxVal))
    // 최적화된 어셈블리 코드로 변환
}

// if 문도 비슷하게 최적화되지만
// min/max가 더 명확하고 읽기 쉬움

3. 가변 인자 오버헤드

// 2-3개 값: min/max 사용 권장
result := min(a, b)           // 빠름
result = min(a, b, c)         // 빠름

// 많은 값: 루프가 더 효율적일 수 있음
result = min(v1, v2, v3, ..., v100)  // 가변 인자 오버헤드

// 슬라이스: 커스텀 함수 사용
result = minSlice(values)  // 더 효율적

일반적인 실수

1. 슬라이스 직접 전달

// ❌ 나쁜 예
func main() {
    values := []int{1, 2, 3, 4, 5}
    // result := min(values...)  // 컴파일 에러
}

// ✅ 좋은 예
func minSlice(values []int) int {
    if len(values) == 0 {
        panic("empty slice")
    }
    
    result := values[0]
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result
}

2. 타입 불일치

// ❌ 나쁜 예
func main() {
    var a int = 10
    var b int64 = 20
    // result := min(a, b)  // 컴파일 에러
}

// ✅ 좋은 예
func main() {
    var a int = 10
    var b int64 = 20
    result := min(int64(a), b)  // 타입 변환
    // 또는
    result = int(min(a, int(b)))
}

3. NaN 무시

// ❌ 나쁜 예
func findMin(values []float64) float64 {
    result := values[0]
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result  // NaN일 수 있음
}

// ✅ 좋은 예
func findMin(values []float64) float64 {
    result := values[0]
    for _, v := range values[1:] {
        // NaN 체크
        if math.IsNaN(v) {
            continue
        }
        result = min(result, v)
    }
    return result
}

4. 빈 슬라이스 처리 누락

// ❌ 나쁜 예
func minValue(values []int) int {
    result := values[0]  // 패닉 가능!
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result
}

// ✅ 좋은 예
func minValue(values []int) (int, bool) {
    if len(values) == 0 {
        return 0, false
    }
    
    result := values[0]
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result, true
}

5. 불필요한 반복 비교

// ❌ 비효율적
func getMiddle(a, b, c int) int {
    if a < b {
        if b < c {
            return b
        } else if a < c {
            return c
        } else {
            return a
        }
    } else {
        // ...복잡한 로직
    }
}

// ✅ 간단하고 명확
func getMiddle(a, b, c int) int {
    return max(min(a, b), min(max(a, b), c))
}

6. math.Min/Max와 혼동

// ❌ 타입 변환 실수
func main() {
    a, b := 10, 20
    
    // math.Min은 float64 반환
    result := math.Min(float64(a), float64(b))  // 10.0
    intResult := int(result)  // 변환 필요
    
    // 내장 함수는 타입 유지
    intResult = min(a, b)  // 10 (변환 불필요)
}

7. Clamp 구현 실수

// ❌ 나쁜 예 (잘못된 순서)
func clampWrong(value, minVal, maxVal int) int {
    return min(maxVal, max(value, minVal))
    // value가 maxVal보다 크면 잘못됨
}

// ✅ 좋은 예
func clamp(value, minVal, maxVal int) int {
    return max(minVal, min(value, maxVal))
    // 또는
    if value < minVal {
        return minVal
    }
    if value > maxVal {
        return maxVal
    }
    return value
}

베스트 프랙티스

1. 명확한 변수 이름

// ✅ 좋은 예
func processTemperature(temp float64) float64 {
    minTemp := -40.0
    maxTemp := 50.0
    return max(minTemp, min(temp, maxTemp))
}

// ❌ 나쁜 예
func processTemperature(t float64) float64 {
    return max(-40.0, min(t, 50.0))  // 의미 불명확
}

2. 경계 값 상수화

const (
    MinAge = 0
    MaxAge = 120
    
    MinSalary = 0.0
    MaxSalary = 1000000.0
)

func validateAge(age int) int {
    return max(MinAge, min(age, MaxAge))
}

func validateSalary(salary float64) float64 {
    return max(MinSalary, min(salary, MaxSalary))
}

3. 제네릭 유틸리티 함수

import "cmp"

func Clamp[T cmp.Ordered](value, minVal, maxVal T) T {
    return max(minVal, min(value, maxVal))
}

func InRange[T cmp.Ordered](value, minVal, maxVal T) bool {
    return value >= minVal && value <= maxVal
}

func MinSlice[T cmp.Ordered](values []T) (T, bool) {
    if len(values) == 0 {
        var zero T
        return zero, false
    }
    result := values[0]
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result, true
}

func MaxSlice[T cmp.Ordered](values []T) (T, bool) {
    if len(values) == 0 {
        var zero T
        return zero, false
    }
    result := values[0]
    for _, v := range values[1:] {
        result = max(result, v)
    }
    return result, true
}

4. 오류 처리

func SafeMin(values []int) (int, error) {
    if len(values) == 0 {
        return 0, fmt.Errorf("cannot find min of empty slice")
    }
    
    result := values[0]
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result, nil
}

func SafeMax(values []int) (int, error) {
    if len(values) == 0 {
        return 0, fmt.Errorf("cannot find max of empty slice")
    }
    
    result := values[0]
    for _, v := range values[1:] {
        result = max(result, v)
    }
    return result, nil
}

5. 문서화

// Clamp restricts a value to be within a specified range.
// If value < minVal, returns minVal.
// If value > maxVal, returns maxVal.
// Otherwise, returns value unchanged.
//
// Example:
//
//	Clamp(5, 0, 10)   // returns 5
//	Clamp(-5, 0, 10)  // returns 0
//	Clamp(15, 0, 10)  // returns 10
func Clamp[T cmp.Ordered](value, minVal, maxVal T) T {
    return max(minVal, min(value, maxVal))
}

6. 테스트

func TestClamp(t *testing.T) {
    tests := []struct {
        name           string
        value, min, max int
        want            int
    }{
        {"within range", 5, 0, 10, 5},
        {"below min", -5, 0, 10, 0},
        {"above max", 15, 0, 10, 10},
        {"at min", 0, 0, 10, 0},
        {"at max", 10, 0, 10, 10},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Clamp(tt.value, tt.min, tt.max)
            if got != tt.want {
                t.Errorf("Clamp(%d, %d, %d) = %d; want %d",
                    tt.value, tt.min, tt.max, got, tt.want)
            }
        })
    }
}

7. 성능 최적화

// 작은 개수: 내장 함수 사용
func findMin2(a, b int) int {
    return min(a, b)  // 가장 빠름
}

func findMin3(a, b, c int) int {
    return min(a, b, c)  // 충분히 빠름
}

// 많은 개수: 루프 사용
func findMinMany(values ...int) int {
    if len(values) == 0 {
        return 0
    }
    result := values[0]
    for _, v := range values[1:] {
        result = min(result, v)
    }
    return result
}

8. Null 안전성

type OptionalInt struct {
    value int
    valid bool
}

func MinOptional(a, b OptionalInt) OptionalInt {
    if !a.valid {
        return b
    }
    if !b.valid {
        return a
    }
    return OptionalInt{
        value: min(a.value, b.value),
        valid: true,
    }
}

정리

  • 기본: min(a, b, …), max(a, b, …) 2개 이상 인자
  • 타입: cmp.Ordered (정수, 실수, 문자열)
  • vs 이전: if 문, 삼항 연산자 대체, math 패키지보다 간편
  • 특수: NaN 전파, 무한대 처리, 제로 값
  • 실전: Clamp, 슬라이스, 범위, 정규화, 날짜, 가격
  • 성능: 인라인 최적화, 2-3개 값에 최적
  • 실수: 슬라이스 전달 불가, 타입 불일치, NaN, 빈 슬라이스
  • 베스트: 상수화, 제네릭, 오류 처리, 문서화, 테스트