Files
cv-site/doc/_go-learning/best-practices/01-code-organization.md
T
juanatsap d95c62bad4 refactor: remove outdated server design documentation
Remove 557-line server-design.md from _go-learning/architecture - content is now covered in updated architecture documentation with real implementation examples and test coverage.
2025-12-02 20:25:05 +00:00

10 KiB

Code Organization Best Practices

Project Structure

Standard Go Project Layout

cv-website/
├── cmd/                    # Main applications
│   └── server/
│       └── main.go        # Application entry point
│
├── internal/              # Private application code
│   ├── config/            # Configuration
│   ├── handlers/          # HTTP handlers
│   ├── middleware/        # HTTP middleware
│   ├── models/            # Data models
│   │   ├── cv/           # CV data structures
│   │   └── ui/           # UI data structures
│   ├── pdf/              # PDF generation
│   ├── routes/           # Route setup
│   └── templates/        # Template management
│
├── data/                  # Static data files
│   ├── cv-en.json
│   ├── cv-es.json
│   ├── ui-en.json
│   └── ui-es.json
│
├── templates/             # HTML templates
│   ├── index.html
│   └── partials/
│       ├── header.html
│       └── footer.html
│
├── static/                # Static assets
│   ├── css/
│   ├── js/
│   └── images/
│
├── tests/                 # Test files
│   └── integration/
│
├── _go-learning/          # Educational documentation
│   ├── diagrams/
│   ├── patterns/
│   ├── refactorings/
│   └── best-practices/
│
├── go.mod                 # Go module definition
├── go.sum                 # Dependency checksums
├── Makefile              # Build automation
└── README.md             # Project documentation

Package Organization Principles

1. Use internal/ for Private Code

// ✅ GOOD: Private to this module
internal/handlers/cv.go

// ❌ BAD: Can be imported by other modules
handlers/cv.go

Why: internal/ prevents external packages from importing your code, enforcing API boundaries.

2. Group by Feature, Not Layer

// ✅ GOOD: Grouped by domain
internal/
├── handlers/
   ├── cv.go
   ├── cv_pages.go
   ├── cv_htmx.go
   ├── cv_pdf.go
   ├── cv_helpers.go
   ├── types.go
   └── errors.go

// ❌ BAD: Grouped by type
internal/
├── controllers/
├── services/
├── repositories/
└── dtos/

Why: Feature-based organization makes code easier to navigate and refactor.

3. Separate Command from Library

// ✅ GOOD: Separate main package
cmd/server/main.go       # Entry point, wiring
internal/handlers/       # Business logic

// ❌ BAD: Everything in main package
main.go
handlers.go
middleware.go

Why: Keeps main package small and focused on wiring, makes code reusable and testable.

File Naming Conventions

1. Descriptive, Lowercase, Underscore-Separated

// ✅ GOOD
cv_pages.go
cv_htmx.go
cv_helpers.go
cv_pages_test.go

// ❌ BAD
cvPages.go         // camelCase
cv-pages.go        // hyphen (not idiomatic)
cvpages.go         // too short, unclear

2. Test Files Mirror Source Files

// Source files
cv_pages.go
cv_htmx.go

// Test files
cv_pages_test.go
cv_htmx_test.go
// Related to CV handler
cv.go              // Constructor, shared state
cv_pages.go        // Page handlers
cv_htmx.go         // HTMX handlers
cv_pdf.go          // PDF export
cv_helpers.go      // Helper functions

// Shared types and errors
types.go           // Request/response types
errors.go          // Error handling

Package Naming

1. Short, Concise, Lowercase

// ✅ GOOD
package handlers
package middleware
package pdf

// ❌ BAD
package cvHandlers      // Don't repeat package name
package cv_handlers     // No underscore
package HTTPHandlers    // No capitals

2. No common, util, base

// ❌ BAD: Generic names
package util
package common
package helpers

// ✅ GOOD: Descriptive names
package validation
package templates
package pdf

3. Singular Names

// ✅ GOOD
package handler    // Even if multiple handlers
package model

// ❌ BAD
package handlers   // Plural (exception: when package name would conflict)
package models

Code Organization Within Files

1. Logical Ordering

// ✅ GOOD: Logical flow
package handlers

// 1. Imports
import (
    "fmt"
    "net/http"
)

// 2. Package-level constants/variables
const MaxRetries = 3

// 3. Types
type CVHandler struct {
    tmpl *templates.Manager
}

// 4. Constructor
func NewCVHandler(tmpl *templates.Manager) *CVHandler {
    return &CVHandler{tmpl: tmpl}
}

// 5. Public methods (alphabetical or logical order)
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
    // ...
}

// 6. Private methods (alphabetical or logical order)
func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, error) {
    // ...
}

// 7. Helper functions
func validateLanguage(lang string) error {
    // ...
}
// ✅ GOOD: Related functions grouped
func (h *CVHandler) ToggleCVLength(w http.ResponseWriter, r *http.Request) {
    // ...
}

func (h *CVHandler) ToggleCVIcons(w http.ResponseWriter, r *http.Request) {
    // ...
}

func (h *CVHandler) ToggleCVTheme(w http.ResponseWriter, r *http.Request) {
    // ...
}

3. Separate Public and Private

// Public API
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request)
func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request)

// Private helpers (lowercase)
func (h *CVHandler) prepareTemplateData(lang string)
func (h *CVHandler) handleError(w http.ResponseWriter, r *http.Request, err error)

Import Organization

1. Group Imports

import (
    // 1. Standard library
    "context"
    "fmt"
    "net/http"

    // 2. External packages
    "github.com/chromedp/chromedp"

    // 3. Internal packages
    "project/internal/middleware"
    "project/internal/models/cv"
)

2. Use Blank Imports Sparingly

// ✅ GOOD: Document why
import (
    _ "github.com/lib/pq" // PostgreSQL driver
)

// ❌ BAD: No comment
import (
    _ "github.com/lib/pq"
)

Avoiding Circular Dependencies

Problem

// package a
import "project/internal/b"

// package b
import "project/internal/a"

// Compilation error: import cycle

Solution 1: Extract Interface

// package common
type ServiceA interface {
    DoA()
}

// package a
import "project/internal/common"

func NewA(b common.ServiceB) *A {
    // Use interface
}

// package b
// No import of package a

Solution 2: Create Third Package

// Before: a ↔ b (circular)

// After: a → shared ← b
//
// shared/ contains types used by both

When to Split a File

Signs a File is Too Large

  1. More than 500 lines
  2. Multiple unrelated responsibilities
  3. Difficult to navigate
  4. Many scroll actions to find code

How to Split

// Before: cv.go (1000+ lines)
// - Constructor
// - Page handlers
// - HTMX handlers
// - PDF handler
// - Helper functions

// After: Split by responsibility
cv.go          // Constructor, shared state
cv_pages.go    // Page handlers (Home, CVContent)
cv_htmx.go     // HTMX handlers (4 toggles)
cv_pdf.go      // PDF export
cv_helpers.go  // Helper functions

Documentation

1. Package Documentation

// Package handlers provides HTTP request handlers for the CV website.
//
// Handlers are organized by resource:
//   - CVHandler: CV page rendering and HTMX updates
//   - HealthHandler: Health check endpoint
//
// All handlers follow the http.HandlerFunc signature and use
// dependency injection for testability.
package handlers

2. Exported Function Documentation

// NewCVHandler creates a new CV handler with the given dependencies.
//
// The template manager is used for rendering HTML responses.
// The host parameter is used to construct absolute URLs for SEO.
//
// Example:
//
//	tmpl, _ := templates.NewManager(config)
//	handler := handlers.NewCVHandler(tmpl, "example.com")
func NewCVHandler(tmpl *templates.Manager, host string) *CVHandler {
    return &CVHandler{
        tmpl: tmpl,
        host: host,
    }
}

3. Complex Logic Documentation

// prepareTemplateData loads and processes all data needed for template rendering.
//
// The process involves:
//  1. Load CV and UI data from JSON files
//  2. Calculate experience durations
//  3. Split skills into columns for display
//  4. Build template data map with SEO metadata
func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, error) {
    // ...
}

Best Practices Checklist

Package Structure

  • Use internal/ for private code
  • Group by feature, not layer
  • Separate cmd/ from library code
  • Avoid circular dependencies

File Organization

  • Descriptive, lowercase names
  • Test files mirror source files
  • Related functionality grouped
  • Files < 500 lines

Code Structure

  • Logical ordering (imports → types → constructor → methods)
  • Public before private
  • Related code grouped
  • Proper documentation

Naming

  • Short package names (no util, common)
  • Clear, descriptive file names
  • Consistent naming across project
  • No redundant prefixes

Anti-Patterns

Flat Structure

// BAD: Everything in root
main.go
handlers.go
middleware.go
models.go
utils.go
helpers.go

Over-Nesting

// BAD: Too many levels
internal/
└── domain/
    └── services/
        └── cv/
            └── handlers/
                └── http/
                    └── v1/
                        └── endpoints/
                            └── cv.go

God Packages

// BAD: One package does everything
package app

// 5000 lines of code handling everything

Real-World Example

This project follows these principles:

✅ Clear package boundaries
✅ Feature-based organization (handlers, models, middleware)
✅ Test files mirror source files
✅ No circular dependencies
✅ Appropriate use of internal/
✅ Well-documented public API
✅ Logical file naming and organization

Further Reading