SaltStack自定义模块
来由
公司项目以前使用的是fabric将代码部署到云平台,现在迁移到本地的服务器,
再使用fabric显然有点不合适,然而使用SaltStack的state来描述发版本的过
程又略显不灵活,所以决定自己编写一个SaltStack的模块来实现版本发布的需求,
下面给出编写自定义模块的方法。
版本发布流程
- 使用git pull最新的master代码。
- 使用脚本替换一些变量。
- 一些其他的操作
- 重启supervisor开启的uwsgi服务。
如何编写SaltStack自定义模块
编写SaltStack自定义模块超级简单,前提是需要你会一些Python编程,具体如下:
步骤
- 首先需要在master机器的file_roots 下建一个_modules文件夹,里面放.py 文件,也就是自定义模块。
- file_roots 在/etc/salt/master 里可以设置,默认是/srv/salt/,即你需要在这个目录下新建文件夹_modules
- 在_modules里新建自定义模块,然后编写相应的代码。
- 比如模块名是foo,在里面定义了方法bar。
- 写完代码后,使用命令 salt '*' saltutil.sync_all 将自定义模块同步到各个minion上。
- 也可以使用 salt '*' saltutil.sync_modules 将自定义模块同步到minion上
- 运行自定也模块,使用命令 salt '*' foo.bar
实例
目录结构:
srv \-- salt \-- _modules \-- foo.py \-- prod ...
自定义模块代码:
# coding: utf-8 def bar(): CMD_RUN = __salt__['cmd.run'] cmd = "ls /etc/init" return CMD_RUN(cmd) # 解释 # 自定义模块会加载__salt__变量,这个dict变量里包含所有的salt function # 上面的cmd.run就是salt的命令运行函数 # __salt__变量必须在自定义的模块里使用,也就是必须在自定一的方法中使用, # 如上面的bar方法,不能将__salt__变量放到全局变量中使用,否则会报__salt__变量为定义
注意事项
- 如果要使用__salt__变量,必须要在自定义的方法中使用,不能在全局变量中使用。
- 如果运行命令报错了,如报 foo.bar is not avaliable,请到minion机器上去查看具体的日志。
- 报上述错误并不是模块没有同步到minion上,而是你编写的模块运行有问题。
- minion的日志记录在/var/log/salt/minion中,可以去这里查看具体错误。
最后的思考
- 编写SaltStack自定义模块很简单、方便。
- 实现自定义模块的过程中,可以发扬NIH(Not invent here)的思想。
- 可以充分利用__salt__变量里的salt自带函数来实现你想要的功能。
saltstack使用笔记
saltstack使用笔记
来由:
公司需要部署把服务部署到自己的服务器中,每次迁移服务都得重新安装软件
配置什么东西,身为程序员的我本身比较懒,就想找个工具把配置写好,下次
部署的时候直接使用配置来玩,而不是每次都手工,所以就发现了 saltstack
这个自动化部署工具了。
简介
salt采用的C/S架构,主机master和客户端minion,master通过命令控制客户端minion
进行相应的任务(软件安装,配置,重启,定时任务,状态监控。。。)。
核心
远程执行引擎是Salt的核心,它能够为多组系统创建高速、安全的双向通讯网络。
基于这个通许系统,Salt提供了一个非常快速、灵活并且容易使用的配置管理系统,
称之为“Salt States”。
安装配置
参考 http://docs.saltstack.cn/topics/tutorials/walkthrough.html
# 运行 :salt-key -L # 如果出现客户端机器,说明配置成功了
使用
salt state
SLS(代表SaLt State文件)只是结构化的数据。
# 运行 salt '*' state.highstate # 会让所有的minion到master上来取走自己的SLS定义 # 模拟运行 salt 'minion1.example.com' state.highstate -v test=True
更多使用教程
salt 配置文件结构
开启salt配置文件
# 修改/etc/salt/master文件,去掉如下注释 file_roots: base: - /srv/salt prod: - /srv/salt/prod dev: - /srv/salt/dev # 如果需要使用pillar,去掉如下注释 # pillar可以允许你定义一些变量,敏感数据,具体参考 http://www.ituring.com.cn/article/42398 pillar_root: base: - /srv/pillar
配置文件格式
- salt 的配置文件使用yaml格式,文件名后缀是.sls
- 关于yaml格式可以参考http://www.yaml.org/spec/1.2/spec.html
- json是yaml的子集,yaml可以描述更多的东西
- yaml的可读性要比json好,但是身为程序员的我觉得json的可读性更好。
- 学习yaml可以使用如下网站进行json和yaml的转换,http://yamltojson.com/ ,这样可以更好的理解yaml。
配置文件的目录结构
tops.sls (描述所有客户端需要执行的状态) prod (生产环境配置) -- core (基本软件安装) -- core.sls -- nginx -- nginx.sls -- nginx.conf -- vim -- vim.sls -- vim.local -- git -- git.sls -- web -- web.sls -- ... -- ... dev (测试环境配置) -- ... -- ...
配置文件解释
core.sls
# /srv/salt/tops.sls prod: '*': # 所有minion都要获取core状态 - core.core # /srv/salt/prod/core/core.sls mypkgs: pkg.installed: - pkgs: - gcc - python-dev - tmux - build-essential - g++ - ...
总结
saltstack 可以让你像编程一样去管理服务器,简单,高效,有趣。
参考
JustForFun读书感想
JustForFun读书感想
来源
无意间发现了linus的自传,周末读了一下,感觉不错,记录一下感想。
摘录
有三件事具有生命的意义:第一是生存,第二是社会秩序,第三是娱乐。
一切事物都将从生存走向娱乐。
Just for Fun
我问他,如果见到比尔盖茨想说些什么,他却说连与后者见 一面的欲望都没有。
“在我们俩之间没有什么关系可言,”他说,“他所做的事是世界上最优秀的,但我却丝 毫不感兴趣。我所做的事在世界上也可能是最优秀的,他也不感兴趣。我对他经商 提不出任何建议,他对我的技术也提不出任何看法。”
强制性服兵役制度的主要原因就是让芬兰男人们在喝啤酒时有话可聊,而且他们能活多久 就能聊多久。生个人都忍受了许多痛苦,那是共同的。他们都恨军队,但事后聊起来时却又 都格外开心。
每个人都会一本改变其一生的书籍,比如《圣经》、《资本论》、《星期二和莫瑞在一起》、 《我想 知道我在幼儿园里学到的一切》等等.
把我推向生命高峰的是安德鲁?塔南鲍姆 (Andrew S. Tanenbaum)写的《操作系统:设计和实现》。
通过阅读和对 UNIX 的了解愈来愈深,我的热情高涨起来。
说实话,我的热情从来没有低落过(我希望你在做某件事时也能说出同样的话)。
UNIX的理念是越小越漂亮。一小堆简单基本的建筑材料,结合起来就能创造出无限的复杂表述。
编程给人带来的最初兴奋的原因有部分是显而易见的,
那就是:通过编程你可以支配一台计算机,你叫计算机做什么,它就做什么,永远准确无误,而且毫无怨言。
一个伟大的 编程者能凭借其聪明的头脑就知道答案是什么。他知道怎样写出漂亮的程序,知道怎样采用
一种全新的但最终会被证明是正确的方法。
在软件世界中,一旦你已解决了最根本的问题,兴趣就容易很快地消失
Linux 所取得的许多成功,其实可以归结为我的缺点所致:
1、 我很懒散。
2、 我喜欢授权给其他人。
我遇上了塔芙。她对我一生的影响甚至比《操作系统:设计与执行》一书对我的影响 还要大。
总结:
1. 做自己想做的事。 2. just for fun
Clojure写DSL
Clojure写DSL
哲学
- 没有银弹,函数式编程也只是一种编程范式。
- 函数式编程的范式是申明式编程,关注于描述问题是什么而不是怎么实现。
- 函数式编程本质并不是高深的S-expr,Macro,不可变状态之类的特性,而是描述要干什么,而不是怎么干。
什么是DSL
DSL即领域编程语言,它是用于解决特定领域问题的语言。
为什么需要DSL
更好的描述问题是什么,增加语言的表现力。
为什么可以用Lisp来写DSL
- Lisp中数据即代码,代码即数据。
- Lisp的Macro可以让你定义自己的控制结构,写出自己的编程语言。
例子
Clojure实现一个Sql的DSL
(ns clojuresql.core) ;;; jdbc 驱动 (require '[clojure.java.jdbc :as jdbc]) ;;; 数据库配置 (def db-spec { :subprotocol "mysql" :subname "//172.17.8.101:3306/didi_db" :user "admin" :password "root"}) ;;; Select 语句处理 (defn select [fields] (clojure.string/join " " (concat ["SELECT"] [(clojure.string/join ", " fields)]))) ;;; from 语句处理 (defn from [& table] (clojure.string/join " " (concat ["FROM"] table))) ;;; 定义控制结构 (defmacro cljsql [& sql_atom] `(jdbc/query db-spec [(clojure.string/join " " (list ~@sql_atom))]) ) ;;; 使用 (cljsql (select ["username", "password"]) (from "auth_user"))
解释
- lisp是提倡自底向上设计的,如果没有思路,不妨从底层开始,如select函数和from函数。
- lisp的哲学每一层为上一层提供抽象愿语,如select和from函数为macro cljsql提供抽象原语。
总结:
Lisp 强大还是在于她本身的语言表现力,描述问题是什么,而不是怎么去做。
Golang实现简单HTTP服务器
Golang实现简单HTTP服务器
设计思想
一图胜千言:
并发模型是,只要有client来访问就开一个goroutine去处理,goroutine之间
不需要通信。
实现的功能
- 并发处理浏览器请求。
- 日志模块。
- 配置模块。
代码结构
. ├── README.md ├── README.org ├── deps # 安装第三方库脚本 ├── install # 编译脚本 └── src # 源代码 ├── config # 配置模块 │ └── config.go ├── github.com # 第三方库 ├── gohttpserver # main模块 │ └── gohttpserver.go ├── logger # 日志模块 │ └── logger.go ├── request # request 模块 │ ├── request.go │ └── request_test.go └── response # response 模块 ├── response.go └── response_test.go.
关键代码
// 处理client的goroutine func (ghs *GoHttpServer) handleClient(conn net.Conn) { reqChans := request.RequestsChans(conn) response.StartResponse(conn, reqChans) } // 服务器永远运行,只要有client就用goroutine去处理 func (ghs *GoHttpServer) ServerForever() { for { conn, err := ghs.listener.AcceptTCP() if err != nil { logger.Logger.Warning( "Accept Client connection error, error msg %s", err.Error(), ) continue } timeout := time.Second * time.Duration(config.SerConfig.Timeout) conn.SetDeadline(time.Now().Add(timeout)) go ghs.handleClient(conn) } }
遇到的问题
如何处理Http/1.1中的Keep-Alive?
解决:使用channel来当作request队列,response模块从队列读取request信息返回。
// return http request channels func RequestsChans(conn net.Conn) chan *Request { reader := bufio.NewReader(conn) reqCap := config.SerConfig.ReqChanCap reqChans := make(chan *Request, reqCap) go func() { reqSlice := make([]string, reqCap) for { line, err := reader.ReadString('\n') if err == nil { line = strings.TrimSpace(line) reqSlice = append(reqSlice, line) if len(line) == 0 { req := parseRequest(reqSlice) loggerReqInfo(conn, req) reqChans <- req reqSlice = reqSlice[:0] } } else { logger.Logger.Debug( "Ip %s connection close, close msg %s", conn.RemoteAddr().String(), err.Error(), ) conn.Close() break } } close(reqChans) }() return reqChans }
完整代码请参照https://bitbucket.org/runforever/gohttpserver/overview
如何正确的打印日志
如果正确的打印日志
哲学
- 日志应该像代码一样易于阅读和理解
- 清楚你在记录什么
- 不要为了日志而记日志
日志级别
一个项目各个log级别的定义应该是清楚明确的,是每个开发人员所遵循的; 即使是TRACE或者DEBUG级别的日志,也应该有一定的规范,要保证除了开发人员自己以外, 包括测试人员和运维人员都可以方便地通过日志定位问题; 对于日志级别的分类,有以下参考: FATAL — 表示需要立即被处理的系统级错误。当该错误发生时,表示服务已经出现了 某种程度的不可用,系统管理员需要立即介入。这属于最严重的日志级别,因此该 日志级 别必须慎用,如果这种级别的日志经常出现,则该日志也失去了意义。 通常情况下,一个进程的生命周期中应该只记录一次FATAL级别的日志,即该进程遇到无 法恢复的错误而退出时。当然,如果某个系统的子系统遇到了不可恢复的错误,那该 子系统的调用方也可以记入FATAL级别日志,以便通过日志报警提醒系统管 理员修复; ERROR — 该级别的错误也需要马上被处理,但是紧急程度要低于FATAL级别。 当ERROR错误发生时,已经影响了用户的正常访问。从该意义上来说,实际上 ERROR错误和FATAL错误对用户的影响是相当的。FATAL相当于服务已经挂了, 而ERROR相当于好死不如赖活着,然而活着却无法提供正常的服务,只能不 断地打印ERROR日志。特别需要注意的是,ERROR和FATAL都属于服务器自己的 异常,是需要马上得到人工介入并处理的。而对于用户自己 操作不当, 如请求参数错误等等,是绝对不应该记为ERROR日志的; WARN — 该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。 对于那些目前还不是错误,然而不及时处理也会变为错误的情况,也可以记为WARN日志, 例如一个存储系统的磁盘使用量超过阀值,或者系统中某个用户的存储配额快用完等等。 对于WARN级别的日志,虽然不需要系统管理员马上处理,也是需要 即使查看并处理的。 因此此种级别的日志也不应太多,能不打WARN级别的日志,就尽量不要打; INFO — 该种日志记录系统的正常运行状态,例如某个子系统的初始化,某个请求的成功执行等等。 通过查看INFO级别的日志,可以很快地对系统中出现的 WARN,ERROR,FATAL错误进行定位。 INFO日志不宜过多,通常情况下,INFO级别的日志应该不大于TRACE日志的10%; DEBUG or TRACE — 这两种日志具体的规范应该由项目组自己定义,该级别日志的 主要作用是对系统每一步的运行状态进行精确的记录。通过该种日志,可以查看 某一个操作每一步的执 行过程,可以准确定位是何种操作,何种参数,何种顺序导致了某种错误的发生。 可以保证在不重现错误的情况下,也可以通过DEBUG(或TRACE)级别的 日志对问题进行诊断。 需要注意的是,DEBUG日志也需要规范日志格式,应该保证除了记录日志的开发人员自己外, 其他的如运维,测试人员等也可以通过 DEBUG(或TRACE)日志来定位问题;
为什么要做日志
- 开发过程中帮组开发人员跟踪程序崩溃的原因(DEBUG)。(个人更喜欢喜欢用print和debug)
- 记录一些程序的运行记录(INFO)。
- 程序运行错误的上下文信息(ERROR)。
Emacs clojure-mode使用
Emacs 中使用Clojure Mode
基本配置
兄弟,不要折腾了,直接照抄Emacs大师Steve Purcell的配置https://github.com/purcell/emacs.d
配置完之后Clojure-Mode已经配置好,附带一些其他的配置。
使用
安装leiningen https://github.com/technomancy/leiningen
leiningen 是一个Clojure的项目管理工具。
常用快捷键
M-x cider-jack-in 打开一个repl 的session,你编写的Clojure代码之后会在这里运行。
C-c C-k 编译Clojure代码,如果编译出错 C-c C-f来定位错误的地方然后修正。
C-c C-, 可以用来运行测试文件,结果会输出到打开的repl session。
C-c M-n 用来切换repl session的namespace, 如果你正在编写一个clojure文件,可以
使用这个快捷键来一边开发,一边测试。
C-c C-o 可以用来清初repl session的无用信息。
C-c C-d 可以用来查看函数的doc。
M-. 可以查看函数的源代码。
M-, 用来查看第三方库。
参考
Docker使用笔记
Docker使用笔记
什么是Docker
docker的英文本意是码头工人,也就是搬运工,这种搬运工搬运的是集装箱(Container),
集装箱里面装的可不是商品货物,而是任意类型的App,Docker把App(叫Payload)装在Container内,
通过Linux Container技术的包装将App变成一种标准化的、可移植的、自管理的组件,
这种组件可以在你的latop上开发、调试、运行,最终非常方便和一致地运行在production环境下。
Docker的特点
1. Docker提供了一种可移植的配置标准化机制,允许你一致性地在不同的机器上运行同一个Container;而LXC本身可能因为不同机器的不同配置而无法方便地移植运行;
2. Docker以App为中心,为应用的部署做了很多优化,而LXC的帮助脚本主要是聚焦于如何机器启动地更快和耗更少的内存;
3. Docker为App提供了一种自动化构建机制(Dockerfile),包括打包,基础设施依赖管理和安装等等;
4. Docker提供了一种类似git的Container版本化的机制,允许你对你创建过的容器进行版本管理,依靠这种机制,你还可以下载别人创建的Container,甚至像git那样进行合并;
5. Docker Container是可重用的,依赖于版本化机制,你很容易重用别人的Container(叫Image),作为基础版本进行扩展;
6. Docker Container是可共享的,有点类似github一样,Docker有自己的INDEX,你可以创建自己的Docker用户并上传和下载Docker Image;
7. Docker提供了很多的工具链,形成了一个生态系统;这些工具的目标是自动化、个性化和集成化,包括对PAAS平台的支持等;
为什么使用Docker
1. 打包的开发环境,方便部署和迁移。
2. 对于没有服务器的程序员可以用来模拟集群。
3. 保持开发机器的干净整洁,将数据库、Web服务器这些东西放到Container里。
Mac上使用Docker
直接根据官网教程安装即可,https://docs.docker.com/docker-for-mac/
使用Docker
首先请查看官方的基础教程,移步https://www.docker.io/gettingstarted/
Docker类似Git,可以使用pull命令获取公共镜像。
如我的开发环境需要Mysql,我可以很方便的使用下面的命令获取镜像。
# 查找包含MySql服务的镜像 docker search mysql # 找到后 docker pull tutum/mysql # 下载完成后运行, -d 是deamon方式运行,-p 是端口转发 # 命令的意思是后台运行这个Container,并且将CoreOS的3306端口 # 转发到Container的3306端口上 # 宿主机器(Mac)就可以通过mysql -h CoreOS的外网IP -u admin -p # 连接Mysql服务了 docker run -d -p 3306:3306 tutum/mysql
Docker 常用命令
# 查看所有image docker images # 查看正在运行的Container,-l 是最后运行的Container,-a 所有运行的Container docker ps # 查看Container的运行信息 docker logs container_id # 查看Container的配置信息。 docker inspect #交互式运行shell,就像ssh到Container一样,可以在Container里Do everything docker run -i -t image_id /bin/bash # 将对Container的修改提交,定制自己的Image docker commit container_id image_name # 删除所有的镜像(慎用) docker kill $(docker ps -q) ; docker rm $(docker ps -a -q) ; docker rmi $(docker images -q -a) # 删除所有的Container docker rm `docker ps -a -q`
参考
Golang单元测试
Golang 单元测试
哲学
测试只能证明你的代码有问题,不能证明你的代码没有问题。
测似的粒度
老板为我的代码付报酬,而不是测试,所以,我对此的价值观是——测试越少越好,
少到你对你的代码质量达到了某种自信(我觉得这种的自信标准应该要高于业内的
标准,当然,这种自信也可能是种自大)。如果我的编码生涯中不会犯这种典型的
错误(如:在构造函数中设了个错误的值),那我就不会测试它。我倾向于去对
那些有意义的错误做测试,所以,我对一些比较复杂的条件逻辑会异常地小心。
当在一个团队中,我会非常小心的测试那些会让团队容易出错的代码。
个人理解:测试需要测有意义的东西,而不是盲目的100%覆盖,需要的是恰到好处
的UT。
TDD(测试驱动开发)
个人理解:
1. 测试不能帮助你写出优秀的设计,只能保证你写的代码不出错。
2. 软件开发是一种发现问题解决问题的过程,TDD并不能达到这样
的效果。
3. TDD保证代码正确,但是什么来保证TDD的case是正确的。
4. TDD的好处大多是理论上的,实际上是不是只有做过才知道。
5. 决定软件工艺的还是设计,程序员需要知道怎么思考,怎么设计,
怎么测试,而不是教条主义的照搬某某理论,某某Best Practice。
6. 软件工程是没有银弹的,TDD也许适合你的项目,你的编程风格,
但是不一定适合所有人。
总结: 软件需要的是恰到好处的设计和单元测试。
Go语言中的单元测试
go语言自带 testing 测试框架, go test 命令可以运行单元测试和性能测试。
使用
exa.go 文件
package main import "fmt" func Add(x, y int) int { return x + y } func main() { x, y := 1, 2 fmt.Println(Add(x, y)) }
exa_test.go 测试文件
package main import ( "testing" ) func TestAdd(t *testing.T) { if x := Add(1, 3); x != 4 { t.Error("error in test Add") } if x := Add(1, 3); x != 5 { t.Error("error in test Add 5") } }
# 在当前文件夹运行go test便可以运行测试 go test
使用原则
1. 文件名必须是_test.go结尾的,这样在执行go test的时候才会执行到相应的代码
2. 你必须import testing这个包
3. 所有的测试用例函数必须是Test开头
4. 测试用例会按照源代码中写的顺序依次执行
5. 测试函数TestXxx()的参数是testing.T,我们可以使用该类型来记录错误或者是
测试状态
6. 测试格式:func TestXxx (t *testing.T),Xxx部分可以为任意的字母数字的组合,
但是首字母不能是小写字母[a-z],例如Testintdiv是错误的函数名。
7. 函数中通过调用testing.T的Error, Errorf, FailNow, Fatal, FatalIf方法,
说明测试不通过,调用Log方法用来记录测试的信息。
参考
Golang面向对象编程
GO 面向对象编程
哲学
Less is more
区别
与Java、C++、Python等面向对象语言的区别
- 没有传统OOP编程中继承、虚函数、构造函数和析构函数、隐藏的this指针等概念。
- 接口无需申明(静态类型的duck typing)。
- 无侵式实现接口,接口与实现分离。
接口
解释
接口就是一个协议,规定了一组成员。
语法
类
/* 定义一个形状类 实现正方形计算面积的方法 实现圆计算面积的方法 */ type Sharp struct { r float64 } func (s Sharp) Square() float64{ return s.r * s.r } func (s Sharp) Circle() float64{ return s.r * s.r * 3.14 }
接口
type Area interface { Square() float64 Circle() float64 } /* 上面类的例子没有继承接口,但是实现了接口的方法,接口无需继承 */ /* 使用 */ var sharp Area = &sharp{4} sharp.Square() sharp.Circle()
完整代码
package main import "fmt" /* 定义一个形状类 实现正方形计算面积的方法 实现圆计算面积的方法 */ type Sharp struct { r float64 } func (s Sharp) Square() float64 { return s.r * s.r } func (s Sharp) Circle() float64 { return s.r * s.r * 3.14 } type Area interface { Square() float64 Circle() float64 } func main() { // 实例化类方法, 引用 var sharpSquare Sharp = Sharp{4} var sharpCircle Sharp = Sharp{4} // 实例化类方法,指针 var sharpSquare1 *Sharp = &Sharp{4} var sharpCircle1 *Sharp = &Sharp{4} // 实例化类方法, 使用new关键字 // new 分配了0值填充的Sharp类型的内存空间,并且返回该空间的地址 sharpSquare2 := new(Sharp) sharpSquare2.r = 5 fmt.Println("引用") fmt.Println(sharpSquare.Square()) fmt.Println(sharpCircle.Circle()) fmt.Println("指针") fmt.Println(sharpSquare1.Square()) fmt.Println(sharpCircle1.Circle()) fmt.Println("new关键字") fmt.Println(sharpSquare2.Circle()) // Sharp 实现了接口的两个方法,已经实现了该接口 // 接口调用 fmt.Println("接口") var area1 Area = Sharp{6} fmt.Println(area1.Square()) fmt.Println(area1.Circle()) }
多态
package main import ( "fmt" ) /* 定义Square和Circle两个类 分别实现Area方法 定义接口PolySharp,接口方法Area Square和Circle相当于实现了接口PolySharp */ type Square struct { width float64 } type Circle struct { redius float64 } func (s Square) Area() float64 { return s.width * s.width } func (s Circle) Area() float64 { return s.redius * s.redius * 3.14 } type PolySharp interface { Area() float64 } func main() { var s1 PolySharp = &Square{4} var s2 PolySharp = &Circle{4} fmt.Println(s1.Area()) fmt.Println(s2.Area()) }
杂项
函数定义文法
type mytype int func (p mytype) funcname(i int) (r int) { return 0 } // 关键字func, 绑定到mytype上(叫做方法method), 函数名, 返回类型
指针:Go语言的指针,基本上只剩下用于区分 byref 和 byval 语义。
解释: 值类型保存的是具体的值 引用类型保存的是值的地址。
package main import "fmt" func byval(a int) { a += 5 fmt.Println(a) } func byref(a *int) { fmt.Println(a) *a += 5 fmt.Println(a) } func main() { a := 4; fmt.Println(a) byval(a) fmt.Println(a) byref(&a) fmt.Println(a) } /* result 4 9 4 9 9 */
总结
- 无侵入式的接口设计确实很新颖和优雅,仔细读代码简单明了。
- golang使用组合替代继承,符合OOP的设计原则。
- OOP的消息传递机制更直接。