3.4 KiB
3.4 KiB
Type Design Subset for Refactoring
Quick reference for type design principles when refactoring. For complete type design guidance, see @code-designing skill.
When Refactoring Reveals Need for Types
Primitive Obsession Signal
During refactoring, if you find:
- Validation repeated across multiple functions
- Complex logic operating on primitives (string, int, float)
- Parameters passed around without type safety
→ Create a self-validating type
Pattern: Self-Validating Type
type TypeName underlyingType
func NewTypeName(input underlyingType) (TypeName, error) {
// Validate
if /* invalid */ {
return zero, errors.New("why invalid")
}
return TypeName(input), nil
}
// Add methods if behavior needed
func (t TypeName) SomeMethod() result {
// Type-specific logic
}
Type Design Checklist
When creating types during refactoring:
- Constructor validates - Check in New* function
- Fields are private - Prevent invalid state
- Methods trust validity - No nil checks
- Type has behavior - Not just data container
- Type in own file - If it has logic
Examples
Example 1: Port Validation
// Before refactoring - Validation scattered
func StartServer(port int) error {
if port <= 0 || port >= 9000 {
return errors.New("invalid port")
}
// ...
}
func ConnectTo(host string, port int) error {
if port <= 0 || port >= 9000 {
return errors.New("invalid port")
}
// ...
}
// After refactoring - Self-validating type
type Port int
func NewPort(p int) (Port, error) {
if p <= 0 || p >= 9000 {
return 0, errors.New("port must be 1-8999")
}
return Port(p), nil
}
func StartServer(port Port) error {
// No validation needed
// ...
}
func ConnectTo(host string, port Port) error {
// No validation needed
// ...
}
Example 2: Parser Complexity
// Before refactoring - One complex Parser
type Parser struct {
// Too many responsibilities
}
func (p *Parser) Parse(input string) (Result, error) {
// 100+ lines parsing headers, path, body, etc.
}
// After refactoring - Separate types by role
type HeaderParser struct { /* ... */ }
type PathParser struct { /* ... */ }
type BodyParser struct { /* ... */ }
func (p *HeaderParser) Parse(input string) (Header, error) {
// Focused logic for headers only
}
func (p *PathParser) Parse(input string) (Path, error) {
// Focused logic for path only
}
func (p *BodyParser) Parse(input string) (Body, error) {
// Focused logic for body only
}
Quick Decision: Create Type or Extract Function?
Create Type When:
- Logic operates on a primitive
- Validation is repeated
- Type represents domain concept
- Behavior is cohesive
Extract Function When:
- Logic is procedural (no state needed)
- Different abstraction level
- One-time operation
- No validation required
Integration with Refactoring
After creating types during refactoring:
- Run tests - Ensure they pass
- Run linter - Should reduce complexity
- Consider @code-designing - Validate type design
- Update tests - Ensure new types have 100% coverage
File Organization
When creating types during refactoring:
package/
├── original.go # Original file
├── new_type.go # New type in own file (if has logic)
└── original_test.go # Tests
For complete type design principles, see @code-designing skill.