summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-07-27 13:33:28 +0800
committereltrac <hi@eltr.ac>2026-07-27 13:33:28 +0800
commit89f76caf897f24d0922505a33661148bb001d6f9 (patch)
tree073768417cb06d5fe00ed90982b52b13945f7c52 /content
parentcb26e6d75ececbd6e59874af7cebbea31cbed32a (diff)
[draft] new
Diffstat (limited to 'content')
-rw-r--r--content/article/locally-remotely-unknowingly.md51
1 files changed, 51 insertions, 0 deletions
diff --git a/content/article/locally-remotely-unknowingly.md b/content/article/locally-remotely-unknowingly.md
new file mode 100644
index 00000000..0ece3554
--- /dev/null
+++ b/content/article/locally-remotely-unknowingly.md
@@ -0,0 +1,51 @@
++++
+title = 'L'
+subtitle = 'Locally and Remotely, Unknowingly.'
+slug = ''
+tags = []
+categories = '代码炼金术'
+date = 2026-07-27T13:06:23+08:00
+draft = true
+toc = true
++++
+
+尽管我在自己的时间里一直坚持手写代码,但工作时难免与 Agents 打交道。一方面是公司推崇这种工具,另一方面是如果我不用的话,我就没办法按时交付工作。无论如何,有一类代码我在任何情况下都是自己设计和自己手写的,那就是接口定义。<!--more-->
+
+## We're Interfacing
+
+我要讨论的不是 RESTful API,也不是 gRPC 的 Protobuf,我说的是 `interface` 类型。不同语言中的 `interface` 都略有不同,但一般都是一系列抽象方法(函数)的集合。抽象方法是说这个方法没有被实现,仅仅定义了函数名、形参数量和类型,也就是函数签名。
+
+`interface` 一般用在面向对象编程中,实现 `interface` 就是定义一个类或命名空间,在其中实现 `interface` 定义的所有函数。在 Java 中,实现接口需要显式声明,例如 `class ConcreteWriter implements Writer`。在 Go 中,实现接口是隐式的,只要某个结构体的方法包含了某个接口定义的所有方法,就视作实现了这个接口。
+
+动态类型语言中也有 `interface`,不过较少使用,我想原因是 `interface` 就是为静态类型系统服务的。实现了接口的某个对象可被视作同一个类型,而架构设计中的一个原则是:依赖关系应该向越来越抽象的方向流动,即抽象的不该依赖具体的、具体的应当依赖抽象的。原因在于,具体的软件本身是易变的(关于易变性带来的复杂性,可以阅读《[什么是工程问题?](/article/engineering-perspective/)》),如果抽象的业务逻辑依赖具体的部分,那么具体实现改变之后,业务逻辑也要频繁改变,这显然是不合理的。软件工程的一个重要问题就是把变更控制在软件的一小部分当中,`interface` 可以做到。
+
+假设我们要编写的软件需要启动一个浏览器实例做某个操作,比起直接在主业务逻辑中添加寻找 Chrome 二进制文件的路径、管理生命周期、发送请求和解析响应体的逻辑,把这部分操作隔离开来是最好的。一般人会这么做:
+
+```go
+func main() {
+ // ...
+
+ url := parseFromUserInput(input)
+
+ chrome := NewChromeInstance()
+ chrome.Start()
+ defer chrome.Close()
+
+ page := chrome.GetPage(url)
+
+ // ...
+}
+```
+
+实际上这也不是最好的做法,如果我们某天决定不用 Chrome,改用 Firefox,那怎么办?我们要修改这个文件里的依赖引入,把 `NewChromeInstance()` 改成 `NewFirefoxInstance()`,假设 `Firefox` 没有 `Close()` 方法,用的是 `Stop()` 这个名字,也要修改。更何况,不同的浏览器可能还有不同的注意事项,需要额外增删代码。
+
+你可能想用适配器设计模式(Adapter Pattern),但既然都要写新的类定义了,为什么不直接写成接口呢?
+
+```go
+type Browser interface {
+ Start()
+ Stop()
+ GetPage(url string)
+}
+```
+