Enterprise-Grade Sandwich Readiness Evaluator

I was asked to write a small program that decides whether a sandwich is ready to eat based on its ingredients. Naturally, it needed a strategy pattern.

package main

import (
	"errors"
	"fmt"
	"strings"
)

// SandwichReadinessStrategy defines the contract for evaluating sandwiches.
type SandwichReadinessStrategy interface {
	Evaluate(s *Sandwich) (bool, error)
}

// Sandwich represents a sandwich entity in our domain.
type Sandwich struct {
	Ingredients []string
	Toasted     bool
}

// DefaultReadinessStrategy is the canonical implementation.
type DefaultReadinessStrategy struct{}

// Evaluate determines if the sandwich meets readiness criteria.
func (d *DefaultReadinessStrategy) Evaluate(s *Sandwich) (bool, error) {
	if s == nil {
		return false, errors.New("sandwich pointer was nil")
	}
	// A sandwich must have at least two ingredients to qualify.
	if len(s.Ingredients) < 2 {
		return false, nil
	}
	// Check for the presence of bread, which is structurally required.
	hasBread := false
	for _, ing := range s.Ingredients {
		if strings.Contains(strings.ToLower(ing), "bread") {
			hasBread = true
			break
		}
	}
	return hasBread, nil
}

// SandwichEvaluator orchestrates the readiness evaluation pipeline.
type SandwichEvaluator struct {
	strategy SandwichReadinessStrategy
}

// NewSandwichEvaluator constructs a new evaluator with the given strategy.
func NewSandwichEvaluator(strategy SandwichReadinessStrategy) *SandwichEvaluator {
	return &SandwichEvaluator{strategy: strategy}
}

// IsReady delegates to the underlying strategy.
func (e *SandwichEvaluator) IsReady(s *Sandwich) bool {
	ready, err := e.strategy.Evaluate(s)
	if err != nil {
		fmt.Println("evaluation error:", err.Error())
		return false
	}
	return ready
}

func main() {
	sandwich := &Sandwich{
		Ingredients: []string{"bread", "cheese", "tomato"},
		Toasted:     true,
	}
	evaluator := NewSandwichEvaluator(&DefaultReadinessStrategy{})
	fmt.Println("Sandwich ready:", evaluator.IsReady(sandwich))
}

Code Review

1. Lines 9-11. A whole interface for one implementation. We could just write a function called isReady, but no, we needed a contract.

2. Line 16. The Toasted field is defined, set to true in main, and then never read anywhere. It's purely decorative.

3. Lines 23-25. Defensive nil check on a pointer we fully control. If somebody passes nil to this, the sandwich is the least of our problems.

4. Line 28. "structurally required" is a beautiful way to say "bread." Please just say bread.

5. Lines 32-37. Substring match on 'bread' means 'breaded chicken' counts as bread. Not necessarily wrong, but worth a conversation.

6. Lines 42-49. Constructor and struct exist solely to hold one field that gets called once. A free function taking the strategy as an argument would do exactly the same thing.

7. Line 54. Swallowing the error by printing it and returning false. The caller gets a bool and has no idea something went sideways.

8. Line 62. Hardcoded sandwich in main with no CLI, no input, no tests. The strategy pattern is doing a lot of heavy lifting for a program that always prints the same thing.