Golang编码规范

package名字:

保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写

文件命名

  • 所以文件名应一律使用小写
  • 不同单词之间用下划线分词,不要使用驼峰式命名
  • 文件若具有平台特性,应以 文件名_平台.go 命名,比如 utils_ windows.go,utils_linux.go,可用的平台有:windows, unix, posix, plan9, darwin, bsd, linux, freebsd, nacl, netbsd, openbsd, solaris, dragonfly, bsd, notbsd, android,stubs
  • 一般情况下应用的主入口应为 main.go,或者以应用的全小写形式命名。比如MyBlog 的入口可以为 myblog.go
  • 如果是测试文件,可以以 _test.go 结尾

常量命名

目前在网络上可以看到主要有两种风格的写法:

  • 第一种是驼峰命名法,比如 appVersion
  • 第二种使用全大写且用下划线分词,比如 APP_VERSION

这两种风格,没有孰好孰弱,可自由选取,我个人更倾向于使用第二种,主要是能一眼与变量区分开来。

如果要定义多个变量,请使用 括号 来组织。

const (
    APP_VERSION = "0.1.0"
    CONF_PATH = "/etc/xx.conf"
)

变量命名

和常量不同,变量的命名,开发者们的喜好就比较一致了,统一使用 驼峰命名法

  • 在相对简单的环境(对象数量少、针对性强)中,可以将完整单词简写为单个字母,例如:user写为u
  • 若该变量为 bool 类型,则名称应以 HasIsCan 或 Allow 开头。例如:isExist ,hasConflict
  • 其他一般情况下首单词全小写,其后各单词首字母大写。例如:numShips 和 startDate
  • 若变量中有特有名词,且变量为私有,则首单词还是使用全小写,如 apiClient
  • 若变量中有特有名词,但变量不是私有,那首单词就要变成全大写。例如:APIClientURLString

函数命名

  • 函数名还是使用 驼峰命名法
  • 但是有一点需要注意,在 Golang 中是用大小写来控制函数的可见性,因此当你需要在包外访问,请使用 大写字母开头
  • 当你不需要在包外访问,请使用小写字母开头

另外,函数内部的参数的排列顺序也有几点原则

  • 参数的重要程度越高,应排在越前面
  • 简单的类型应优先复杂类型
  • 简单的类型应优先复杂类型

接口命名

使用驼峰命名法,可以用 type alias 来定义大写开头的 type 给包外访问:

type helloWorld interface {
    func Hello();
}

type SayHello helloWorld

当你的接口只有一个函数时,接口名通常会以 er 为后缀

type Reader interface {
    Read(p []byte) (n int, err error)
}

注释规范

包注释:

  • 位于 package 之前,如果一个包有多个文件,只需要在一个文件中编写即可
  • 如果你想在每个文件中的头部加上注释,需要在版权注释和 Package前面加一个空行,否则版权注释会作为Package的注释。

代码注释:

用于解释代码逻辑,可以有两种写法,单行注释使用 // ,多行注释使用 /* comment */

特别注释:

  • TODO:提醒维护人员此部分代码待完成
  • FIXME:提醒维护人员此处有BUG待修复
  • NOTE:维护人员要关注的一些问题说明

Golang学习笔记-2

Golang结构体初始化

Go 通过类型别名(alias types)和结构体的形式支持用户自定义类型。
结构体是复合类型,当需要定义类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。

结构体也是值类型,因此可以通过 new 函数来创建

组成结构体类型的那些数据成为字段(fields)。每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。

一,结构体定义

结构体定义的一般方式如下:

type identifier struct {
    field type1
    field type2
}

type T struct {a, b int} 也是合法的语法,它更适用于简单的结构体
结构体里的字段都有 名字,像 field1、field2 等,如果字段在代码中从来也不会被用到,那么可以命名它为 _。
结构体类型和字段的命名遵循可见性规则,所以可能存在一个结构体类型的某些字段是导出的,而另一些没有导出。
结构体的字段可以是任何类型,甚至是结构体本身,也可以是函数或者接口。可以声明结构体类型的一个变量,然后像下面这样给它的字段赋值:

var s T
s.a = 5
s.b = 8

数组也可以看作是一种结构体类型,不过它使用下标而不是具名的字段

二,初始化

方式一:通过 var 声明结构体

在 Go 语言中当一个变量被声明的时候,系统会自动初始化它的默认值,比如 int 被初始化为 0,指针为 nil。
var 声明同样也会为结构体类型的数据分配内存,所以我们才能像上一段代码中那样,在声明了 var s T 之后就能直接给他的字段进行赋值

方式二:使用 new

使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)。

type struct1 struct {
    i1 int
    f1 float32
    str string
}

func main() {
    ms := new(struct1)
    ms.i1 = 10
    ms.f1 = 15.5
    ms.str= "Chris"

    fmt.Printf("The int is: %d\n", ms.i1)
    fmt.Printf("The float is: %f\n", ms.f1)
    fmt.Printf("The string is: %s\n", ms.str)
    fmt.Println(ms)
}

与面向对象语言相同,使用点操作符可以给字段赋值:structname.fieldname = value
同样的,使用点操作符可以获取结构体字段的值:structname.fieldname

方式三:使用字面量

type Person struct {
    name string
    age int
    address string
}

func main() {
    var p1 Person
    p1 = Person{"lisi", 30, "shanghai"}   //方式A
    p2 := Person{address:"beijing", age:25, name:"wangwu"} //方式B
    p3 := Person{address:"NewYork"} //方式C
}

在(方式A)中,值必须以字段在结构体定义时的顺序给出。(方式B)是在值前面加上了字段名和冒号,这种方式下值的顺序不必一致,并且某些字段还可以被忽略掉,就想(方式C)那样。
除了上面这三种方式外,还有一种初始化结构体实体更简短和常用的方式,如下:

ms := &Person{"name", 20, "bj"}
ms2 := &Person{name:"zhangsan"}

&Person{a, b, c} 是一种简写,底层仍会调用 new(),这里值的顺序必须按照字段顺序来写,同样它也可以使用在值前面加上字段名和冒号的写法(见上文的方式B,C)。

表达式 new(Type) 和 &Type{} 是等价的。

三,几种初始化方式之间的区别

到目前为止,我们已经了解了三种初始化结构体的方式:

//第一种,在Go语言中,可以直接以 var 的方式声明结构体即可完成实例化
var t T
t.a = 1
t.b = 2

//第二种,使用 new() 实例化
t := new(T)

//第三种,使用字面量初始化
t := T{a, b}
t := &T{} //等效于 new(T)

使用 var t T 会给 t 分配内存,并零值化内存,但是这个时候的 t 的类型是 T
使用 new 关键字时 t := new(T),变量 t 则是一个指向 T 的指针
从内存布局上来看,我们就能看出这三种初始化方式的区别:
使用 var 声明:

使用 new 初始化:

使用结构体字面量初始化:


Golang学习笔记-1

Go 中的所有东西都是按值传递的。每次调用函数时,传入的数据都会被复制。对于具有值接收者的方法,在调用该方法时将复制该值。

因为所有的参数都是通过值传递的,这就可以解释为什么 *Cat 的方法不能被 Cat 类型的值调用了。任何一个 Cat 类型的值可能会有很多 *Cat 类型的指针指向它,如果我们尝试通过 Cat 类型的值来调用 *Cat 的方法,根本就不知道对应的是哪个指针。相反,如果 Dog 类型上有一个方法,通过 *Dog 来调用这个方法可以确切的找到该指针对应的 Gog 类型的值,从而调用上面的方法。运行时,Go 会自动帮我们做这些,所以我们不需要像 C语言中那样使用类似如下的语句 d->Speak() 。

解释一下导入包名前面的”_”作用:

import 下划线(如:import _ github/demo)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import _ 引用该包。

Golang的interface{}

golang的interface{}一个是可以代表设计模式中的接口,一个可以看做类似C中的void*

强类型语言为了保持动态语言的优势添加的。类似于java中的Object。golang的面向对象设计就是基于interface{}的。

interface和interface{}的区别,一个是go的语法机制用于数据抽象和解耦,一个是这个机制的具体实现。前者应该鼓励使用,而后者应该谨慎使用。

 Go 中的接口,记住下面这些结论:

  • 通过考虑数据类型之间的相同功能来创建抽象,而不是相同字段
  • interface{} 的值不是任意类型,而是 interface{} 类型
  • 接口包含两个字的大小,类似于 (type, value)
  • 函数可以接受 interface{} 作为参数,但最好不要返回 interface{}
  • 指针类型可以调用其所指向的值的方法,反过来不可以
  • 函数中的参数甚至接受者都是通过值传递
  • 一个接口的值就是就是接口而已,跟指针没什么关系
  • 如果你想在方法中修改指针所指向的值,使用 * 操作符

Go中的方法与接收器:

  • 接收器类型
    • 指针接收器:指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的this,由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。
    • 非指针接收器:Go语言会在代码运行时将接收器的值复制一份。在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。
  • 如何选择
    • 在计算机中,小对象由于值复制时的速度比较快,所以适合使用非指针接收器。大对象因为复制性能低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只传递指针。
    • 有修改成员变量的需求,用指针类型的接收器。

WordPress后台加载ajax.googleapi.com导致打开很慢的解决方案

打开wordpress后台,发现很卡,通过开发者工具看到是因为加载http://ajax.useso.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.css这个元素导致的。

<link rel='stylesheet' id='jquery-ui-smoothness-css'  href='http://ajax.useso.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.css' type='text/css' media='all' />

解决方案:

在functions.php里面添加如下代码:

function hc_cdn_callback($buffer) {
    return str_replace('googleapi.com', 'useso.com', $buffer);
}
function hc_buffer_start() {
    ob_start("hc_cdn_callback");
}
function izt_buffer_end() {
    ob_end_flush();
}
add_action('init', 'hc_buffer_start');
add_action('shutdown', 'hc_buffer_end');

GONE

For both better and worse, the internet landscape moves fast.

Shortening attention spans and memories all over the world.

But every once in a while, we get a reminder of what once was.

无论好坏,互联网格局都在快速发展。

缩短全世界的注意力和记忆。

但每隔一段时间,我们就会想起曾经是什么。

 

DAO

建立 DAO 最难的部分不是你想的那样
这不是技术或筹款。

如果 DAO 的领导者希望扩大规模,他们不仅要精通技术,还要精通人性。

Python 坏习惯

1、拼接字符串用 + 号

坏的做法

def manual_str_formatting(name, subscribers):
    if subscribers > 100000:
        print("Wow " + name + "! you have " + str(subscribers) + " subscribers!")
    else:
        print("Lol " + name + " that's not many subs")

调整后的做法是使用 f-string,而且效率会更高

def manual_str_formatting(name, subscribers):
    # better
    if subscribers > 100000:
        print(f"Wow {name}! you have {subscribers} subscribers!")
    else:
        print(f"Lol {name} that's not many subs")

2、使用 finaly 而不是上下文管理器

坏的做法:

def finally_instead_of_context_manager(host, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect((host, port))
        s.sendall(b'Hello, world')
    finally:
        s.close()

调整后的做法是使用上下文管理器,即使发生异常,也会关闭 socket

def finally_instead_of_context_manager(host, port):
    # close even if exception
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((host, port))
        s.sendall(b'Hello, world')

3、尝试手动关闭文件

坏的做法:

def manually_calling_close_on_a_file(filename):
    f = open(filename, "w")
    f.write("hello!\n")
    f.close()

调整后的做法是使用上下文管理器,即使发生异常,也会自动关闭文件,凡是有上下文管理器的,都应该首先采用:

def manually_calling_close_on_a_file(filename):
    with open(filename) as f:
        f.write("hello!\n")
    # close automatic, even if exception

4、except 后面什么也不写

坏的做法

def bare_except():
    while True:
        try:
            s = input("Input a number: ")
            x = int(s)
            break
        except:  # oops! can't CTRL-C to exit
            print("Not a number, try again")

这样会捕捉所有异常,导致按下 CTRL-C 程序都不会终止,调整后的做法是

def bare_except():
    while True:
        try:
            s = input("Input a number: ")
            x = int(s)
            break
        except Exception:  # 比这更好的是用 ValueError
            print("Not a number, try again")

5、函数参数使用可变对象

如果函数参数使用可变对象,那么下次调用时可能会产生非预期结果,坏的做法

def mutable_default_arguments():
    def append(n, l=[]):
        l.append(n)
        return l

    l1 = append(0)  # [0]
    l2 = append(1)  # [0, 1]

调整后的做法,如下

def mutable_default_arguments():

    def append(n, l=None):
        if l is None:
            l = []
        l.append(n)
        return l

    l1 = append(0)  # [0]
    l2 = append(1)  # [1]

6、从不用推导式

坏的做法

squares = {}
for i in range(10):
    squares[i] = i * i

调整后的做法

odd_squares = {i: i * i for i in range(10)}

7、推导式用的上瘾

推导式虽然好用,但是不可以牺牲可读性,坏的做法

c = [
    sum(a[n * i + k] * b[n * k + j] for k in range(n))
    for i in range(n)
    for j in range(n)
]

调整后的做法,如下:

c = []
for i in range(n):
    for j in range(n):
        ij_entry = sum(a[n * i + k] * b[n * k + j] for k in range(n))
        c.append(ij_entry)

8、用 == 判断是否单例

坏的做法

def equality_for_singletons(x):
    if x == None:
        pass

    if x == True:
        pass

    if x == False:
        pass

调整后的做法,如下

def equality_for_singletons(x):
    # better
    if x is None:
        pass

    if x is True:
        pass

    if x is False:
        pass

9、使用类 C 风格的 for 循环

坏的做法

def range_len_pattern():
    a = [1, 2, 3]
    for i in range(len(a)):
        v = a[i]
        ...
    b = [4, 5, 6]
    for i in range(len(b)):
        av = a[i]
        bv = b[i]
        ...

调整后的做法,如下:

def range_len_pattern():
    a = [1, 2, 3]
    # instead
    for v in a:
        ...

    # or if you wanted the index
    for i, v in enumerate(a):
        ...

    # instead use zip
    for av, bv in zip(a, b):
        ...

10、不使用 dict.items

坏的做法

def not_using_dict_items():
    d = {"a": 1, "b": 2, "c": 3}
    for key in d:
        val = d[key]
        ...

调整后的做法,如下

def not_using_dict_items():
    d = {"a": 1, "b": 2, "c": 3}
    for key, val in d.items():
        ...

11、记录日志使用 print 而不是 logging

坏的做法

def print_vs_logging():
    print("debug info")
    print("just some info")
    print("bad error")

调整后的做法,如下

def print_vs_logging():
    # versus
    # in main
    level = logging.DEBUG
    fmt = '[%(levelname)s] %(asctime)s - %(message)s'
    logging.basicConfig(level=level, format=fmt)

    # wherever
    logging.debug("debug info")
    logging.info("just some info")
    logging.error("uh oh :(")

12、调用外部命令时使用 shell=True

坏的做法

subprocess.run(["ls -l"], capture_output=True, shell=True)

如果 shell=True,则将 ls -l 传递给/bin/sh(shell) 而不是 Unix 上的 ls 程序,会导致 subprocess 产生一个中间 shell 进程, 换句话说,使用中间 shell 意味着在命令运行之前,命令字符串中的变量、glob 模式和其他特殊的 shell 功能都会被预处理。比如,$HOME 会在在执行 echo 命令之前被处理处理。

调整后的做法是拒绝从 shell 执行,如下:

subprocess.run(["ls", "-l"], capture_output=True)

13、从不尝试使用 numpy

坏的做法

def not_using_numpy_pandas():
    x = list(range(100))
    y = list(range(100))
    s = [a + b for a, b in zip(x, y)]

调整后的的做法,如下:

import numpy as np
def not_using_numpy_pandas():
    # 性能更快
    x = np.arange(100)
    y = np.arange(100)
    s = x + y

14、喜欢 import *

调整后的做法,如下:

from itertools import *

count()

这样的话,没有人知道这个脚本到底有多数变量, 比较好的做法:

from mypackage.nearby_module import awesome_function

def main():
    awesome_function()

if __name__ == '__main__':
    main()

ESP_8_BIT:ESP32 带你体验怀旧游戏机

Rossum 是一位有名的 maker,他在最近的一篇博文中向大家介绍了他的最新作品 ESP_8_BIT。这是一个有趣的小工具,只用一颗 ESP32 芯片,即可让用户在电视上享受 Atari 8-bit、NES (Nintendo Entertainment System,红白机) 和 SMS (Sega Master System) 游戏机带来的怀旧游戏体验。

ESP_8_BIT 基于 Arduino IDE 框架,可在 ESP32 上正常运行。它支持 NTSC/PAL 彩色合成视频输出,其 4x 彩色载波信号是由音频 PLL/DAC 在 14.318180 MHz 或 17.734476 MHz 下产生的。ESP_8_BIT 支持经典蓝牙,以及各种 IR 键盘和操纵杆,它包括一个在 VHCI API 上实现的 HCI/L2CAP/HID 栈,支持蓝牙 EDR 外设、WiiMotes 等。ESP_8_BIT 的视频和音频技术十分出色,蓝牙性能也很突出,正因如此,其代码质量也非常高,看起来非常清晰直观。

点此在 YouTube 上观看 ESP_8_BIT 的运行情况。ESP_8_BIT 示意图如下所示:

EESP_8_BIT 示意图

这个项目基于 Atari800 模拟器实现了对 Atari 的支持,乐鑫的 ESP32 在其中也扮演了核心角色。Rossum 对此幽默地表示:“我实在太喜欢 Atari 8-bit 了!四十年了,每当我看到经典的蓝色背景和摇摆的字体时都会十分兴奋。它奇妙的工业设计和磁盘驱动器是一个传奇,很开心看到它以这种新的形式回归!”

ESP_8_BIT 基于 smsplus、playing .sms (Sega Master System) 和 .gg (Game Gear) ROM,实现了对 Sega Master System (SMS) 的支持。正如 Rossum 所言:“这个项目所用的模拟器,就是当时 SpriteTM 第一次向我们展示功能强大的 ESP32 时用的那一个!” 他所提到的 Sprite TM,也是乐鑫的软件工程师、技术营销经理 Jeroen Domburg。Rossum 评价 Jeroen 是“丰富了 ESP32 生态,使其更加有趣和更有价值的一位重要人物。”

点此访问 Rossum 的个人博客,查看更多关于该项目的详细内容

https://zhuanlan.zhihu.com/p/145662878