505 lines
10 KiB
Markdown
505 lines
10 KiB
Markdown
|
|
# 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)
|