Skip to Content
Week 09Day 7 - 复盘与巩固

Day 7 - 复盘与巩固

建议用时:120-150 分钟

你将学会什么

  • 怎么把 Week 9 的知识连成一条主线
  • 怎么判断一个功能有没有进入“可交付”状态
  • 怎么用一个小程序复盘性能、持久化和日志
  • 怎么写验收清单
  • 怎么从“能运行”提升到“能检查、能排错、能发布”

本页不再新增 API。今天把本周内容串起来:数据量变大怎么处理、文件怎么保存、错误怎么记录、发布后怎么检查。能把流程说清楚,比只记住某个命令更重要。

本页固定顺序

  1. 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
  2. 再学第二部分:把刚学的知识组合成一个完整例子。
  3. 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
  4. 最后做第四部分:先独立完成作业,再用完整答案检查。

学习衔接

上一页学习的是“错误处理与日志”,今天继续学习“复盘与巩固”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。

今天的最低通过线

第一次学习不要求背完整页。完成下面 3 项,就可以继续:

  • 能用自己的话说明“复盘与巩固”解决什么问题。
  • 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
  • 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。

第一部分:先学原理和最小知识

这一部分从最小知识开始。先读解释,再把紧跟着的短例子敲一遍。复盘不是把前 6 天重新读一遍,而是把它们变成一条可以复用的开发路线。

Week 9 的主线

本周主题可以用一句话概括:

程序不只要能写出来,还要能在真实运行时保持可用、可保存、可排查、可发布。

前几周更多关注“功能怎么写”。Week 9 开始关注“功能运行久了、数据多了、发布出去了,会不会出问题”。

每天对应的问题

日期主题解决的问题
Day 1性能认知知道慢在哪里,不靠猜
Day 2大列表和虚拟化数据多时不要一次显示太多
Day 3文件与对话框用户选择文件,程序安全读写
Day 4数据持久化程序关闭后数据还能恢复
Day 5打包与发布从开发目录变成发布目录
Day 6错误处理与日志出错后有提示,也有排查线索
Day 7复盘与巩固把这些点连成完整流程

可交付功能要有 5 个部分

一个功能只写正常流程是不够的。

可交付功能至少包含:

部分要回答的问题例子
输入数据从哪里来输入框、文件、列表
处理程序做什么搜索、保存、读取
输出结果到哪里去界面、JSON、日志
失败处理出错时怎么办提示、拦截、catch
验收怎么证明可用正常跑一次,错误跑一次

本页的 Program.cs 就是按这 5 个部分组织的。

复盘时不要只看“会不会写”

复盘要问 6 个问题:

  1. 这个功能输入是什么?
  2. 处理规则是什么?
  3. 输出结果是什么?
  4. 数据量变大时会不会卡?
  5. 失败时用户看到什么?
  6. 失败细节保存在哪里?

如果这 6 个问题都能回答,说明这部分知识已经能用于项目。

为什么要写验收清单

验收清单是为了防止“我以为好了”。

例如“保存 JSON”不能只看代码里有 WriteAllTextAsync,还要检查:

  • 文件是否真的生成。
  • 文件内容是不是 JSON。
  • 读取回来条数对不对。
  • 文件不存在时会不会崩溃。
  • 发布后保存路径还能不能写。

没有验收清单,很多问题要到交付后才暴露。

本周最重要的判断方法

遇到功能问题,按这个顺序判断:

  1. 先看是否能复现。
  2. 再用耗时或日志定位位置。
  3. 数据多时先减少界面显示量。
  4. 文件读写先处理取消、缺失、权限。
  5. 保存数据先明确路径。
  6. 发布后必须从发布目录运行。

这就是 Week 9 的实践路线。

Week 9 性能与发布常用操作总表

主题必须会的操作
性能测量Stopwatch
大列表TakeWhereVisibleProducts
文件对话框StorageProviderOpenReadAsyncOpenWriteAsync
JSON 保存JsonSerializerFile.ReadAllTextWriteAllText
发布dotnet publish -c Release
日志时间、级别、操作、错误原因

第二部分:把知识组合成完整例子

今天用一个控制台程序复盘 Week 9。

为什么用控制台程序?因为今天的目标是把流程跑清楚,不让界面代码干扰理解。

这个程序会做 6 件事:

  1. 生成 5000 条商品。
  2. 只取前 100 条作为首屏数据。
  3. Stopwatch 记录处理耗时。
  4. 把首屏数据保存成 JSON。
  5. 再从 JSON 读取回来。
  6. 故意读取不存在的文件,把错误写入日志。

完整 Program.cs

文件位置:

Program.cs
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json; string appFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Week09Review"); Directory.CreateDirectory(appFolder); string jsonPath = Path.Combine(appFolder, "products.json"); string logPath = Path.Combine(appFolder, "app.log"); var watch = Stopwatch.StartNew(); List<Product> allProducts = Enumerable .Range(1, 5000) .Select(i => new Product($"Product {i:0000}", i * 1.5m)) .ToList(); List<Product> firstPage = allProducts .Take(100) .ToList(); string json = JsonSerializer.Serialize(firstPage, new JsonSerializerOptions { WriteIndented = true }); await File.WriteAllTextAsync(jsonPath, json); string loadedJson = await File.ReadAllTextAsync(jsonPath); List<Product> loadedProducts = JsonSerializer.Deserialize<List<Product>>(loadedJson) ?? new List<Product>(); watch.Stop(); Console.WriteLine($"全部商品:{allProducts.Count}"); Console.WriteLine($"首屏商品:{firstPage.Count}"); Console.WriteLine($"读取回来:{loadedProducts.Count}"); Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}ms"); Console.WriteLine($"JSON 文件:{jsonPath}"); Console.WriteLine($"日志文件:{logPath}"); try { string missingPath = Path.Combine(appFolder, "missing.json"); await File.ReadAllTextAsync(missingPath); } catch (Exception ex) { Task LogAsync(string level, string message, Exception? ex = null) { string detail = ex is null ? message : $"{message}{Environment.NewLine}{ex}"; string line = $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff zzz} [{level}] {detail}{Environment.NewLine}"; return File.AppendAllTextAsync(logPath, line); } await LogAsync("ERROR", "读取 missing.json 失败", ex); Console.WriteLine("故意制造的错误已写入日志。"); } record Product(string Name, decimal Price);

正常运行后应该看到

全部商品:5000 首屏商品:100 读取回来:100 耗时:若干 ms JSON 文件:... 日志文件:... 故意制造的错误已写入日志。

这个小程序把 Week 9 的主线全部串起来了:数据量、性能测量、JSON、文件、日志、异常。

第三部分:跟着敲代码

从这里开始动手。今天用控制台项目完成复盘程序。

第 1 步:创建控制台项目

在一个练习目录里执行:

dotnet new console -n Week09Review cd Week09Review

打开 Program.cs,清空原内容。

第 2 步:写 using

using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json;

解释:

  • DiagnosticsStopwatch
  • IO 用文件读写。
  • LinqRangeSelectTake
  • Text.Json 用 JSON 序列化。

第 3 步:准备保存目录

string appFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Week09Review"); Directory.CreateDirectory(appFolder); string jsonPath = Path.Combine(appFolder, "products.json"); string logPath = Path.Combine(appFolder, "app.log");

解释:

  • appFolder 是本程序的数据目录。
  • jsonPath 是 JSON 文件路径。
  • logPath 是日志文件路径。
  • CreateDirectory 保证目录存在。

第 4 步:写日志函数

Task LogAsync(string level, string message, Exception? ex = null) { string detail = ex is null ? message : $"{message}{Environment.NewLine}{ex}"; string line = $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff zzz} [{level}] {detail}{Environment.NewLine}"; return File.AppendAllTextAsync(logPath, line); }

解释:

  • level 是 INFO 或 ERROR。
  • message 是动作说明。
  • ex 是异常详情,可有可无。
  • AppendAllTextAsync 是追加日志,不覆盖旧日志。

第 5 步:生成 5000 条商品

var watch = Stopwatch.StartNew(); List<Product> allProducts = Enumerable .Range(1, 5000) .Select(i => new Product($"Product {i:0000}", i * 1.5m)) .ToList();

解释:

  • Stopwatch.StartNew() 开始计时。
  • Range(1, 5000) 生成 5000 个数字。
  • Select 把数字变成商品。
  • ToList 生成商品列表。

第 6 步:只取首屏 100 条

List<Product> firstPage = allProducts .Take(100) .ToList(); await LogAsync("INFO", $"准备商品 {allProducts.Count} 条,首屏 {firstPage.Count} 条");

解释:

  • allProducts 是全部数据。
  • firstPage 是当前处理的数据。
  • 这对应 Day 2 的大列表思路。

第 7 步:保存 JSON

string json = JsonSerializer.Serialize(firstPage, new JsonSerializerOptions { WriteIndented = true }); await File.WriteAllTextAsync(jsonPath, json); await LogAsync("INFO", $"已保存 JSON:{jsonPath}");

解释:

  • Serialize 把商品列表变成 JSON。
  • WriteIndented = true 让 JSON 文件更容易阅读。
  • 保存成功后写 INFO 日志。

第 8 步:读取 JSON

string loadedJson = await File.ReadAllTextAsync(jsonPath); List<Product> loadedProducts = JsonSerializer.Deserialize<List<Product>>(loadedJson) ?? new List<Product>();

解释:

  • 先从文件读取 JSON 字符串。
  • 再把 JSON 转回 List<Product>
  • ?? new List<Product>() 防止结果为空。

第 9 步:输出结果

watch.Stop(); Console.WriteLine($"全部商品:{allProducts.Count}"); Console.WriteLine($"首屏商品:{firstPage.Count}"); Console.WriteLine($"读取回来:{loadedProducts.Count}"); Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}ms"); Console.WriteLine($"JSON 文件:{jsonPath}"); Console.WriteLine($"日志文件:{logPath}");

解释:

  • 输出总数、首屏数量、读取数量。
  • 如果读取数量不是 100,就说明保存或读取有问题。

第 10 步:故意制造错误并写日志

try { string missingPath = Path.Combine(appFolder, "missing.json"); await File.ReadAllTextAsync(missingPath); } catch (Exception ex) { await LogAsync("ERROR", "读取 missing.json 失败", ex); Console.WriteLine("故意制造的错误已写入日志。"); }

解释:

  • missing.json 不存在,所以会抛异常。
  • catch 捕获异常。
  • LogAsync("ERROR", ..., ex) 把错误详情写入日志。

第 11 步:添加 Product 记录类型

record Product(string Name, decimal Price);

解释:

  • Product 有商品名和价格。
  • record 适合这种只保存数据的类型。

第 12 步:运行并检查

执行:

dotnet run

检查:

  1. 控制台显示全部商品 5000。
  2. 控制台显示首屏商品 100。
  3. 控制台显示读取回来 100。
  4. JSON 文件路径能打开。
  5. 日志文件路径能打开。
  6. 日志里有 INFO 和 ERROR。

小白重复敲写训练

复盘日完成“测量、保存、处理错误”三个独立小程序。

训练 1:测量耗时

var watch = Stopwatch.StartNew(); await Task.Delay(300); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds);

训练 2:保存设置

var settings = new { Theme = "Dark", PageSize = 20 }; string json = JsonSerializer.Serialize(settings); await File.WriteAllTextAsync("settings.json", json);

训练 3:安全读取

try { Console.WriteLine(await File.ReadAllTextAsync("settings.json")); } catch (Exception ex) { Console.WriteLine($"读取失败: {ex.Message}"); }

关掉答案,把三段组合成“保存后再读取”的完整程序。

每日小测

做完本页后,用这 5 题检查是否真的掌握。

1. 判断题

本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。

答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。

2. 填空题

本页主题是:复盘与巩固。今天至少要掌握的 3 个点是:

1. 怎么把 Week 9 的知识连成一条主线 2. 怎么判断一个功能有没有进入“可交付”状态 3. 怎么用一个小程序复盘性能、持久化和日志

答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。

3. 流程题

遇到本页相关功能时,先按什么顺序处理?

答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。

4. 找错误题

如果本页代码运行失败,第一步应该做什么?

答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。

5. 改需求题

在本页完整例子跑通后,至少改一个小需求。

可选改法:

  • 改一个字段名称。
  • 多加一个校验条件。
  • 多输出一行结果。
  • 把固定数据改成用户输入。
  • 把一次处理改成多条数据处理。

答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:怎么把 Week 9 的知识连成一条主线。

上位机专项练习

本周过关标准是大数据列表不卡、配置能保存、文件能导入导出、Release 包能在目标电脑运行。

下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。

专项例子 1:复习性能检查

Console.WriteLine("[ ] 10000 条列表滚动正常"); Console.WriteLine("[ ] UI 刷新频率受控"); Console.WriteLine("[ ] 后台采集不阻塞界面");

运行结果或界面效果:

三项性能检查清楚

改动任务: 实际测试后把方框改成完成。

专项例子 2:复习数据持久化

await SaveSettingsAsync(settings); AppSettings loaded = await LoadSettingsAsync(); Console.WriteLine(loaded.LastDeviceId);

运行结果或界面效果:

保存后能再次读回相同设置

改动任务: 故意破坏 JSON,处理读取失败。

专项例子 3:复习发布验收

string[] checks = ["可启动", "配置可改", "日志可找", "异常有提示"]; foreach (string check in checks) Console.WriteLine($"验收: {check}");

运行结果或界面效果:

发布包有四项明确验收

改动任务: 在另一台 Windows 电脑执行检查。

第四部分:作业完整答案

作业要求:用一个控制台程序复盘 Week 9,程序要包含大数据、首屏限制、JSON 保存、JSON 读取、耗时记录、错误日志。

答案文件:Program.cs

using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json; string appFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Week09Review"); Directory.CreateDirectory(appFolder); string jsonPath = Path.Combine(appFolder, "products.json"); string logPath = Path.Combine(appFolder, "app.log"); var watch = Stopwatch.StartNew(); List<Product> allProducts = Enumerable .Range(1, 5000) .Select(i => new Product($"Product {i:0000}", i * 1.5m)) .ToList(); List<Product> firstPage = allProducts .Take(100) .ToList(); string json = JsonSerializer.Serialize(firstPage, new JsonSerializerOptions { WriteIndented = true }); await File.WriteAllTextAsync(jsonPath, json); string loadedJson = await File.ReadAllTextAsync(jsonPath); List<Product> loadedProducts = JsonSerializer.Deserialize<List<Product>>(loadedJson) ?? new List<Product>(); watch.Stop(); Console.WriteLine($"全部商品:{allProducts.Count}"); Console.WriteLine($"首屏商品:{firstPage.Count}"); Console.WriteLine($"读取回来:{loadedProducts.Count}"); Console.WriteLine($"耗时:{watch.ElapsedMilliseconds}ms"); Console.WriteLine($"JSON 文件:{jsonPath}"); Console.WriteLine($"日志文件:{logPath}"); try { string missingPath = Path.Combine(appFolder, "missing.json"); await File.ReadAllTextAsync(missingPath); } catch (Exception ex) { Task LogAsync(string level, string message, Exception? ex = null) { string detail = ex is null ? message : $"{message}{Environment.NewLine}{ex}"; string line = $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff zzz} [{level}] {detail}{Environment.NewLine}"; return File.AppendAllTextAsync(logPath, line); } await LogAsync("ERROR", "读取 missing.json 失败", ex); Console.WriteLine("故意制造的错误已写入日志。"); } record Product(string Name, decimal Price);

验收结果

运行后必须看到这些结果:

  1. 控制台显示 全部商品:5000
  2. 控制台显示 首屏商品:100
  3. 控制台显示 读取回来:100
  4. 控制台显示 JSON 文件路径。
  5. 控制台显示日志文件路径。
  6. 日志文件里有 [INFO]
  7. 日志文件里有 [ERROR]
  8. JSON 文件里能看到商品名称和价格。

为什么这个答案是对的

这个答案把本周知识连成了一条完整链路:

Week 9 知识程序里的位置作用
性能认知Stopwatch记录耗时
大列表处理Take(100)控制首屏数量
文件读写File.WriteAllTextAsync / File.ReadAllTextAsync保存和读取文件
数据持久化JsonSerializer对象和 JSON 互转
错误处理try/catch捕获失败
日志LogAsync保存排查线索

能把这条链路跑通,Week 9 的重点就不是散的:数据多了要控制显示量,保存数据要明确路径,失败要有提示和日志,发布后要重新验收。