Contents
Use full words rather than abbreviations (e.g., compensatingTransaction instead of ct). Commonly accepted abbreviations like db are fine.
(略語ではなく完全な単語を使う(例:ctではなくcompensatingTransaction)。dbのように一般的に受け入れられている略語は問題ない)
解説
略語は、書いた本人以外には意味が不明確で、コードの理解を妨げます。完全な単語を使用することで、コードの意図が明確になり、新しいメンバーでも容易に理解できます。現代のエディタには自動補完機能があるため、長い名前でも入力の手間は問題になりません。ただし、広く認知されている略語(例:db、api、html、url)は使用しても問題ありません。
具体例
// 悪い例(独自の略語を使用)
func ProcUsrData(usr *Usr) error {
// usr, Procは何の略か不明
res := CalcTtl(usr.Itms)
// res, Ttl, Itmsは何の略か不明
if err := SaveToDb(res); err != nil {
return err
}
return nil
}
type Usr struct {
Nm string // Nmは何の略?
Em string // Emは何の略?
Ph string // Phは何の略?
Itms []Item
}
func CalcTtl(itms []Item) float64 {
ttl := 0.0
for _, itm := range itms {
ttl += itm.Prc
}
return ttl
}
// 良い例(完全な単語を使用)
func ProcessUserData(user *User) error {
total := CalculateTotal(user.Items)
if err := SaveToDatabase(total); err != nil {
return err
}
return nil
}
type User struct {
Name string
Email string
Phone string
Items []Item
}
func CalculateTotal(items []Item) float64 {
total := 0.0
for _, item := range items {
total += item.Price
}
return total
}
// 一般的な略語は使用可(良い例)
type UserRepository struct {
db *sql.DB // dbは一般的
api APIClient // apiは一般的
}
func FetchFromAPI(url string) ([]byte, error) {
// url, apiは一般的
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func GenerateHTML(data TemplateData) string {
// htmlは一般的
return renderTemplate(data)
}
参考リンク
Contents
Even if two functions perform the same process, separate them when their purposes differ.
(2つの関数が同じ処理を実行する場合でも、目的が異なる場合は分離する)
解説
同じ処理内容であっても、目的やコンテキストが異なる場合は別々の関数として定義すべきです。これにより、将来的に要件が変わり処理内容が分岐した際に、影響範囲を限定できます。共通化を優先しすぎると、異なる目的の処理が密結合になり、一方の変更が他方に意図しない影響を与えるリスクが生じます。目的に応じた関数分離は、変更に強い設計の基本です。
具体例
// 悪い例(目的が異なるのに共通化)
func calculateDiscount(price float64) float64 {
return price * 0.9 // 10%割引
}
func processNewUserOrder(price float64) float64 {
return calculateDiscount(price) // 新規ユーザー割引
}
func processSeasonalSale(price float64) float64 {
return calculateDiscount(price) // シーズンセール割引
}
// 良い例(目的ごとに分離)
func calculateNewUserDiscount(price float64) float64 {
return price * 0.9 // 新規ユーザー割引
}
func calculateSeasonalDiscount(price float64) float64 {
return price * 0.9 // シーズンセール割引(将来変更可能性あり)
}
func processNewUserOrder(price float64) float64 {
return calculateNewUserDiscount(price)
}
func processSeasonalSale(price float64) float64 {
return calculateSeasonalDiscount(price)
}
参考リンク
Contents
Keep closely related data and behavior together in a class for high cohesion and clear separation of concerns. Conversely, do not split strongly related elements across separate functions.
(密接に関連するデータと振る舞いをクラスにまとめて高い凝集度と明確な関心の分離を実現する。逆に、強く関連する要素を別々の関数に分割しない)
解説
高い凝集度は、関連するデータと操作を1箇所にまとめることで実現されます。データとそれを操作する振る舞いが離れていると、変更時に複数箇所を修正する必要が生じ、整合性を保つことが困難になります。クラス(構造体)内で関連する要素を集約することで、コードの理解が容易になり、変更の影響範囲が明確になります。
具体例
// 悪い例
type User struct {
Name string
Email string
}
func validateUserEmail(email string) bool {
return strings.Contains(email, "@")
}
func formatUserName(name string) string {
return strings.Title(name)
}
// 良い例
type User struct {
Name string
Email string
}
func (u *User) ValidateEmail() bool {
return strings.Contains(u.Email, "@")
}
func (u *User) FormatName() string {
return strings.Title(u.Name)
}
参考リンク
Contents
Use meaningful names for variables and functions (e.g., good: getUser() / bad: method1()).
(変数と関数には意味のある名前を使う(例: 良い getUser() / 悪い method1()))
解説
変数や関数に意味のある名前を付けることは、コードの可読性と保守性を高める基本原則です。名前から目的や役割が明確に伝わることで、他の開発者がコードを理解しやすくなります。抽象的な名前や番号付きの名前は、コードレビューやデバッグ時に混乱を招き、バグの温床となります。MUST項目として、すべての変数と関数に対して例外なく適用されるべきルールです。
具体例
// 悪い例
func method1() User {
var a = "john@example.com"
var b = fetchData(a)
return b
}
// 良い例
func getUser() User {
userEmail := "john@example.com"
user := fetchUserByEmail(userEmail)
return user
}
参考リンク
Contents
Prefer pure functions that return results rather than causing side effects. Use pointers only when necessary (e.g., large slices or shared transactions).
(副作用を起こすのではなく、結果を返す純粋関数を優先する。ポインタは必要な場合のみ使用する(例:大きなスライスや共有トランザクション))
解説
純粋関数は、同じ入力に対して常に同じ出力を返し、外部状態を変更しません。これにより、関数の振る舞いが予測可能になり、テストが容易になります。副作用を持つ関数は、呼び出し順序や状態に依存するため、デバッグが困難です。ポインタを多用すると、意図しない状態変更が発生しやすくなるため、必要な場合のみ使用すべきです。純粋関数は並行処理でも安全です。
具体例
// 悪い例(副作用を持つ関数)
var globalCounter int
func ProcessItem(item *Item) {
item.Price *= 1.1 // 引数を直接変更(副作用)
globalCounter++ // グローバル状態を変更(副作用)
}
func CalculateTotal(items []*Item) float64 {
total := 0.0
for _, item := range items {
ProcessItem(item) // 元のデータが変更される
total += item.Price
}
return total
}
// 良い例(純粋関数)
func ApplyTax(price float64) float64 {
return price * 1.1 // 新しい値を返す
}
func CalculateTaxedPrice(item Item) Item {
// 元のitemを変更せず、新しいItemを返す
return Item{
Name: item.Name,
Price: ApplyTax(item.Price),
}
}
func CalculateTotal(items []Item) float64 {
total := 0.0
for _, item := range items {
taxedPrice := ApplyTax(item.Price)
total += taxedPrice
// 元のitemsは変更されない
}
return total
}
// ポインタが必要な例(大きなデータ構造の効率的な処理)
type LargeDataSet struct {
Data []byte // 数MBのデータ
}
func ProcessLargeData(data *LargeDataSet) error {
// 大きなデータのコピーを避けるためポインタを使用
return processBatch(data.Data)
}
// トランザクション内での共有が必要な例
func UpdateWithTransaction(tx *sql.Tx, user User) error {
// トランザクションは共有する必要がある
return tx.Exec("UPDATE users SET name = ?", user.Name)
}
参考リンク
Contents
Don’t use getters unnecessarily.
(不必要にゲッターを使わない)
解説
すべてのフィールドに機械的にゲッターを用意することは、カプセル化の本質を見失った形式的な実装です。ゲッターは、外部に公開する必要がある情報に対してのみ提供すべきです。不必要なゲッターは、内部実装の詳細を露出させ、クラスの責任を不明確にします。本当に必要な場合のみゲッターを作成することで、適切なカプセル化が実現され、変更の影響範囲が限定されます。
具体例
// 悪い例(すべてのフィールドにゲッターを用意)
type Order struct {
id string
customerID string
items []Item
subtotal float64
tax float64
shipping float64
createdAt time.Time
updatedAt time.Time
}
// すべてのフィールドに機械的にゲッターを用意
func (o *Order) ID() string { return o.id }
func (o *Order) CustomerID() string { return o.customerID }
func (o *Order) Items() []Item { return o.items }
func (o *Order) Subtotal() float64 { return o.subtotal }
func (o *Order) Tax() float64 { return o.tax }
func (o *Order) Shipping() float64 { return o.shipping }
func (o *Order) CreatedAt() time.Time { return o.createdAt }
func (o *Order) UpdatedAt() time.Time { return o.updatedAt }
// 良い例(本当に必要なゲッターのみ提供)
type Order struct {
id string
customerID string
items []Item
subtotal float64
tax float64
shipping float64
createdAt time.Time
updatedAt time.Time
}
// 外部に公開が必要な情報のみゲッターを提供
func (o *Order) ID() string {
return o.id
}
func (o *Order) CustomerID() string {
return o.customerID
}
// 計算されたtotalのみ公開(内訳は公開しない)
func (o *Order) Total() float64 {
return o.subtotal + o.tax + o.shipping
}
// ItemsのゲッターではなくItemの数のみ公開
func (o *Order) ItemCount() int {
return len(o.items)
}
// さらに良い例(ドメインメソッドとして提供)
func (o *Order) IsExpired() bool {
return time.Since(o.createdAt) > 30*24*time.Hour
}
func (o *Order) CanBeCancelled() bool {
return time.Since(o.createdAt) < 24*time.Hour
}
// 内部計算フィールドは完全に隠蔽
func (o *Order) calculateSubtotal() float64 {
total := 0.0
for _, item := range o.items {
total += item.Price * float64(item.Quantity)
}
return total
}
func (o *Order) calculateTax() float64 {
return o.subtotal * 0.1
}
参考リンク
Contents
Do not reuse a variable within the same scope.
(同一スコープ内で変数を再利用しない)
解説
同一スコープ内で変数を異なる目的に再利用すると、変数の意味が曖昧になり、バグの原因となります。変数の値が途中で別の意味を持つようになると、コードを読む人が混乱し、意図しない値の参照が発生する可能性があります。各変数は一つの明確な目的を持つべきです。MUST項目として、変数のライフサイクルを明確にし、コードの予測可能性を保証します。
具体例
// 悪い例
func processData() {
result := fetchUserCount() // ユーザー数を取得
fmt.Println(result)
result = calculateTotal() // 合計金額を計算(同じ変数を再利用)
fmt.Println(result)
}
// 良い例
func processData() {
userCount := fetchUserCount()
fmt.Println(userCount)
totalAmount := calculateTotal()
fmt.Println(totalAmount)
}
参考リンク
Contents
Avoid hard-coding values. Place frequently changing values in configuration; rarely changing values may be declared as package-level constants.
(値をハードコーディングしない。頻繁に変わる値は設定に配置し、稀にしか変わらない値はパッケージレベルの定数として宣言してもよい)
解説
ハードコードされた値は、環境ごとの違いや将来的な変更に対応できず、コードの修正とデプロイを強制します。頻繁に変更される値は設定ファイルや環境変数に外部化し、変更時にコードの再コンパイルを不要にすべきです。定数として宣言すべき値と設定として外部化すべき値を適切に判断することで、保守性の高いコードベースが実現されます。
具体例
// 悪い例(ハードコーディング)
func ProcessPayment(amount float64) error {
tax := amount * 0.1 // 税率がハードコード
total := amount + tax
if total > 10000 { // 閾値がハードコード
return errors.New("amount too large")
}
apiURL := "https://api.payment.com/v1/charge" // URLがハードコード
return callAPI(apiURL, total)
}
// 良い例(定数と設定の適切な使用)
const (
TaxRate = 0.1 // 法律で定められた税率(稀に変更)
)
type PaymentConfig struct {
MaxAmount float64
APIBaseURL string
}
type PaymentService struct {
config PaymentConfig
}
func NewPaymentService(config PaymentConfig) *PaymentService {
return &PaymentService{config: config}
}
func (s *PaymentService) ProcessPayment(amount float64) error {
tax := amount * TaxRate
total := amount + tax
if total > s.config.MaxAmount {
return errors.New("amount too large")
}
apiURL := s.config.APIBaseURL + "/v1/charge"
return callAPI(apiURL, total)
}
参考リンク
Contents
Prefer block statements over deep nesting where possible.
(可能な限り、深いネストよりもブロック文を優先する)
解説
深いネストは、コードの可読性を著しく低下させ、ロジックの理解を困難にします。早期リターン(ガード節)を使用することで、ネストを浅く保ち、正常系のフローを明確にできます。ネストが深くなるほど、エラー処理や条件分岐が複雑になり、バグの混入リスクが高まります。フラットなコード構造は、コードレビューやデバッグを容易にします。
具体例
// 悪い例(深いネスト)
func ProcessOrder(order Order) error {
if order.ID != "" {
if order.Amount > 0 {
if order.Status == "pending" {
if err := validateOrder(order); err == nil {
if err := saveOrder(order); err == nil {
if err := sendConfirmation(order); err == nil {
return nil
} else {
return err
}
} else {
return err
}
} else {
return err
}
} else {
return errors.New("invalid status")
}
} else {
return errors.New("invalid amount")
}
} else {
return errors.New("invalid ID")
}
}
// 良い例(早期リターンでフラット化)
func ProcessOrder(order Order) error {
if order.ID == "" {
return errors.New("invalid ID")
}
if order.Amount <= 0 {
return errors.New("invalid amount")
}
if order.Status != "pending" {
return errors.New("invalid status")
}
if err := validateOrder(order); err != nil {
return err
}
if err := saveOrder(order); err != nil {
return err
}
if err := sendConfirmation(order); err != nil {
return err
}
return nil
}
参考リンク
Contents
Actively use Value Objects for variables representing important domain concepts.
(重要なドメイン概念を表す変数には、Value Objectを積極的に使用する)
解説
プリミティブ型(文字列、数値など)だけでドメイン概念を表現すると、型安全性が失われ、無効な値の混入リスクが高まります。Value Objectを使用することで、ドメインルールをカプセル化し、型システムの力でバグを防止できます。重要なドメイン概念(メールアドレス、金額、ユーザーIDなど)をValue Objectとして定義することで、コードの表現力と安全性が向上します。
具体例
// 悪い例(プリミティブ型のみ使用)
type User struct {
ID string
Email string
}
func CreateUser(id, email string) (*User, error) {
// バリデーションが散在
if id == "" || email == "" {
return nil, errors.New("invalid input")
}
return &User{ID: id, Email: email}, nil
}
func SendEmail(email string) error {
// emailの妥当性チェックが毎回必要
if !strings.Contains(email, "@") {
return errors.New("invalid email")
}
return emailClient.Send(email, "Hello")
}
// 良い例(Value Object使用)
type UserID struct {
value string
}
func NewUserID(id string) (UserID, error) {
if id == "" {
return UserID{}, errors.New("user ID cannot be empty")
}
return UserID{value: id}, nil
}
func (u UserID) String() string {
return u.value
}
type Email struct {
value string
}
func NewEmail(email string) (Email, error) {
if !strings.Contains(email, "@") {
return Email{}, errors.New("invalid email format")
}
return Email{value: email}, nil
}
func (e Email) String() string {
return e.value
}
type User struct {
ID UserID
Email Email
}
func CreateUser(id UserID, email Email) *User {
// Value Objectは既に検証済みなので追加チェック不要
return &User{ID: id, Email: email}
}
func SendEmail(email Email) error {
// Emailは既に妥当性が保証されている
return emailClient.Send(email.String(), "Hello")
}
参考リンク
Contents
Do not add variables or functions that the specification does not require (YAGNI principle).
(仕様に必要のない変数や関数を追加しない(YAGNI原則))
解説
YAGNI(You Aren’t Gonna Need It)原則は、現時点で必要のない機能を実装しないという考え方です。将来必要になるかもしれないと予測して実装した機能の多くは、実際には使われず、コードの複雑性を増すだけです。未使用のコードはメンテナンスコストを増加させ、バグの温床となります。仕様に明記された要件のみを実装することで、シンプルで保守しやすいコードベースを維持できます。
具体例
// 悪い例(仕様にない機能を実装)
type User struct {
Name string
Email string
Age int // 仕様に不要
Phone string // 仕様に不要
}
func (u *User) GetFullProfile() map[string]interface{} {
// 仕様に不要な複雑な処理
return map[string]interface{}{
"name": u.Name,
"email": u.Email,
"age": u.Age,
"phone": u.Phone,
}
}
// 良い例(仕様に必要な要素のみ)
type User struct {
Name string
Email string
}
func (u *User) GetEmail() string {
return u.Email
}
参考リンク
Contents
Implement every detail stated in the specification.
(仕様に記載されたすべての詳細を実装する)
解説
仕様書に記載された要件は、ステークホルダーとの合意事項であり、完全に実装される必要があります。仕様の一部を省略したり、勝手に変更したりすることは、期待される機能が提供されないことを意味し、プロジェクトの失敗につながります。不明点や実装困難な要件がある場合は、実装前にステークホルダーと協議し、仕様を明確にする必要があります。実装後は、仕様との整合性を確認するテストが必須です。
具体例
// 仕様:
// - ユーザー登録時にメールアドレスの形式を検証する
// - パスワードは8文字以上必要
// - 登録完了メールを送信する
// 悪い例(仕様の一部のみ実装)
func RegisterUser(email, password string) error {
if !strings.Contains(email, "@") {
return errors.New("invalid email")
}
return saveUser(email, password)
// パスワード長チェック未実装
// メール送信未実装
}
// 良い例(仕様をすべて実装)
func RegisterUser(email, password string) error {
if !strings.Contains(email, "@") {
return errors.New("invalid email format")
}
if len(password) < 8 {
return errors.New("password must be at least 8 characters")
}
if err := saveUser(email, password); err != nil {
return err
}
if err := sendWelcomeEmail(email); err != nil {
log.Printf("Failed to send email: %v", err)
// メール送信失敗は登録処理を失敗させない
}
return nil
}
参考リンク
Contents
Remove or comment out dead code that is no longer referenced.
(参照されなくなったデッドコードは削除またはコメントアウトする)
解説
使用されていないコードは、コードベースを不必要に複雑にし、メンテナンスコストを増加させます。デッドコードは開発者を混乱させ、誤って呼び出される可能性もあります。バージョン管理システムを使用していれば、削除したコードは履歴から復元できるため、コメントアウトして残す必要はありません。定期的にデッドコードを検出・削除することで、コードベースの品質を維持できます。
具体例
// 悪い例
type UserService struct {
db Database
}
// もう使われていない古い実装
// func (s *UserService) CreateUserOld(name string) error {
// return s.db.InsertOld(name)
// }
func (s *UserService) CreateUser(name string) error {
return s.db.Insert(name)
}
// 誰も呼んでいない関数
func (s *UserService) DeprecatedMethod() {
// ...
}
// 良い例
type UserService struct {
db Database
}
// 使用されている関数のみ存在
func (s *UserService) CreateUser(name string) error {
return s.db.Insert(name)
}
参考リンク
Contents
Access struct fields through getter methods; avoid setters.
(構造体のフィールドへのアクセスはゲッターメソッドを通して行い、セッターは避ける)
解説
セッターメソッドを提供すると、オブジェクトの状態がいつでも変更可能になり、不変性が損なわれます。ゲッターのみを提供することで、オブジェクトの読み取り専用アクセスを実現し、予期しない状態変更を防げます。状態の変更が必要な場合は、ビジネスロジックを含むドメインメソッドとして実装すべきです。これにより、状態変更に対する制御が強化され、バグの混入を防ぎます。
具体例
// 悪い例(セッターが存在)
type Account struct {
balance float64
}
func (a *Account) GetBalance() float64 {
return a.balance
}
func (a *Account) SetBalance(amount float64) {
a.balance = amount // 検証なしで変更可能
}
func main() {
acc := &Account{balance: 1000}
acc.SetBalance(-500) // 不正な状態を作成可能
}
// 良い例(ゲッターのみ、状態変更はドメインメソッド)
type Account struct {
balance float64
}
func NewAccount(initialBalance float64) (*Account, error) {
if initialBalance < 0 {
return nil, errors.New("initial balance must be positive")
}
return &Account{balance: initialBalance}, nil
}
func (a *Account) Balance() float64 {
return a.balance
}
func (a *Account) Deposit(amount float64) error {
if amount <= 0 {
return errors.New("deposit amount must be positive")
}
a.balance += amount
return nil
}
func (a *Account) Withdraw(amount float64) error {
if amount <= 0 {
return errors.New("withdraw amount must be positive")
}
if a.balance < amount {
return errors.New("insufficient balance")
}
a.balance -= amount
return nil
}
参考リンク
Contents
Struct fields should be unexported (lowercase) and initialized only via a constructor function.
(構造体のフィールドは非公開(小文字)にし、コンストラクタ関数経由でのみ初期化する)
解説
構造体のフィールドを非公開にすることで、不正な状態での初期化を防ぎ、データの整合性を保証できます。コンストラクタ関数を使用することで、必須フィールドの初期化漏れや不正な値の設定を防止し、常に有効な状態のオブジェクトを作成できます。これは、カプセル化の原則に基づく重要な設計パターンであり、バグの混入を防ぎます。
具体例
// 悪い例(フィールドが公開されている)
type User struct {
Name string
Email string
Age int
}
func main() {
user := User{Name: "John"} // Emailが空で不正な状態
// ...
}
// 良い例(フィールドが非公開、コンストラクタ使用)
type User struct {
name string
email string
age int
}
func NewUser(name, email string, age int) (*User, error) {
if name == "" {
return nil, errors.New("name is required")
}
if !strings.Contains(email, "@") {
return nil, errors.New("invalid email")
}
if age < 0 {
return nil, errors.New("age must be positive")
}
return &User{
name: name,
email: email,
age: age,
}, nil
}
func (u *User) Name() string {
return u.name
}
func (u *User) Email() string {
return u.email
}
参考リンク
Contents
Remove obsolete or incorrect comments.
(古いまたは誤ったコメントを削除する)
解説
コードが変更されたにもかかわらずコメントが更新されないと、コメントが嘘の情報源となり、開発者を誤った方向に導きます。古いコメントは、コードの理解を助けるどころか混乱を招き、バグの原因となります。コードを変更する際は、関連するコメントも同時に更新するか、不要になったコメントは削除すべきです。正確なコメントのみが価値を持ちます。
具体例
// 悪い例(古いコメントが残っている)
type UserService struct {
db Database
}
// CreateUser creates a new user and sends a welcome email
// この関数はemailとpasswordを受け取ります
func (s *UserService) CreateUser(name string) error {
// 実際にはnameしか受け取っていない
// メール送信機能も削除済み
return s.db.Insert(name)
}
// ValidatePassword checks if password meets requirements
// パスワードは8文字以上必要です
func ValidatePassword(password string) bool {
// 実際には12文字以上に変更された
return len(password) >= 12
}
// 良い例(正確なコメント)
type UserService struct {
db Database
}
// CreateUser creates a new user with the given name
func (s *UserService) CreateUser(name string) error {
return s.db.Insert(name)
}
// ValidatePassword checks if password is at least 12 characters
func ValidatePassword(password string) bool {
return len(password) >= 12
}
参考リンク
Contents
Never store sensitive information (e.g., API keys) in plain text within the project. Fetch them at runtime from environment variables or external services such as SSM Parameter Store. Encrypted storage is acceptable, but do not bundle the decryption key in the project.
(機密情報(APIキーなど)をプロジェクト内にプレーンテキストで保存しない。実行時に環境変数やSSM Parameter Storeなどの外部サービスから取得する。暗号化ストレージは許容されるが、復号化キーをプロジェクトに含めてはいけない)
解説
APIキーやパスワードなどの機密情報をソースコードに直接記述することは、重大なセキュリティリスクです。コードリポジトリが漏洩した場合、即座に悪用される可能性があります。環境変数や外部の秘密管理サービスを使用することで、機密情報とコードを分離し、アクセス制御が可能になります。暗号化する場合も、復号化キーを同じリポジトリに含めては意味がありません。
具体例
// 悪い例
const APIKey = "sk_live_1234567890abcdef" // ハードコード
func callExternalAPI() {
client := NewAPIClient(APIKey)
client.Request()
}
// 良い例
func callExternalAPI() {
apiKey := os.Getenv("API_KEY") // 環境変数から取得
if apiKey == "" {
log.Fatal("API_KEY not set")
}
client := NewAPIClient(apiKey)
client.Request()
}
// さらに良い例(AWS SSM使用)
func getAPIKey() (string, error) {
sess := session.Must(session.NewSession())
svc := ssm.New(sess)
param, err := svc.GetParameter(&ssm.GetParameterInput{
Name: aws.String("/myapp/api_key"),
WithDecryption: aws.Bool(true),
})
if err != nil {
return "", err
}
return *param.Parameter.Value, nil
}
参考リンク
Contents
Name functions with a verb or verb + object.
(関数名は動詞または動詞+目的語で命名する)
解説
関数は動作を表すため、動詞で始まる名前が自然で理解しやすくなります。動詞+目的語の形式は、関数が何を行うかを明確に伝え、コードの可読性を向上させます。名詞だけの関数名は、その関数が何をするのか不明確であり、混乱を招きます。一貫した命名規則に従うことで、チーム全体でのコードの理解が容易になります。
具体例
// 悪い例(名詞のみ、または不明瞭)
func User(id string) User {
// Userは名詞のみで、何をする関数か不明
}
func Data() []byte {
// 何のデータを取得するのか不明
}
func Process() {
// 何を処理するのか不明
}
func Order(items []Item) {
// 注文を作成?取得?
}
// 良い例(動詞+目的語)
func GetUser(id string) User {
// ユーザーを取得することが明確
}
func FetchUserData() []byte {
// ユーザーデータを取得することが明確
}
func ProcessOrder(order Order) error {
// 注文を処理することが明確
}
func CreateOrder(items []Item) (Order, error) {
// 注文を作成することが明確
}
func ValidateEmail(email string) bool {
// メールアドレスを検証することが明確
}
func CalculateTotalPrice(items []Item) float64 {
// 合計金額を計算することが明確
}
func SendNotification(user User, message string) error {
// 通知を送信することが明確
}
func UpdateUserProfile(userID string, profile Profile) error {
// ユーザープロフィールを更新することが明確
}
func DeleteExpiredSessions() error {
// 期限切れセッションを削除することが明確
}
参考リンク
Contents
A function should have no more than five parameters. More than five suggests too many responsibilities or insufficient aggregation.
(関数のパラメータは5つ以内にすべき。5つを超える場合は、責任が多すぎるか、集約が不足していることを示す)
解説
パラメータが多い関数は、理解と使用が困難であり、単一責任原則に違反している可能性が高いです。パラメータが5つを超える場合、関連するパラメータをオブジェクトにまとめるか、関数の責任を分割すべきです。パラメータを集約することで、関連する値が明確になり、関数シグネチャがシンプルになります。これにより、コードの可読性と保守性が向上します。
具体例
// 悪い例(パラメータが多すぎる)
func CreateUser(
firstName string,
lastName string,
email string,
phone string,
address string,
city string,
zipCode string,
country string,
) error {
// パラメータの順序を覚えるのが困難
// ...
}
func main() {
CreateUser("John", "Doe", "john@example.com", "123-456",
"123 Main St", "Tokyo", "100-0001", "Japan")
}
// 良い例(パラメータをオブジェクトに集約)
type UserProfile struct {
FirstName string
LastName string
Email string
Phone string
}
type Address struct {
Street string
City string
ZipCode string
Country string
}
func CreateUser(profile UserProfile, address Address) error {
// 関連するデータがグループ化され、理解しやすい
// ...
}
func main() {
profile := UserProfile{
FirstName: "John",
LastName: "Doe",
Email: "john@example.com",
Phone: "123-456",
}
address := Address{
Street: "123 Main St",
City: "Tokyo",
ZipCode: "100-0001",
Country: "Japan",
}
CreateUser(profile, address)
}
// さらに良い例(責任を分割)
func CreateUser(profile UserProfile) (UserID, error) {
// ユーザー作成のみに集中
// ...
}
func SetUserAddress(userID UserID, address Address) error {
// 住所設定を別関数に分離
// ...
}
参考リンク
Contents
Keep each function to 100 lines or fewer (logical lines; wrapped lines count as one).
(各関数は100行以内に収める(論理行;折り返された行は1行とカウント))
解説
長い関数は、複数の責任を持っている可能性が高く、理解とテストが困難です。100行を超える関数は、処理を小さな関数に分割することで、可読性と再利用性が向上します。短い関数は、単一の責任を持ち、名前から目的が明確になります。論理行でカウントするため、コメントや空行、折り返しは複数行とはみなされません。適切な粒度の関数は、バグの発見と修正を容易にします。
具体例
// 悪い例(長すぎる関数 - 200行以上)
func ProcessOrder(order Order) error {
// バリデーション(30行)
if order.ID == "" {
return errors.New("order ID is required")
}
if order.CustomerID == "" {
return errors.New("customer ID is required")
}
// ... さらに20項目のバリデーション
// 在庫チェック(40行)
for _, item := range order.Items {
stock, err := db.GetStock(item.ProductID)
if err != nil {
return err
}
// ... 複雑な在庫ロジック
}
// 価格計算(40行)
subtotal := 0.0
for _, item := range order.Items {
subtotal += item.Price * float64(item.Quantity)
}
// ... 割引、税金、送料の計算
// 決済処理(40行)
// ... 決済ゲートウェイとの通信
// メール送信(30行)
// ... メール作成と送信
// データベース保存(20行)
// ... 複数テーブルへの保存
return nil
}
// 良い例(適切に分割された関数)
func ProcessOrder(order Order) error {
if err := validateOrder(order); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
if err := checkInventory(order.Items); err != nil {
return fmt.Errorf("inventory check failed: %w", err)
}
total, err := calculateOrderTotal(order)
if err != nil {
return fmt.Errorf("calculation failed: %w", err)
}
if err := processPayment(order.CustomerID, total); err != nil {
return fmt.Errorf("payment failed: %w", err)
}
if err := saveOrder(order); err != nil {
return fmt.Errorf("save failed: %w", err)
}
if err := sendConfirmationEmail(order); err != nil {
log.Printf("email failed: %v", err)
// メール失敗は注文処理を失敗させない
}
return nil
}
// 各関数は単一の責任を持ち、100行以内
func validateOrder(order Order) error {
if order.ID == "" {
return errors.New("order ID is required")
}
if order.CustomerID == "" {
return errors.New("customer ID is required")
}
// ... その他のバリデーション
return nil
}
func checkInventory(items []Item) error {
for _, item := range items {
if err := verifyStockAvailability(item); err != nil {
return err
}
}
return nil
}
func calculateOrderTotal(order Order) (float64, error) {
subtotal := calculateSubtotal(order.Items)
discount := calculateDiscount(order, subtotal)
tax := calculateTax(subtotal - discount)
shipping := calculateShipping(order)
return subtotal - discount + tax + shipping, nil
}
参考リンク
Contents
Write a one-line comment summarizing the purpose of each function. Argument explanations are unnecessary except in special cases.
(各関数の目的を要約する1行コメントを書く。引数の説明は特別な場合を除いて不要)
解説
関数の目的を簡潔に説明するコメントは、コードの理解を助ける重要な要素です。1行のコメントで「何をする関数か」を明確にすることで、コードレビューや保守作業が効率化されます。ただし、引数や戻り値の詳細な説明は、型や名前から自明な場合は不要です。コメントは実装の詳細ではなく、関数の意図や目的を伝えることに焦点を当てるべきです。
具体例
// 悪い例(コメントなし、または冗長)
func ProcessOrder(order Order) error {
// この関数はOrderを受け取り、それを処理し、エラーがあればエラーを返します
// 引数: order - 処理するOrder型の変数
// 戻り値: error - エラーがあればerror、なければnil
// ...
}
// 良い例
// ProcessOrder は注文を検証して保存し、確認メールを送信する
func ProcessOrder(order Order) error {
// ...
}
// CalculateTax は商品価格に対する消費税額を計算する
func CalculateTax(price float64) float64 {
return price * 0.1
}
// FetchActiveUsers はアクティブなユーザー一覧をデータベースから取得する
func FetchActiveUsers() ([]User, error) {
// ...
}
// GenerateInvoicePDF は注文データからPDF形式の請求書を生成する
// pdfOptions: マージン、フォントサイズなど細かい制御が必要な場合のみコメント
func GenerateInvoicePDF(order Order, pdfOptions PDFOptions) ([]byte, error) {
// ...
}
参考リンク
Contents
Each class (struct + methods) should work independently.
(各クラス(構造体+メソッド)は独立して動作するようにする)
解説
クラスが他のクラスやグローバル状態に暗黙的に依存していると、テストが困難になり、再利用性が低下します。各クラスは、必要な依存関係を明示的に受け取り、独立して動作すべきです。これにより、単体テストでモックやスタブを容易に注入でき、クラスの振る舞いを分離して検証できます。独立性の高いクラスは、異なるコンテキストでの再利用も容易です。
具体例
// 悪い例(グローバル状態に依存)
var globalDB *Database // グローバル変数
type UserService struct{}
func (s *UserService) GetUser(id string) (User, error) {
return globalDB.Query(id) // 暗黙的な依存
}
// 良い例(依存関係を明示的に注入)
type UserService struct {
db Database
}
func NewUserService(db Database) *UserService {
return &UserService{db: db}
}
func (s *UserService) GetUser(id string) (User, error) {
return s.db.Query(id) // 明示的な依存
}
// テストでのモック使用
type MockDatabase struct{}
func (m *MockDatabase) Query(id string) (User, error) {
return User{ID: id, Name: "Test User"}, nil
}
func TestGetUser(t *testing.T) {
mockDB := &MockDatabase{}
service := NewUserService(mockDB)
user, err := service.GetUser("123")
if err != nil {
t.Fatal(err)
}
if user.Name != "Test User" {
t.Errorf("unexpected user name: %s", user.Name)
}
}
参考リンク
Contents
Do not expose a function externally unless it truly needs to be called from outside. A public function must represent an external interface.
(外部から呼ばれる必要がない限り、関数を外部に公開しない。公開関数は外部インターフェースを表現する必要がある)
解説
関数の可視性を必要最小限に抑えることは、カプセル化の原則に基づく重要な設計です。不必要に公開された関数は、意図しない外部からの呼び出しを許し、内部実装の変更を困難にします。公開関数は契約として機能するため、変更時には後方互換性を考慮する必要があります。内部実装の詳細は非公開にすることで、リファクタリングの自由度が高まります。
具体例
// 悪い例
type UserService struct {
db Database
}
// 内部処理なのに公開されている
func (s *UserService) ValidateUserInput(name string) bool {
return len(name) > 0
}
func (s *UserService) CreateUser(name string) error {
if s.ValidateUserInput(name) {
return s.db.Insert(name)
}
return errors.New("invalid input")
}
// 良い例
type UserService struct {
db Database
}
// 内部処理は非公開
func (s *UserService) validateUserInput(name string) bool {
return len(name) > 0
}
// 外部インターフェースのみ公開
func (s *UserService) CreateUser(name string) error {
if s.validateUserInput(name) {
return s.db.Insert(name)
}
return errors.New("invalid input")
}
参考リンク
Contents
Use Value Objects for both arguments and return values when possible.
(可能な限り、引数と戻り値の両方にValue Objectを使用する)
解説
関数のシグネチャでValue Objectを使用することで、型安全性が向上し、無効な値の受け渡しを防げます。プリミティブ型では、引数の順序を間違えたり、無効な値を渡したりするミスが発生しやすくなります。Value Objectを使用することで、コンパイル時に型チェックが働き、ドメインルール違反を早期に検出できます。これにより、実行時エラーではなくコンパイルエラーとして問題を発見できます。
具体例
// 悪い例(プリミティブ型の引数)
func TransferMoney(fromAccount, toAccount string, amount float64) error {
// 引数の順序ミスが起きやすい
// 負の金額が渡される可能性がある
if amount <= 0 {
return errors.New("invalid amount")
}
// ...
}
func main() {
// 引数の順序を間違える可能性
TransferMoney("ACC002", "ACC001", 100.0)
// 負の値を渡せてしまう
TransferMoney("ACC001", "ACC002", -50.0)
}
// 良い例(Value Objectの引数と戻り値)
type AccountID struct {
value string
}
func NewAccountID(id string) (AccountID, error) {
if id == "" || !strings.HasPrefix(id, "ACC") {
return AccountID{}, errors.New("invalid account ID")
}
return AccountID{value: id}, nil
}
type Amount struct {
value float64
}
func NewAmount(amount float64) (Amount, error) {
if amount <= 0 {
return Amount{}, errors.New("amount must be positive")
}
return Amount{value: amount}, nil
}
func (a Amount) Value() float64 {
return a.value
}
type TransferResult struct {
TransactionID string
CompletedAt time.Time
}
func TransferMoney(from, to AccountID, amount Amount) (TransferResult, error) {
// Value Objectは既に検証済み
// 型が異なるため引数の順序ミスも防げる
result := TransferResult{
TransactionID: generateID(),
CompletedAt: time.Now(),
}
return result, nil
}
func main() {
from, _ := NewAccountID("ACC001")
to, _ := NewAccountID("ACC002")
amount, _ := NewAmount(100.0)
// 型安全性により、引数の順序ミスはコンパイルエラーになる
result, err := TransferMoney(from, to, amount)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Transfer completed: %s\n", result.TransactionID)
}
参考リンク
Contents
For dependency injection, depend on interfaces, not concrete implementations.
(依存性注入では、具体的な実装ではなくインターフェースに依存する)
解説
依存性注入において、具体的な実装ではなくインターフェースに依存することで、疎結合な設計が実現されます。インターフェースに依存することで、実装の差し替えが容易になり、テスト時にモックやスタブを注入できます。この原則は依存性逆転の原則(DIP)の実践であり、変更に強く拡張しやすいコードベースを構築するための基盤となります。
具体例
// 悪い例(具体的な実装に依存)
type MySQLDatabase struct{}
func (db *MySQLDatabase) Query(sql string) []Row {
// MySQL固有の実装
return nil
}
type UserService struct {
db *MySQLDatabase // 具体的な実装に依存
}
// 良い例(インターフェースに依存)
type Database interface {
Query(sql string) []Row
}
type MySQLDatabase struct{}
func (db *MySQLDatabase) Query(sql string) []Row {
return nil
}
type UserService struct {
db Database // インターフェースに依存
}
func NewUserService(db Database) *UserService {
return &UserService{db: db}
}
参考リンク
Contents
Instead of flag parameters that toggle behavior inside a function, split the function.
(関数内部で振る舞いを切り替えるフラグパラメータを使う代わりに、関数を分割する)
解説
フラグパラメータを持つ関数は、実質的に2つ以上の異なる処理を1つの関数に詰め込んでおり、単一責任原則に違反します。フラグによって振る舞いが変わる関数は、呼び出し側でフラグの意味を理解する必要があり、可読性が低下します。関数を分割することで、各関数の意図が明確になり、使用方法が直感的になります。これにより、コードの保守性と理解しやすさが向上します。
具体例
// 悪い例(フラグパラメータで振る舞いを変更)
func ProcessUser(user User, sendEmail bool) error {
if err := validateUser(user); err != nil {
return err
}
if err := saveUser(user); err != nil {
return err
}
if sendEmail {
// メール送信処理
if err := sendWelcomeEmail(user.Email); err != nil {
return err
}
}
return nil
}
func main() {
ProcessUser(newUser, true) // trueは何を意味する?
ProcessUser(existingUser, false) // falseは何を意味する?
}
// 良い例(関数を分割)
func RegisterNewUser(user User) error {
if err := validateUser(user); err != nil {
return err
}
if err := saveUser(user); err != nil {
return err
}
if err := sendWelcomeEmail(user.Email); err != nil {
return err
}
return nil
}
func UpdateExistingUser(user User) error {
if err := validateUser(user); err != nil {
return err
}
if err := saveUser(user); err != nil {
return err
}
// メール送信なし
return nil
}
func main() {
RegisterNewUser(newUser) // 意図が明確
UpdateExistingUser(existingUser) // 意図が明確
}
参考リンク
Contents
Keep domain responsibilities out of utility packages.
(ドメインの責務をユーティリティパッケージに入れない)
解説
ユーティリティパッケージは、文字列操作や日付計算など、ドメイン非依存の汎用的な機能を提供すべきです。ドメイン固有のビジネスロジックをユーティリティに配置すると、ドメイン層の概念が分散し、ビジネスロジックの理解が困難になります。ドメインロジックは、適切なエンティティやドメインサービスに配置することで、コードの意図が明確になり、保守性が向上します。
具体例
// 悪い例(ドメインロジックがutilに配置)
package util
func CalculateOrderTotal(items []Item) float64 {
total := 0.0
for _, item := range items {
total += item.Price * float64(item.Quantity)
}
// 1万円以上で送料無料(ドメインルール)
if total < 10000 {
total += 500 // 送料
}
return total
}
// 良い例(ドメインロジックはドメイン層に配置)
package domain
type Order struct {
items []Item
}
func (o *Order) CalculateTotal() float64 {
subtotal := 0.0
for _, item := range o.items {
subtotal += item.Price * float64(item.Quantity)
}
shippingFee := o.calculateShippingFee(subtotal)
return subtotal + shippingFee
}
func (o *Order) calculateShippingFee(subtotal float64) float64 {
const freeShippingThreshold = 10000
const standardShippingFee = 500
if subtotal >= freeShippingThreshold {
return 0
}
return standardShippingFee
}
// utilパッケージは汎用機能のみ
package util
func FormatCurrency(amount float64) string {
return fmt.Sprintf("¥%.0f", amount)
}
参考リンク
Contents
Provide unit tests for every function, and ensure all tests pass.
(すべての関数にユニットテストを提供し、すべてのテストが合格することを保証する)
解説
ユニットテストは、コードの品質を保証し、リファクタリング時の安全性を提供する重要な要素です。すべての関数にテストがあることで、変更時に既存機能が破壊されていないことを迅速に検証できます。テストが存在しないコードは、変更のリスクが高く、技術的負債となります。MUST項目として、例外なくすべての関数にテストを記述し、継続的に実行して合格状態を維持する必要があります。
具体例
// 実装
func Add(a, b int) int {
return a + b
}
func ValidateEmail(email string) bool {
return strings.Contains(email, "@")
}
// テスト
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
func TestValidateEmail(t *testing.T) {
if !ValidateEmail("test@example.com") {
t.Error("Valid email should pass")
}
if ValidateEmail("invalid") {
t.Error("Invalid email should fail")
}
}
参考リンク
Contents
A class must: 1. Explicitly receive all dependencies (via DI) — it should not secretly rely on any helper. 2. Maintain a consistent responsibility.
(クラスは次を満たす必要がある:1. すべての依存関係を明示的に受け取る(DI経由)— ヘルパーに密かに依存してはいけない。2. 一貫した責任を維持する)
解説
依存関係が暗黙的だと、テストやデバッグが困難になり、コードの理解に時間がかかります。すべての依存関係をコンストラクタで明示的に注入することで、クラスが必要とするものが一目でわかり、モックやスタブを使ったテストが容易になります。また、一貫した責任を維持することで、クラスの目的が明確になり、変更の影響範囲が限定されます。これは保守性の高いコード設計の基本原則です。
具体例
// 悪い例(暗黙的な依存)
type OrderService struct {
// 依存関係が明示されていない
}
func (s *OrderService) CreateOrder(items []Item) error {
// 内部でグローバルなhelperを使用
db := GetGlobalDB() // 暗黙的な依存
logger := GetGlobalLogger() // 暗黙的な依存
total := calculateTotal(items)
if err := db.Insert(total); err != nil {
logger.Error(err)
return err
}
return nil
}
// 良い例(明示的な依存注入)
type OrderService struct {
db Database
logger Logger
pricer PriceCalculator
}
func NewOrderService(db Database, logger Logger, pricer PriceCalculator) *OrderService {
return &OrderService{
db: db,
logger: logger,
pricer: pricer,
}
}
func (s *OrderService) CreateOrder(items []Item) error {
total := s.pricer.CalculateTotal(items)
if err := s.db.Insert(total); err != nil {
s.logger.Error(err)
return err
}
return nil
}
参考リンク
Contents
Do not write comments that merely restate the code (e.g., int a // declare integer a).
(コードを単に言い換えるだけのコメントを書かない(例:int a // 整数aを宣言))
解説
コードをそのまま言い換えただけのコメントは、情報を追加せず、コードのノイズとなります。コメントは、コードから読み取れない意図や理由、背景を説明するべきです。自明な内容をコメントで繰り返すことは時間の無駄であり、コメントのメンテナンスコストを不必要に増加させます。意味のあるコメントのみを書くことで、コードの可読性が向上します。
具体例
// 悪い例(自明な内容の繰り返し)
// increment i by 1
i++
// create a new user
user := User{}
// check if name is empty
if name == "" {
return errors.New("name is required")
}
// loop through all items
for _, item := range items {
// process item
process(item)
}
// 良い例(意図や理由を説明)
// ユーザーIDは1から開始(0は予約済み)
i := 1
// デフォルト値で初期化(後でAPIレスポンスで上書き)
user := User{Status: "pending"}
// 空の名前は無効(ビジネスルール)
if name == "" {
return errors.New("name is required")
}
// すべてのアイテムに対して在庫を減算
// エラーが発生してもロールバックしない(べき等性保証)
for _, item := range items {
process(item)
}
// コメント不要な例(コード自体が明確)
func calculateTax(price float64) float64 {
return price * 0.1
}
if user.IsActive() {
sendNotification(user)
}
参考リンク
Contents
When an Entity requires domain-specific validation, perform it inside the Entity’s own methods.
(エンティティがドメイン固有の検証を必要とする場合、エンティティ自身のメソッド内で実行する)
解説
ドメイン層のエンティティは、自身のビジネスルールを理解し、検証する責任を持つべきです。検証ロジックをエンティティ外部に配置すると、ビジネスルールが分散し、変更時の整合性維持が困難になります。エンティティ内部にドメイン検証を配置することで、ビジネスロジックがカプセル化され、ルールの変更が局所化されます。これは、ドメイン駆動設計における重要な原則です。
具体例
// 悪い例(検証がエンティティ外部)
type Order struct {
Amount float64
Status string
}
func ValidateOrder(order Order) error {
if order.Amount < 0 {
return errors.New("amount must be positive")
}
if order.Status != "pending" && order.Status != "completed" {
return errors.New("invalid status")
}
return nil
}
// 良い例(検証がエンティティ内部)
type Order struct {
amount float64
status string
}
func NewOrder(amount float64) (*Order, error) {
o := &Order{amount: amount, status: "pending"}
if err := o.validate(); err != nil {
return nil, err
}
return o, nil
}
func (o *Order) validate() error {
if o.amount < 0 {
return errors.New("amount must be positive")
}
return nil
}
func (o *Order) Complete() error {
if o.status != "pending" {
return errors.New("only pending orders can be completed")
}
o.status = "completed"
return nil
}
参考リンク
Contents
Do not swallow errors.
(エラーを握りつぶさない)
解説
エラーを無視したり握りつぶしたりすると、問題の発生源を特定できず、デバッグが著しく困難になります。エラーは適切にログに記録するか、呼び出し元に返すべきです。エラーを無視することは、システムの健全性を監視できなくなり、予期しない動作やデータ破損につながる可能性があります。すべてのエラーは、少なくともログ記録または上位層への伝播のいずれかで処理されるべきです。
具体例
// 悪い例(エラーを握りつぶす)
func ProcessData() {
data, err := fetchData()
if err != nil {
// エラーを無視
}
_ = saveData(data) // エラーを無視
}
func SendEmail(to string) {
err := emailClient.Send(to, "Hello")
// errをチェックせずに無視
}
// 良い例(適切なエラー処理)
func ProcessData() error {
data, err := fetchData()
if err != nil {
return fmt.Errorf("failed to fetch data: %w", err)
}
if err := saveData(data); err != nil {
return fmt.Errorf("failed to save data: %w", err)
}
return nil
}
func SendEmail(to string) error {
if err := emailClient.Send(to, "Hello"); err != nil {
return fmt.Errorf("failed to send email to %s: %w", to, err)
}
return nil
}
// ログ記録する場合
func BackgroundTask() {
if err := doSomething(); err != nil {
log.Printf("background task failed: %v", err)
// 呼び出し元に返せない場合はログに記録
}
}
参考リンク
Contents
Leave no TODOs or partially implemented code.
(TODOや部分的に実装されたコードを残さない)
解説
TODOコメントや未実装のコードは、不完全な状態でコードがマージされていることを示します。これらは時間とともに忘れられ、技術的負債として蓄積します。コードレビュー時にもノイズとなり、本当に重要な問題を見逃す原因となります。すべての機能は完全に実装され、テストが通った状態でコミットされるべきです。やむを得ず後回しにする場合は、課題管理システムに登録し、コード内には残しません。
具体例
// 悪い例
func ProcessOrder(order Order) error {
// TODO: バリデーション追加
if err := saveOrder(order); err != nil {
return err
}
// TODO: メール送信実装
// sendConfirmationEmail(order.Email)
return nil
}
// 良い例
func ProcessOrder(order Order) error {
if err := validateOrder(order); err != nil {
return err
}
if err := saveOrder(order); err != nil {
return err
}
if err := sendConfirmationEmail(order.Email); err != nil {
return err
}
return nil
}
func validateOrder(order Order) error {
if order.Amount <= 0 {
return errors.New("invalid amount")
}
return nil
}
func sendConfirmationEmail(email string) error {
// 完全に実装済み
return emailClient.Send(email, "Order Confirmed")
}
参考リンク
Contents
Replace switch/if-elseif branching with interfaces and polymorphism when appropriate.
(適切な場合、switch/if-elseif分岐をインターフェースとポリモーフィズムで置き換える)
解説
型や状態による分岐が多数存在すると、新しい型を追加する際にすべての分岐を更新する必要があり、変更が困難になります。インターフェースとポリモーフィズムを使用することで、Open-Closed原則に従った拡張可能な設計が実現されます。新しい型を追加する際、既存のコードを変更せずに済むため、バグの混入リスクが減少します。ただし、単純な分岐まで無理に抽象化する必要はありません。
具体例
// 悪い例(型による分岐が多数)
func CalculateShipping(orderType string, weight float64) float64 {
if orderType == "standard" {
return weight * 10
} else if orderType == "express" {
return weight * 20
} else if orderType == "overnight" {
return weight * 30
} else if orderType == "international" {
return weight * 50
}
return 0
}
func GetDeliveryDays(orderType string) int {
if orderType == "standard" {
return 5
} else if orderType == "express" {
return 2
} else if orderType == "overnight" {
return 1
} else if orderType == "international" {
return 10
}
return 0
}
// 良い例(ポリモーフィズム使用)
type ShippingMethod interface {
CalculateShipping(weight float64) float64
GetDeliveryDays() int
}
type StandardShipping struct{}
func (s StandardShipping) CalculateShipping(weight float64) float64 {
return weight * 10
}
func (s StandardShipping) GetDeliveryDays() int {
return 5
}
type ExpressShipping struct{}
func (e ExpressShipping) CalculateShipping(weight float64) float64 {
return weight * 20
}
func (e ExpressShipping) GetDeliveryDays() int {
return 2
}
type OvernightShipping struct{}
func (o OvernightShipping) CalculateShipping(weight float64) float64 {
return weight * 30
}
func (o OvernightShipping) GetDeliveryDays() int {
return 1
}
// 新しいタイプを追加しても既存コードは変更不要
type InternationalShipping struct{}
func (i InternationalShipping) CalculateShipping(weight float64) float64 {
return weight * 50
}
func (i InternationalShipping) GetDeliveryDays() int {
return 10
}
// 使用例
func ProcessOrder(method ShippingMethod, weight float64) {
cost := method.CalculateShipping(weight)
days := method.GetDeliveryDays()
fmt.Printf("Cost: %.2f, Days: %d\n", cost, days)
}
参考リンク
Contents
Even if a file exceeds 200 lines, do not place multiple classes (structs) in it. If you have multiple classes in a file over 200 lines, split them into separate files.
(ファイルが200行を超えても、複数のクラス(構造体)を配置しない。200行を超えるファイルに複数のクラスがある場合は、別々のファイルに分割する)
解説
1ファイル1クラスの原則に従うことで、ファイルの責任が明確になり、目的のクラスを素早く見つけられます。複数のクラスが1ファイルにあると、ファイルが肥大化し、コードレビューやマージコンフリクトの解決が困難になります。特に200行を超える大きなファイルでは、クラスを分離することで可読性と保守性が大幅に向上します。ファイル名とクラス名を一致させることも推奨されます。
具体例
// 悪い例(1ファイルに複数のクラス)
// user_service.go (300行)
package service
type UserService struct {
db Database
}
func (s *UserService) CreateUser(name string) error {
// ... 100行の実装
}
type OrderService struct {
db Database
}
func (s *OrderService) CreateOrder(items []Item) error {
// ... 100行の実装
}
type PaymentService struct {
gateway PaymentGateway
}
func (s *PaymentService) ProcessPayment(amount float64) error {
// ... 100行の実装
}
// 良い例(クラスごとに分離)
// user_service.go (100行)
package service
type UserService struct {
db Database
}
func (s *UserService) CreateUser(name string) error {
// ... 実装
}
// order_service.go (100行)
package service
type OrderService struct {
db Database
}
func (s *OrderService) CreateOrder(items []Item) error {
// ... 実装
}
// payment_service.go (100行)
package service
type PaymentService struct {
gateway PaymentGateway
}
func (s *PaymentService) ProcessPayment(amount float64) error {
// ... 実装
}
参考リンク
Contents
Assign one responsibility to one function. Do not pack multiple responsibilities into a single function. The same applies to classes (or “struct + methods” in Go) — follow the Single Responsibility Principle.
(1つの関数には1つの責任のみを割り当てる。複数の責任を1つの関数に詰め込んではいけない。クラス(またはGoの「構造体+メソッド」)にも同様に単一責任原則を適用する)
解説
単一責任原則は、ソフトウェア設計の基本原則の一つです。1つの関数が複数の責任を持つと、変更の影響範囲が広がり、テストが困難になります。責任が明確に分離されていれば、変更時に影響を受ける箇所が限定され、バグの混入リスクが減少します。関数やクラスは「変更する理由が1つだけ」であるべきです。
具体例
// 悪い例
func processUser(id string) {
user := fetchUserFromDB(id)
validateUser(user)
sendEmail(user.Email, "Welcome")
logActivity("User processed")
}
// 良い例
func processUser(id string) User {
user := fetchUserFromDB(id)
validateUser(user)
return user
}
func notifyUser(user User) {
sendEmail(user.Email, "Welcome")
}
func logUserActivity(message string) {
logActivity(message)
}
参考リンク