从 Java 的角度看待 Go 的编码规范和性能调优| 青训营笔记
本文最后更新于 701 天前,其中的信息可能已经有所发展或是发生改变。

从 Java 的角度看待 Go 的编码规范和性能调优| 青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第 3 天

前言

本系列文章试图从一名 Java 开发者(有时也会穿插其他语言)的角度窥探 Go 语言,并以注释的方式提及 Go 与 Java 的一些区别,方便 Java 开发者迅速入门 Go 语言。

这是该系列的第三章,将从 Go 的编码规范和性能调优带领读者走近 Go 语言。

Go 高质量编程

编码规范

代码格式

可以使用 gofmt 和 goimports 工具自动格式化代码。对于 GoLand,可以在 Settings - Tools - Actions on Save 中打开 Reformat codeOptimize imports(不出意外的话,它们应当是默认启用的)。也可通过 Ctrl+Alt+L 快捷键格式化代码(其实所有 Jetbrains 的 IDE 都支持使用这个快捷键格式化代码)。

注释

一个好的代码注释应当做到以下几点:

  • 解释代码作用;
  • 解释代码如何做的;
  • 解释代码实现的原因;
  • 解释代码什么情况会出错;
  • 公共符号始终要注释(但是不需要注释实现接口的方法)。

作为 Java 开发者想要吐槽的是,Go 的注释系统实在太简陋了,我认为:

/**
 * A mutable sequence of characters.  This class provides an API compatible
 * with {@code StringBuffer}, but with no guarantee of synchronization.
 * This class is designed for use as a drop-in replacement for
 * {@code StringBuffer} in places where the string buffer was being
 * used by a single thread (as is generally the case).   Where possible,
 * it is recommended that this class be used in preference to
 * {@code StringBuffer} as it will be faster under most implementations.
 *
 * <p>The principal operations on a {@code StringBuilder} are the
 * {@code append} and {@code insert} methods, which are
 * overloaded so as to accept data of any type. Each effectively
 * converts a given datum to a string and then appends or inserts the
 * characters of that string to the string builder. The
 * {@code append} method always adds these characters at the end
 * of the builder; the {@code insert} method adds the characters at
 * a specified point.
 * <p>
 * For example, if {@code z} refers to a string builder object
 * whose current contents are "{@code start}", then
 * the method call {@code z.append("le")} would cause the string
 * builder to contain "{@code startle}", whereas
 * {@code z.insert(4, "le")} would alter the string builder to
 * contain "{@code starlet}".
 * <p>
 * In general, if sb refers to an instance of a {@code StringBuilder},
 * then {@code sb.append(x)} has the same effect as
 * {@code sb.insert(sb.length(), x)}.
 * <p>
 * Every string builder has a capacity. As long as the length of the
 * character sequence contained in the string builder does not exceed
 * the capacity, it is not necessary to allocate a new internal
 * buffer. If the internal buffer overflows, it is automatically made larger.
 *
 * <p>Instances of {@code StringBuilder} are not safe for
 * use by multiple threads. If such synchronization is required then it is
 * recommended that {@link java.lang.StringBuffer} be used.
 *
 * <p>Unless otherwise noted, passing a {@code null} argument to a constructor
 * or method in this class will cause a {@link NullPointerException} to be
 * thrown.
 *
 * @apiNote
 * {@code StringBuilder} implements {@code Comparable} but does not override
 * {@link Object#equals equals}. Thus, the natural ordering of {@code StringBuilder}
 * is inconsistent with equals. Care should be exercised if {@code StringBuilder}
 * objects are used as keys in a {@code SortedMap} or elements in a {@code SortedSet}.
 * See {@link Comparable}, {@link java.util.SortedMap SortedMap}, or
 * {@link java.util.SortedSet SortedSet} for more information.
 *
 * @author      Michael McCloskey
 * @see         java.lang.StringBuffer
 * @see         java.lang.String
 * @since       1.5
 */
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, Comparable<StringBuilder>, CharSequence
{ ...

比:

// A Builder is used to efficiently build a string using Write methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}

从各种意义上讲都更加直观。(当然以上举例并不是想说明 Java 注释写得比 Go 详细,而是想说明 Go 的注释就只是单纯的注释而已,它不支持 Java 的 javadoc 注释那样丰富的富文本和标签支持,这就导致看起来十分模糊)

命名规范

变量
  • 简洁胜于冗长;
  • 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写(例如使用 ServerHTTP 而不是 ServerHttp,使用 XMLHTTPRequest 或是 xmlHTTPRequest 而不是 XmlHttpRequest);
  • 变量距离其被使用的地方越远,则需要携带更多的上下文信息(尤其是全局变量)。

举个例子,在一个经典的三段 for 循环中:

// Bad
for index := 0; index < len(s); index++ {
    // do sth.
}

// Good
for i := 0; i < len(s); i++ {
    // do sth.
}

由于 index 变量作用域仅限于 for 循环内,更长的注释反而对增加阅读的理解没什么作用,所以采用更短的 i 变量。(我个人倒是觉得见仁见智了,虽然如果换我我也会用 i

// Good
func (c *Client) send(req *Request, deadline time.Time)

// Bad
func (c *Client) send(req *Request, t time.Time)

deadline 换成 t 降低了变量名的信息量。前者特指截止时间,而后者仅是代表某个时间。

函数
  • 函数名不应携带包名的上下文信息,因为两者总是成对出现;

  • 函数名应当尽量简短;

  • 当名为 foo 包的某个函数返回类型为 Foo 时,可以省略类型信息而不导致歧义;
  • 当名为 foo 包的某个函数返回类型为 T 而不是 Foo 时,应该在函数名中加入类型信息。
package http

// Bad
func ServeHTTP(I net.Listener, handler Handler) error

// Good
func Serve(I net.Listener, handler Handler) error
  • 只由小写字母组成。不包含大写字母和下划线等字符;
  • 简短并包含一定的上下文信息;
  • 不要和标准库同名。
  • 不使用常用变量名作为包名。例如使用 bufio 而不是 buf
  • 使用单数而不是复数。例如使用 encoding 而不是 encodings
  • 谨慎的使用缩写。例如使用 fmt 在不破坏上下文的情况下比 fotmat 更简短。

总体来说,Go 的包命名规范也体现了 Go 大道至简的风格。

编码规范

控制流程
  • 避免嵌套,保持正常流程清晰;
// Bad
if foo {
    return x
} else {
    return nil
}

// Good
if foo {
    return x
}
return nil
  • 尽量保持正常代码路径为最小缩进;
错误和异常处理
  • 对于简单错误(仅出现一次,在其他地方不需要捕获),优先使用 errors.New 创建匿名变量直接表示;如有格式化需求,使用 fmt.Errorf;
  • fmt.Errorf 中使用 %w 将一个错误关联至错误链中;
  • 使用 errors.Is 判定一个错误为特性错误,比起直接使用 == 的好处是可以判断错误链上的所有错误是否含有特定错误;
  • 使用 errors.As 获取错误链上特定种类的错误;
  • 只有在程序启动阶段发生不可逆转的错误时才使用 panic(类似于 Java java.lang.Error 的地位,但是 Go 可以使用 revover 语句来从 panic 中恢复;

性能优化

  • 使用 Benchmark 进行基准测试;

  • 尽可能为 slice 和 map 预分配内存(通过在 make 时指定容量信息);

  • 注意为切片创建切片不会创建新的底层数组,这可能会导致内存泄漏发生,此时可用 copy 代替 re-slice;
  • 多个字符串拼接时,使用 strings.Builder 比直接使用 + 或使用 bytes.Buffer 更快(这和 Java 倒是十分相似,Java 也推荐使用 StringBuilder 拼接多个字符串;其实他们的底层逻辑都是类似的);
  • 当需要占位符时,可使用空结构体(struct{})代替,其不会占据镇和内存空间;
  • 使用 atomic 包代替锁修改变量;

引用

该文章部分内容来自于以下课程或网页:

  • 字节内部课:高质量编程与性能调优实战

分发

This work is licensed under CC BY-SA 4.0

扫码关注 HikariLan's Blog 微信公众号,及时获取最新博文!


微信公众号图片
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇