0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goで学ぶコンピューターサイエンスAdvent Calendar 2024

Day 24

【Goで学ぶコンピューターサイエンス】設計と開発の基本原則

Posted at

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)

  • サブクラスはスーパークラスの機能を壊さずに置き換え可能であるべき
  • : RectangleSquareを使うとき、SquareRectangleの機能を損なわない

悪い例: サブタイプがスーパークラスの期待を満たさない

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()
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?