無標題文檔

分享下自己的家庭网络拓扑和结构

上次对于树莓派搭建的家庭网络可能说了个大概,其实根据每个家庭的需求不同,网络的拓扑其实也会有些调整。下面从应用的切面角度分享下目前家庭的网络拓扑情况,这个是个大概的图:

Network

旁路由

家里的路由器性能其实不差,但考虑其实很多的工作(网关、代理、端口转发、等等)如果在路由器上处理,往往路由器的性能会打个折扣,同时也会影响稳定性。

同时,其实路由器上配置多了以后,对于以后更换路由器重新配置也是件比较麻烦的事情。所以,我使用了树莓派分担了部分路由器的任务,主要是些网络寻址以及服务发现等相关的服务,例如 DNS、DHCP 以及防火墙等。

DNS 这块主要使用 DNSMasq,做了很多使用这个应用应该做的事情,例如「境外网络加速」、对接 Consul 作为内部的无痛服务发现、网络内容过滤(去广告)等功能,同时为了加块解析速度本地加大了缓存。

后面考虑由于 斐讯 N1 综合性价比比树莓派高出很多,因此会考虑这部份的服务将往 N1 的硬件上迁移。同时,树莓派打算用来做 4G 的网关,搭配 UPS 这样子家庭网络就不会断网了。

监控

监控使用了 Prometheus 以及 Grafana 同时独立管理,具体的可以参看这里

应用层

应用层主要使用了虚拟机,系统方面主要是 CentOS 以及 Manjaro 以及 Debian 还有几台 FreeBSD。主要应用场景分别是用来做 CI、以及数据抓取(爬虫)以及其他些比较费时的操作。

虚拟机的寻址主要使用 Consul 搭配 DNSMasq 完成,写个 go 小程序用来注册以及健康检查。其中台虚拟机还对接了统一的通知服务,例如收发邮件以及短信。

数据层

数据层相对比较简单,使用了两台物理机:分别是台联想的 i7 四代小主机、16g 的内存(性能不差因此拿来开虚拟机用);以及还有台相对比较弱的奔腾机接了 Raid1 阵列柜,专门用来做数据存储和同步。后面会考虑使用黑群晖,将这部份硬件的可靠性强化下。

物理机

以上

在 Xorg 中配置苹果的蓝牙键盘

目前同时有台 MacBook Pro 以及 Thinkpad X1,所以想同时使用苹果的蓝牙键盘链接,碰到了键位映射的问题,所以记录下。这次折腾的主要原因是 Thinkpad 的左下角功能键的键位和苹果键盘是不一致,如图:

Apple Keyboard

最主要是对比 Thinkpad 的 Win(Meta) 键和 Alt 和苹果蓝牙键盘的 Apple 和 Alt 是相反的,如果同时使用会有转换的过程,因此需要转换下键位。

硬件方面链接蓝牙的过程就不复述了,Arch Wiki 中有很详细的说明。我使用的是 bluetoothctl 连接硬件,同时使用 BlueMan 作为 GUI 的前端。同时将 BlueMan 加入到 i3wm 的启动脚本中,能够做到自动链接,方便。

键盘链接成功以后,使用 xinput list 查看对应的 device id:

$ xinput list 
⎡ Virtual core pointer                        id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                  id=4    [slave  pointer  (2)]
⎜   ↳ Synaptics TM3072-003                        id=11    [slave  pointer  (2)]
⎜   ↳ Logitech M570                               id=14    [slave  pointer  (2)]
⎜   ↳ TPPS/2 IBM TrackPoint                       id=12    [slave  pointer  (2)]
⎣ Virtual core keyboard                       id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard                 id=5    [slave  keyboard (3)]
    ↳ Power Button                                id=6    [slave  keyboard (3)]
    ↳ Video Bus                                   id=7    [slave  keyboard (3)]
    ↳ Sleep Button                                id=8    [slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard                id=9    [slave  keyboard (3)]
    ↳ ThinkPad Extra Buttons                      id=10    [slave  keyboard (3)]
    ↳ Integrated Camera: Integrated C             id=13    [slave  keyboard (3)]
    ↳ Apple Inc. Magic Keyboard                   id=15    [slave  keyboard (3)]

其中,能够看到苹果的蓝牙键盘的 device id 是 15,需要暂时记下这个值。然后使用

$ setxkbmap -option altwin:swap_alt_win -device $DEVICE_ID

就可以测试 Win 键和 Alt 键是否对调成功。顺便说一句,对应的还有个命令可以将 Caps Lock 利用起来:

$ setxkbmap -option ctrl:nocaps -device $DEVICE_ID

这样子 Caps Lock 就映射到 Ctrl 键了。具体更多的设置可以在这里 查看(我是 Arch Wiki 的搬运工 😄)。

这样子还有点不方便的地方,就是每次链接蓝牙键盘的时候都要跑下这个命令,于是还是考虑到使用 udev 脚本去自动化。下面是我的 udev 脚本,可以供参考:

ACTION=="bind", SUBSYSTEM=="hid*", DRIVER=="*apple*", RUN+="/usr/bin/sudo -i -u <user> /<path>/key-mapping.sh"

然后还需要考虑到插入 USB 键盘的情况,这再多加一条规则:

ACTION=="bind", SUBSYSTEM=="usb", ENV{ID_VENDOR}=="Apple*", RUN+="/usr/bin/sudo -i -u <user>  /<path>/key-mapping.sh"

其中,key-mapping.sh 是复制下上面命令的脚本。

然后,将这个命令保存到个 rules 文件中,例如我的文件命名是 90-bluetooth-keyboard-mapping.rules 然后放到 /etc/udev/rules.d

最后,重启 udev 规则 sudo udevadm control -R 即可。

- eof -

Arch Linux 下安装 EM7345 4G 网卡小记

💊 后记(10/18):人生苦短,后来发现安装完 Manjaro Linux 以后,这个 4G 网卡已经都配置好可以直接使用。

-

一直使用台 ThinkPad X1 Carbon 用来当做开发用机,使用方面都很稳定系统方面选择安装了 Arch Linux 。

由于硬件方面并不是算很新,所以系统支持的硬件都很顺利。还注意到 X1 Carbon 支持 4G 网卡,手头上刚好有一张联通的流量卡,考虑能否用起来。但是打开了盖板以后发现只是预留了插槽和天线,并没有 4G 网卡模块。

https://friable.rocks/_/2018_09_20/1537424625.jpg

根据 ArchLiux 的文档,淘宝上找了家靠谱的卖家,名为  Sierra EM7345 的模块很便宜,原装拆机的只要一百多,而且对 Linux 的支持很好,于是下单。

还是要夸下 ThinkPad 的,硬件安装非常方便,拧开螺丝就可以安装上去。硬件安装好了以后,启动使用 # lsusb 就能看到硬件了。

然后安装对应的软件包:

# pacman -S usbutils usb_modeswitch modemmanager mobile-broadband-provider-info

但是启动 ModemManage 的时候发现了问题,有报错信息:

ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:14.0/usb2/2-1': not supported by any plugin
ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:19.0': not supported by any plugin
ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:1c.1/0000:04:00.0': not supported by any plugin

好在 ArchLinux 的 Wiki 上有对应的信息说明,对应的应该是 udev 的权限问题。于是增加相应的配置文件,发现并不成功。然后搜索了一圈,并没有对应的解决方案。

后来,在 rules.d 目录中发现了另外个文件 /lib/udev/rules.d/77-mm-sierra.rules 里面虽然没有对应的 Vendor, 但是从注释可以看出来这是 Sierra 相对应的 udev 权限配置文件,于是将配置加入到 /etc/udev/rules.d 其中。

注意,直接修改 /lib/udev/rules.d/ 目录的 rule 文件可能在下次更新的时候被覆盖掉。

ACTION!="add|change|move|bind", GOTO="mm_sierra_end"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1199", GOTO="mm_sierra"
GOTO="mm_sierra_end"

LABEL="mm_sierra"

# @see https://wiki.archlinux.org/index.php/USB_3G_Modem
# @see https://support.lenovo.com/us/en/solutions/pd031021
# @see https://www.freedesktop.org/software/ModemManager/api/1.8.0/ref-overview-modem-filter.html

ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{FILTER_RULE_TTY_ACM_INTERFACE}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_CDC_WDM}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_EXPLICIT_WHITELIST}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_NET}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_BLACKLIST}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_DEFAULT_ALLOWED}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_DRIVER}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_PLATFORM_DRIVER}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_WITH_NET}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_VIRTUAL}="1"

LABEL="mm_sierra_end"

然后,刷新 udev 的配置,并通知 kernel 启用新的权限:

# udevadm control -R && udevadm trigger

再看下对应设备文件文件的 rule 是否已经启用成功:

# udevadm info /dev/cdc-wdm0

对应的输出

P: /devices/pci0000:00/0000:00:14.0/usb2/2-4/2-4:1.0/usbmisc/cdc-wdm0
N: cdc-wdm0
E: DEVNAME=/dev/cdc-wdm0
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb2/2-4/2-4:1.0/usbmisc/cdc-wdm0
E: FILTER_RULE_TTY_ACM_INTERFACE=1
E: ID_MM_CANDIDATE=1
E: ID_MM_DEVICE_PROCESS=1
E: MAJOR=180
E: MINOR=0
E: MM_FILTER_RULE_CDC_WDM=1
E: MM_FILTER_RULE_EXPLICIT_WHITELIST=1
E: MM_FILTER_RULE_NET=1
E: MM_FILTER_RULE_TTY=1
E: MM_FILTER_RULE_TTY_BLACKLIST=1
E: MM_FILTER_RULE_TTY_DEFAULT_ALLOWED=1
E: MM_FILTER_RULE_TTY_DRIVER=1
E: MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY=1
E: MM_FILTER_RULE_TTY_PLATFORM_DRIVER=1
E: MM_FILTER_RULE_TTY_WITH_NET=1
E: MM_FILTER_RULE_VIRTUAL=1
E: SUBSYSTEM=usbmisc
E: USEC_INITIALIZED=10423134

配置生效,kernel 的配置看起来一切正常。

这时候启动 ModemManage 还是发现有错误信息,但是过了一段时间以后对应的 Modem 已经是 enable 可用状态(这点百思不得解,可能是 4G 网络连接需要初始化比较慢)。

Sep 20 14:25:44 x1-carbon ModemManager[2047]: opening device...
Sep 20 14:25:44 x1-carbon ModemManager[2047]: [/dev/cdc-wdm0] Read max control message size from descriptors file: 512
Sep 20 14:25:44 x1-carbon ModemManager[2047]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:14.0/usb2/2-1': not supported by any plugin
Sep 20 14:25:50 x1-carbon ModemManager[2047]: <info>  Modem: state changed (unknown -> disabled)
Sep 20 14:25:50 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (disabled -> enabling)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (enabling -> enabled)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: 3GPP Registration state changed (unknown -> registering)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: 3GPP Registration state changed (registering -> home)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (enabled -> registered)

然后在控制台下使用 mmcli 获取相关的信息,具体的 mmcli 可以参考这里的文档。发现了一个不大不小的问题,就是 sim 卡的日期还是 20040101 ,很明显没有和网络同步(后来发现这块并不重要)。

$ mmcli -m 0 --simple-status

/org/freedesktop/ModemManager1/Modem/0
  -------------------------
  Status |          state: 'connected'
         | signal quality: '67' (recent)
         |          bands: 'unknown'
         |    access tech: 'lte'
  -------------------------
  3GPP   |   registration: 'home'
         |  operator code: '46001'
         |  operator name: 'CHN-UNICOM'
         |   subscription: 'unknown'

再尝试启动 modem-manage-gui 看其信息,过一会发现已经注册了网络并能获取对应的信息。

https://friable.rocks/_/2018_09_20/1537427550.png

硬件方面的配置没有问题,那么软件方面的设置就简单多了。然后使用 Gnome 的网络配置,配置 APN 已经相关的网络参数,过一会就能看到久违的信号了。

https://friable.rocks/_/2018_09_20/1537427570.png

总结下,这个问题困扰了我一个晚上的时间,然后也走了很多的弯路。

主要还是在硬件资源的权限方面,这块还是要多看 Arch 的 Wiki 以及对应软件的 Manual(开源社区的锅都扔给用户了)。

相关的参考链接:

PS,有什么靠谱的联通流量卡推荐下?

- eof -

使用 Nginx 反向代理阿里云 OSS

上次将 Blog 的主机重新整理下了以后,这次抽空将站点的静态资源也整理了下。

虽然使用又拍云有一段时间了,也非常的稳定,但毕竟要「居安思危」,又拍云同时又提供了「融合云」的第三方存储的同步方案,于是就选择了阿里云的 OSS 作为存储的备份方案。

Aliyun OSS

上图是我目前的方案,客户端这边通过脚本或者其他工具上传到又拍云上,然后又拍云自动将增量数据同步到阿里云的 OSS,最后我再使用 Nginx 反向代理去访问两块同步的资源。

为什么使用反向代理,是因为由于「众所周知」的原因,在国内绑定 HTTP(S) 服务的域名是需要备案的,我觉得麻烦同时也不想浪费个域名做这样的事情。

同时使用反向代理的方式,后面自己还可以控制缓存、负载均衡等,可操作性更大一些。长话短说,在配置阿里云 OSS 的反向代理的时候碰到了些坑,在这里记录下。

首先,在阿里云 OSS 后台务必设置好权限,在 Bucket 中设置读写权限为「公共读」,以及在 Refer 设置为自己实际的情况(很重要)。

下面是目前的针对阿里云 OSS 的 Nginx 反向代理的具体配置,供参考:

location /<base_url>/ {
    proxy_pass http://<bucket>.oss-cn-shanghai.aliyuncs.com/;

    proxy_redirect off;
    proxy_set_header Host '<bucket>.oss-cn-shanghai.aliyuncs.com';
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header User-Agent $http_user_agent;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_cache_valid 200 302 3600m;

    proxy_buffer_size 256k;
    proxy_buffers 4 256k;
    proxy_read_timeout 600s;
    proxy_send_timeout 300s;
    proxy_temp_file_write_size 256k;

    expires max;
    etag off;
}

proxy_set_headerHost 必须要设定自己对应的主机名,例如我的阿里云 OSS 对应的主机在华东2(也就是上海机房)阿里云 OSS 是根据 Host 确定 Bucket 的名称的。顺便提一句,如果 Nginx 主机也是和 OSS 同个机房,那么可以使用内部 VPC 内网节省流量。

OSS Endpoint

由于反向代理的是静态资源,所以我考虑尽可能的多缓存时间以加快速度和减少请求流量,所以设置了 proxy_cache_valid 200 302 3600m 以及 expires max 强制缓存。

然后 reload Nginx 以后就可以看到效果了。由于目前 又拍云 和 阿里云 OSS 的内容是同步的,所以后面使用 Nginx 做负载均衡也是比较方便的事情,这里就不重复贴配置了。

顺便在这里吐槽下,个人不是很推荐买阿里云的香港服务器!因为又是那个「众所周知」的原因,阿里云的香港节点 HTTPS 流量是会被部分 ISP 给污染的

这块个人也是困扰了很久,然而不打算处理了,毕竟现在写 Blog 的人不多看 Blog 的人更少,被墙了反而能够更加自在些。

- EOF -

随谈 Java 的空指针(翻译和整理)

整理自: http://www.yegor256.com/2014/05/13/why-null-is-bad.html

在日常编码中,有个经典的 Java 空指针(NULL)调用如下:

public Employee getByName(String name) {
  int id = database.find(name);
  if (id == 0) {
    return null;
  }
  return new Employee(id);
}

那么这段方法有什么问题?这个方法的最大问题就是有可能会返回 NULL 对象。空指针(NULL)问题在面向对象编程角度上说是个很严重的问题,所有面对对象编程的过程中都会碰到类似的问题。甚至 Tony Hoare 向全世界道歉,忏悔他曾经发明了「空指针」这个玩意

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement.

原来,在程序语言中加入空指针设计,其实并非是经过深思熟虑的结果,而仅仅是因为它很容易实现而已。这个设计是如此的影响深远,以至于后来的编程语言都不假思索的继承了这一设计,这个范围几乎包括了目前业界所有的流行的编程语言。

对许多程序员来说,早就已经习惯了空指针的存在,就像日常生活中的空气和水一样。那么,空指针究竟有什么问题?

额外的错处处理

当然,有经验的编码人员自然会时刻意识到空指针问题的存在,那么有可能在编码的过程中加入额外的判断,就会规避这个问题,例如下面的写法:

// this is a terrible design, don't reuse
Employee employee = dept.getByName("Jeffrey");
if (employee == null) {
  System.out.println("can't find an employee");
  System.exit(-1);
} else {
  employee.transferTo(dept2);
}

那么这样的话就会多了额外的流程去处理因为空指针判断而多加的判断。例如,上面的处理逻辑和流程中,如果按照面向对象的思维书写,不考虑空指针的情况,只是简单的:

dept.getByName("Jeffrey").transferTo(dept2);

就可以。加入空指针判断以后,程序往往会有额外以及复杂的逻辑判断。

语义不清晰

上面的问题自然可以规避,例如在 getByName() 这个方法中加入异常的判断,那么重新命名这个方法的名字为 getByNameOrNullIfNotFound() 。这样子从代码可读性的角度上说,从方法名字就可以判断这个方法会返回什么,
但是自然而然为了表意清晰就会让方法的名字变得越来越长(感觉有点在黑 Objective-C)。

为了表明这个歧义,除了正常的返回值以外,其他的空指(NULL Object)针情况都会返回一个异常。那么这里又会引申出另外一个问题,就是性能。从上面的代码考虑,如果返回了 NULL 以后再抛出一个异常,实际上这段代码是已经被执行了的,然后再加的判断。

例如,在示例中 getByName() 通常是个 Map 然后执行了 get 方法去搜索是否存在这个名字,如果不存在再抛异常,这样就会造成额外的执行时间:

Employee employee = employees.get("Jeffrey");
if (employee == null) {
  throw new EmployeeNotFoundException();
}
return employee;

当然,这个问题也是很好规避,例如只需要先搜索它的索引即可,然后再取值返回:

if (!employees.containsKey("Jeffrey")) { // first search
  throw new EmployeeNotFoundException();
}
return employees.get("Jeffrey"); // second search

那么,这部分操作判断只是针对空指针就多了一次搜索操作。我们考虑再优化下,直接使用迭代器处理:

Iterator found = Map.search("Jeffrey");
if (!found.hasNext()) {
  throw new EmployeeNotFoundException();
}
return found.next();

这样子似乎很好的解决了问题。

但是,我们编写这段代码的业务初衷似乎越来越远,然后方法的名字也变得越来越长,自然而然代码也变得越来越复杂。

如何最大程度得避免空指针?

本篇开头的这个问题一样,空指针的问题可以追溯到计算法发展史时期,同时空指针异常的情况也很多,甚至在程序运行阶段也无法避免空指针的情况。那么,在编码层面,我们需要注意哪些呢?

确认调用的的每个变量都已经被初始化

这点说起来很简单,但事实上随着业务的发展项目代码也会越来越庞大。这时候方法之间调用的关系也会越来越复杂,很难避免使用到的方法都已经明确被初始化。

所以这块单独放在这里,需要我们在编码的实话重点考虑变量存在的可能性,这其实大体上基于自己的实际编码经验。

尽量使用明确的值调用

如果已经明确某个变量(常量)的值,那么是可以安全调用它的方法的。例如对比下面的几行代码:

String a = null;
a.equal("b");   // 会产生空指针异常
"b".equal(a);   // 推荐的写法

很明显使用常量去做调用这代码会更健壮一些。

尽量避免在函数中返回 NULL

当如果在编写方法中考虑返回 NULL,这个时候则需要冷静下是否真的需要这样子做。因为,通常来说会有比返回 NULL 更好的处理方式。

自动装箱需谨慎

自动装箱确实为编写程序带来很多方便,但我们在编程时候也不能滥用自动装箱。

比如,下面这个程序依然存在空指针异常隐患:

Person jack = new Person("jack");
int weight = jack.getWeight();

这种异常在我们使用一些 ORM 框架中会碰到,如果数据库对应的对象并不存在该值,而我们又在类中使用了一个基本类型与之对应,依然就会抛出空指针异常。在这种情况下就尽量使用包装类来对应,并且在使用该值时候先判断是否为空。

遍历谨防集合为空

for (int num : list) {
    // for each num in list
}

及时验证外部数据

在代码运行的过程中,尤其在解析外部数据的时候可能会引发影响不到的问题。例如下面的 Json 数据

{"name": null, age: 28}

如果不处理完善,虽然这可能是外部原因造成,但直接使用 name 属性也会导致空指针的问题。

使用第三方库加强验证

很多第三方的 Common 库都会有验证空指针的方法,例如 Guava 中针对空指针的判断有个单独的包去处理。

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

或者过滤 NULL 也会更加的方便

Joiner joiner = Joiner.on("; ").skipNulls();
return joiner.join("Harry", null, "Ron", "Hermione");

使用 @NotNull 或 @Nullable 注解

强烈建议多使用注解来增加代码的可读性,例如多增加 @NotNull 或 @Nullable 注解,也可以加强代码静态检查方面可能会造成空指针的可能性,具体可以参见这里

使用 Java8 的 Optional

很多「现代」语言都会有针对变量为空的可选链式判断,例如

Groovy 语言有一个 ?. 的操作符,可以安全地处理潜在可能的空引用(据说 Java7 曾被建议引入这个但是并没有发布)。它是这么用的:

 String version = computer?.getSoundcard()?.getUSB()?.getVersion();

虽然 Java 看起来非常的保守,但好在 Java8 中增加了 Option[T] 这个对象包来代表类型 T 的某一个值存在或者没有。那么上面的代码可以写成:

String name = computer.flatMap(Computer::getSoundcard)
                      .flatMap(Soundcard::getUSB)
                      .map(USB::getVersion)
                      .orElse("UNKNOWN");

看起来似乎有点麻烦,但相信我你会爱上这样的写法,具体可以参见这里

好了,针对空指针的总结和整理先到这里,如果你有更好的意见和建议,欢迎不吝提出。

参考资源

-- eof --

我的照片

嗨!我叫「明城」,八零后、码农、宁波佬,现居杭州。除了这里,同时也欢迎您关注我的 GitHubTwitterInstagram 等。

这个 Blog 原先的名字叫 Gracecode.com 、现在叫 「無標題文檔」 。 其实无所谓叫什么名字,作为码农知道取名是件很难的事情。最后想到的这个名字,其实都没啥特别的含义,系统默认的文件名而已。

作为八零后,自认为还仅存点傲娇式的幽默感,以及对平淡生活的追求和向往。 为了免得对号入座和不必要的麻烦,声明本站点所持观点仅代表个人意见,不代表自己所服务公司的立场。

如果您想联系我,可以发我邮件 `echo bWluZ2NoZW5nQGdyYWNlY29kZS5jb20K | base64 -d`

文章

项目