图灵奖系列 · DoggyDad 原创

Barbara Liskov:她定义了"抽象数据类型"和"里氏替换原则",让面向对象编程真正可用

Barbara Liskov:她定义了"抽象数据类型"和"里氏替换原则",让面向对象编程真正可用

ANSWER-FIRST SUMMARY

本文回答什么问题

Barbara Liskov:她定义了"抽象数据类型"和"里氏替换原则",让面向对象编程真正可用

  • 主题分类:图灵奖系列
  • 关键词:图灵奖、计算机历史、编程语言、算法、人工智能、数据库
  • 人物实体:Barbara Liskov

图灵奖第四十三届(2008)| Barbara Liskov:她定义了”抽象数据类型”和”里氏替换原则”,让面向对象编程真正可用

一句话概括:当程序员还在纠结如何组织复杂代码时,Liskov创造了抽象数据类型(ADT)和子类型理论——她的CLU语言首次实现了模块化编程的理想,她的替换原则(LSP)成为面向对象设计的基石。

🏆 获奖简介

Barbara Liskov(芭芭拉·利斯科夫,1939-)是抽象数据类型的发明者、程序设计语言的先驱、分布式系统的奠基人

Barbara Liskov

  • 出生时间:1939年11月7日

  • 出生地点:美国加利福尼亚州洛杉矶

  • 主要成就:抽象数据类型(ADT)、CLU语言、里氏替换原则(LSP)、分布式系统(Argus)

  • 机构:麻省理工学院(MIT)

  • 获奖年份:2008年

  • 获奖原因:表彰她在实践和理论上为程序设计语言和系统设计奠定的基础,尤其是数据抽象、容错和分布式计算方面的贡献(for contributions to practical and theoretical foundations of programming language and system design, especially related to data abstraction, fault tolerance, and distributed computing)

为什么她是第四十三位? 1970年代初,软件危机肆虐——程序越来越复杂,bug越来越多,维护越来越难。问题的根源是:代码缺乏组织,数据和操作混在一起,修改一处影响全局。1972年,在MIT的Liskov提出了革命性的概念:抽象数据类型(ADT)——把数据和操作封装在一起,对外只暴露接口,隐藏实现。这个看似简单的想法,改变了编程的范式。1974-1977年,她领导设计的CLU语言首次系统化实现了ADT、异常处理、迭代器等现代概念。1987年,她提出里氏替换原则(LSP):子类型对象必须能替换父类型对象,而不破坏程序正确性——这成为面向对象设计的核心准则(SOLID的L)。1980-90年代,Liskov转向分布式系统,设计的Argus语言率先支持原子性、嵌套事务、分布式垃圾回收,为云计算和微服务奠基。从数据抽象到子类型,从语言设计到分布式系统,Liskov不仅创造了理论,更将理论转化为工程实践。今天,Java的interface、Python的类、微服务的容错机制,无一不承载着她的智慧。

🚀 Barbara Liskov的重大贡献

1. 抽象数据类型(ADT):封装的革命

问题(1970年代初):

  • 代码组织混乱:数据和操作分离,难以维护
  • 全局状态:任何代码都能修改任何数据
  • 难以理解:不知道哪些操作合法

Liskov的洞见

数据抽象的核心思想

传统方式(结构化编程):

{ 定义数据结构 }
type Stack = record
  items: array[1..100] of integer;
  top: integer;
end;

{ 操作散落在各处 }
procedure push(var s: Stack; x: integer);
begin
  s.top := s.top + 1;
  s.items[s.top] := x;
end;

{ 用户可以直接访问内部 }
s.top := 0;  { 危险!破坏了不变式 }

Liskov的ADT方式(抽象数据类型):

stack = cluster[T: type] is create, push, pop, top, empty
  rep = record[items: array[T], top: int]

  create = proc() returns (cvt)
    return(rep${items: array[T]$[], top: 0})
  end create

  push = proc(s: cvt, x: T)
    s.top := s.top + 1
    s.items[s.top] := x
  end push

  pop = proc(s: cvt) returns (T)
    x: T := s.items[s.top]
    s.top := s.top - 1
    return(x)
  end pop

  { 不变式:0 ≤ s.top ≤ size(s.items) }
end stack

关键特性

  1. 封装:数据表示(rep)对外不可见
  2. 接口:只能通过操作(create, push, pop)访问
  3. 不变式:封装保证不变式总是成立
  4. 抽象:用户不知道(也不需要知道)内部实现

ADT的威力

例子:改变实现: 假设Stack最初用数组实现,后来改为链表:

rep = record[items: array[T], top: int]  { 旧实现 }

rep = oneof[empty: null, node: record[val: T, next: stack[T]]]  { 新实现 }

用户代码无需修改

  • 接口不变(push, pop, top)
  • 内部实现自由变化
  • 模块化:修改局部化

2. CLU语言:理论到实践的桥梁

1974-1977年:Liskov领导MIT团队设计CLU(CLUster)

CLU的创新特性

1. Cluster(集群)

  • ADT的实现机制
  • 类似今天的类(class),但更严格
  • rep(representation):私有数据
  • cvt(conversion):类型转换,确保封装

2. 异常处理问题:操作可能失败(如栈空时pop)

CLU的解决

pop = proc(s: cvt) returns (T) signals (empty)
  if s.top = 0 then signal empty end
  x: T := s.items[s.top]
  s.top := s.top - 1
  return(x)
end pop

{ 调用者必须处理异常 }
x := stack$pop(s) except when empty: ... end

影响

  • 第一个将异常作为一等公民的语言
  • 启发了Java的try-catch、Python的异常

3. 迭代器问题:如何遍历数据结构而不暴露内部?

CLU的yield

elements = iter(s: cvt) yields (T)
  for i: int in int$from_to(1, s.top) do
    yield(s.items[i])
  end
end elements

{ 使用 }
for x: int in stack$elements(s) do
  ... { 处理x }
end

影响

  • Python的yield
  • C#的yield return
  • JavaScript的iterator protocol

4. 泛型(Parametric Polymorphism)问题:如何写通用代码?

CLU的类型参数

stack = cluster[T: type] is ...
  { T是类型参数,可以是任何类型 }
end stack

{ 实例化 }
int_stack = stack[int]
string_stack = stack[string]

影响

  • Java的泛型(
  • C++的模板
  • ML/Haskell的参数化类型

5. 多返回值

divmod = proc(a, b: int) returns (int, int)
  return(a / b, a // b)  { 商和余数 }
end divmod

q, r := divmod(17, 5)  { q=3, r=2 }

影响

  • Python的多返回值
  • Go的多返回值

3. 里氏替换原则(LSP):子类型的正确性

1987年:Liskov与Jeannette Wing发表论文 “A Behavioral Notion of Subtyping”

问题:什么是”正确的”子类型?

场景

class Rectangle:
    def set_width(self, w): self.width = w
    def set_height(self, h): self.height = h
    def area(self): return self.width * self.height

class Square(Rectangle):
    def set_width(self, w):
        self.width = w
        self.height = w  # 保持正方形性质
    def set_height(self, h):
        self.width = h
        self.height = h

测试代码

def test(r: Rectangle):
    r.set_width(5)
    r.set_height(4)
    assert r.area() == 20  # 期望5*4=20

Bug

test(Rectangle())  # ✓ 通过:5*4=20
test(Square())     # ✗ 失败:4*4=16(不是20!)

问题:Square不是Rectangle的正确子类型!

Liskov替换原则(LSP)

定义

设φ(x)是关于类型T的对象x的可证性质。那么对于类型S的对象y,如果S是T的子类型,则φ(y)应该成立。

通俗版

  • 子类型对象必须能替换父类型对象
  • 替换后,程序的正确性不变

正式版(契约式设计): 子类型必须满足:

  1. 前置条件不强化:子类型方法的前置条件≤父类型
  2. 后置条件不弱化:子类型方法的后置条件≥父类型
  3. 不变式保持:父类型的不变式在子类型中仍成立

例子:正确的继承

class Bird:
    def move(self):
        """后置条件:位置改变"""
        pass

class Sparrow(Bird):  # 麻雀
    def move(self):
        """飞行,满足"位置改变",OK"""
        self.fly()

class Penguin(Bird):  # 企鹅
    def move(self):
        """游泳,满足"位置改变",OK"""
        self.swim()

例子:错误的继承

class Bird:
    def fly(self):
        """后置条件:在空中"""
        pass

class Penguin(Bird):
    def fly(self):
        """企鹅不会飞!违反后置条件"""
        raise Exception("Can't fly")

正确做法

class Animal:
    def move(self): pass

class FlyingAnimal(Animal):
    def fly(self): pass

class Bird(FlyingAnimal): pass
class Penguin(Animal): pass  # 不继承FlyingAnimal

LSP的影响

SOLID原则

  • S:单一职责原则
  • O:开放封闭原则
  • L:Liskov替换原则 ← Liskov的贡献
  • I:接口隔离原则
  • D:依赖倒置原则

设计指导

  • 继承要小心,不是”is-a”就该继承
  • 关注行为契约,而非名称
  • 组合优于继承

4. Argus:分布式编程的先驱

1980年代:Liskov转向分布式系统

挑战:分布式环境的复杂性

问题

  1. 部分失败:某台机器崩溃,其他机器正常
  2. 并发:多个操作同时执行,冲突?
  3. 一致性:如何保证数据一致?

Argus语言的创新

1. 原子性(Atomicity)

概念:一组操作要么全部成功,要么全部失败

Argus的action

action transfer(from, to: account; amount: int)
  from.withdraw(amount)  # 可能失败
  to.deposit(amount)     # 可能失败
  { 两者要么都成功,要么都不发生(回滚) }
end transfer

实现

  • 两阶段提交(2PC)
  • 日志记录:用于回滚
  • 锁机制:避免冲突

2. 嵌套事务(Nested Transactions)

问题:事务内部调用另一个事务怎么办?

Argus支持嵌套

action outer()
  ...
  action inner()  # 嵌套事务
    ...
  end inner
  ...
end outer

语义

  • 内层事务失败→只回滚内层
  • 外层事务失败→内外层都回滚
  • 灵活性:提高并发度

3. 分布式垃圾回收

问题

  • 本地垃圾回收:容易(引用计数、标记-清除)
  • 分布式:对象可能在多台机器上被引用

Argus的算法

  • 引用列表:每个对象记录哪些机器引用它
  • 消息协议:引用变化时通知
  • 一致性:容错处理(网络分区、机器崩溃)

影响

  • Java RMI的GC
  • 分布式对象系统(CORBA、DCOM)

4. Promise(承诺)

问题:远程调用慢,等待浪费时间

Argus的call-stream

p: promise[result] := remote_call@server()  # 立即返回
...  { 可以做其他事 }
res: result := p.get()  { 需要时才等待 }

影响

  • JavaScript的Promise
  • Python的Future
  • Java的CompletableFuture

5. 理论与实践的结合

Liskov的特点

  • 不仅提理论:设计实际语言(CLU、Argus)
  • 不仅写论文:构建真实系统
  • 实践验证理论:CLU证明ADT可行,Argus证明分布式编程可控

影响模式

  1. 识别问题:观察实践中的困难
  2. 提出理论:抽象、形式化
  3. 构建系统:实现、验证
  4. 迭代改进:反馈理论

🌍 对世界的深远影响

编程语言设计:ADT成为标准

CLU的直接后代

  • Java
    • class:CLU的cluster
    • interface:ADT的接口
    • 异常:try-catch源自CLU
    • 泛型List<T>来自CLU
  • Python
    • 迭代器:yield借鉴CLU
    • 异常:try-except
  • C++
    • 模板:泛型
    • 异常处理

间接影响

  • 所有现代语言都有类、封装、异常
  • ADT成为”常识”,但源自Liskov

面向对象设计:LSP的指导

SOLID原则

  • 软件工程师必知必会
  • L是核心:保证继承正确性

设计模式

  • 策略模式:LSP保证可替换
  • 模板方法:子类扩展,不破坏父类契约

代码审查

  • “这个继承满足LSP吗?”
  • 成为标准检查项

分布式系统:Argus的遗产

事务处理

  • 数据库:ACID中的A(原子性)
  • 分布式事务:两阶段提交
  • 嵌套事务:SAP HANA、某些数据库支持

微服务

  • Saga模式:长事务的分解,类似嵌套事务
  • 分布式追踪:监控分布式调用

云计算

  • AWS Lambda、Azure Functions:承诺(Promise)异步调用
  • 分布式锁:Zookeeper、etcd
  • 容错:重试、补偿机制

教育:影响一代程序员

MIT课程

  • Liskov教授软件工程课程数十年
  • 培养大批学生,传播ADT和设计原则

教材

  • 《Program Development in Java》(Liskov著)
    • 系统讲解ADT、LSP
    • 全球大学使用

行业影响

  • Google、Microsoft、Facebook等公司的工程师
  • 很多人的设计理念源自Liskov

🏆 获奖理由(通俗版)

ACM官方表彰:“表彰她在实践和理论上为程序设计语言和系统设计奠定的基础,尤其是数据抽象、容错和分布式计算方面的贡献。”

更通俗的理解

如果没有Liskov

  • 我们可能仍在写”意大利面代码”
  • 继承可能仍然混乱(Square继承Rectangle的bug到处都是)
  • 分布式系统可能更难写

Liskov做了什么?

  • 数据抽象:封装数据和操作,模块化
  • LSP:定义了正确的子类型,指导OOP设计
  • CLU和Argus:将理论变成可用的语言
  • 分布式系统:原子性、嵌套事务、GC

本质: 她让复杂软件变得可管理,让分布式系统变得可编程。

👤 个人生平与传奇

早年与教育(1939-1968)

童年

  • 1939年:出生于加州洛杉矶
  • 犹太裔家庭
  • 从小对数学感兴趣

加州大学伯克利分校

  • 1961年:数学学士
  • 当时计算机科学刚起步

转折点

  • 1961年:想读数学研究生
  • 普林斯顿不招女生:那时还是男校(!)
  • 转向计算机:机缘巧合

斯坦福大学

  • 1965年:计算机科学硕士
  • 1968年:计算机科学博士
  • 导师:John McCarthy(人工智能先驱,图灵奖得主)
  • 论文:程序验证

历史意义

  • 美国第一批女性计算机科学博士之一(1968年)
  • 当时全美计算机博士中女性不到1%

MIT生涯(1972-至今)

加入MIT(1972):

  • 助理教授
  • 立即开始CLU项目

CLU时期(1974-1980):

  • 领导团队设计、实现CLU
  • 发表大量论文
  • ADT概念逐渐被接受

Argus时期(1980-1990):

  • 转向分布式系统
  • 设计Argus语言
  • 解决实际问题(银行、航空订票)

后期研究(1990-2010):

  • 拜占庭容错:如何在部分节点恶意时保持一致性
  • Theta语言:继续探索抽象
  • 持久化编程:数据在程序重启后保留

教学

  • MIT软件工程课程主讲人
  • 培养大批PhD
  • 影响数千学生

荣誉

  • 图灵奖(2008)
  • 美国国家工程院院士
  • 美国艺术与科学院院士
  • IEEE John von Neumann Medal

至今

  • 仍活跃于学术(85岁!)
  • MIT名誉教授
  • 偶尔参加学术会议

性格与风格

严谨

  • 对理论精确性要求极高
  • 论文逻辑严密

实践导向

  • 不满足于纯理论
  • 必须构建真实系统验证

导师

  • 学生回忆她要求高但公平
  • 鼓励独立思考
  • 培养了很多成功的研究者

低调

  • 不追求媒体曝光
  • 专注研究和教学
  • 成果说话

女性榜样

打破障碍

  • 1960年代:计算机几乎全是男性
  • 普林斯顿拒绝:因为性别
  • 坚持:转向其他学校,最终成功

成就

  • 第二位女性图灵奖得主(第一位是Frances Allen)
  • 证明女性在计算机科学同样卓越

对女性的影响

  • 很多女性研究者受她激励
  • MIT女性学生的榜样
  • 推动计算机科学性别平等

她的观点

“性别不应该是障碍。专注你的工作,用成果证明自己。”

💭 为什么她值得纪念?

1. 她让软件开发变得系统化

之前

  • 代码混乱,难以理解、维护
  • “意大利面代码”

之后

  • ADT提供了组织代码的方法
  • 模块化、封装成为标准实践

影响

  • 软件工程成为一门工程学科
  • 大型软件项目变得可管理

2. 她定义了正确的继承

LSP之前

  • 继承滥用,bug频发
  • Square继承Rectangle类的问题到处都是

LSP之后

  • 明确什么是正确的子类型
  • 设计原则清晰
  • OOP更可靠

3. 她让分布式编程成为可能

Argus之前

  • 分布式系统难写、易错
  • 部分失败、一致性问题困扰开发者

Argus之后

  • 原子性、事务简化编程
  • 嵌套事务提高灵活性
  • 分布式垃圾回收自动管理资源

今天的云计算、微服务

  • 都建立在她开创的概念之上

4. 她证明了理论与实践可以结合

Liskov的模式

  • 理论→实现→验证→改进
  • 不是象牙塔,也不是只写代码

启示

  • 深刻的理论可以解决实际问题
  • 实践反过来推动理论发展
  • 两者结合才有持久影响

🔍 技术深度:ADT的形式化

代数规范(Algebraic Specification)

ADT的数学定义

签名(Signature):

sort: Stack[T]
operations:
  create: → Stack[T]
  push: Stack[T] × T → Stack[T]
  pop: Stack[T] → Stack[T]
  top: Stack[T] → T
  empty: Stack[T] → Boolean

公理(Axioms):

pop(push(s, x)) = s
top(push(s, x)) = x
empty(create()) = true
empty(push(s, x)) = false

意义

  • 完全定义了Stack的行为
  • 不涉及实现细节
  • 可以用于验证实现正确性

表示不变式(Representation Invariant)

例子:Stack的不变式

I(s) ≡ 0 ≤ s.top ≤ size(s.items)

抽象函数(Abstraction Function):

AF: rep → abstract
AF(s) = <s.items[1], s.items[2], ..., s.items[s.top]>

正确性

  • 每个操作必须保持I(s)
  • AF(实现后) = 规范

LSP的形式化

契约

{ P } m() { Q }
  • P:前置条件(precondition)
  • Q:后置条件(postcondition)

替换原则: 如果S <: T(S是T的子类型),则:

P_S → P_T  (前置条件:子更弱)
Q_T → Q_S  (后置条件:子更强)

例子

{ x > 0 } T.m(x) { result > 0 }  # 父类型
{ x > 5 } S.m(x) { result > 10 } # 子类型

检查:

  • x > 5 → x > 0 ✓(前置条件更弱)
  • result > 10 → result > 0 ✓(后置条件更强)
  • 满足LSP

📚 延伸阅读

书籍

Liskov的著作

  • 《Abstraction and Specification in Program Development》(1986)
    • ADT的权威著作
  • 《Program Development in Java》(2001)
    • 教学用书,系统讲解设计原则

相关书籍

  • 《Object-Oriented Software Construction》(Bertrand Meyer)
    • 契约式设计
  • 《Design Patterns》(GoF)
    • 设计模式,LSP是基础

论文

经典论文

  • “A Design Methodology for Reliable Software Systems” (1972)
    • 早期软件工程思想
  • “CLU Reference Manual” (1979)
    • CLU语言定义
  • “A Behavioral Notion of Subtyping” (1987)
    • LSP的正式定义
  • “Distributed Programming in Argus” (1988)
    • Argus语言

在线资源

  • Liskov的MIT主页:个人简介和论文列表
  • CLU资料:可以在线阅读CLU手册
  • ACM图灵奖演讲:Liskov的获奖演讲视频

🌟 精神遗产

”封装是基础”

Liskov的核心理念

  • 隐藏实现细节
  • 暴露抽象接口
  • 模块化是可维护性的关键

影响

  • 所有现代语言都支持封装
  • API设计的黄金准则

”契约高于实现”

LSP的哲学

  • 子类型必须遵守父类型的契约
  • 行为兼容性比名称重要
  • 设计时考虑替换性

影响

  • 契约式设计(Design by Contract)
  • 接口设计的指导原则

”理论指导实践”

Liskov的工作模式

  • 不满足于空谈理论
  • 构建系统验证理论
  • 理论和实践相互促进

启示

  • 最好的理论来自实践
  • 最好的实践基于理论
  • 两者不应分离

总结语:Barbara Liskov用一生证明了:好的抽象能驾驭复杂性。她创造的数据抽象、子类型理论、分布式编程概念,不仅改变了编程语言的设计,更改变了我们思考软件的方式。

从1972年的ADT到1987年的LSP,从CLU的封装到Argus的分布式事务,Liskov不仅提出理论,更将理论转化为工程实践。今天,当你写一个Java类、定义一个Python模块、设计一个微服务,你都在使用她创造的概念。

她是第二位女性图灵奖得主,但她希望被记住的不是性别,而是贡献。从被普林斯顿拒绝到MIT终身教授,从早期的性别歧视到今天的学术偶像,Liskov用成果说话,用智慧开路。她证明了:伟大不分性别,只看思想的深度和影响的广度。


最后更新: 2024年12月 本文为图灵奖系列文章,旨在以通俗方式介绍计算机科学先驱的贡献

DISCUSSION

评论与补充