Files
cv-site/doc/_go-learning/best-practices/01-code-organization.md
T

505 lines
10 KiB
Markdown
Raw Normal View History

# 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
```go
// ✅ 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
```go
// ✅ 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
```go
// ✅ 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
```go
// ✅ 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
```go
// Source files
cv_pages.go
cv_htmx.go
// Test files
cv_pages_test.go
cv_htmx_test.go
```
### 3. Group Related Functionality
```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
```go
// ✅ 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`
```go
// ❌ BAD: Generic names
package util
package common
package helpers
// ✅ GOOD: Descriptive names
package validation
package templates
package pdf
```
### 3. Singular Names
```go
// ✅ 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
```go
// ✅ 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 {
// ...
}
```
### 2. Group Related Code
```go
// ✅ 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
```go
// 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
```go
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
```go
// ✅ GOOD: Document why
import (
_ "github.com/lib/pq" // PostgreSQL driver
)
// ❌ BAD: No comment
import (
_ "github.com/lib/pq"
)
```
## Avoiding Circular Dependencies
### Problem
```go
// package a
import "project/internal/b"
// package b
import "project/internal/a"
// Compilation error: import cycle
```
### Solution 1: Extract Interface
```go
// 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
```go
// 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
```go
// 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
```go
// 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
```go
// 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
```go
// 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
```go
// BAD: Everything in root
main.go
handlers.go
middleware.go
models.go
utils.go
helpers.go
```
### ❌ Over-Nesting
```go
// BAD: Too many levels
internal/
domain/
services/
cv/
handlers/
http/
v1/
endpoints/
cv.go
```
### ❌ God Packages
```go
// 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
- [Go Project Layout](https://github.com/golang-standards/project-layout)
- [Package Organization](https://go.dev/blog/package-names)
- [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)