1. 高凝集・低結合
概要
- 高凝集: クラスやモジュールの内部では、似たような機能をまとめ、一貫した目的を持たせます
- 低結合: 他のクラスやモジュールとの依存関係を減らします
メリット
- 修正が容易になり、他のモジュールへの影響を最小限に抑えられます
- テストが容易になります
例
高凝集・低結合の悪い例
1つのクラスに異なる機能を詰め込んでいる
type BadExample struct{}
func (b *BadExample) SaveUserToDB(user string) {
// データベースへの保存
}
func (b *BadExample) SendEmail(email string) {
// メール送信
}
改善例
異なる機能を異なるクラスに分割
type UserRepository struct{}
func (r *UserRepository) SaveUserToDB(user string) {
// データベースへの保存
}
type EmailService struct{}
func (s *EmailService) SendEmail(email string) {
// メール送信
}
2. KISS (Keep It Simple, Stupid)
概要
- シンプルで分かりやすいコードを書くことを目指します
- 不要な複雑さを避け、誰でも理解できる設計にします
メリット
- 保守性が向上し、バグを減らせます
例
悪い例: 複雑すぎるロジック
func Calculate(value int) int {
if value < 10 {
return value * 2
} else if value >= 10 && value < 20 {
return value * 3
} else {
return value * 4
}
}
改善例: シンプルで直感的なロジック
func Calculate(value int) int {
multiplier := 2
if value >= 10 {
multiplier = 3
}
if value >= 20 {
multiplier = 4
}
return value * multiplier
}
3. DRY (Don't Repeat Yourself)
概要
- 同じコードやロジックを複数箇所で繰り返さないようにします
- 再利用可能なコードに抽象化します
メリット
- 一箇所を修正すれば全体に反映されるため、保守が容易になります
例
悪い例: 重複したコード
func SendWelcomeEmail(email string) {
fmt.Printf("Sending email to: %s\n", email)
}
func SendPasswordResetEmail(email string) {
fmt.Printf("Sending email to: %s\n", email)
}
改善例: 共通の処理を抽出
func SendEmail(email string, message string) {
fmt.Printf("Sending email to: %s with message: %s\n", email, message)
}
func SendWelcomeEmail(email string) {
SendEmail(email, "Welcome!")
}
func SendPasswordResetEmail(email string) {
SendEmail(email, "Reset your password")
}
4. YAGNI (You Aren't Gonna Need It)
概要
- 必要になるか分からない機能を予め実装しない
- 必要性が明確になるまで作業を延期します
メリット
- 不必要なコードを減らし、開発コストを削減できます
例
悪い例: 必要になるか分からない機能の実装
type PaymentProcessor struct{}
func (p *PaymentProcessor) ProcessCreditCard() {
// 実装
}
func (p *PaymentProcessor) ProcessCryptocurrency() {
// 実装(必要になるか不明だが追加)
}
改善例: 必要になったときに追加
type PaymentProcessor struct{}
func (p *PaymentProcessor) ProcessCreditCard() {
// 実装
}
5. SOLID原則
S: 単一責任の原則 (Single Responsibility Principle)
- 1つのクラスが1つの責任(目的)のみを持つべき
-
例:
User
クラスはユーザー管理のみを担当し、メール送信など他の責任を持たない
悪い例: 複数の責任を持つ構造体
type User struct {
Name string
Email string
}
func (u *User) SaveToDB() {
fmt.Println("Saving user to database")
}
func (u *User) SendWelcomeEmail() {
fmt.Println("Sending welcome email")
}
改善例: 責任を分離
type User struct {
Name string
Email string
}
type UserRepository struct{}
func (r *UserRepository) SaveToDB(user User) {
fmt.Println("Saving user to database:", user.Name)
}
type EmailService struct{}
func (e *EmailService) SendWelcomeEmail(user User) {
fmt.Println("Sending welcome email to:", user.Email)
}
// 利用例
func main() {
user := User{Name: "John", Email: "john@example.com"}
repo := UserRepository{}
emailService := EmailService{}
repo.SaveToDB(user)
emailService.SendWelcomeEmail(user)
}
O: 開放・閉鎖の原則 (Open/Closed Principle)
- コードは拡張に対して開かれ、変更に対して閉じているべき
- 例: 新しい支払い方法を追加する場合でも、既存のコードを変更せずに対応できる設計にする
悪い例: 支払い方法を追加する際に既存コードを変更
type PaymentProcessor struct{}
func (p *PaymentProcessor) ProcessPayment(method string) {
if method == "credit_card" {
fmt.Println("Processing credit card payment")
} else if method == "paypal" {
fmt.Println("Processing PayPal payment")
} else {
fmt.Println("Unknown payment method")
}
}
改善例: 新しい支払い方法を追加しても既存コードを変更しない
type PaymentMethod interface {
Pay()
}
type CreditCardPayment struct{}
func (c CreditCardPayment) Pay() {
fmt.Println("Processing credit card payment")
}
type PayPalPayment struct{}
func (p PayPalPayment) Pay() {
fmt.Println("Processing PayPal payment")
}
func ProcessPayment(method PaymentMethod) {
method.Pay()
}
// 利用例
func main() {
creditCard := CreditCardPayment{}
paypal := PayPalPayment{}
ProcessPayment(creditCard)
ProcessPayment(paypal)
}
L: リスコフ置換の原則 (Liskov Substitution Principle)
- サブクラスはスーパークラスの機能を壊さずに置き換え可能であるべき
-
例:
Rectangle
とSquare
を使うとき、Square
がRectangle
の機能を損なわない
悪い例: サブタイプがスーパークラスの期待を満たさない
type Rectangle struct {
Width int
Height int
}
func (r Rectangle) Area() int {
return r.Width * r.Height
}
type Square struct {
Side int
}
func (s Square) Area() int {
// スーパークラスの期待を破壊
return 4 * s.Side
}
改善例: サブタイプがスーパークラスとして動作
type Shape interface {
Area() int
}
type Rectangle struct {
Width int
Height int
}
func (r Rectangle) Area() int {
return r.Width * r.Height
}
type Square struct {
Side int
}
func (s Square) Area() int {
return s.Side * s.Side
}
// 利用例
func main() {
var shapes []Shape
shapes = append(shapes, Rectangle{Width: 10, Height: 5})
shapes = append(shapes, Square{Side: 4})
for _, shape := range shapes {
fmt.Println("Area:", shape.Area())
}
}
I: インターフェース分離の原則 (Interface Segregation Principle)
- 1つの大きなインターフェースを小さなインターフェースに分割する
- 例: 不要な機能を含まないインターフェースを提供
悪い例: すべての機能を1つのインターフェースにまとめる
type Worker interface {
Work()
Eat()
}
type Robot struct{}
func (r Robot) Work() {
fmt.Println("Robot is working")
}
// RobotはEatメソッドを必要としないが、実装を強いられる
func (r Robot) Eat() {
fmt.Println("Robot does not eat")
}
改善例: インターフェースを分割
type Worker interface {
Work()
}
type Eater interface {
Eat()
}
type Human struct{}
func (h Human) Work() {
fmt.Println("Human is working")
}
func (h Human) Eat() {
fmt.Println("Human is eating")
}
type Robot struct{}
func (r Robot) Work() {
fmt.Println("Robot is working")
}
// 利用例
func main() {
var worker Worker
worker = Human{}
worker.Work()
worker = Robot{}
worker.Work()
}
D: 依存性逆転の原則 (Dependency Inversion Principle)
- 高レベルモジュールは低レベルモジュールに依存してはならず、抽象化に依存すべき
悪い例: 高レベルモジュールが低レベルモジュールに依存
type MySQLDatabase struct{}
func (db MySQLDatabase) Connect() {
fmt.Println("Connecting to MySQL database")
}
type Application struct {
Database MySQLDatabase
}
func (app Application) Start() {
app.Database.Connect()
}
改善例: 抽象化を使用して依存を逆転
type Database interface {
Connect()
}
type MySQLDatabase struct{}
func (db MySQLDatabase) Connect() {
fmt.Println("Connecting to MySQL database")
}
type PostgreSQLDatabase struct{}
func (db PostgreSQLDatabase) Connect() {
fmt.Println("Connecting to PostgreSQL database")
}
type Application struct {
Database Database
}
func (app Application) Start() {
app.Database.Connect()
}
// 利用例
func main() {
mysql := MySQLDatabase{}
postgres := PostgreSQLDatabase{}
app := Application{Database: mysql}
app.Start()
app = Application{Database: postgres}
app.Start()
}