Ethics - 系统应该允许什么

核心问题:

我们应该允许什么?不应该允许什么?

Dimension 1 定义世界。

Dimension 2 验证世界。

Dimension 3 约束世界。

软件工程里的伦理不是抽象说教,而是系统如何分配:

  • 权力
  • 数据
  • 默认值
  • 责任
  • 机会
  • 纠错路径
  • 后果

核心句:

技术上能做,不代表系统应该允许。

1. 权限是在分配权力

权限不是普通配置。

它决定:

  • 谁能看什么?
  • 谁能改什么?
  • 谁能删除什么?
  • 谁能代表别人行动?
  • 谁能绕过规则?

判断问题

  1. 这个主体是谁?人、服务、组织还是外部应用?
  2. 它完成任务所需的最小能力是什么?
  3. 权限绑定在哪个资源范围?
  4. 默认是否拒绝?
  5. 高风险操作是否有审计?

核心句:

默认拒绝,明确允许。

2. Admin 不是万能神

坏模式:

if (user.role === "admin") return true

更好的方向:

canViewUserProfile(adminId, accountId)
canRefundPayment(adminId, paymentId)
canExportUserData(adminId)
canImpersonateUser(adminId, accountId)

高风险能力应该拆开,并加上:

  • 二次确认
  • 审计日志
  • 审批
  • 时间限制
  • 数据脱敏

核心句:

管理员不是一种身份,而是一组高风险能力的集合。

3. 隐私从不收集开始

隐私不只是防泄露。

更根本的是:

不该收集的数据,最好一开始就不要进入系统。

新增字段前问:

  1. 这个数据是否必要?
  2. 是否可以用更粗粒度数据代替?
  3. 是否可以临时使用,不持久化?
  4. 是否会进入日志、缓存、分析或第三方?
  5. 保留多久?
  6. 如何删除或匿名化?

核心句:

不必要的数据不是资产,是风险。

4. 日志不是隐私垃圾桶

坏日志:

logger.info("request", {
  body: req.body,
  headers: req.headers,
})

可能记录:

  • password
  • token
  • cookie
  • payment data
  • PII
  • 私密内容

好方向:

logger.info("payment_request_created", {
  accountId,
  paymentId,
  amount,
  currency,
  requestId,
})

核心句:

日志应该记录排查所需证据,不记录不必要的私人信息。

5. 系统要有对抗性思维

不要只写 user story,也要写 abuse case。

作为攻击者,我想批量注册账号领取试用,以便免费访问付费课程。
作为恶意用户,我想重复调用退款接口,以便获得退款但保留访问权。

设计功能时问:

  1. 这个能力能否被批量调用?
  2. 客户端传来的哪些字段不可信?
  3. 重复请求是否安全?
  4. 是否需要限流?
  5. 是否需要幂等键?
  6. 被滥用后如何止损?

核心句:

系统不仅要服务善意用户,也要防止恶意使用。

6. 客户端输入不能直接信

坏代码:

await purchaseCourse({
  accountId: req.body.accountId,
  courseId: req.body.courseId,
  price: req.body.price,
})

好方向:

const accountId = session.accountId
const course = await courses.findById(req.body.courseId)
const price = await pricing.currentPriceFor(course.id)

核心句:

客户端可以表达意图,服务器必须重新验证事实。

7. 指标就是价值判断

推荐、排序、风控、审核都不是中立的。

优化什么,就会塑造什么。

优化指标可能偏向
点击率标题党、刺激内容
观看时长让用户停留更久的内容
完课率短课程、容易完成的课程
收入高价课程

设计指标时问:

  1. 它鼓励什么行为?
  2. 它会牺牲什么?
  3. 谁受益?
  4. 谁被边缘化?
  5. 有没有 guardrail metric?

核心句:

没有中立指标。

8. 自动化决定必须可解释、可申诉

高影响决定包括:

  • 封号
  • 拒绝提现
  • 下架课程
  • 拒绝认证
  • 风控拦截

这些决定应该有:

  • decision record
  • reason code
  • policy version
  • audit trail
  • appeal path

核心句:

自动化可以执行决定,但不能承担责任。

9. Reason Code 是伦理基础设施

不要只返回:

Rejected

要有稳定原因:

ACCOUNT_SUSPENDED
COURSE_ARCHIVED
PAYMENT_RISK_HIGH
REFUND_WINDOW_EXPIRED

Reason code 服务:

  • 用户解释
  • 客服处理
  • 工程排查
  • 监控聚合
  • 审计复盘
  • 申诉定位

核心句:

失败方式也应该被系统承认和命名。

10. 反操控设计

Dark patterns 是利用设计诱导用户做出不符合真实意愿的选择。

危险信号:

  • 取消订阅很难
  • 默认勾选营销邮件
  • 隐藏真实价格
  • 拒绝按钮羞辱用户
  • 默认数据共享
  • 用视觉强弱误导选择

判断问题:

  1. 用户是否清楚知道自己在选择什么?
  2. 拒绝、取消、退出是否容易?
  3. 默认值是否保护用户?
  4. 摩擦是在保护用户,还是阻碍用户?

核心句:

界面不是中立容器,界面会影响选择。

11. 安全默认值

默认配置应该保护用户和系统。

好默认:

default deny
default private
short-lived token
read-only API token
safe error response
redacted logs
share link expires

核心句:

安全不应该依赖记忆,应该依赖默认值。

12. 人类介入不是绕过系统

人工 override 必须被系统承认和记录。

type OverrideRecord = {
  decisionId: string
  actorId: string
  action: "approve_appeal" | "reject_appeal" | "manual_override"
  reason: string
  occurredAt: Date
}

核心句:

人类介入不是绕过系统,而是系统承认并记录的一种路径。

13. 后台是权力集中区

坏后台:

搜索用户 -> 查看所有信息 -> 任意修改 -> 无审计

好后台:

搜索用户 -> 默认脱敏 -> 查看敏感信息需理由 -> 所有操作审计 -> 高风险操作二次确认

核心句:

不要把后台当成没有伦理边界的地方。

14. 伦理要落到工程构件

原则工程构件
最小权限policy, scoped role, deny by default
隐私最小化PII classification, retention job
可解释性reason code, decision record
责任归属audit trail, actorId, policyVersion
申诉纠错appeal workflow, reviewer tooling
反操控UX review, guardrail metrics
安全默认值default private, short-lived token
滥用防护rate limit, idempotency

核心句:

如果原则找不到工程落点,它很可能只是口号。

15. 伦理债

系统也会积累伦理债:

  • 没有删除机制
  • 后台无审计
  • 管理员权限过大
  • 日志里有 PII
  • 用户无法申诉
  • 取消订阅过难
  • 风控误伤没有复核

核心句:

伦理债和技术债一样,会积累利息。

一页总口诀

加权限前,先问最小能力。
看数据前,先问是否必要。
写日志前,先问是否敏感。
接输入前,先问是否可信。
做增长前,先问是否操控。
做推荐前,先问优化了什么。
做风控前,先问误伤怎么办。
做自动化前,先问谁能申诉。
设默认值前,先问保护谁。
做后台前,先问如何审计。

最终心法

软件系统不是中立机器。它会分配权力、塑造选择、保存记忆、制造后果。

Dimension 3 的目标,是让工程师在设计系统时,不只问:

能不能实现?

还要问:

谁获得了能力?
谁承担了风险?
谁能看到数据?
谁能纠正错误?
谁会被默认值影响?