Skip to content

[duplicate-code] Duplicate Code Pattern: Concurrent tag-set methods in Label and Capabilities #8772

Description

@github-actions

Part of duplicate code analysis: #8771

Summary

internal/difc/capabilities.go and internal/difc/labels.go both define a concurrent tag-set backed by map[Tag]struct{} + sync.RWMutex. Five core methods — Add, AddAll, Remove, Contains, and GetAll/GetTags — are nearly identical between the two types, differing only in receiver name and optional debug-log calls.

Duplication Details

Pattern: Concurrent map[Tag]struct{} mutation methods

  • Severity: Medium
  • Occurrences: 5 method pairs (10 methods total)
  • Locations:
    • internal/difc/capabilities.go (lines 27–86) — Capabilities.Add, Capabilities.AddAll, Capabilities.Remove, Capabilities.Contains, Capabilities.GetAll
    • internal/difc/labels.go (lines 42–154) — Label.Add, Label.AddAll, Label.Remove, Label.Contains, Label.GetTags

Code Sample — Add (Capabilities vs Label):

// capabilities.go
func (c *Capabilities) Add(tag Tag) {
    logCapabilities.Printf("Adding tag: %s", tag)
    c.mu.Lock()
    defer c.mu.Unlock()
    c.tags[tag] = struct{}{}
}

// labels.go
func (l *Label) Add(tag Tag) {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.tags[tag] = struct{}{}
}

Code Sample — GetAll/GetTags (Capabilities vs Label):

// capabilities.go
func (c *Capabilities) GetAll() []Tag {
    c.mu.RLock()
    tags := make([]Tag, 0, len(c.tags))
    for tag := range c.tags {
        tags = append(tags, tag)
    }
    c.mu.RUnlock()
    logCapabilities.Printf("GetAll: returning %d tags", len(tags))
    return tags
}

// labels.go
func (l *Label) GetTags() []Tag {
    l.mu.RLock()
    defer l.mu.RUnlock()
    tags := make([]Tag, 0, len(l.tags))
    for tag := range l.tags {
        tags = append(tags, tag)
    }
    return tags
}

Impact Analysis

  • Maintainability: Any fix, optimization, or logging change to the mutex-locking pattern must be applied twice. The recent commit ([log] Add debug logging to difc/capabilities.go #8744) added logging only to Capabilities — if the same logging is wanted in Label.GetTags() or Label.Contains(), it requires another PR.
  • Bug Risk: A concurrency bug (e.g. defer vs manual unlock ordering) fixed in one place may be missed in the other.
  • Code Bloat: ~25 lines of duplicated logic across two files in the same package.

Refactoring Recommendations

  1. Extract a tagSet helper type (preferred)

    • Create internal/difc/tagset.go with an unexported tagSet struct that holds tags map[Tag]struct{} and mu sync.RWMutex
    • Implement add, addAll, remove, contains, getAll, clear, count on tagSet
    • Embed or delegate from both Label and Capabilities
    • Estimated effort: 1–2 hours
    • Benefits: single source of truth for mutex discipline; debug logging added once
  2. Embed Label inside Capabilities

    • Capabilities wraps a *Label and delegates Add, AddAll, Remove, Contains, GetAll to it
    • Simpler change, but Label has set-algebra methods (Union, Intersect) that Capabilities does not need — embedding exposes more API surface than desired
    • Estimated effort: 30 minutes

Implementation Checklist

  • Review duplication findings
  • Decide on refactoring approach (extract tagSet vs embed Label)
  • Implement changes in internal/difc/
  • Update existing unit tests in capabilities_test.go and labels_test.go
  • Verify go test ./internal/difc/ passes
  • Verify no functionality broken

Parent Issue

See parent analysis report: #8771
Related to #8771

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Duplicate Code Detector · 105.9 AIC · ⊞ 7.7K ·

  • expires on Jul 13, 2026, 3:43 AM UTC

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions