随记_2021-04-05


清明假期最后一天陪家人来了一次清明一日游,早起给儿子去法喜寺求福,然后去老头儿吃了午饭,吃完以后去城市阳台转了转,回家小憩一会之后带着儿子去游泳课。用老婆的话来说“痛并快乐的一天”,晚上虽然累,还是想把这一天零零碎碎的想法记录下来。

  • 原来法喜寺又叫做法喜讲寺,也叫上天竺,在越国就有了
  • 今天看到了法喜寺有一个很大的牌匾,大概的意思是宗教山之首,我感慨了一下,貌似有点冒犯神明
  • 今天在法喜寺参拜的时候,虽然去之初是有目的的,但是当我闭上眼睛参拜的时候,一时间忘记自己应该怎么跟佛祖对话表达自己的愿望,不知道是心诚还是不诚。参拜的时候在想佛祖的存在虚无缥缈,众生所求是什么呢?心安、名利、抑或是寄托,我愿意相信有佛祖这样的存在,只是芸芸众生之一的我大概率是不会遇到这样的存在,所以我所求是什么?在参拜的时候,我在想应该会有拖着病体前来祈求的人,可是病痛真的会少吗?或者心理觉得好过一点,其实并没有真正的变好
  • 城市阳台的游玩充分释放了儿子的活力,难得的亲子时间,家人脸上散开的笑容让我感受到了家的温馨,脑海中突然冒出了一段话”个人性格和经历造成了我自身存在的一些问题,不善沟通、敏感偏执,但是幸运的是家人让我学会了爱和被爱“
  • 剩下的,好像老婆对自己的身材有非常大的不满,我打算找找减肥计划打印出俩给她,她很懒估计肯定不会去打印一个什么减肥计划,至于减肥计划的坚持实施只能留到后面再想办法

P.S. 我很好奇为什么老婆的膀胱如此强大,在外面玩的时候我去洗手间的频率比她高得多,难道是肾虚。。。

谈谈最近看到的一本书


最近看了一本书,我觉得是一本很神奇的书,书名《鹅鹅鹅》,作者叫二冬。这本书大概是长下面这样的:

刚开始读这本书的时候,有点平淡如水的感觉,就好像刚吃了一顿美味的“麻辣烫”之后再喝粥,这其中味道的寡淡不言而喻。由于是通勤的时候随手拿起来的,没有很强的目的性来读这本书,不知不觉也就这么看下去了,就像喝水一样就这么喝着喝着。两天之后回过头来看发现自己随着作者带入到他书中描述的生活中了,大鹅、摩托车、蜱虫、狗、山。没有大道理,有一些生活感悟也是蜻蜓点水般带过。之所以说这本书神奇恰恰就是这里,我居然就这么沉浸其中了,愿意看读下去不觉得乏味,细细想来应该是这本书的字里行间充斥生活气息,少了微信、头条这类媒体上泛滥的焦虑感。

我开始对作者很好奇,不过还没有时间去对作者做一些了解,不过我敢肯定,他的文风是我喜欢的,如果我坚持写作的话,我希望自己也可以这样。

Golang中的SOLID原则


SOLID原则是2002年提出的,关于这个原则的描述和应用大多都是用Java代码作为示例,对于在SOLID原则之后十多年才被创造出来的Golang跟Java这类古老的语言还是有明显的差异的,所以有必要看看如何基于SOLID原则设计Golang的程序。

单一职责原则

A class should have one, and only one, reason to change.

单一职责最大的好处是最小的代码量达成修改目标,提到最小修改代码量就是涉及到耦合和内聚问题。Go里面没有class的概念,在Go中所有的代码都在一个package中,通过import使两个package建立源码级别的耦合,因而从粗粒度上来看Go的单一职责发生在package,当然从函数和方法的角度也会涉及到单一职责问题,这个问题是各种语言都会碰到的。Go标准库中的一些优秀 package 示例:

  • net/http – 提供 http 客户端和服务端
  • os/exec – 执行外部命令
  • encoding/json – 实现 JSON 文档的编码和解码

开放/封闭原则

Software entities should be open for extension, but closed for modification.

如下面的代码所示,我们有一个go类型A ,有一个字段year和一个方法Greet。我们有第二种类型B,它嵌入了一个A,因为A嵌入,因此调用者看到 B 的方法覆盖了A的方法。因为A作为字段嵌入B ,B可以提供自己的Greet方法,掩盖了A的Greet方法。但嵌入不仅适用于方法,还可以访问嵌入类型的字段。如您所见,因为A和B都在同一个包中定义,所以B可以访问A的私有year字段,就像在B中声明一样。因此嵌入是一个强大的工具,允许go的类型对扩展开放。

package main

type A struct {
        year int
}

func (a A) Greet() { fmt.Println("Hello Golang", a.year) }

type B struct {
        A
}

func (b B) Greet() { fmt.Println("Welcome to Golang", b.year) }

func main() {
        var a A
        a.year = 2021
        var b B
        b.year = 2021
        a.Greet() // Hello Golang 2021
        b.Greet() // Welcome to Golang 2021
}

里氏替换原则

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

在基于类的语言中,里氏替换原则通常被解释为,具有各种具体子类型的抽象基类的规范,但是go没有类和继承,所以没有办法根据抽象类层次结构实现替换,go中需要实现替换的是接口(即interface),这一点深刻影响了go的变成范式:go的类型(struct)不需要指定它们实现特定接口(interface)而是任何类型都可以实现接口,只要它具有的函数签名与接口声明的方法匹配即可

如下代码所示,crypto.randos.File都实现了io.Reader接口,分别操作/dev/random设备和磁盘文件:

// io.Reader
type Reader interface {
        // Read reads up to len(buf) bytes into buf.
        Read(buf []byte) (n int, err error)
}

// crypto.rand.Reader
type devReader struct {
    name string
    f    io.Reader
    mu   sync.Mutex
    used int32 // atomic; whether this devReader has been used
}
func (r *devReader) Read(b []byte) (n int, err error) {
    if atomic.CompareAndSwapInt32(&r.used, 0, 1) {
        // First use of randomness. Start timer to warn about
        // being blocked on entropy not being available.
        t := time.AfterFunc(60*time.Second, warnBlocked)
        defer t.Stop()
    }
    if altGetRandom != nil && r.name == urandomDevice && altGetRandom(b) {
        return len(b), nil
    }
    r.mu.Lock()
    defer r.mu.Unlock()
    if r.f == nil {
        f, err := os.Open(r.name)
        if f == nil {
            return 0, err
        }
        if runtime.GOOS == "plan9" {
            r.f = f
        } else {
            r.f = bufio.NewReader(hideAgainReader{f})
        }
    }
    return r.f.Read(b)
}

// os.File
type File struct {
    *file // os specific
}
func (f *File) Read(b []byte) (n int, err error) {
    if err := f.checkValid("read"); err != nil {
        return 0, err
    }
    n, e := f.read(b)
    return n, f.wrapErr("read", e)
}

接口隔离原则

Clients should not be forced to depend on methods they do not use.

接口隔离原则简单来说就是建立单一的接口, 不要建立臃肿庞大的接口。也就是接口尽量细化,同时接口中的方法尽量少,保持接口纯洁性。go的标准库io里面定义了大量的interface,每个interface的函数都不多,由于interface都比较小,所以他们能够方便的互相组合,比如ReadWriter是有Reader和Writer组成的。

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered. Even if Read
// returns n < len(p), it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read. It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil. The next Read should
// return 0, EOF.
//
// Callers should always process the n > 0 bytes returned before
// considering the error err. Doing so correctly handles I/O errors
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
//
// Implementations of Read are discouraged from returning a
// zero byte count with a nil error, except when len(p) == 0.
// Callers should treat a return of 0 and nil as indicating that
// nothing happened; in particular it does not indicate EOF.
//
// Implementations must not retain p.
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Closer is the interface that wraps the basic Close method.
//
// The behavior of Close after the first call is undefined.
// Specific implementations may document their own behavior.
type Closer interface {
    Close() error
}

// Seeker is the interface that wraps the basic Seek method.
//
// Seek sets the offset for the next Read or Write to offset,
// interpreted according to whence:
// SeekStart means relative to the start of the file,
// SeekCurrent means relative to the current offset, and
// SeekEnd means relative to the end.
// Seek returns the new offset relative to the start of the
// file and an error, if any.
//
// Seeking to an offset before the start of the file is an error.
// Seeking to any positive offset is legal, but the behavior of subsequent
// I/O operations on the underlying object is implementation-dependent.
type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
    Reader
    Writer
}

// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
    Reader
    Closer
}

// WriteCloser is the interface that groups the basic Write and Close methods.
type WriteCloser interface {
    Writer
    Closer
}

// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// ReadSeeker is the interface that groups the basic Read and Seek methods.
type ReadSeeker interface {
    Reader
    Seeker
}

// WriteSeeker is the interface that groups the basic Write and Seek methods.
type WriteSeeker interface {
    Writer
    Seeker
}

// ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods.
type ReadWriteSeeker interface {
    Reader
    Writer
    Seeker
}

依赖倒置原则

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

依赖调换

依赖倒置其实就是变换依赖方和被依赖方的位置:

  • 高层模块不应该依赖底层模块,都应该依赖于抽象
  • 抽象不依赖具体实现
  • 具体实现依赖抽象

如下图所示,对象A依赖对象B,经过依赖倒置之后对象A依赖变成了抽象A(interfaceA),然后对象B实现了抽象A。

package A

type B_Abstract interface {
    GetB() int
}

type A struct {
    Va int
}

func (a *A) Add(b B_Abstract) int {
    return b.GetB() + a.a
}
package B

type B struct {
    Vb int
}

func(b *B) GetB() int {
    return b.b
}
package main

import (
    "A"
    "B"
)

func main() {
    a := A.A{Va:1}
    b := B.B{Vb:1}
    a.Add(b)
}

依赖管理

如果go的项目已经遵守了前面的4个原则,那么代码应该已经被分解到不同的package中了,每个package都有一个明确定义的责任或目的。项目中代码应该根据接口描述其依赖关系,并且应该考虑这些接口以仅描述这些函数所需的行为。除此之外,还需要注意项目的依赖关系即项目的import graph结构,在go中import graph必须是非循环的。不遵守这种非循环要求将导致编译失败,但更为严重地是它代表设计中存在严重错误。

在所有条件相同的情况下,精心设计的go程序的import graph应该是宽的,相对平坦的,而不是高而窄的。 如果你有一个package,其函数无法在不借助另一个package的情况下运行,那么这或许表明代码没有很好地沿pakcage边界分解。

依赖倒置原则鼓励将特定的责任,沿着import graph尽可能的推向更高层级,推给main package或顶级处理程序,留下较低级别的代码来处理抽象接口。

总结来讲,如果按照SOLID原则来安排go的项目,cycle import等问题可以迎刃而解。

Clean Code Rules


Clean Code Rules

General rules

  • Follow standard conventions.
  • Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  • Boy scout rule. Leave the campground cleaner than you found it.
  • Always find the root cause. Always look for the root cause of a problem.

Design rules

  • Keep configurable data at high levels.
  • Prefer polymorphism to if/else or switch/case.
  • Separate multi-threading code.
  • Prevent over-configurability.
  • Use dependency injection.
  • Follow the Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  • Be consistent. If you do something a certain way, do all similar things in the same way.
  • Use explanatory variables.
  • Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  • Prefer dedicated value objects to a primitive types.
  • Avoid logical dependency. Don’t write methods that work correctly depending on something else in the same class.
  • Avoid negative conditionals.

Names rules

  • Choose descriptive and unambiguous names.
  • Make a meaningful distinction.
  • Use pronounceable names.
  • Use searchable names.
  • Replace magic numbers with named constants.
  • Avoid encodings. Don’t append prefixes or type information.
  • Functions rules
  • Small.
  • Do one thing.
  • Use descriptive names.
  • Prefer fewer arguments.
  • Have no side effects.
  • Don’t use flag arguments. Split method into several independent methods that can be called from the client without the flag.

Comments rules

  • Always try to explain yourself in code.
  • Don’t be redundant.
  • Don’t add obvious noise.
  • Don’t use closing brace comments.
  • Don’t comment out code. Just remove.
  • Use as explanation of intent.
  • Use as clarification of code.
  • Use as warning of consequences.

Source code structure

  • Separate concepts vertically.
  • Related code should appear vertically dense.
  • Declare variables close to their usage.
  • Dependent functions should be close.
  • Similar functions should be close.
  • Place functions in the downward direction.
  • Keep lines short.
  • Don’t use horizontal alignment.
  • Use white space to associate related things and disassociate weakly related.
  • Don’t break indentation.

Objects and data structures

  • Hide internal structure.
  • Prefer data structures.
  • Avoid hybrids structures (half object and half data).
  • Should be small.
  • Do one thing.
  • Small number of instance variables.
  • Base class should know nothing about their derivatives.
  • Better to have many functions than to pass some code into a function to select a behavior.
  • Prefer non-static methods to static methods.

Tests

  • One assert per test.
  • Readable.
  • Fast.
  • Independent.
  • Repeatable.

2020


2020总结——看见与成长

黄龙国际难得在晚上8点的时候就如此冷清,提交了2020的最后一个commit和文档工作之后,自己的节奏就这样慢了下来,挂上降噪耳机过去的一年如同电影画面一帧一帧在脑海中掠过,庚子年见证历史,也是见证我自己的一年,一句话总结自己的2020——看见与成长。

2020流水

回顾2020魔幻而精彩,疫情、美股暴跌、中美贸易战、澳洲大火、Kobe Bryant逝世、美国大选、Black Lives Matter、蚂蚁IPO,我自己也经历了经历了看见与成长。

成长

2020我自己也魔幻而精彩,回顾下来主要有:

  1. 学习理财,从当初不懂任何财经知识的小白变成了一个会分得清楚股票和债券、懂得选基金需要关注哪些信息、能够初步看懂公司的财务报表并切大概根据财报分析和判断公司的发展和前景。今天大概算了一下,经过一年的学习,自己的投资回报率已经达到16.3%。
  2. 锻炼酒量,年初红酒(酒的名字很漂亮:Luna,感谢大王的赠酒ω),春天威士忌,夏天啤酒,然后就一直啤酒。。。现在终于是能喝一点了,也慢慢懂得了酒的乐趣,它是苦恼时的朋友,是高兴时的观众
  3. 沟通技巧,以前的自己不自信,在工作中不敢于表达自己,不知道如果表达自己,这一年走出了自己的舒适圈,打破自己一直“收”的状态,走出去让大家知道我了解我
  4. 效率提升,这一年对自己的投资大部分在效率工具和服务的购买(订阅TickTick、购买了Alfred),学会GTD,学会了使用Notion,学会了番茄工作法
  5. 技术成长,经过Raven一年多的打磨,对golang越来越熟悉也越来清楚go与C的差异,对k8s的代码学习(借此学会go的Visitor模式、委托反转控制等等);大致看了一些kernel代码弄懂了ebpf的原理,借着文件保险箱学了一些kernel开发的经验;从前端开始搭建了一套插件系统,也从中学到了一些产品思维

看见

2020出了成长我也看到了很多:

  1. 搬新家了,有自己的房子可以住很开心
  2. 看着周则夷小朋友一点点长大
  3. 通勤由自己开车变成地铁,利用1个小时的时间进行阅读,剔除技术类书籍今年读了10本书,这是一个非常大的进步
  4. 结识了3个新朋友,老中青三代人,不知道被我说成“老”朋友的那位会不会生气(´▽`),也跟远在他乡的老朋友保持联系
  5. 开始经历真正意义上的婚后生活,总体来讲挺不满意的有难过有无奈,少有感动和共情,尝试重拳和轻抚的方式希望找寻到合适的方法,可是生活依旧平淡如水少有滋味,直到快年底的时候想通了,就像庄子说的“知其不可奈何而安之若命。”,人生之根本,是如何与自己相处

2020我想说

狄更斯说:“这是最好的时代,也是最坏的时代”,不管工作还是生活我还是愿意抱着积极的心态面对,心里面唯一有些不忿的恐怕就是那时不时在心里作祟的理想主义,朋友在给我的postcard里面感慨道:

世人慌慌张张,不过图的是碎银几两。可偏偏就是这碎银几两,能解世间万千惆怅;偏偏就是这几两碎银,可让父母安康,可护幼子成长。但也偏偏是这几两碎银,断了儿时念想,让少年染上沧桑,压弯了脊梁。

2020我想说的是:我路过山时山不说话,但是我不会难过因为我知道路是自己选的,若不是生活所迫,谁愿意把自己弄的一身才华

2021我想说

Only is better than best!

“任抛星汉归园圃,留取乾坤盛酒囊”,我相信也坚持看见自己。

“海棠断枝不见血,机关算尽悲无常‘,我愿意也保持自我成长。