Go 哲学和最佳编程实践系列(六)

234 阅读15分钟

1 格式化和代码风格:编写可读且可维护的 Go 代码

1.1 GoFmt: Your Friend in Formatting Consistency

Go Fmt,即Go Formatter,是 Go 生态系统中一款功能强大的工具,可帮助开发人员保持代码格式的一致性。它根据一组预定义的样式指南自动格式化 Go 源代码,确保项目中的所有代码都遵循相同的约定。

在本文中,我们将探讨 GoFmt 的重要性、它的好处以及有效使用它的最佳实践。

1.1.1 Importance of GoFmt

一致的代码格式对于软件开发项目中的可读性、可维护性和协作至关重要。在强调代码可读性的 Go 中,遵守一致的格式样式更为重要。GoFmt 可确保项目中的所有代码都具有一致的格式,无论各个开发人员的偏好或习惯如何。这种一致性可提高代码理解能力,减少认知开销,并增强代码库内的凝聚力。

1.1.2 Benefits of GoFmt

  1. Consistency 一致性:GoFmt 在整个代码库中强制执行一致的编码风格,使开发人员更容易阅读和理解彼此的代码。
  2. Readability 可读性:通过根据预定义样式格式化代码,GoFmt 提高了代码的可读性,从而更容易发现错误、识别模式和跟踪控制流。
  3. Maintainablility 可维护性:格式一致的代码更易于维护和修改。开发人员可以快速浏览和理解代码,从而更快地修复错误、增强功能和重构。
  4. Collaboration 协作:GoFmt 消除了对代码格式偏好的争论,从而促进了团队成员之间的协作。开发人员可以专注于编写代码,而不是争论格式约定。

1.1.3 Using GoFmt

GoFmt 是 Go 发行版中的一个命令行工具。可以使用 gofmt 命令来调用它,命令后跟要格式化的文件或目录。默认情况下,GoFmt 会将格式化后的源代码打印到标准输出。要就地格式化文件,请使用 -w 标志。

gofmt -w main.go

另外,GoFmt 也可以集成到文本编辑器和集成开发环境中,让开发人员在编写代码时自动格式化代码。许多流行的编辑器,如 Visual Studio Code 和 IntelliJ IDEA,都内置支持了 GoFmt 集成的插件或扩展。

1.1.4 Best Practices for Using GoFmt

  1. Integrate Into Workflow 集成到工作流中:将 GoFmt 纳入您的开发工作流,确保在提交更改之前,代码格式保持一致。使用预提交钩子或 CI/CD 管道自动执行代码格式标准。
  2. Configure Editor Integration 配置编辑器集成:配置文本编辑器或集成开发环境,使用 GoFmt 自动格式化代码。这可确保代码在编写过程中格式保持一致,最大限度地减少人工干预的需要。
  3. Use Editor Plugins 使用编辑器插件: 利用可与 GoFmt 无缝集成的编辑器插件或扩展。这些插件可在保存代码时自动格式化代码,或提供键盘快捷键以按需格式化代码。
  4. Respect Project Conventions 尊重项目惯例:尊重项目或团队制定的格式约定。如果是在现有的代码库上工作,则应遵守现有的格式风格,以保持一致性。
  5. Understand Formatting Rules 理解格式规则:熟悉 GoFmt 执行的格式化规则。了解这些规则后,您就可以编写从一开始就遵守样式指南的代码,从而减少日后对格式进行大量修改的需要。

GoFmt 是一个在 Go 项目中保持代码格式一致性的重要工具。通过自动执行预定义的格式样式,GoFmt 提高了代码的可读性、可维护性以及团队成员之间的协作。通过将 GoFmt 集成到开发工作流中、配置编辑器集成和遵守项目约定,开发人员可以确保他们的代码格式一致,并遵守既定的样式准则。最终,GoFmt 可促进良好的编码实践,提高 Go 应用程序的整体质量。

2 命名规范:为变量、函数和包选择清晰且描述性的名称

命名约定在用 Go 编写干净、可读且可维护的代码方面起着至关重要的作用。为变量、函数和包选择清晰且具有描述性的名称可以增强代码理解力减少认知负担促进开发人员之间的协作。在本文中,我们将探讨 Go 中命名约定的重要性,以及有效选择名称的最佳实践和编程风格。

推荐阅读:juejin.cn/post/704412…

2.1 Importance of Naming Conventions

命名约定是代码库内沟通的基础。精心挑选的名称可以提供上下文、传达意图并记录代码元素的用途,使开发人员更容易理解、浏览和修改代码。在 Go 中,简单性和可读性受到重视,因此遵守清晰一致的命名约定尤为重要。

2.2 Best Practices for Naming Conventions

  1. Use Descriptive Names 使用描述性名称: 选择准确描述变量、函数和包的用途或功能的名称。名称应具有自解释性和意义,从而减少对额外注释或文档的需求。
// Good: descriptive names
var currentUserID int
func CalculateTotalPrice(itemList []Item) float64
  1. Follow Conventions 遵循规范:遵守 Go 社区和项目内既定的命名规范。命名风格的一致性可提高可读性并减少开发人员的认知开销。
  2. Use CamelCase 使用驼峰式命名法:在 Go 中,使用驼峰式命名法来命名变量、函数和方法。第一个单词以小写字母开头,其余单词大写。避免使用下划线或大小写混合。
// Good: camelCase
var totalAmount int
func calculateTaxAmount(subtotal int)
  1. Avoid Abbreviations 避免使用缩写:避免使用缩写或首字母缩略词,除非它们在代码库的上下文中被广泛理解。为了简洁起见,最好使用描述性名称,而不是缩写词。
// Bad: abbreviations
var usrID int
func calcTaxAmt(subt int) int
  1. Be Consistent 保持一致: 在整个代码库中保持命名约定的一致性。对相似的实体使用相同的命名样式,并避免在同一范围内混合使用不同的样式。
  2. Use Nouns for Variables 使用名词作为变量:使用名词或名词短语来命名变量,表示它们保存或表示的数据。选择准确描述变量用途或内容的名称。
// Good: noun-based names
var userID int
var orderTotal float64
  1. Use Verbs for Functions and Methods 使用动词表示功能和方法:使用动词或动词短语来命名函数和方法,表示它们执行的操作。选择能够反映所执行的行为或操作的名称。
// Good: verb-based names
func calculateTotalPrice(itemList []Item) float64
func getUserByID(userID int) User
  1. Avoid Generic Names 避免使用通用名称: 避免使用“数据”、“临时”或“值”等通用名称,因为这些名称没有太多背景或含义。相反,应选择能够传达有关实体目的或内容的具体信息的名称。

2.3 Package Naming Conventions

  1. Use Lowercase Letters 使用小写字母:Go 中的软件包名称应为小写,不带下划线或大小写混合。使用简短、有意义的名称来反映软件包的用途或领域。
  2. Avoid Naming Redundancy 避免命名冗余:避免在包中的类型或变量名称中包含包名称。选择简洁且能反映实体用途且不含多余限定符的名称。
// Good: clear and descriptive names
var currentUserID int
var orderTotalAmount float64

func calculateTotalPrice(itemList []Item) float64 {
	// implementtation
}

type UserService struct {
	// fields...
}

func (s *UserService) getUserByID(userID int) User {
	// implementation...
}

命名约定对于用 Go 编写干净、可读且可维护的代码至关重要。通过为变量、函数和包选择清晰且描述性的名称,开发人员可以增强代码理解力、减少认知负荷并促进代码库内的协作。通过遵循命名约定的最佳实践和编程风格,开发人员可以确保他们的代码一致、自文档化且易于理解。最终,清晰且描述性的名称有助于提高 Go 应用程序的整体质量和可维护性。

3 代码注释:在不杂乱的情况下添加上下文

代码注释在提供代码库中的上下文、解释和文档方面起着至关重要的作用。如果使用得当,注释可以增强代码理解、方便维护并促进开发人员之间的协作。在重视简单性和可读性的 Go 中,编写清晰简洁的注释尤为重要。

在本文中,我们将探讨 Go 中代码注释的重要性,以及有效编写注释的最佳实践和注释风格。

3.1 Importance of Code Comments

  1. Explanation 注释:注释为代码提供了解释和背景,帮助开发人员理解实施决策背后的目的、行为和理由。
  2. Documentation 文档:注释记录使用情况、接口和期望,使开发人员更容易使用和集成代码组件到更大的系统中。
  3. Clarification 澄清:注释阐明了复杂或不直观的代码,帮助开发人员驾驭和理解复杂的算法、逻辑或业务规则。
  4. Maintenance 可维护性:注释通过提供对代码预期功能的了解并突出显示可能需要注意的区域,有助于维护和调试。

3.2 Best Practices for Code Comments

  1. Be Clear and Concise 清晰简洁:撰写清晰、简洁、切中要点的注释。避免不必要的冗长或过于详细的解释,因为这些会使代码混乱并影响其可读性。
  2. Focus on Why, Not What 关注为什么,而不是做什么(代码提供):不要仅仅描述代码的作用,而要重点解释代码为什么这样做。深入了解实施选择背后的理由、意图或动机。
  3. Avoid Redundancy 避免冗余:避免使用冗余或显而易见的注释,这些注释只是重复代码本身已经很明显的内容。注释应该通过提供额外的背景信息或见解来增加价值。
  4. Update Comments with Code Changes 根据代码更改更新注释:保持注释与代码更改同步。修改或重构代码时,确保修改相关注释以反映更新的功能或行为。
  5. Use Complete Sentences 使用完整的句子:用完整的句子来写评论,并注意语法、标点和大小写。清晰且结构良好的评论可提高可读性和理解力。
  6. Use Imperative Form for Directives 使用命令式的指令形式:对于提供指令、指示或行动建议的评论,请使用命令式。这有助于传达一种紧迫感或重要性。

3.3 Types of Comments in Go

  1. Documentation Comments 文档注释:文档注释以 /* ... */的形式书写,用于生成包、函数、类型和变量的文档。它们应该以完整的句子书写,并对导出的实体提供详细的解释。
// CalculateTotalPrice calculates the total price of items in the list.
func CalculateTotalPrice(itemList []Item) float64 {
	// Implementation...
}
  1. Inline Comments 内联注释:行内注释与代码写在同一行,用于对特定语句或表达式提供简短的解释或说明。应谨慎使用行内注释,并且仅在需要提供附加上下文时使用。
var totalAmount float64 // totalAmount stores the calculated total amount
  1. Block Comments 代码块注释:块注释使用 /* ... */书写,用于较长的解释或临时注释掉大段代码。应谨慎使用,避免用于内联文档。
/*
This function calculates the total price of items in the list.
It iterates over each item and sums up their prices.
*/

3.4 Example

// CalculateTotalPrice calculates the total price of items in the list.
func CalculateTotalPrice(itemList []Item) float64 {
	total := 0.0 // total stores the calculated total price
	for _, item := range itemList {
		total += item.Price
	}
	return total
}

代码注释对于在代码库中提供上下文、解释和文档至关重要。通过遵循在 Go 中编写注释的最佳实践和编程风格,开发人员可以增强代码理解、促进维护并促进团队成员之间的协作。无论是为导出的实体编写文档注释、提供内联解释,还是使用块注释进行较长的解释,清晰简洁的注释都有助于提高 Go 应用程序的整体可读性和可维护性。最终,有效的代码注释是开发人员理解、使用和维护代码的宝贵辅助工具。

5 Best Practices for Clean and Readable Go Code

编写干净易读的代码对于维护健康且可持续的 Go 代码库至关重要。干净的代码可以提高代码理解度、减少错误并增强开发人员之间的协作。

5.1 Use Descriptive Variable and Function Names

使用描述性变量和函数名称

为变量、函数和类型选择清晰且具有描述性的名称。描述性名称可提高代码的可读性并使代码具有自文档性。

// Good: descriptive names
var currentUserID int
func CalculateTotalPrice(itemList []Item) float64 {}

5.2 Keep Functions Short and Focused

Write functions that do one thing and do it well. Keep functions short and focused, adhering to the Single Responsibility Priciple (SRP).

编写只做一件事且做得很好的函数。遵守 "单一职责原则"(SRP),使函数简短且重点突出。

// Good: short and focused function
func CalculateTotalPrice(itemList []Item) float64 {}

5.3 Favor Composition Over Inheritance 组合优于继承

优先使用组合而不是继承来创建可重用和可组合的代码。将功能分解为更小的可组合单元,然后将它们组合起来以构建更大的组件。

type UserService struct {
	UserRepository UserRepository
}

func (s *UserService) GetUserByID(userID int) User {
	return s.UserRepository.GetByID(uesrID)
}

5.4 Use Constants for Magic Values

避免在代码中使用魔法值。相反,为具有特殊意义的值定义常量。常量可提高代码的可读性和可维护性。

const DefaultPageSize = 10
func GetUsers(page int) ([]User, error) {
	// Implementation...
}

5.5 Handle Errors Gracefully

在整个代码库中以优雅且一致的方式处理错误。使用惯用的错误处理技术,例如将错误作为值返回并在函数调用后立即检查错误。

func CalculateTotalPrice(itemList []Item) (float64, error) {
	// implementation...
	if err != nil {
		return 0, err
	}
}

5.6 Avoid Nested Control Structures 避免嵌套控制结构

避免控制结构的深度嵌套,如 if 语句和循环。使用提前返回来处理错误条件,并使代码结构扁平化。

对于需要 ifelse 判断的编程逻辑,先处理可以直接返回的部分,使代码主体逻辑清晰。

func CalculateTotalPrice(itemList []Item) float64 {
	if len(itemList) == 0 {
		return 0
	}
	// calculation logic...
}

5.7 Use Named Return Values for Clarity 使用命名返回值以提高清晰度

使用命名的返回值可以提高函数的清晰度,尤其是对于具有多个返回值的函数。命名的返回值充当自文档代码。

func Divide(dividened, divisor float64) (quotient, remainder float64) {
	quotient = dividend / divisor
	remainder = dividend % divisor
	return quotient, remainder
}

命名返回值不是洪水猛兽,在多个返回值情况下,如果使用命名返回值可以帮助理解那就放心用!某种意义上好的命名就等于好的注释,注释应该专注于 why 即代码为什么这样写,而不是 how 即代码如何写。how 部分是你的读者通过阅读代码得到的,但是 why 是你自己的编程思路。如果读者读完代码也无法得知这段代码在做什么,即使这段代码运用了高级的语法,那毫无疑问这段代码也是垃圾代码。

每个程序员都可以写代码,而好的程序员可以写出人人都能够理解的代码,即每个程序员都应该追求的那像诗一样的代码。很多时候我们无法为包、函数、变量提供好的命名,这并不是我的实践经验问题,而是我们语言级别功底的欠缺,多读读文言文、多读读诗,从古人寥寥数句或者寥寥数字中体会文字的魅力。等到语言功底达到一定境界,你的命名就会从更抽象、更深层次去思考,那时候你就可以使用非常简短精炼的单词表达最恰当的意思了。当然那时候你更多时间不是在 coding 上,而是在大量的信息输入上。

5.8 编写符合语言习惯的代码

遵循 Go 语言的编程风格和最佳实践。熟悉 Go 语言规范和标准库,以便编写符合既定惯例的代码。

5.9 Comment When Necessary 必要时给出注释

  1. 算法
  2. 实现思路(why)
  3. 比较难理解的逻辑

写注释来为复杂或不明显的代码提供上下文、解释和文档。谨慎使用注释,重点解释为什么,而不是什么。

// CalculateTotalPrice calculates the total price of items in the list.
func CalculateTotalPrice(itemList []Item) float64 {
	// implementation...
}

5.10  Format Code Consistently 一致的格式代码

使用 gofmt 或编辑器插件,根据 Go 风格指南统一代码格式。统一的代码格式可提高代码的可读性,并促进开发人员之间的协作。

总结

编写简洁、可读的 Go 代码对于维护健康、可持续的代码库至关重要。通过遵循最佳实践,如使用描述性名称、保持函数简短且重点突出、优雅地处理错误以及遵循语言编程风格,开发人员可以编写易于理解、维护和协作的代码。通过优先考虑可读性和简洁性,开发人员可以创建出健壮、可靠和可扩展的代码。归根结底,简洁易读的代码有助于提高 Go 应用程序的整体质量和可维护性。

第六系列完。