Chapter 3. Designing function signatures and types

覆盖function signature信息设计、data objects、Unit与Option,把absence、completion、failure和invalid state前移到type boundary。

学习目标

  • 能分析function signature泄露的hidden input/output,设计domain value、Option与Either式return使outcome可见
  • 能解释Unit与void的value/composition差异,判断何时成功但无业务数据仍需value-level表示
  • 能设计Option pipeline和boundary adapter,区分None、external null、invalid input与fallback ownership

机制总览

Chapter 3. Designing function signatures and types:机制路径

  1. 1

    为什么Signature是最便宜的Architecture

    Function body可以写得很functional,但若signature接收primitive soup、读取global、通过out parameter或exception偷偷返回结果,caller仍无法组合和推理。Signature是每个consumer必经的architecture s…

  2. 2

    Function signature design

    先把function写成arrow: A - B ,再问是否真的只有A输入、B输出。Clock、configuration、repository若藏在object fields/global中也是inputs;mutation、logging、out/ref、exception是额外outputs/…

  3. 3

    Capturing data with data obje…

    Data object把相关values作为一个immutable snapshot传递,避免long parameter list和temporal mutation。它应表达construction invariant、value equality需求与serialization boundar…

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

Chapter 3. Designing function signatures and types:机制与证据

切换《Chapter 3. Designing function signatures and types》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么Signature是最便宜的Architecture

Function body可以写得很functional,但若signature接收primitive soup、读取global、通过out parameter或exception偷偷返回结果,caller仍无法组合和推理。Signature是每个consumer必经的architecture s…

可核验证据

以确定输入重复运行「为什么Signature是最便宜的Architecture」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

学完《Chapter 3. Designing function signatures and types》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 3. Designing function signatures and types:失效与核验

为什么Signature是最便宜的Architecture

典型失效

若把「为什么Signature是最便宜的Architecture」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「为什么Signature是最便宜的Architecture」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

Function signature design

典型失效

若把「Function signature design」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「Function signature design」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

Capturing data with data obje…

典型失效

若把「Capturing data with data obje…」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「Capturing data with data obje…」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么Signature是最便宜的Architecture

Function body可以写得很functional,但若signature接收primitive soup、读取global、通过out parameter或exception偷偷返回结果,caller仍无法组合和推理。Signature是每个consumer必经的architecture surface:它决定哪些states能表示、哪些dependencies显式、哪些outcomes必须处理。

先预测:bool TryX(out T)是否和Option<T>表达完全相同;Unit是否等于null;Option是否适合所有failure;把string换成CustomerId是否只是多一个class;nullable reference是否自动替代Option。答案都是否。

Function signature design

先把function写成arrow:A -> B,再问是否真的只有A输入、B输出。Clock、configuration、repository若藏在object fields/global中也是inputs;mutation、logging、out/ref、exception是额外outputs/effects。把稳定dependency部分应用到function,可得到更小的domain operation。

Parameter order影响partial application:先放configuration/dependency,后放变化频繁的data,便于composition root固定前者。多个同type primitives易swap,应提升为validated domain value。Return type区分routine absence、expected validation failure与unexpected dependency fault。

public static Func<Order, Price> PriceWith(
    TaxPolicy taxPolicy,
    Currency currency) =>
    order => Price.Calculate(order, taxPolicy, currency);
分步1 / 3

切换bool/out、nullable、Option与Either

Capturing data with data objects

Data object把相关values作为一个immutable snapshot传递,避免long parameter list和temporal mutation。它应表达construction invariant、value equality需求与serialization boundary。第一版写作时没有C# 9 records,常用constructor + getter-only properties;modern records可减少ceremony,但需单独标版本。

不要用“data object”制造anemic bag后在多个functions重复validate。Boundary先parse/validate成domain value,core接收valid state。需要变化时返回copy/new value;若identity和lifecycle重要,则明确entity与value的差异。

Modern representation boundary

C# 9 records可自动提供value-oriented syntax/equality,但不会自动验证domain invariant,也不会决定serialization compatibility。第一版使用class/getter-only properties表达同一principle;modern替换必须保持constructor/factory validation、external schema和equality contract。

public sealed class Transfer
{
    public AccountId From { get; }
    public AccountId To { get; }
    public Money Amount { get; }
 
    public Transfer(AccountId from, AccountId to, Money amount) =>
        (From, To, Amount) = amount.IsPositive
            ? (from, to, amount)
            : throw new ArgumentOutOfRangeException(nameof(amount));
}

Modeling the absence of data with Unit

Unit表示只有一个可能value的type。它不是absence,而是“计算成功完成,但没有额外业务value”。C# void不能作为generic type argument或普通value;Unit让Func<Unit>Either<Error,Unit>等pipeline统一处理完成结果。

Unit不应替代meaningful result。保存订单若需要generated id/etag,应返回它;只在success本身就是全部information时用Unit。Implementation可用singleton/empty struct,重点是equality和single-value semantics。

分步1 / 3

切换void、Unit、Action与Func-Unit

Modeling the possible absence of data with Option

Option有两个states:Some(value)和None。它适合routine absence,例如lookup没找到、optional middle name;caller通过Map/Bind/Match/IfNone显式处理,不直接deref null。Option让fallback推迟到最了解context的consumer,而不是repository提前返回empty object。

Option<Customer> customer = repository.Find(id);
 
string email = customer
    .Map(value => value.Email)
    .IfNone("missing@example.invalid");

External null必须在adapter转换:允许absence则None,不允许则validation error。Some(null)是否允许要由Option implementation明确;通常禁止以避免双重absence。Option也不携带failure reason,validation/dependency error更适合Either/Result或exception boundary。

分步1 / 3

切换Some、None、external null与fallback

本章回顾:让Type保留Caller必须知道的信息

  1. Signature显式暴露inputs、outputs、absence与failure,是最早的architecture boundary。
  2. Domain data objects收敛validation并以immutable snapshot跨function传递。
  3. Unit表示successful no-data completion,不是null/absence。
  4. Option表示routine possible absence,fallback由consumer owner决定。
  5. Type复杂度必须对应真实domain states,不能为functional外观而堆generic wrapper。

练习

问题 1:Customer? Find(id)应何时改成Option?

问题 2:发送Email成功时返回Unit是否合理?

问题 3:怎样重构四个string参数避免swap?

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

signature information surface
domain value boundary
data object snapshot
unit completion value
option absence contract

原版目录概念补充核对

以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。

Capturing data with data objects:机制、边界与证据

Chapter 3. Designing function signatures and types中的Capturing data with data objects应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

Modeling the absence of data with Unit:机制、边界与证据

Chapter 3. Designing function signatures and types中的Modeling the absence of data with Unit应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

讨论

评论区加载中…