DEV Community

Valeria Muhembele
Valeria Muhembele

Posted on

The Complete Guide to Go Naming: From Conventions to the Art of Good Names

Introduction

"There are only two hard things in Computer Science: cache invalidation and naming things." - Phil Karlton

If you've been writing Go for a while, you've probably stared at your screen wondering: "What should I call this function?" or "Is data a good variable name here?" You're not alone. Good naming is one of the most underestimated skills in programming, yet it's what separates readable, maintainable code from cryptic puzzles.

In this comprehensive guide, we'll explore not just Go's naming conventions, but more importantly, the thought process behind creating meaningful names. By the end, you'll have a mental framework for naming that will make your Go code clearer and more professional.

Table of Contents


Go File Naming Conventions

Go has specific conventions that differ from other languages. Let's start with the fundamentals:

Basic File Naming

// ✅ Good - lowercase with underscores
user.go
user_service.go
payment_handler.go
database_connection.go
auth_middleware.go

// ❌ Avoid - camelCase or PascalCase in file names
userService.go
PaymentHandler.go
DatabaseConnection.go
Enter fullscreen mode Exit fullscreen mode

Special File Types

// Test files - always end with _test.go
user_test.go
user_service_test.go
payment_handler_test.go

// Build constraint files
config_dev.go
config_prod.go
database_postgres.go
auth_linux.go

// Documentation and examples
doc.go          // Package documentation
example_test.go // Example functions
Enter fullscreen mode Exit fullscreen mode

Project Structure Example

project/
├── cmd/
│   └── myapp/
│       └── main.go
├── internal/
│   ├── auth/
│   │   ├── auth.go
│   │   ├── auth_test.go
│   │   └── middleware.go
│   └── user/
│       ├── user.go
│       ├── user_test.go
│       └── service.go
├── pkg/
│   └── utils/
│       ├── string_utils.go
│       └── time_utils.go
└── go.mod
Enter fullscreen mode Exit fullscreen mode

Variable Naming in Go

Go's visibility rules through capitalization make variable naming unique. Let's break it down:

Export Visibility Rules

Exported (Public) Variables:

// Visible outside the package - start with uppercase
var DatabaseURL string
var MaxConnections int
var DefaultTimeout time.Duration

const APIVersion = "v1"
const MaxRetries = 3
Enter fullscreen mode Exit fullscreen mode

Unexported (Private) Variables:

// Package-private - start with lowercase
var databaseURL string
var maxConnections int
var defaultTimeout time.Duration

const apiVersion = "v1"
const maxRetries = 3
Enter fullscreen mode Exit fullscreen mode

Idiomatic Go: Short Names in Limited Scopes

One of Go's unique characteristics is embracing short variable names in limited scopes:

// ✅ Idiomatic Go - short names in limited scope
for i, v := range items {
    fmt.Printf("Item %d: %v\n", i, v)
}

for k, v := range userMap {
    fmt.Printf("User %s: %v\n", k, v)
}

// Common short names that Gophers expect
var (
    i, j, k int           // loop indices
    n       int           // count/length
    c       chan string   // channel
    ctx     context.Context
    req     *http.Request
    resp    *http.Response
    err     error
    b       []byte
    s       string
)
Enter fullscreen mode Exit fullscreen mode

Boolean Variables

// ✅ Good - clear, positive language
var isActive bool
var hasPermission bool
var canEdit bool
var shouldValidate bool

// ❌ Avoid - unclear or negative
var active bool       // unclear type
var notDisabled bool  // double negative
var flag bool         // too generic
Enter fullscreen mode Exit fullscreen mode

Collections

// Slices and Arrays - use plural nouns
var users []User
var activeConnections []net.Conn
var errorMessages []string

// Maps - describe the relationship
var usersByID map[int]User
var settingsByKey map[string]string
var cacheEntries map[string]CacheEntry

// Channels - often end with 'Chan'
var userChan chan User
var errorChan chan error
var doneChan chan struct{}
Enter fullscreen mode Exit fullscreen mode

Function Naming Patterns

CRUD Operations

// Create operations
func NewUser(name, email string) *User        // Constructor
func CreateUser(user *User) error             // Database create
func AddUser(user *User) error                // Collection add

// Read operations  
func GetUser(id int) (*User, error)           // Single item
func FindUserByEmail(email string) (*User, error) // Search
func ListUsers() ([]*User, error)             // Multiple items

// Update operations
func UpdateUser(user *User) error
func ModifyUserProfile(userID int, changes map[string]interface{}) error

// Delete operations
func DeleteUser(id int) error
func RemoveUser(id int) error
Enter fullscreen mode Exit fullscreen mode

Boolean-Returning Functions

func IsValid(email string) bool
func HasPermission(user *User, resource string) bool
func CanAccess(user *User, resource string) bool
func ShouldRetry(err error) bool
func Exists(filename string) bool
Enter fullscreen mode Exit fullscreen mode

Method Naming with Receivers

type User struct {
    ID    int
    Name  string
    Email string
}

// Use consistent, short receiver names
func (u *User) GetFullName() string { ... }
func (u *User) SetEmail(email string) error { ... }
func (u *User) IsActive() bool { ... }
func (u *User) Save() error { ... }
func (u *User) Delete() error {... }
Enter fullscreen mode Exit fullscreen mode

The Art of Naming: Mental Process

Here's where things get interesting. How do you actually come up with these names? It's not magic—there's a systematic thought process.

Step 1: Ask "What Does This Do?"

Before naming anything, clearly understand its purpose:

// Mental process: "What does this function do?"
// - Opens a file ✓
// - Reads its contents ✓  
// - Returns the data as bytes ✓
// - Can return an error ✓
// 
// Process: "Read" + "File" = ReadFile

func ReadFile(filename string) ([]byte, error) {
    file, err := os.Open(filename)  // What's this? → A file handle → "file"
    if err != nil {
        return nil, err  // Error case → "err"
    }
    defer file.Close()

    data, err := io.ReadAll(file)  // What's this? → The actual data → "data"
    return data, err
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Think in Terms of Actions and Objects

For Functions - Use Verbs:

// Mental process: Action + Object + Context
func GetUserByID(id int) (*User, error)           // Get + User + By ID
func CalculateTotalPrice(items []Item) float64    // Calculate + Total + Price  
func SendEmailNotification(user *User, msg string) error // Send + Email + Notification
Enter fullscreen mode Exit fullscreen mode

For Variables - Use Nouns:

// Mental process: "What is this thing? What role does it play?"

// Opening a database connection
conn, err := sql.Open("postgres", dsn)
// Think: "What is this?" → A connection → "conn"

// Getting user data from API
userData, err := api.GetUser(userID)  
// Think: "What is this?" → Data about a user → "userData"

// Creating HTTP server
server := &http.Server{Addr: ":8080", Handler: router}
// Think: "What is this?" → An HTTP server → "server"
Enter fullscreen mode Exit fullscreen mode

Step 3: Follow the Data Flow

Variables often represent data at different stages of processing:

func ProcessOrder(orderData []byte) (*Order, error) {
    // Raw input → "orderData" (bytes from HTTP request)

    var orderRequest OrderRequest
    err := json.Unmarshal(orderData, &orderRequest)
    // Parsed input → "orderRequest" (structured request)

    order, err := validateAndCreateOrder(orderRequest)  
    // Business object → "order" (the actual domain object)

    savedOrder, err := repository.SaveOrder(order)
    // Persisted result → "savedOrder" (order after database save)

    return savedOrder, nil
}
Enter fullscreen mode Exit fullscreen mode

Real-World Naming Examples

Let's walk through the thought process for common Go patterns:

Example 1: HTTP Handler

// What am I doing? Handling user login requests
func handleUserLogin(w http.ResponseWriter, r *http.Request) {
    // What's this? → Login credentials from request
    credentials, err := parseLoginRequest(r)
    if err != nil {
        http.Error(w, "Invalid request", http.StatusBadRequest)
        return
    }

    // What's this? → The authenticated user  
    user, err := authenticateUser(credentials)
    if err != nil {
        http.Error(w, "Authentication failed", http.StatusUnauthorized)
        return
    }

    // What's this? → JWT token for the session
    token, err := generateAuthToken(user)
    if err != nil {
        http.Error(w, "Token generation failed", http.StatusInternalServerError)
        return
    }

    // What's this? → Response data structure
    response := LoginResponse{
        Token: token,
        User:  user,
    }

    json.NewEncoder(w).Encode(response)
}
Enter fullscreen mode Exit fullscreen mode

Example 2: Database Repository

// What am I doing? Finding users in the database
func FindUserByEmail(email string) (*User, error) {
    // What's this? → SQL query string
    query := "SELECT id, name, email FROM users WHERE email = $1"

    // What's this? → Database row result
    row := db.QueryRow(query, email)

    // What's this? → The user object we're building
    var user User
    err := row.Scan(&user.ID, &user.Name, &user.Email)
    if err != nil {
        if err == sql.ErrNoRows {
            return nil, ErrUserNotFound
        }
        return nil, err
    }

    return &user, nil
}
Enter fullscreen mode Exit fullscreen mode

Example 3: API Client

// What am I doing? Fetching user data from external API
func FetchUserFromAPI(userID int) (*User, error) {
    // What's this? → The URL we're calling
    url := fmt.Sprintf("https://api.example.com/users/%d", userID)

    // What's this? → HTTP response
    resp, err := http.Get(url)
    if err != nil {
        return nil, fmt.Errorf("API request failed: %w", err)
    }
    defer resp.Body.Close()

    // What's this? → Raw response body bytes
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("failed to read response: %w", err)
    }

    // What's this? → The parsed user object
    var user User
    err = json.Unmarshal(body, &user)
    if err != nil {
        return nil, fmt.Errorf("failed to parse user data: %w", err)
    }

    return &user, nil
}
Enter fullscreen mode Exit fullscreen mode

Common Naming Mistakes

Mistake 1: Too Generic

// ❌ Bad - What kind of data? What processing?
func ProcessData(data []byte) error

// ✅ Good - Specific about purpose  
func ValidateUserProfileData(profileData []byte) error
Enter fullscreen mode Exit fullscreen mode

Mistake 2: Inconsistent Patterns

// ❌ Bad - Different verbs for similar operations
func GetUser(id int) (*User, error)
func RetrieveOrder(id int) (*Order, error)  
func FetchProduct(id int) (*Product, error)

// ✅ Good - Consistent pattern
func GetUser(id int) (*User, error)
func GetOrder(id int) (*Order, error)
func GetProduct(id int) (*Product, error)
Enter fullscreen mode Exit fullscreen mode

Mistake 3: Technical Instead of Domain Focus

// ❌ Bad - Focuses on implementation
func ExecuteSQLQuery(sql string) (*sql.Rows, error)

// ✅ Good - Focuses on business purpose
func FindActiveUsers() ([]*User, error)
Enter fullscreen mode Exit fullscreen mode

Mistake 4: Unclear Abbreviations

// ❌ Bad - Cryptic abbreviations
func ProcUsrReq(req *http.Request) error

// ✅ Good - Clear, full words
func ProcessUserRequest(req *http.Request) error
Enter fullscreen mode Exit fullscreen mode

Go Commit Message Best Practices

Good naming extends to your commit messages. Here's how to write clear, professional commits for Go projects:

Structure

<type>(<scope>): <subject>

<body>

<footer>
Enter fullscreen mode Exit fullscreen mode

Go-Specific Types and Scopes

// Types
feat     // New feature
fix      // Bug fix  
refactor // Code refactoring
perf     // Performance improvement
test     // Adding tests
docs     // Documentation
style    // Formatting (gofmt, goimports)
chore    // Dependencies, build changes

// Common Go scopes
api      // API changes
auth     // Authentication  
db       // Database
http     // HTTP handlers
cmd      // CLI changes
internal // Internal packages
pkg      // Public packages
Enter fullscreen mode Exit fullscreen mode

Go Commit Examples

# Feature addition
feat(auth): implement JWT token authentication

- Add JWT token generation and validation
- Create middleware for protected routes  
- Update user service to handle token refresh
- Add unit tests for token operations

Closes #123

# Bug fix
fix(db): resolve connection pool exhaustion

The database connection pool was not being properly released
causing connection exhaustion under high load.

- Fix connection leak in user repository
- Add proper defer statements for cleanup
- Update connection pool configuration  

Fixes #456

# Performance improvement
perf(search): optimize user search query

- Add database index on email field
- Implement query result caching
- Reduce N+1 query problem

Improves search performance by 60%

# Refactoring
refactor(user): extract validation logic to separate package

- Move user validation to internal/validator
- Create reusable validation rules
- Update user service to use new validator
- Maintain backward compatibility

No functional changes.
Enter fullscreen mode Exit fullscreen mode

Practice Exercises

Let's put this knowledge to work! Try naming these scenarios:

Exercise 1: Authentication Function

// You're building a function that:
// - Takes username and password
// - Validates credentials against database
// - Returns JWT token if successful
// - Returns error if authentication fails

func _________(username, password string) (string, error) {
    // Your implementation here
}

// Think about it:
// - What's the primary action? (Authenticate? Login? Validate?)
// - What's the main purpose? (Generate token? Verify user?)
// - What would be most clear to other developers?

// Possible answers:
// - AuthenticateUser
// - LoginUser
// - GenerateAuthToken  
// - ValidateCredentials
Enter fullscreen mode Exit fullscreen mode

Exercise 2: File Processing Variables

// You're converting CSV to JSON:
func ConvertCSVToJSON(csvPath, jsonPath string) error {
    // What would you call these variables?

    _______, err := os.ReadFile(csvPath)        
    // Options: csvData? rawData? fileContent? csvBytes?

    _______ := csv.NewReader(bytes.NewReader(_____))
    // Options: reader? csvReader? parser?

    _______ := make([]map[string]string, 0)     
    // Options: records? rows? data? entries?

    // ... processing logic

    _______, err := json.Marshal(_______)       
    // Options: jsonData? jsonBytes? output?

    return os.WriteFile(jsonPath, _______, 0644)
}
Enter fullscreen mode Exit fullscreen mode

My suggested answers:

func ConvertCSVToJSON(csvPath, jsonPath string) error {
    csvData, err := os.ReadFile(csvPath)        // Raw CSV bytes
    if err != nil {
        return err
    }

    reader := csv.NewReader(bytes.NewReader(csvData)) // CSV parser

    records := make([]map[string]string, 0)     // Parsed CSV records

    // ... processing logic to populate records

    jsonData, err := json.Marshal(records)      // JSON bytes
    if err != nil {
        return err
    }

    return os.WriteFile(jsonPath, jsonData, 0644)
}
Enter fullscreen mode Exit fullscreen mode

The Naming Checklist

Before finalizing any name, ask yourself:

For Functions:

  • ✅ Does the name clearly describe what the function does?
  • ✅ Would a new team member understand the purpose immediately?
  • ✅ Is it consistent with similar functions in the codebase?
  • ✅ Does it use domain language that stakeholders understand?

For Variables:

  • ✅ Does the name describe what the variable contains?
  • ✅ Is the scope appropriate for the name length?
  • ✅ Does it distinguish itself from similar variables?
  • ✅ Would I understand this name in 6 months?

Key Takeaways

  1. Start with purpose: Always understand what you're naming before you name it
  2. Use domain language: Names should make sense to business stakeholders
  3. Be consistent: Similar operations should have similar naming patterns
  4. Context matters: Longer names for wider scopes, shorter for limited scopes
  5. Follow Go idioms: Embrace short names in limited scopes, use capitalization for visibility
  6. Iterate and improve: Don't be afraid to rename as your understanding evolves

Conclusion

Good naming isn't just about following conventions—it's about making your code tell a story. When you name things well, your code becomes self-documenting. Other developers (including future you) can read it like prose and understand not just what the code does, but why it exists.

The next time you're staring at your screen wondering what to call that function or variable, remember: good naming is thinking made visible. Take the time to understand the purpose, consider the context, and choose names that reveal your intent.

Your future self—and your teammates—will thank you.


What naming challenges do you face in your Go projects? Share your experiences in the comments below! 👇


Further Reading


Tags: #go #golang #coding #bestpractices #cleancode #naming #conventions

Top comments (0)