首页 > 后端开发 > Golang > 正文

发展原则

王林
发布: 2024-09-03 11:41:29
原创
287 人浏览过

Development Principles

编程是一个不断改进的旅程。随着经验的积累,我们会遇到一些实践和原则,这些实践和原则可以帮助我们完善我们的技术,从而产生更高质量的代码。本文将指导您了解可帮助您成为更好的程序员的关键原则和实践,包括 SOLID 原则、设计模式和编码标准。我们将探讨这些概念如何改进您的开发流程。

关于示例的说明:我选择 Go 作为代码示例是因为它的简单性和可读性 - 它通常被描述为“可执行伪代码”。如果您不熟悉 Go,请不要担心!这是一个很好的学习机会。

当您遇到不熟悉的语法或概念时,请花点时间查找它们。这个发现的过程可以为您的学习体验带来有趣的奖励。请记住,我们正在讨论的原则适用于各种编程语言,因此请重点理解概念而不是 Go 语法的细节。

1. 拥抱编程标准

成为更好的程序员的第一步就是遵守编程标准。标准提供了命名约定、代码组织和文件结构等方面的指南。通过遵循这些约定,您可以确保代码一致且易于阅读,这对于协作和长期维护至关重要。

每种编程语言通常都有自己的一套约定。对于 Go,官方 Go 风格指南中概述了这些内容。学习并接受与您的环境相关的标准非常重要,无论是您团队的惯例还是特定于语言的指南。

让我们看一个示例,了解以下标准如何提高代码可读性:

// Before: Inconsistent styling
func dosomething(x int,y string)string{
if x>10{
return y+"is big"
}else{
return y+"is small"
}}
登录后复制

此代码有几个问题:

  1. 不一致的间距和缩进使代码结构难以理解。
  2. 函数名称不遵循 Go 的驼峰命名约定。
  3. 不必要的 else 子句增加了认知复杂性。

现在,让我们重构此代码以遵循 Go 标准:

// After: Following Go standards
func doSomething(x int, y string) string {
    if x > 10 {
        return y + " is big"
    }
    return y + " is small"
}
登录后复制

在此改进版本中:

  1. 正确的缩进可以清楚地显示代码的结构。
  2. 函数名称遵循 Go 的命名约定。
  3. 删除了 else 子句,简化了逻辑。

这些变化不仅仅与美观有关;还与美观有关。它们显着提高了代码的可读性和可维护性。在团队中工作时,一致应用这些标准可以让每个人更轻松地理解和使用代码库。

即使您的团队没有既定标准,您也可以主动遵循编程社区中广泛接受的约定。随着时间的推移,这种做法将为您的项目带来更具可读性、可维护性的代码。

2.遵循设计原则

编程设计原则是帮助您编写更好代码的指南。这些原则不仅可以应用于代码架构,还可以应用于系统设计,甚至开发过程的某些方面。
设计原则有很多,其中一些在特定情况下更相关。其他的则更通用,例如 KISS(保持简单、愚蠢)或 YAGNI(你不需要它)。
在这些一般原则中,SOLID 原则是最具影响力的原则之一。让我们探索每个原则,重点关注它们如何改进您的代码。

单一责任原则(SRP)

这一原则鼓励您设计具有单一、明确定义目的的组件(函数、类或模块)。当一个组件承担多重职责时,它会变得更难理解、测试和维护。

让我们看一个重构函数以遵守 SRP 的示例:

// Before: A function doing too much
func processOrder(order Order) error {
    // Validate order
    if order.Total <= 0 {
        return errors.New("invalid order total")
    }
    // Save to database
    db.Save(order)
    // Send confirmation email
    sendEmail(order.CustomerEmail, "Order Confirmation", orderDetails(order))
    // Update inventory
    for _, item := range order.Items {
        updateInventory(item.ID, item.Quantity)
    }
    return nil
}
登录后复制

该函数正在执行多个不相关的任务:验证订单、将其保存到数据库、发送电子邮件和更新库存。让我们将其分解为单独的函数,每个函数都有一个职责:

// After: Breaking it down into single responsibilities
func processOrder(order Order) error {
    if err := validateOrder(order); err != nil {
        return err
    }
    if err := saveOrder(order); err != nil {
        return err
    }
    if err := sendOrderConfirmation(order); err != nil {
        return err
    }
    return updateInventoryForOrder(order)
}
登录后复制

现在,让我们实现以下每个功能:

func validateOrder(order Order) error {
    if order.Total <= 0 {
        return errors.New("invalid order total")
    }
    return nil
}

func saveOrder(order Order) error {
    return db.Save(order)
}

func sendOrderConfirmation(order Order) error {
    return sendEmail(order.CustomerEmail, "Order Confirmation", orderDetails(order))
}

func updateInventoryForOrder(order Order) error {
    for _, item := range order.Items {
        if err := updateInventory(item.ID, item.Quantity); err != nil {
            return err
        }
    }
    return nil
}
登录后复制

在此重构版本中:

  • 每个职能都有一个单一、明确的职责。
  • 代码更加模块化,更容易测试。
  • 各个组件可以重复使用或修改,而不会影响其他组件。

相信我,未来的你会感谢你的这种级别的组织。

开闭原则(OCP)

开闭原则建议软件实体应该对扩展开放,对修改关闭。这意味着您应该能够在不更改现有代码的情况下添加新功能。

Liskov Substitution Principle (LSP)

LSP states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. This ensures that inheritance hierarchies are well-designed and maintainable.

Interface Segregation Principle (ISP)

ISP suggests that no code should be forced to depend on methods it does not use. In practice, this often means creating smaller, more focused interfaces.This modularity makes your code easier to manage and test.

Here's an example demonstrating ISP:

// Before: A large interface that many structs only partially implement
type Worker interface {
    DoWork()
    TakeBreak()
    GetPaid()
    FileTicket()
}
登录后复制

This large interface forces implementers to define methods they might not need. Let's break it down into smaller, more focused interfaces:

// After: Smaller, more focused interfaces
type Worker interface {
    DoWork()
}

type BreakTaker interface {
    TakeBreak()
}

type Payable interface {
    GetPaid()
}

type TicketFiler interface {
    FileTicket()
}
登录后复制

Now, structs can implement only the interfaces they need:

type Developer struct{}

func (d Developer) DoWork() {
    fmt.Println("Writing code")
}

func (d Developer) TakeBreak() {
    fmt.Println("Browsing Reddit")
}

func (d Developer) FileTicket() {
    fmt.Println("Creating a Jira ticket")
}

type Contractor struct{}

func (c Contractor) DoWork() {
    fmt.Println("Completing assigned task")
}

func (c Contractor) GetPaid() {
    fmt.Println("Invoicing for work done")
}
登录后复制

This modularity makes your code easier to manage and test. In Go, interfaces are implicitly implemented, which makes this principle particularly easy to apply.

Dependency Inversion Principle (DIP)

DIP promotes the use of abstractions rather than concrete implementations. By depending on interfaces or abstract classes, you decouple your code, making it more flexible and easier to maintain. This also facilitates easier testing by allowing mock implementations.


Applying the SOLID (see what I did there?) principles leads to decoupled, modular code that is easier to maintain, scale, reuse, and test. I’ve found that these principles, while sometimes challenging to apply at first, have consistently led to more robust and flexible codebases.

While these principles are valuable, remember that they are guidelines, not strict rules.There will always be exceptions to the rules, but it’s important to remember that following these principles is a continuous process and not a one-time event. It will take time and effort to develop good habits, but the rewards are well worth it.

3. Utilize Design Patterns

Design patterns provide reusable solutions to common programming problems. They are not rigid implementations but rather templates that can be adapted to fit specific needs. Many design patterns are related to SOLID principles, often aiming to uphold one or more of these principles in their design.

Design patterns are typically categorized into three types:

Creational Patterns

These patterns deal with object creation mechanisms. An example is the Factory Method pattern, which creates objects based on a set of criteria while abstracting the instantiation logic.

Let's look at a simple Factory Method example in Go:

type PaymentMethod interface {
    Pay(amount float64) string
}

type CashPayment struct{}

func (c CashPayment) Pay(amount float64) string {
    return fmt.Sprintf("Paid %.2f using cash", amount)
}

type CreditCardPayment struct{}

func (cc CreditCardPayment) Pay(amount float64) string {
    return fmt.Sprintf("Paid %.2f using credit card", amount)
}
登录后复制

Here, we define a PaymentMethod interface and two concrete implementations. Now, let's create a factory function:

func GetPaymentMethod(method string) (PaymentMethod, error) {
    switch method {
    case "cash":
        return CashPayment{}, nil
    case "credit":
        return CreditCardPayment{}, nil
    default:
        return nil, fmt.Errorf("Payment method %s not supported", method)
    }
}
登录后复制

This factory function creates the appropriate payment method based on the input string. Here's how you might use it:

method, err := GetPaymentMethod("cash")
if err != nil {
    fmt.Println(err)
    return
}
fmt.Println(method.Pay(42.42))
登录后复制

This pattern allows for easy extension of payment methods without modifying existing code.

Structural Patterns

Structural patterns deal with object composition, promoting better interaction between classes. The Adapter pattern, for example, allows incompatible interfaces to work together.

Behavioral Patterns

Behavioral patterns focus on communication between objects. The Observer pattern is a common behavioral pattern that facilitates a publish-subscribe model, enabling objects to react to events.


It's important to note that there are many more design patterns, and some are more relevant in specific contexts. For example, game development might heavily use the Object Pool pattern, while it's less common in web development.

Design patterns help solve recurring problems and create a universal vocabulary among developers. However, don't feel pressured to learn all patterns at once. Instead, familiarize yourself with the concepts, and when facing a new problem, consider reviewing relevant patterns that might offer a solution. Over time, you'll naturally incorporate these patterns into your design process.

4. Practice Good Naming Conventions

Clear naming conventions are crucial for writing readable and maintainable code. This practice is closely related to programming standards and deserves special attention.

Use Descriptive Names

Choose names that clearly describe the purpose of the variable, function, or class. Avoid unnecessary encodings or cryptic abbreviations.

Consider this poorly named function:

// Bad
func calc(a, b int) int {
    return a + b
}
登录后复制

Now, let's improve it with more descriptive names:

// Good
func calculateSum(firstNumber, secondNumber int) int {
    return firstNumber + secondNumber
}
登录后复制

However, be cautious not to go to the extreme with overly long names:

// Too verbose
func calculateSumOfTwoIntegersAndReturnTheResult(firstInteger, secondInteger int) int {
    return firstInteger + secondInteger
}
登录后复制

Balance Clarity and Conciseness

Aim for names that are clear but not overly verbose. Good naming practices make your code self-documenting, reducing the need for excessive comments.

Prefer Good Names to Comments

Often, the need for comments arises from poorly named elements in your code. If you find yourself writing a comment to explain what a piece of code does, consider whether you could rename variables or functions to make the code self-explanatory.

Replace Magic Values with Named Constants

Using named constants instead of hard-coded values clarifies their meaning and helps keep your code consistent.

// Bad
if user.Age >= 18 {
    // Allow access
}

// Good
const LegalAge = 18
if user.Age >= LegalAge {
    // Allow access
}
登录后复制

By following these naming conventions, you'll create code that's easier to read, understand, and maintain.

5. Prioritize Testing

Testing is an essential practice for ensuring that your code behaves as expected. While there are many established opinions on testing methodologies, it's okay to develop an approach that works best for you and your team.

Unit Testing

Unit tests focus on individual modules in isolation. They provide quick feedback on whether specific parts of your code are functioning correctly.

Here's a simple example of a unit test in Go:

func TestCalculateSum(t *testing.T) {
    result := calculateSum(3, 4)
    expected := 7
    if result != expected {
        t.Errorf("calculateSum(3, 4) = %d; want %d", result, expected)
    }
}
登录后复制

Integration Testing

Integration tests examine how different modules work together. They help identify issues that might arise from interactions between various parts of the code.

End-to-End Testing

End-to-end tests simulate user interactions with the entire application. They validate that the system works as a whole, providing a user-centric view of functionality.

Remember, well-tested code is not only more reliable but also easier to refactor and maintain over time. The SOLID principles we discussed earlier can make testing easier by encouraging modular, decoupled code that is simpler to isolate and validate.

6. Take Your Time to Plan and Execute

While it might be tempting to rush through projects, especially when deadlines are tight, thoughtful planning is crucial for long-term success. Rushing can lead to technical debt and future maintenance challenges.

Take the time to carefully consider architectural decisions and plan your approach. Building a solid foundation early on will save time and effort in the long run. However, be cautious not to over-plan—find a balance that works for your project's needs.

Different projects may require varying levels of planning. A small prototype might need minimal upfront design, while a large, complex system could benefit from more extensive planning. The key is to be flexible and adjust your approach based on the project's requirements and constraints.

Conclusion

By following these principles and practices, you can become a better programmer. Focus on writing code that is clear, maintainable, and thoughtfully structured. Remember the words of Martin Fowler: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."

Becoming a better programmer is a continuous process. Don't be discouraged if you don't get everything right immediately. Keep learning, keep practicing, and most importantly, keep coding. With time and dedication, you'll see significant improvements in your skills and the quality of your work.

Here are some resources you might find helpful for further learning:

  • For general programming principles: Programming Principles
  • For understanding the importance of basics: The Basics
  • For a deep dive into software design: The Philosophy of Software Design and its discussion
  • For practical programming advice: The Pragmatic Programmer
  • For improving existing code: Refactoring

Now, armed with these principles, start applying them to your projects. Consider creating a small web API that goes beyond simple CRUD operations, or develop a tool that solves a problem you face in your daily work. Remember, the best way to learn is by doing. Happy coding!

以上是发展原则的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!