Skip to content

Commit 4350215

Browse files
committed
new file: _posts/2018-02-07-defensive-programming.md
1 parent 4cb12da commit 4350215

File tree

4 files changed

+112
-5
lines changed

4 files changed

+112
-5
lines changed

_git-workflow/2016-09-02-git-workflow-branch.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ git checkout -b test
3535
git checkout master
3636
# 删除`test`分支:
3737
git branch -D test
38+
# 从远程仓库删除
39+
git push --delete origin test
3840
```
3941

4042
## 分支重命名
@@ -52,7 +54,7 @@ git push origin :old_branch
5254
git push --set-upstream origin new_branch
5355
```
5456

55-
> 如果要重命名的是当前分支,可以直接`git branch -m new_branch`
57+
如果要重命名的是当前分支,可以直接`git branch -m new_branch`
5658

5759
## 分支合并
5860

_includes/head.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121
<link rel="stylesheet" href="/assets/lib/font-awesome/4.2.0/css/font-awesome.min.css">
2222
<link rel="stylesheet" href="/assets/css/site.css">
2323

24-
<script defer src="/assets/lib/jquery/jquery-2.0.3.min.js"></script>
24+
<script src="/assets/lib/jquery/jquery-2.0.3.min.js"></script>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
title: 适当地引入防卫性编程
3+
tags: JavaScript 封装 接口 重构 弱类型
4+
---
5+
6+
> Anything that can go wrong will go wrong. -- Edward Murphy
7+
8+
**防卫性编程(Defensive Programming)** 是指限制对程序的不可预见的使用,增加软件的安全性。
9+
防卫性编程在程序鲁棒性、可维护性上都有帮助,尤其是在你不幸地选择弱类型语言编写源码时。
10+
11+
在 C++ STL 程序设计中,我们称函数模板和类模板为 [隐式接口][cpp-41],这些接口描述了编译期多态。
12+
在 JavaScript 中,接受一个对象时也不需要声明其类型,只有后续对它的使用方式描述了它的接口。
13+
[Harttle](/) 把 JavaScript 中的这一现象称为 **隐式接口**
14+
15+
**隐式接口调试困难**。顾名思义隐式接口是无法使用工具检查的,因此只能依赖运行时调试。
16+
那么当 `page.controller` 为空时,抛出的错误会包含有用信息吗?
17+
18+
```javascript
19+
function init(page) {
20+
page.controller();
21+
}
22+
```
23+
24+
具体的错误消息取决于你的JavaScript引擎,可能是 "page.controller is not a function",
25+
可能是 "undefined is not a function"。
26+
如果是 uglify 后的代码,那就更难定位问题了。
27+
28+
**隐式接口容错困难**。因为接受的对象没有类型保证,但我们可以进行容错。
29+
思路很简单:为了避免被调用接口为空,我们事先判断它。
30+
31+
```javascript
32+
function init(page) {
33+
page && page.view && page.view.start && page.view.start()
34+
page && page.controller && page.controller.start && page.controller.start()
35+
}
36+
```
37+
38+
这样的 Duck Test 其实在 JavaScript 中随处可见,然而这样正确性就不明显了:
39+
40+
* 如果 `view.controller` 为空,应该吞掉这个错误吗?假设我们都认为随处吞掉错误是很烂的实践。
41+
* 即使 `view.start` 存在,那么它是一个 `function` 么?
42+
* 即使 `view.start` 是一个 `function`,那么它是想要的那个 `function` 么?
43+
44+
**隐式接口重构困难**。因为没有限制接受的输入范围,可能会有很多作者预期之外的使用方式,
45+
这些使用方式会让重构变得困难,例如:
46+
47+
我们有一个发送 HTTP 请求的接口,我们只想让它发送 POST/PUT 请求,
48+
并对这两种请求做了特殊处理,比如加了时间戳或者安全性封装等等:
49+
50+
```javascript
51+
function writeRequest(url, method) {
52+
let req = construct(url, method)
53+
return req.send()
54+
}
55+
```
56+
57+
有一天可能需要对 POST 和 PUT 做单独的逻辑,我们把它重构成如下形式:
58+
59+
```javascript
60+
function writeRequest(url, method) {
61+
let req
62+
if (method === 'POST') {
63+
req = construct(url, 'POST')
64+
// do some thing here ...
65+
} else {
66+
// WHAT IF method != 'PUT' ?
67+
req = construct(url, 'PUT')
68+
}
69+
req.send()
70+
}
71+
```
72+
73+
在 ELSE 分支中,如果 `method !== "PUT"` 怎么办?一搜代码库,发现真有地方 `method` 就是 `GET`
74+
重构前的代码无意中支持了 `GET`?!
75+
现在如果不再支持 GET 则会不兼容地挂掉客户代码,
76+
如果转向支持 GET 则会与设计初衷,甚至函数名 `writeRequest` 相违背。
77+
78+
> 这里虽然是取值范围引起的重构困难,而根据定义,数据的取值范围就是类型(比如把它做成一个枚举类型就可以等价)。
79+
80+
在 JavaScript 中我们确实无法编译期检查类型(甚至没有编译阶段),
81+
你可以选择类似 TypeScript 之类的语言。或者在设计接口时引入 **防卫性编程的思想**
82+
83+
1. 确定可接受的输入范围
84+
2. 在入口处检查这一范围是否得到了满足
85+
3. 对所有输入都产出符合预期的输出(行为/返回值),最好再配一项测试
86+
87+
例如:
88+
89+
```javascript
90+
function writeRequest(url, method) {
91+
assert(url, 'cannot POST to malformed url')
92+
assert(/^POST|PUT$/.test(method), `method not supported: ${method}`)
93+
94+
let req = construct(url, method)
95+
return req.send()
96+
}
97+
```
98+
99+
这样上文中提到的隐式接口的各种问题都可以得到不同程度的解决:
100+
101+
* 调试困难。现在可以抛出具有足够信息的错误,便于调试。上述例子中 `assert` 的第二个参数可以极尽详细地描述错误。
102+
* 容错困难。实现应满足一切合法输入,不再需要在实现的过程中进行容错,减少容错也让正确性更加明显。
103+
* 重构困难。防卫性的接口描述可以做到足够清晰,接口描述不再影响重构。上述例子中,只需要继续支持 `POST`, `PUT` 即可保持接口的向后兼容。
104+
105+
[cpp-41]: /2015/09/08/effective-cpp-41.html

tags.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@
3030

3131
</div>
3232

33-
<script src="/assets/lib/jqcloud/jqcloud.min.js"></script>
34-
<script src="http://code.highcharts.com/highcharts.js"></script>
35-
<script src="/assets/js/tags.js"></script>
33+
<script defer src="/assets/lib/jqcloud/jqcloud.min.js"></script>
34+
<script defer src="http://code.highcharts.com/highcharts.js"></script>
35+
<script defer src="/assets/js/tags.js"></script>

0 commit comments

Comments
 (0)