构建软件

通常,多个算法或实现可以正确解决相同的任务。 你选择的代码体现一种设计理念。 读取代码的开发人员和编码代理使用其结构来推断你的意图:每个操作需要的内容、它所属的位置以及何时运行。

Tip

本文属于面向掌握基本 C# 表达式、语句和方法的开发人员的 基础知识 部分。 如果你不熟悉编程,请先 学习入门 教程。

本部分比较了各种有效的算法方案,并解释了它们在行为上的细微差异以及读者如何解读它们。 你将组合熟悉的语言元素,例如类型、表达式、语句和方法,并选择能够传达你想要表达的设计的形式。

比较行为和设计意向

在选择实现之前,请考虑其他代码如何使用它。 该代码是 调用方:它提供输入、调用操作并使用结果。 它是否需要立即得到完整结果,或者是否可以一次处理一个值? 此操作的辅助程序是单独使用,还是其他操作也会使用它? 这些问题有助于比较所需的行为和代码传达的设计。

简短的内联操作会在使用位置向读者显示操作步骤。 例如, Where(task => task.Priority > 3) 使筛选规则立即可见。 命名的帮助程序(例如 Where(IsUrgent))强调规则的目的,并允许读者单独检查其详细信息。 根据步骤还是其用途更能解释周围代码来进行选择。

在何处声明辅助程序,体现了其预期作用域。 位于 ShowDashboard 内部的本地函数指示帮助程序属于该操作,并阻止其他成员调用它。 包含类型的私有方法使帮助程序可供其他成员使用,这表明你希望在该类型中的其他位置重复使用。 两者都可以以相同方式呈现同一个计算,但它们会让读者对其作用产生不同的预期。

如何提供帮助程序的输入体现其依赖关系。 将限值作为参数传递,例如 IsUrgent(task, minimumPriority),使该输入在每次调用时可见。 本地函数或 lambda 可以改为 捕获minimumPriority其外围作用域中的变量,而无需将其作为参数接收。 捕获将帮助程序连接到包含操作的上下文;显式参数使提供的数据更易于识别。 当你想强制其不捕获周围状态时,请使用 static 本地函数或 lambda。

你返回结果的方式表明了工作何时发生。 生成并返回集合的方法在调用方接收集合之前完成该工作。 迭代器在调用方请求元素时生成元素,因此调用方可以在生成所有元素之前停止。 两者可能生成相同的值,但调用方需要了解接收到的是已完成的结果,还是其计算仍在后续进行的序列。

对于每个选项,首先确定调用代码需要的行为。 然后,考虑另一个开发人员或编码代理将从名称、范围、输入和结果推断出什么。 选择一种形式,使这些期望与你的设计相匹配,并解释仅凭代码结构本身无法体现的重要行为。 目标是在有效的替代方案之间做出有意的选择,而不是在任何地方应用一种形式。

了解示例中的设计选项

以下示例演示了多个选项如何传达设计的不同部分。 它显示紧急且已准备好开始的任务。 一个 ProjectTask 包含任务的标题、优先级和阻止状态。

var tasks = new[]
{
    new ProjectTask("Confirm venue", Priority: 3, IsBlocked: false),
    new ProjectTask("Print badges", Priority: 2, IsBlocked: true),
    new ProjectTask("Test microphones", Priority: 2, IsBlocked: false),
};

ShowDashboard("Community meetup", tasks, minimumPriority: 2);

static void ShowDashboard(
    string projectName,
    IEnumerable<ProjectTask> tasks,
    int minimumPriority)
{
    var urgentTasks = tasks.Where(
        task => task.Priority >= minimumPriority);

    foreach (ProjectTask task in ReadyTasks(urgentTasks))
    {
        Console.WriteLine(FormatTask(task));
    }

    string FormatTask(ProjectTask task) =>
        $"{projectName}: {task.Title}";
}

static IEnumerable<ProjectTask> ReadyTasks(
    IEnumerable<ProjectTask> tasks)
{
    foreach (ProjectTask task in tasks)
    {
        if (!task.IsBlocked)
        {
            yield return task;
        }
    }
}

record ProjectTask(string Title, int Priority, bool IsBlocked);

每个选项都会使工作的不同部分保持清晰:

  • 传递给 Where 的 lambda 使短优先级规则位于使用它的操作旁边。
  • 本地函数 FormatTask 表示仅属于 ShowDashboard 的一个格式设置步骤。
  • 迭代器 ReadyTasks 使调用方无需创建中间集合即可处理已就绪的任务。
  • 该 foreach 循环使最终操作(写入每个结果)显式。

为了进行比较, Where(static task => !task.IsBlocked) 可以替换 ReadyTasks ,因为此就绪情况测试是一个简单的筛选器。 这两种形式都将筛选延迟到枚举时才执行。 lambda 将规则置于调用站点; ReadyTasks 为规则指定名称。 根据有助于读者理解调用方的信息,在两者之间进行选择。

后续文章使用此类比较来解释每种方法何时适合,以及读者可以从你的选择中推断出什么。

后续步骤