A Little Architecture 【译】

by Robert C. Martin (Uncle Bob)
https://blog.cleancoder.com/uncle-bob/2016/01/04/ALittleArchitecture.html
@04 January 2016

译者注:这是很久前看到的一篇关于架构师的文章,出自 Bob 大叔,这篇文章曾被我分享给了好多人,好多不知什么是架构师的人,我不确定他们有看。但我确定我很认可这其中的道理:什么是架构师要关注的,不是数据库,不是库,也不是服务器,是业务及一些顶级的设计原则。
在当下,顶级的架构变成了云设计师专属,而中低层则是数据库直连的时代,再次把这篇文章翻译出来,给那些有志的开发者,关注哪些方能成就自己,勿做井底之蛙。



我想成为一名软件架构师。

这对于一个年轻的开发者来说是一个很好的目标。

我想要带团队做重要决策,包括数据库,框架和服务器以及其它一切。

恩,这样听起来,你并不是想成为一名架构师。

我当然想成为架构师,要在团队里做重大决策的那种。

但你刚才并没有指哪任何重要决策,你说的都是不想干的事项。

什么意思?数据库难道不是重要决策?你不知道我们花了多少钱在这上面?

可能很多,但,数据库不属于重要决策。

怎么能这么讲?数据库简直就是整个系统的内核了!它是整个数据的归集,排序,索引和访问的地方。没有它就没有整个系统。

数据库只是一个 IO 设施。它是提供了一些功能来做数据的排序、查询及报表,但这都只是系统架构的辅助。

辅助??这种解释太不可理喻了。

没错,就只是辅助。系统的业务规则可能会使用到这样那样的一些工具,但工具本身不是业务所固有的。也就是说,如果需要,你可以替换这些工具,但业务规则不会变。

恩,但我依然需要数据库来记录这些数据。

那是你的问题!

什么意思?

你认为业务规则依赖于你的数据库工具。但不是的,最少好的架构不应该有这样的依赖关系。

搞笑,如果不用这些必须的工具, 那我如何创建业务规则。

我并没有说不用这些工具,我是说不应该依赖于它们。业务规则应该不知道你用的是哪种具体的数据库。

如何让业务规则使用工具但不去了解工具?

你搞反了依赖。你的数据库依赖于业务。你需要明确业务不依赖于数据库。

胡扯。

正相反,我在讲计算机架构语言。这是一种依赖反转原则。低级策略应该依赖于高级策略。

更胡扯!高级策略(我想你是指业务)调用低级策略(这里指数据库)。所以是高级依赖于低级,就像呼叫者依赖于被呼者。路人皆知。

在运行时,这样讲是对的。但在编译时,我们希望的是依赖反转。高层级代码不应该直接标明低层级代码。

得了吧!不可能调用一个方法但又不提及它。

当然可以,这就是面向对象技术在做的事。

面向对象是指创建直接世界的对象。它是把数据和方法放在一起的内聚对象。它是把代码按直观的结构进行的一种组织。

谁给你讲的?

每个人都是这样理解的,绝对正确。

恩恩。使用面象对象原则你可以在不提及一个方法时来调用它。

你知道如何在一个面向对象设计中,对象间如何发送消息吗?

当然。

那你应该知道消息的发送者不知道接收者的类型吧。

这个取决于语言,在 Java 中,发送者要知道接收者的 Base type,在 Ruby 中,也要确定接收者能处理发送者所发送的消息。

是的。但这两种示例中,发送者都不知道具体的接收都类型。

恩,是的。

因此,发送方可以执行接收方的函数,而不需要指明接收方的确切类型。

我知道,但发送方依然依赖于接收方。

在运行时,是的。但在编译时,发送方的代码没有指明,或依赖于接收方的相关代码。事实上,代码层面,接收方依赖于发送方。

什么!发送方依然依赖于发送的 class。

来点代码吧。先看看 sender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package sender;

public class Sender {
private Receiver receiver;

public Sender(Receiver r) {
receiver = r;
}

public void doSomething() {
receiver.receiveThis();
}

public interface Receiver {
void receiveThis();
}
}

这是 receiver

1
2
3
4
5
6
7
8
9
package receiver;

import sender.Sender;

public class SpecificReceiver implements Sender.Receiver {
public void receiveThis() {
//do something interesting.
}
}

注意了!receiver 依赖于 sender。还要注意  SpecificReceiver 依赖于 Sender。另外还要注意 senderreceiver 一无所知。

你这作弊了。你在 sender 的类里引用了 receiver 的接口。

你开始懂了。

懂什么?

架构原则。当然发送者有它的接口,接收者必须要实现该接口。

这表示我必须使用嵌入对象,然后。。。

嵌套对象只是一种达到目的的方法,这里是其它的。

等下。这些东西怎么使用数据库,回到讨论的起点。

那看更多的代码。首先看个简单的业务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package businessRules;

import entities.Something;

public class BusinessRule {
private BusinessRuleGateway gateway;

public BusinessRule(BusinessRuleGateway gateway) {
this.gateway = gateway;
}

public void execute(String id) {
gateway.startTransaction();
Something thing = gateway.getSomething(id);
thing.makeChanges();
gateway.saveSomething(thing);
gateway.endTransaction();
}
}

That business rule doesn’t do much.
这个业务倒是很简单。

It’s just an example. You’d likely have many classes like this, implementing lots of different business rules.
这只是个示例,你可以多弄点类,实现更多的业务。

OK, so what’s that Gateway thingy?
恩,Gateway 是个啥?

It supplies all the data access methods used by the business rule. It’s implemented as follows:
它提供了业务所需的数据访问方法。它的实现如下:

1
2
3
4
5
6
7
8
9
10
package businessRules;

import entities.Something;

public interface BusinessRuleGateway {
Something getSomething(String id);
void startTransaction();
void saveSomething(Something thing);
void endTransaction();
}

注意哦,这个包名是  businessRules

恩,那 Something 呢?

这里描述了一个简单的业务对象。我把它放到  entities 的包里。

1
2
3
4
5
6
7
package entities;

public class Something {
public void makeChanges() {
//...
}
}

这里是 BusinessRuleGateway 的最终实现。这个类知道真实的数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package database;

import businessRules.BusinessRuleGateway;
import entities.Something;

public class MySqlBusinessRuleGateway implements BusinessRuleGateway {
public Something getSomething(String id) {
// use MySql to get a thing.
}

public void startTransaction() {
// start MySql transaction
}

public void saveSomething(Something thing) {
// save thing in MySql
}

public void endTransaction() {
// end MySql transaction
}
}

再次注意,在运行时业务方法调用数据库,但在编译时,database 包提及并依赖于 businessRules 包。

恩恩,我想我有点懂了。你在用一个多态来对业务隐藏数据库实现。但你依然对业务提供了数据库的接口。

不,不完全是。我们并没有尝试为业务提供所有的数据库。相反,我们只为业务创建了所需要的接口。这些接口的实现方来调用相应的数据库。

恩,但如果所有的业务需要所有的工具,然后你就不得不把所有的工具写在 gateway 的接口里。

看来你还是不大明白。

明白什么?这对我来说已经很清除了。

每个业务都为自己的数据访问定义单独的接口。

等等?

这叫接口分离原则。每个个业务类将只使用数据库数据的一部分。所以每个业务都提供一个接口去访问该部分数据。

这不就意味着要有一堆的接口,并且有一堆的类来调用其它的数据库类。

这才算开窍吗。

但这不乱吗,又浪费时间!为啥这样干啊?

你想又清晰,又省时间。

这简直是为了代码而代码。

相反,相对于那些不相关的决策,这才是最后要的架构。

这啥意思?

还记得你开始时说要成为一名软件架构师?你想做那些真正的重要的决策?

是啊,我是想这样啊。

在这些决策间,你想的是确定使用哪些数据库,哪些服务器,哪些框架。

恩,然后你说这不是重要的决策,这是不相关的。

是的,对架构师而言,重要决策,不包括使用什么样的数据库,服务器和框架。

但你总要先决定这些吧!

不,你不用。事实上,你更希望在开发周期更迟些时再做这些决定,这样你可以有更多信息。
有时,架构师早早的决定上个数据库,最后发现搞个文件写写就够了。
有时,架构师早早的上了个 Web 服务器,最后发现团队想要的只是一个 socker 接口。
有时,架构师早早的选定了一个框架,最后发现框架提供了一堆没用的功能,还增加了一堆有的没的限制。
可幸的是,团队架构师有一种方法论,可以将这些决策推迟到有足够的信息时再来做。
可幸的是,架构师将团队从速度慢、资源匮乏的 IO 设备和框架中隔离出来,使得团队能够创建快速、轻量级的测试环境
祝福那些架构师关心真正重要的事情的团队,并推迟那些不重要的事情。

胡扯。不知所云。

如果你还没有进入管理层,也许十年后你可以的。

版本规则介绍

▎认识版本号

这是 Windows 10 的版本号,相信在每日的使用与升级中,大家都有所目睹。


▎为什么需要版本

> 1. 对于软件工程,简单的说,因为有依赖。

现代软件工程,是模块和依赖构建,越大的规模,涉及的包越多,而依赖包会有如下更新情况:

在软件开发过程中,会有三种常见升级情况:

  • 修复问题,没有加新功能
  • 加入新功能,不影响当前功能
  • 大的变动,和现有版本不兼容

如果没有一些规则,对每个依赖库的升级,就会变成一种灾难。

所以我们需要一种约定、规范,来实现依赖之间的合作,升级,及让开发者能清晰的识别。

> 2. 另一种情况是,信息确认

通常人们说升级了一个新版本,用户需要来通过这个值确定是不是已经升到了最新。
其实,当用户在遇到问题时,客服、开发者需要知道用户的问题是出现在哪个版本下的。

从上可见,这种需求更多的适用于没有软件工程中库的依赖的顶级 APP。


▎常用版本格式

以上是当下最常见的两种通用版本格式,适用于主流应用,库及一些模块。


> SemVer | 语义化版本 2.0.0

版本格式:主版本号.次版本号.修订号,例如 1.9.15

版本号递增规则如下:

  • 主版本号:当你做了不兼容的 API 修改,
  • 次版本号:当你做了向下兼容的功能性新增,
  • 修订号:当你做了向下兼容的问题修正。

先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

该规范对于依赖库,非常实用。它可以通过通用的规范来做 Bug Fix 级别升级,或进行 Feature 级别的更新,事实也是如此。

- Bundler 实例

这里用 Ruby Gemfile 的管理器 Bundler 来举例:

1
2
3
4
gem 'nokogiri'
gem 'rails', '3.0.0.beta3'
gem 'rack', '>=1.0'
gem 'thin', '~>1.1'
  • = 指定版本 3.0.0,表示仅限制于该版本
  • >= 如字面意思,大于等于该版本都可升级
  • ~> 2.0 是指可以升级所有的功能更新 [‘>= 2.0’, ‘< 3.0’]
  • ~> 2.2.0 是指问题修复升级 [‘>= 2.2.0’, ‘< 2.3.0’]

- 最佳实践

在项目

  • 0.1.0 - Init 项目初始化
  • 0.2.0 - 加入了新的功能
  • 1.0.0 - 版本正式上线
  • 1.0.1 - Bug 修复
  • 2.0.0 - 重构了新的 API,与 1.x 不兼容

> ChronVer | 日期式版本 2019.05.19

Chronologic Versioning “日期版本”,软件版本按照年月日编排。

版本格式:A.B.C.D (Year.Month.Day.Change)

因为用户在使用场景上更在意的是版本的发布时间,语义化版本号对用户认知有一定的门槛。

如果是同一天发布多次,版本号如何定呢?

虽然官网讲了很多案例,但这种版本方式非常不适用在软件依赖包中,因为日期是没有功能的规则的。


> 对比

综上,SemVer 语义化版本适用于所有的情况。包括各种开源代码库,二进制包,终端应用产品。

在上家公司,让人印象最深的一个解释,PR 在给客户介绍产品时,指着下面的版本号,讲道,知道这个 6 代表着什么吗,代表着公司人的努力印迹,在几年的时间不断迭代,这已经是第六代产品了,那种自豪感,非常强烈。

SemVer 用在终端应用上的一个常见问题就是,在很长一个时间内,版本会一直维护在 1.xx.xx 上。

ChronVer 日期版本,更多适用于和用户产品相关的末端应用,像各种客户端,在场景上,使用者都是各行业者,在技术上它不被其它产品依赖。


▎错误示范

> 对 Docker 的吐槽

  • Docker Desktop 2.2.0.0
  • Engine 19.03.5
  • Compose v1.25.2
  • Notary 0.6.1
  • Kubernetes v1.15.5

天那,这都是什么样的版本命名呀。

Desktop 是一款面向用户的 UI APP,比较适合的方式是 ChronVer,用 SemVer 也合适,但这里用了四个字段来表示,不知最后一个代表什么。

而 Engine 更像是一个库,它被上面的 Desktop 依赖,这就表示它需要被知道是哪种类别的更新,更适合的版本方式是 SemVer。

Compose 和 Kubernetes 前面又加了个 v。

> 对 Golang 的吐槽

Golang 在 2018 2019 年最大的变化就是加入了 Go Module,那么来看看它是如何使用的吧:

1
2
3
4
5
6
7
require (
github.com/BurntSushi/toml v0.3.1
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.5.0
}

Go Module 起源于 8012 年,在此之前有各种语言作为参考,但很遗憾,依赖设计出这么丑陋的模块依赖。

  • 依赖配置不是由开发者指定,而是代码分析生成
  • 依赖中大量的库没有版本号,Go 默认使用 Git 日期和 Commit Sha1
  • 间接依赖也在配置中?这就不懂了,不是有 go.sum 吗。

做好一门语言,最难的部分就在于依赖库管理,但这不应该是 Go 的借口,毕竟不是单片机时代。


▎ REF::

解锁新年

今年是个暖冬,去年的此时,还下了一场小雪,而今年一切都平静了很多。临近年底,人却得了感冒,各种发炎,喉咙像冒了火,一直咳嗽,病怏怏的弄了好几天,吃药也不见效果。

时间飞逝,新年的倒计时还是如约而致,都想用一些特别的方式来致敬新的一天,当然对于自己来说,其实可选的也就是跑步了。临近的这两年也都有报名,但种种原因,空气质量、气温过低,熬夜等等原因,都让自己没能战胜被窝。而今年又恰逢生病。

朋友都建议感冒放弃,但对于我,这个起跑的意义似乎又大了一些,不止是一场跑步,而是一个态度,一种对待未来,对待难题的态度。于是乎,没有选择就是最好的结果。

定好了 5:20 的闹钟,相对于上海的比赛,会发现每次外地的比赛会有更多的休息时间,因为会住附近的酒店,酒店会有早餐,等等。

为了御寒,翻出来好久不穿的长裤,准备了 3 件跑步的短袖,又准备了一件外套。加了一件薄款的羽绒服,出门时用。

最后一个交易日,市场行情也琢磨不定,没再多花心思。

> 赛事

6 点上海的冬日,还是挺冷的,走在高架上,城市的建筑笼罩在淡淡的黑色中,房顶镶着金红色的边,一切都这般的美。南京路上的彩灯应该是通宵的,回想在它乡的傍晚,不禁感叹了上海这个不夜之城。

存包,预热,等开赛,哦,忘记说了,这次的迎新赛事是静安区举办的,从静安寺出发,过石门一路、上海站到前闸北公园。静安一直都很高大上,这次,终于有机会用另一种方式看看这个区域。

在存包里,没有脱掉里面的跑步外套,于是穿着有史最厚的衣服,开始漫漫征途,不知是不是冷的原因,耳机一直有种要掉的感觉,于是取了下来拿在手里。

南京路的灯光在晨光中漫漫暗了下去,随即转入其它的道路。边上的建筑也开始从金碧辉煌变得普通起来,陆续开始有民宅,街上的行人也开始多了。

大约过了 4 公里,才隐约感觉身上开始起热,这是多冷的天呀。

边上跑了一位静安公安的帅哥,一路上就见他和周边的警察打招呼,像是在自己的主场。

可能是不用担心散热问题,一路跑下来感觉很轻松,没有因为劳累而采用跟随策略,也很放空,所以全程很舒服的一个状态就差不多结束了。

赛事的最后一公里,是穿越一个隧道,下坡时那种感觉真的健步如飞,而平时所有的隧道都是开车经过的,跑起来的距离感明显不同。在后面的爬坡路段,因为临近终点,所以也是满速向前的,感觉心率都 180 了。

解锁城市

我很喜欢用脚步去丈量城市,这样的速度能让人看到更多,这样的疲惫更让人印象深刻。

随车开过的地方,总像两个场景,人在车里,而城市在车外。

生活需要感悟,用的是慢速度。

> 祝语新年

拿到奖牌,拍了两张照,到附近的咖啡厅休息,临窗的位置刚好有阳光晒进来,照在身上,暖暖的,一杯太妃的甜唤醒一年的美好。

我是个定了计划会想尽千方百计去完成的人,这也是为什么会在意新年第一天的计划。

保持好节奏,新的一年,接着干!全力冲梦想!

2019 回顾 - 我的设备

2019 又要结束了,回望这一年真的是经历颇多,感触也不少。想写一些东西来记录下,但又不知如何下笔。打开电脑,盒上电脑,换台电脑,反复间,好像一个轮回,于是想找一些简单的话题先开始写写。

作为一个数码爱好者,就从身边的设备写起吧。

> 显示器

年初时心痒痒,一心想入手点东西,思来想去,还是换个显示器吧,当时在公司,两台 U2518 来回接线的体验一点都不好,就定来下这次买显示器,指标上一定要支持 USB Type-C,这样一根线可以充电、传数据、接显示器。选了好久,甚至做了张表单,最后才定了 U3218Q(从官方通过 QQ 下单的感觉很复古),主要参数:

  • 32寸
  • 4K
  • USB Type-C (内建 DP1.4,PD 60W,USB 3.0)

32 寸大屏配上窄边框的视觉效果非常好,4K 的分辨率显的字有点小。但常用的工作模式依然是 4K,把编辑器的字体放大点就好,也偶尔也尝试在 3360x1890 下。字体大小会更舒服一些,但会有缩放产生的模糊。

最近需要聚焦的工作,全新尝试 1920x1080,没想到效果非常的好,一来显示面积小了,就不会去打开一些多余的软件摆在可视区域,减少了视觉噪音。二来,这就是标准的 HiDPI,清晰度非常的高,视觉上带来了全新体验。

如果是 6K/7K,打开 HiDPI 便是极好。

年中 Apple 预告了 Pro Display XDR。这简直就是爱情的模样了。直到价格出来 :(。
如果 DELL 能出这么一款,就用 LCD 普通面板,价格在 1W 多,该多好呢。

> 电脑

18 年底入了台 15 MacBook Pro 中配,16G 内存加升 512 SSD,一切正常,表面上贴满了标志,经常连笔记本键盘使用,整机非常新。

这个机器的键盘依然是蝶式,有好几个朋友的同款都已经维修过了。

因为太过正常,所以没什么感觉,就是最近经常出差,带上这么重的一东西,肩膀有点受不了。即使是换了双肩包后。

公司的一台 DIY PC,因办公地点原因,现在家使用,了解了下 UEFI 并安装好最新的 macOS Catalina 后,这台 Hackintosh 成了现在的办公主力。

毕竟 32G 内存,i7-8900K 的配置做什么都不在话下,速度带来的幸福感提升还是很明显的。

要说问题吗,就是这么大的机箱,在房价的面前显的有点昂贵。

用完黑苹果后,也有考虑过 NUC,但还是等它出了 USB-C 供电版本后再说吧。

当然如果 iMac 出一款 32 寸屏幕的话,那便是极香的。毕竟是一直想再入一台 iMac 的,直到 3218 用过后再也回不到 27 的屏了。

> 键盘鼠标

从日本带了几把 Filco Minila Air,留下了 Brown 和 Black,但 Black 实在太重了,出掉后,又跑秋叶原入了一把双模 87 Brown,当然还是大F。

论手感 Minila 真的很赞,很紧致,87 有点旷的感觉。同样的茶轴,有两种不一样的感觉。

但蓝牙 3 的品质真的是头疼,现在 87 是用有线连接使用,Minila 则在包里随我征战各方。

Magic Mouse 1 已经用了大约 7 年了,算是长情,虽然一直会断开重联,但也没想过要换掉,就一直在包里随用随取。

她的灵魂在于上面那个触摸板。

G903 陪了我一年,电池已经不怎么行了。但优雅的造型和清晰的指向,清脆的点击,都深得我心。

当下这款有个 Bug,按键会失灵,官方会换新,这就是我的期待。

> 手机

今年入手了一台小米 9SE,主力机器依然是 iPhone X。

入米九的原因主要是公司的华为测试机太难用了,而 iPhone 不支持 NFC,这对于上下班刷门卡来说,太不方便了,同时还有夜景的需求。9SE 的大小和 iPhone X 一致,叠放时比较方便拿。

iPhone X 表现依然超凡,外形上也很漂亮,像件实用的艺术品,一直在服务着,从没掉过链子,这一点小米就完全不行,在稍差点的环境中,简直就是个 Bug。

iPhone 11 在店里感受了下,说实话,真心没感动。更像是疲劳期,对新的东西也没什么更高的期待。

iPad Pro 今年的使用率稍微高一点点,主要是各种画图,也就是那支笔。经常拿出来画一些架构图,流程图之类,或做一些速记。

> 手表

Apple Watch 4 好像是今年买的。一直带着,设定好了指标,坚持完成着,几乎没有间断。

常和朋友开玩笑说:一直以为自己保持身材的秘方只是贫穷,后来看到 Apple Activity 记录时,才发现还是有很多的努力的。

> HomePod

记不清是什么时间入的了,在香港广场,带着她坐 71 路回的家。

使用频率还可以。满意度很高,虽然偶尔也会断线,但相对于之前的那款水晶音箱来说,这连接便捷性,连接品质和音质都让人很满意。

安安静静的放在书房的桌面,你用她时,她都在,无论从哪个设备连接,都很快,很少出问题,简直就是完美了,除非哪天想不开,叫了声 Hey Siri。

> AirPods

AirPods 2 是刚出的第二天在香港广场入的。这款产品是今年所有产品里使用率最高的了,几乎每天都带在耳朵上。

起初担心跑步会掉,还用个头巾包住,后来各种长跑、马拉松,耳机都是必备了。

音质不做评论,不专业。但苹果对蓝牙的把控,真的是绝世高手,蓝牙耳机玩过很多,但 AirPods 这种多机共联,而且如此稳定,快捷的,也只能是苹果了。

不便的话,就是调节音量了。

AirPods Pro 去南京东小试了下,有点想吐,是中号的耳塞,Apple 说有马达通风来保证耳道压强,但自己从小就不喜欢入耳式。

> 小结

这些设备早已超出自身的范畴了,她们带来各种功能、便利性的同时,也在影响着生活的方式。

安静时,找个沿江的 Bar,听着音乐,发发呆,做些有创造性的工作,亦或写写人生感悟。傍晚时分,用新的夜景模式去拍拍城市的灯光。

2020 生活还会继续,而这些设备也会继续安静的服务着生活。

Homebrew in Action [revised]

▎介绍

Homebrew 作为 macOS 上强大的包管理器,为系统软件提供了非常方便的安装方式,独特式的解决了包的依赖问题,并不再需要烦人的 sudo,一键式编译,无参数困扰,值得拥有。


▎安装

Homebrew 依赖于 Xcode Command Line 和 Ruby。可以在终端中输入下面命令查看本机的 Ruby 安装情况。

1
$ ruby -v

确认 Ruby 可被正常使用后,继续输入下面命令,进行 brew 的安装:

1
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

> 安装背后

Homebrew 将本机的 /usr/local 目录初始化为 Git 的工作树,并将目录所有者变更为 $USER,也就是你当前所操作的用户,所以以后的操作都不再需要 sudo,这是安全的,全新的 macOS 默认是没有该目录的,也就是说该目录并非是系统所有。

/usr/local 下创建以下目录:

1
2
3
4
5
6
- bin          用于存放所安装程充的启动链接(相当于快捷方式)  
- Cellar 所有 brew 安装的程序,都将以[程序名/版本号]存放于本目录下
- etc brew 所安装程序的配置文件默认存放路径
- Library Homebrew 系统自身文件夹
+– Formula 程序的下载路径和编译参数及安装路径等配置文件存放地
+– Homebrew brew 程序自身命令集

上述为 Homebrew 所用用到的几个目录,其它也有使用,但于 brew 关联不大,请参考 Unix 系统相关资料。

最后,将 brew 的程序启动项放到 /usr/local/bin 下,所以,必须保证该目录属于 Bash 的环境变量,关于如何配置环境变量,请查阅相关资料。


▎开始使用

用 brew 安装第一个程序,这里使用 node 作为示例

1
$ brew install node

有些程序会同时提供两个下载,一个为稳定版,另一个为尝鲜版(通过head参数获取),如果已安装过稳定版的程序后,想同时安装尝鲜版可以通过 force 参数来完成。

1
2
$ brew install --HEAD node
$ brew install --force --HEAD node

接下来可以看到 brew 启动下载,通过 Formula 目录中的 node.rb 文件中的路径去下载源码,然后就是 configure,同时指定 prefix 到 Cellar 目录,接下来 make & install。最后,将 node 的运行文件通过软链接到 /usr/loca/bin 下,方便命令行启用。因为 /usr/local 的目录所有者为当前用户,所以这里不再需要 sudo 进行提权,很方便。

然后,可以很方便的通过以下命令来卸载所安装程序

1
$ brew uninstall node
1
2
# 显示当前所有已安装程序的名称
$ brew list

Homebrew 提供 update 参数来对系统进行更新,其实就是对 Formula 目录进行 Git pull,将服务器最新的程序安装配置文件同步到本地,同时,会标明新加入和更新,同时会对本机已经安装并有更新的程序用*号来标名。

1
$ brew update

通过 outdated 命令查看哪些 Formula 有更新

1
$ brew outdated

如果看到已安装程序有更新版本,像 Node.js 刚发布 13.5.0,这里运行完 brew update,会看到node*的标志出现,运行以下命令来安装最新版的 Node.js 13.5.0。

1
2
$ brew upgrade [formula]
$ brew upgrade

这时会出现和安装一样的过程,再次在终端中输入 node -v 可看到已是最新版本。但如果同时出现多个程序的升级,一个个通过程序名升级未免过于麻烦,这时,试下下面的这条命令,所有有更新的程序都会被自动下载,编译安装。

安装完最新版本后,旧的版本会自动删除,但仍有旧版本存在的可能性,可通过 cleanup 命令来删除,和 upgrade 一样,提供单程序和所有程序旧版的移除。

1
2
$ brew cleanup [formula]
$ brew cleanup

如何知道 Homebrew 官方是否有某个程序的安装呢,通过 search 命令来查看

1
$ brew search [formula]
查看已安装程序信息,brew 提供了 info 和 list 参数:
1
2
3
4
5
$ brew info [formula]
显示该程序的安装路径,相关提示及 formula 服务器所在位置

$ brew list [formula]
# 显示该formula的所有文件列表

▎介绍 Bottles

Mac 的平台特性比较标准和简单,比如说只有不多的 macOS 版本,CPU 架构也会比较简单只有 X64 64Bit。

在这种条件下,如果每个人都要下载源码,然后再 build,会浪费大量的时间。如果能按平台、架构来提前构建好,直接使用编译后的二进制,就会省下很多时间。

这就是 Homebrew Bottles。

brew 在安装应用时,会先去查一下对应机器和环境的 Bottle 是否存在,如果存在就下载。如果不存在或下载失败,则进行二进制源码下载、构建安装。


▎介绍 Cask

这是 cli 的部分:https://github.com/Homebrew/homebrew-cask
core,也就是最常装的 app,都在这里:https://github.com/Homebrew/homebrew-core
https://github.com/Homebrew/homebrew-cask-fonts

Homebrew-Cask extends Homebrew and brings its elegance, simplicity, and speed to macOS applications and large binaries alike.

一些传统软件的安装都是手工的点击下一步,如何实现自动化呢,Brew cask 就是做这件事情的。

像一些图形软件,商业软件,都可以通过 Cask 来进行安装,像 Google Chrome, VSCode,iTerm2 等。

homebrew-cask 现在默认集成在主程序中。

主要命令

1
2
3
4
5
6
7
$ brew cask tap 
# brew tap homebrew/cask-fonts

$ brew search [formula]

$ brew install [formula]
# 这里并没有用 brew cask install

常用的 Casks

cask-fonts

一些开源的字库,都在这里被包含了。


▎Formula 分析

所有的 formula 都在这个仓库里:https://github.com/Homebrew/homebrew-core

在终端中输入 brew edit [formula],启用默认编辑器打开该 formula。

1
2
3
4
5
6
7
8
9
10
class Wget < Formula
homepage "https://www.gnu.org/software/wget/"
url "https://ftp.gnu.org/gnu/wget/wget-1.15.tar.gz"
sha256 "52126be8cf1bddd7536886e74c053ad7d0ed2aa89b4b630f76785bac21695fcd"

def install
system "./configure", "--prefix=#{prefix}"
system "make", "install"
end
end

可以看出几个属性:

  • url 是指当前稳定版的下载路径
  • head 是尝鲜版的路径,可以使用 github 项目路径
  • homepage 指向到 formule 的官方网站
  • sha256 则对应 formula 的下载文件校验值
  • depends_on 属于用于解决依赖关系,brew如发现当前程序依赖其它程序,便会检查依赖程序是否已经安装,如未安装,则会先安装依赖程序的稳定版。
  • options 用来指定默认编译参数
  • install 则是安装的过程,指定编译参数,make & install
  • caveats 用作安装完成之后的输出提示。

> 制作自己的 Formula

Homebrew 提供 create 命令来快速创建一个新的 Formula,通过指定下载文件源码路径,或 github 程序位置(含可用版版本号,Git Tag)即可,创建完成后,会自动打开所新建formula的配置文件,填入homepage等参数,然后在终端中通过audit命令来进行检验。

1
2
$ brew create http://example.com/foo-0.1.tar.gz
$ brew audit foo

当然,如果你想将该程序保存到github上,可以直接fork Homebrew的项目,然后通过git进行commit,push到自己的fork中,也可以将该文件合并到官方的库中,参见github。

1
2
$ git commit Library/Formula/foo.rb
$ git push

▎国内用户

因为访问 github 及 bottles 比较慢,所以这样使用替换访问源的方式,来加速。

分别是 homebrew 本身,以及 homebrew bottles 两个部分的替换。

> 替换及重置 Homebrew 默认源

Homebrew 自身是使用 Github 来更新的,所以替换的方法是将 git 地址指向到新的境内的地址上即可。

- 清华

1
2
3
4
5
6
7
git -C "$(brew --repo)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git

git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git

git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git

brew update

- 还原为默认

1
2
3
4
5
6
7
git -C "$(brew --repo)" remote set-url origin https://github.com/Homebrew/brew.git

git -C "$(brew --repo homebrew/core)" remote set-url origin https://github.com/Homebrew/homebrew-core.git

git -C "$(brew --repo homebrew/cask)" remote set-url origin https://github.com/Homebrew/homebrew-cask.git

brew update

> Homebrew Bottles 源

1
2
3
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles' >> ~/.bash_profile

source ~/.bash_profile

▎问答:

Q: 如何将第三方提供的 Formula 在本地使用?

A: Homebrew 暂不提供第三方源的支持,但官方已表示未来会加入该功能,所以现在能做的就是将第三方 formula 下载后,放到 /usr/local/Library/Formulas/ 然后用文件名作 formula 正常安装。
这里也提供一个非官方的源,安程程序会更多一些:https://github.com/adamv/homebrew-alt

Q: 关于 Ruby 的安装

A: 对于 Ruby 自身,更推荐使用 rbenv 来进行多版本的安装管理。

Q: 如何安装旧版本的 Formula?

A: 参见 http://stackoverflow.com/questions/3987683/homebrew-install-specific-version-of-formula

Q: 尝试着加了几个 Formula,怎么删除?

A: 到 /usr/local/Library/Formula 目录下,找到所加的formula对应文件,删除即可。如果你了解git,也可到/usr/local下运行 git clean -d -f,来清除未被track的脏数据。


▎参考:

OpenJDK 配置使用

OpenJDK 是 JDK 的开放源码版本,以 GPL 协议的形式发布,而 JDK 则是采用 JRL 协议,对商业使用进行收费。


▎JDK Versions


> OracleSDK / JavaSE

根据付费商业许可(但可供开发使用) , 适用于那些不想使用 GPLv2 + CE,或者将 Oracle JDK 与 Oracle 产品或服务一起使用的用户。


> Oracle OpenJDK

Oracle 自身还维护着另一个协议的 Java 版本:OpenJDK,像是 RedHat 和 CentOS 的关系。

1
2
3
4
5
$ java --version

openjdk 14 2020-03-17
OpenJDK Runtime Environment (build 14+36-1461)
OpenJDK 64-Bit Server VM (build 14+36-1461, mixed mode, sharing)

> AdoptOpenJDK

- AdoptOpenJDK builds

该版本也是免费的、完全无品牌的 OpenJDK 版本,基于 GPL 开源协议(+Classpath Extension),以免费软件的形式提供社区版的 OpenJDK 二进制包,公司也可安全且放心使用。与由 Oracle 的 OpenJDK 构建版本不同,这些版本会提供更长的支持,像 Java 11 一样,至少提供 4 年的免费长期支持(LTS)计划。

AdoptOpenJDK 是一个由社区驱动的项目,如果其他群组在 OpenJDK 的源码仓库中创建和发布了安全修复程序,它们也会提供构建。 IBM 和 Red Hat 也曾表示他们打算提供这些安全补丁。

1
2
3
4
5
6
7
8
9
10
brew tap AdoptOpenJDK/openjdk
brew search openjdk


Where version is:

adoptopenjdk
adoptopenjdk11-openj9
adoptopenjdk12
adoptopenjdk13

▎macOS x64 OpenJDK 安装

Make sure you have downloaded the latest macOS x64 jdk binary to a directory that will not move or be deleted, and use Terminal/Command Prompt to cd into it.

手动下载安装

提取 .tar.gz。将下载好的文件放到:/Library/Java/JavaVirtualMachines

使用 homebrew
1
2
brew tap AdoptOpenJDK/openjdk
brew cask install adoptopenjdk13

该安装方式并不会自动配置 ENV,需要按下面方式指定配置:

将指定版本的 Java 添加到您的PATH中:
1
export PATH=$PWD/jdk-11.0.2+9/Contents/Home/bin:$PATH
1
2
3
4
5
6
# Set PATH for JAVA
# ===================
# export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.0.1.jdk/Contents/Home
export JAVA_HOME=/Library/Java/JavaVirtualMachines/openjdk-14.jdk/Contents/Home
export CLASS_HOME=$JAVA_HOME/lib
export PATH=$JAVA_HOME/bin:$PATH
检查Java是否正确安装:
1
2
3
4
5
$ java --version

openjdk 14 2020-03-17
OpenJDK Runtime Environment (build 14+36-1461)
OpenJDK 64-Bit Server VM (build 14+36-1461, mixed mode, sharing)

▎支持开源软件的哲学思考

软件产业已经普通认同开源软件推动了行业的发展。 由于其开源自由的性质,OSS 许可证允许人们修改和改编其他人的软件,而不用担心会受到惩罚。

许多人也喜欢 OSS 软件中 的 像啤酒一样免费 的性质,但这给作者带来了巨大的成本! 像其他任何人一样,软件开发人员需要生存。 像 Oracle 供应商 (每年至少投入上千万美金) 投入大量的资金 到 Java SE 的开发上,他们也需要获取相应的补偿。

所以,尽管你不会被强制付费去使用一个软件,但有时我们最终用户也应该思考一下如何能支持 Java SE 生态系统,以确保它有一个持久的未来!

谈谈 iPhone 11

Camera

这次的新品弥补并超越了这两年 iPhone 在拍照和夜景上的不足,特别是那个超广角,Keynote 上一大段时间都在讲 A13 和相机之前的调优,这才是 Apple 出品,也显得安卓阵营对硬件的整合能力不足。

Display / Super Retina XDR

Pro 上的那块屏幕也达到了无限好,看参数应该就是 Samsung Note 10 上的那块,真香。

Performance

A13 的 7nm 各种快,快来带的好处就是可以更短时间做完事然后关掉一部分处理单元从而省电。

一直领先的 CPU & GPU 没啥好说的。
双 AirPods 支持,虐狗。
Wi-Fi 6,这个比较给力。
UWB 支持,更快的网络。

Design

摄像头是两段突出,感觉 Apple 太不上进了,怎么会有这样的产品出现,而且丑,真丑,几个月了都不敢相信这是 Apple 设计出来的产品。

相对来说 iPhone 11 的双摄好看一点点。

5G

5G 果然缺失,对于 Android 品牌来说不是大问题,产品线多,发布周期一般 4-7 个月,可以很快补上,但 iPhone 周期过长。

这本来也不是大问题,5G 发展本身也需要周期,但国内最诡异的是运营商下调 4G 速度,很明显最近一段时间手机网络变慢。

Connector

Type-C 依然没给,差评,号称 100% 环保,但却让人多带一根线。

虽然上了 18W 但又如何,现在敌营无线充都 40W 了。

Price & Price

价格,iPhone 11 比上代 XR 便宜了 1000,但 Pro 的价格没怎么变,256 的两个产品也在 10,000 左右,贵,真贵,非常的贵了。

以前买 iPhone 真不眨眼出了买就是了,现在手机的技术红利期也过了,机型间差别很小了。1W 的手机和 3K 的比都没显得突出。那你是愿意选 3K 的每年换还是用一个 1W 的没有 5G 的手机呢?

还有一个问题,iOS 的优化这次做的太好了,以致于 iPhone X 特别流畅,如果对拍照没有特别需求,有升级的必要吗?

编程最佳字体

最近一直在寻找一款适合自己书写和编程的字体。

中文是没有什么问题的。主要是英文。

需求是等宽字体,数字字母清晰可辨,像 0O1lI 这种。

对于编程最火的几个字体是:

  • Source Code Pro
  • SF Mono / Menlo / Monaco
  • FiraCode / Hack

> Source Code Pro

Adobe 出品,在一些常见的开源编辑器都是默认首选。

> SF Mono / Menlo / Monaco

这是一个递进关系的相关字体。

出新的系统已经换成了 San Francisco Mono

> FiraCode

也是开源字体里选择比较多的。

这几个字体都属于矮胖型的。有点不是很喜欢。

> Pragmata Pro

Pragmata Pro 从样式上是我最喜欢的,但是收费颇高,而且非 ASCII 标点支持的很差,对于需要大量中英混排的人来说,简直是个灾难。

如果它能解决标点问题,顺便把授权费用降低点,也会是一个不错的选择。

> Noto Sans Mono CJK SC

不过我最终还是找到了一个不错的字体。

  • 首先这个字体是中英文一体的,做了统一适配,不会出现中文和英文混排时的冲突。
  • 英文字体属于瘦高型的,符合需求。

缺点是:

  • 不能单独安装。需要安装整个 Noto Sans CJK SC
  • 对数字 0 没有中间标识。不易区分。
  • 和 Noto Sans Mono 不同,这款字体的英文是专门为 CJK SC 设计过的。

如何下载安装

https://www.google.com/get/noto/help/cjk/

NotoSansMonoCJKsc 一共有两个字重,分别对应下载文件:

或者通过 brew 来进行安装。

1
brew install font-noto-sans-cjk-sc

适用 APP

  • Markdown Editor
  • VSCode
  • iTerm

基本上常用的编辑器就是 VSCode,Markdown 以及最常用的终端。

这是 VSCode 的 FontFamily 设置:

1
'Noto Sans Mono CJK SC', 'SF Mono', 'Fira Code', Hack, Menlo, Monaco

snap/snapcraft 使用指南

为每个 Linux 桌面、服务器、云端或设备打包任何应用程序,并且直接交付更新。

snapcraft 是一个正在为其在 Linux 中的地位而奋斗的包管理系统,它为你重新设想了分发软件的方式。这套新的跨发行版的工具可以用来帮助你构建和发布 snap 软件包。接下来我们将会讲述怎么使用 CircleCI 2.0 来加速这个过程以及一些在这个过程中的可能遇到的问题。

# 介绍

> Channels

Channels allow users to subscribe to different release cadences for an app, be it by major version bumps or in-development releases.

$ sudo snap install docker –channel=17.03/stable

# 常用使用说明

1
2
3
4
5
6
7
8
9
$ snap download hello-world

# Fetching snap "hello-world”
# Fetching assertions for "hello-world”

# $ sudo snap ack hello-world_27.assert
# $ sudo snap install hello-world_27.snap

hello-world 6.3 from 'canonical' installed
1
2
3
4
5
$ snap list

# Name Version Rev Developer Notes
# <snip>
# hello-world 6.3 27 canonical -

# 背后的原理

现在系统装软件,都是用这个装的了。

/snap 目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
:/snap$ tree -L 1
.
├── bin
├── chromium
├── core
├── core18
├── gnome-3-26-1604
├── gnome-calculator
├── gnome-characters
├── gnome-logs
├── gnome-system-monitor
├── gtk-common-themes
├── node
├── postman
└── README

# 常用软件安装

Package any app for every Linux desktop, server, cloud or device, and deliver updates directly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
find
abort Abort a pending change
ack Adds an assertion to the system
alias Sets up a manual alias
aliases Lists aliases in the system
buy Buys a snap
changes List system changes
connect Connects a plug to a slot
disable Disables a snap in the system
disconnect Disconnects a plug from a slot
download Downloads the given snap
enable Enables a snap in the system
find Finds packages to install
get Prints configuration options
help Help
info show detailed information about a snap
install Installs a snap to the system
interfaces Lists interfaces in the system
known Shows known assertions of the provided type
list List installed snaps
login Authenticates on snapd and the store
logout Log out of the store
prefer Prefer aliases from a snap and disable conflicts
refresh Refreshes a snap in the system
remove Removes a snap from the system
revert Reverts the given snap to the previous state
run Run the given snap command
set Changes configuration options
tasks List a change's tasks
try Tests a snap in the system
unalias Unalias a manual alias or an entire snap
version Shows version details
watch Watch a change in progress
  • Download snap “core” (3017) from channel “stable” (The download has been cancelled: context canceled)

core 16-2.28.1 canonical - snapd runtime environment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ sudo snap install --help
Usage:
snap [OPTIONS] install [install-OPTIONS] <snap>...

The install command installs the named snap in the system.

Application Options:
--version Print the version and exit

Help Options:
-h, --help Show this help message

[install command options]
--channel= Use this channel instead of stable
--edge Install from the edge channel
--beta Install from the beta channel
--candidate Install from the candidate channel
--stable Install from the stable channel
--devmode Put snap in development mode and disable security confinement
--jailmode Put snap in enforced confinement mode
--classic Put snap in classic mode and disable security confinement
--revision= Install the given revision of a snap, to which you must have developer access
--dangerous Install the given snap file even if there are no pre-acknowledged signatures for it, meaning it was not
verified and could be dangerous (--devmode implies this)
--unaliased Install the given snap without enabling its automatic aliases

一个问题,这玩意太慢了,有没有办法加个代理,换个 mirror
或者先离线下载,然后再手动安装呢????

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Usage:
snap [OPTIONS] <command>

Install, configure, refresh and remove snap packages. Snaps are
'universal' packages that work across many different Linux systems,
enabling secure distribution of the latest apps and utilities for
cloud, servers, desktops and the internet of things.

This is the CLI for snapd, a background service that takes care of
snaps on the system. Start with 'snap list' to see installed snaps.


Application Options:
--version Print the version and exit

Help Options:
-h, --help Show this help message

Available commands:
abort Abort a pending change
ack Adds an assertion to the system
alias Sets up a manual alias
aliases Lists aliases in the system
buy Buys a snap
changes List system changes
connect Connects a plug to a slot
disable Disables a snap in the system
disconnect Disconnects a plug from a slot
download Downloads the given snap
enable Enables a snap in the system
find Finds packages to install (aliases: search)
get Prints configuration options
help Help
info show detailed information about a snap
install Installs a snap to the system
interface Lists snap interfaces
interfaces Lists interfaces in the system
known Shows known assertions of the provided type
list List installed snaps
login Authenticates on snapd and the store
logout Log out of the store
logs Retrieve logs of services
prefer Prefer aliases from a snap and disable conflicts
refresh Refreshes a snap in the system
remove Removes a snap from the system
restart Restart services
revert Reverts the given snap to the previous state
run Run the given snap command
services Query the status of services
set Changes configuration options
start Start services
stop Stop services
switch Switches snap to a different channel
tasks List a change's tasks (aliases: change)
try Tests a snap in the system
unalias Unalias a manual alias or an entire snap
version Shows version details
watch Watch a change in progress
whoami Prints the email the user is logged in with.

# 实例

安装 Node 10 LTS

https://snapcraft.io/node

sudo snap install node –channel=10/stable —classic

竟然自带了 yarn

删除呢?