随记_2021-05-18


随记_2021–05–18

饭后畅想退休生活,感觉开个咖啡馆挺不错的,像这样的:

下班路上看到了一个酒吧,感觉开一个也挺不错的,像这样:

然后倒一杯咖啡或者精酿一坐一天,为什么能坐一天呢?一来不差钱,一来年纪大了,毕竟上了年纪的人目之所及皆是回忆,心之所想皆是过往;眼之所看皆是遗憾,耳之所闻皆是呢喃!轻呷一口,厚重的嗓音随后响起:

“辛丑年,钱塘江”

“互联网江湖”

“乱”

“很乱”

“非常乱”

……

有点古龙的味道,也有点期待退休的日子,按照国家规定算来算去我还有40+年才能这么做,哎~~

随记_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.