2026-09-03 博客:配置与密码 · 合并代码 · 超时排查

2026-09-03 博客:配置与密码 · 合并代码 · 超时排查

今天聊了三件事:① 程序的「设置」和「登录密码」为什么是两个抽屉;② 多条分支代码怎么合到一起 + 三个防卡死小套路;③ 一个「超时」报错怎么定位(三板斧)。


第一件:配置文件 和 登录密码,是两个抽屉

1. 两个抽屉:设置 和 密码,不在一处

先想一个生活场景:

你有一台单反相机,它有两样家当要保管:

  • 相机的设置(白平衡、对焦微调、镜头标定)------决定"拍出来准不准"。
  • 锁屏密码------决定"你能不能开机"。

这两样东西物理上存在完全不同的地方:设置存在相机机身里,锁屏密码存在你脑子里(或密码管理器里)。

程序也一样,它有两样家当:

复制代码
程序根目录/
│
├── 配置文件夹/          ← 「设置」住这里(标定 / 连接参数)
│   ├── 机器配置.json     ← 数据(校准等)
│   └── 连接配置.json     ← 连接参数(设备地址、端口、连接串)
│
├── 程序.exe             ← 程序本体
├── 账号库.db            ← 「密码」住这里(账号 + 密码的哈希)
└── 记住密码.dat         ← 勾了「记住密码」后生成的加密文件

关键就一句话:

标定(设置)在「配置文件夹」里,密码在「程序旁边」------两个抽屉,不挨着。

所以你「备份配置文件夹」,只保住了设置(标定),没保住密码。恢复之后,设置回来了,密码还是得重新输。

2. 程序"缺了才补",不会主动删

再想一个生活场景:

仓库管理员清点货架,他的原则是------货架空了才补货,有货就绝不动

程序启动时对配置和数据库,也是这个原则:

csharp 复制代码
// 配置文件:文件不存在才生成一份默认的,存在就直接读
if (!File.Exists(配置路径))
    生成默认配置();

// 账号数据库:表不存在才建表,有表就原样用(不删、不覆盖)
建表如果不存在(账号库);

为什么这么设计?因为程序不敢动你已有的数据

  • 标定数据是现场辛辛苦苦校准出来的,程序不能一启动就重置它。
  • 账号密码是真实存在的,程序不能一启动就清空。

所以「密码没了、标定错了」,不是程序自己干的,是外面的动作(换程序、放错位置)弄丢的。

3. 那「重新编译程序」会改配置吗?

先把两个动作分清:

  1. 编译 :把代码变成 exe,只写「程序文件」,不碰配置文件夹
  2. 运行新程序:这才可能碰配置。

而「运行」时,两种配置文件的态度还不一样------像两种收银员:

  • 甲收银员 (连接配置):每次下班都把账本重抄一遍(读完 → 整理 → 写回)。如果新版程序改了字段结构,这一重抄就会加字段、丢字段。
  • 乙收银员 (标定配置):账本在就只看不动,账本丢了才补一本新的。
csharp 复制代码
// 甲:每次启动都回写(可能改内容)
配置 = 读取(路径);
迁移旧字段(配置);
配置.写回();               // ← 每次都写

// 乙:缺了才写,在就只读
if (!File.Exists(标定路径))
    生成默认标定();

第二件:合并代码的几种姿势 + 三个防卡死套路

1. git 合并的几种姿势

先想一个生活场景:

你同时维护两本账:一本「主线账」(大家都在用),一本「你的草稿账」(你私下加的功能)。现在要把草稿里的东西并回主线。

merge vs rebase ------ 两种「并账」方式

merge(合并):把草稿账直接「钉」进主线,中间留一张「此处合并了草稿」的纸条。

复制代码
主线:  A → B → C ──┐
                   ├→ M (合并提交,纸条)
草稿:       B → D → E ┘

rebase(变基):把草稿里的 D、E 拆下来,重新「抄」到主线最新处,历史成一条直线,不留纸条。

复制代码
主线:  A → B → C
草稿:  A → B → C → D' → E'   (D、E 重抄成 D'、E')
  • 区别一句话:merge 留「合并痕迹」,rebase 让历史「一条线、干净」。
bash 复制代码
git pull            # merge:直接拉,会产生一条「合并 xxx」提交
git pull --rebase   # rebase:本地提交重放到远程最新之上,历史线性

squash ------ 多张草稿揉成一张正稿

你为一个小功能改了 5 次(5 个提交)。并入主线时不必把 5 个都带过去,可以揉成一个提交

bash 复制代码
git reset --soft <基准>   # 把 5 个提交退回到暂存区(改动不丢)
git commit                # 一次性重新提交成 1 个
  • 类比:5 张草稿纸 → 揉成 1 张干净的正稿交上去。

cherry-pick ------ 从一盆水果里挑几个

不必把整条草稿分支都合过来,只挑某几个提交

bash 复制代码
git cherry-pick <提交A> <提交B>
git cherry-pick -n <提交A>   # -n:只应用改动、先不提交,攒几个再统一提交

顺序依赖 ------ 先合谁,决定会不会撞

两个提交,若 B 是在 A 基础上改的(B 依赖 A):

  • 先合 A、再合 B → 顺理成章,零冲突

  • 单独合 B → B 里带着 A 的改动,会和主线撞车。

  • 类比:先铺地板、再放家具(顺序对,很顺);反过来先放家具再铺地板就卡住。

冲突两类型:位置冲突 vs 内容冲突

两边代码改到同一个位置,git 分不清先后 → 冲突。但冲突分两种:

类型 场景 解法
位置冲突 两人往同一个空白处写字,内容不矛盾(一个写"登记"、一个写"签名") A+B 两边都留,排个先后
内容冲突 内容互相矛盾(一个写"开"、一个写"关") 只能选一个,或重写
  • 大多数情况是「位置冲突」:两边内容互补、都能要,解法就是都留(A 在前 B 在后)。

2. 三个「防卡死」小套路

套路 1:回环自激 → 闸门标志

现象:点一下 A,B 跟着变;B 一变,又去触发 A......像两面镜子对放,光在里面无限反射。

复制代码
A 变 → B 变
 ↑       ↓
 └── B 又触发 A ──┘   (无限循环)

通用解法:闸门标志(flag)------区分「人点的」和「程序自己改的」:

csharp 复制代码
bool 闸门 = false;

void A变了()
{
    if (闸门) return;          // 闸门关着:程序自己在改,别理
    ...正常响应...
}

void 程序要回写B()
{
    闸门 = true;               // 先关门
    try { B = ...; }           // 改 B
    finally { 闸门 = false; }  // 改完开门
}

套路 2:磁盘满了自动清 → 水位法

现象:磁盘越写越满,满了程序就崩。想在「快满」时自动删旧文件。

通用解法:两个水位------到「高水位」开始删(从旧到新),删到「低水位」就停:

复制代码
使用率
  高水位 ────────── 触发:开始删旧文件
  ...
  低水位 ────────── 停止:不删了
csharp 复制代码
if (磁盘使用率 > 高水位)
{
    foreach (旧文件 in 从旧到新)
    {
        删除(旧文件);
        if (磁盘使用率 <= 低水位) break;   // 降到低水位就停
    }
}
  • 类比:水桶满到警戒线开始往外倒,倒到一半就停,别倒干。

套路 3:取消异常,别被「大网」接住

现象:用户点了「取消」,程序却在弹「出错了」。因为「取消」被当成普通错误记了下来。

csharp 复制代码
// 错:大网把「取消」也接住了,取消时误报错
catch (Exception)
{
    记错误日志();
}

// 对:先单独放走「取消」,再处理「真错误」
catch (OperationCanceledException) { throw; }   // 取消是正常退出,不是错误
catch (Exception)
{
    记错误日志();
}
  • 类比:渔网捕鱼,先把手里的「放生鱼」(取消)捞出来放掉,再处理「真鱼」(真错误)。

第三件:超时排查的三板斧

背景:现场报错「读取图像超时」。怎么定位?三招。

第一招:先搞清「超时」是怎么判的 ------ 竞速

程序等一张图,不是傻等,而是「读图」和「计时器」赛跑,谁先到终点谁赢:

csharp 复制代码
await Task.WhenAny(读图任务, Task.Delay(超时时长));
// 计时器先完成 → 判定超时
  • 类比:等快递,同时设个闹钟;闹钟先响、快递没到 → 超时。
  • 关键:这不是「读了一半」,是「闹钟响了,一张都没来」。

第二招:看「收到计数」区分「掉图」还是「没触发」

程序每收到一张图,计数 +1。日志里「收到 0」= 一张都没来

  • 「收到 0」≠「掉图」(掉图是收到几张后断了),而是「从头到尾一张没来」→ 问题在前端(没触发),不在传输中途。

第三招:按「链路分段」逐段排除

图像从设备到程序的链路,拆成三段:

复制代码
[段1] 上位机 → 控制器  发采集信号     ✅ 正常
[段2] 控制器 → 设备    发触发脉冲     ❓ 唯一没验的断点
[段3] 设备   → 上位机  传回图像       ✅ 正常
  • 段1、段3 都正常,只剩段2「控制器有没有真发触发」------用排除法(掉图❌、卡死❌、断线❌、全程坏❌)逼到唯一断点。
  • 类比:破案,逐条排除不在场证明,剩下的那个就是真相。

附:排查前先确认「用的是哪套驱动」------ DI 工厂

同一个接口,底下可能有两套实现(一套自带收图线程、一套纯回调)。排查前必须先确认现场到底实例化了哪套,否则容易分析错对象。

  • 类比:打客服电话,先按号码转对「接线员」,别对着 A 接线员分析 B 的问题。

语法附录

文件 / 路径

语法 作用
File.Exists(路径) 判断文件在不在(返回 bool)
Path.Combine(目录, 文件名) 拼路径,自动补斜杠
AppDomain.CurrentDomain.BaseDirectory 拿「程序自己所在的目录」

git

命令 作用
git pull --rebase 变基拉取,历史一条线
git reset --soft <基准> 多提交揉成一个前的准备
git cherry-pick <提交> / -n 挑提交 / 挑但不提交
git log --author=X A..B 查某人在 A 有、B 没有的提交

C# 三个骨架

  • 闸门标志if (_flag) return; + try { _flag=true; ... } finally { _flag=false; }
  • 水位法if (使用率 > 高水位) 里循环删,if (使用率 <= 低水位) break
  • 竞速超时await Task.WhenAny(读图任务, Task.Delay(时长))
  • 放走取消catch (OperationCanceledException) { throw; } 放前面

一句话总结

  • 设置和密码是两个抽屉,备份要两个都备份。
  • 合并代码:rebase 让历史干净、squash 揉多为一、cherry-pick 挑着拿、顺序对就零冲突。
  • 三个套路:闸门标志防「自己触发自己」、水位法防「磁盘写满」、先放走取消再抓真错误。
  • 超时排查:先懂「竞速」怎么判、再看「收到计数」分掉图/没触发、最后按链路逐段排除。
相关推荐
格林威3 小时前
C#图像夜间识别率提升:使用OpenCvSharp和Halcon融合DeepSORT实现复杂场景下准确识别
开发语言·人工智能·数码相机·机器学习·计算机视觉·c#·视觉检测
曹牧5 小时前
C#:线程间操作无效,不是从创建控件线程访问
开发语言·c#
anlog13 小时前
ini文件读取,EXE同目录文件读取
c#·ini
rockey62714 小时前
AScript之编译递归函数
c#·.net·script·动态脚本
yue00820 小时前
winform控件篇-按钮
c#
DevOpenClub1 天前
Markdown、HTML 和 PPT 如何稳定交付:文档转换任务的幂等发布流程
开发语言·前端·c#·html·powerpoint
软件黑马王子1 天前
3.单例模式问题1:构造函数
开发语言·单例模式·c#
软件黑马王子1 天前
4.单例模式问题2:重复挂载
开发语言·单例模式·c#
夏霞1 天前
c# 不支持的目标框架 解决方案
开发语言·c#