ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C#中if/else的正确写法与重构思路

C#中if/else的正确写法与重构思路 很多人觉得 if/else 是编程入门第一课的内容简单到没什么好聊的。但我在做代码评审、带新人、以及面试候选人的过程中几乎每周都能看到把简单条件分支写成一团浆糊的程序三层嵌套起步、条件表达式写成天书、能用 if 走天下绝不换姿势。C# 这么多年演进下来语法层面早就提供了很多更安全、更易读的写法但很多人还在用老一套的习惯硬写。这篇文章想跟你聊聊C# 里 if/else 的正确写法和那些我见过的高频反例顺便结合上位机、Socket 通讯这类实际场景看看一个“最简单的语法”在工程里到底该怎么用。先说明一下这篇文章不是给刚学会变量赋值的纯新手准备的语法教程而是给那些已经写了几个月甚至几年 C#却总觉得自己代码里的 if/else “哪里不对”的程序员。文中会讲清楚背后的判断逻辑、常见反例以及我自己的实操心得。看完之后你再回头翻自己的代码大概率会有一种“当时怎么这么写”的感慨。1. 基础语法if / else 看起来简单坑其实不少1.1 else 的匹配规则与省略大括号的代价C# 里 if/else 的语法本身不复杂但正因为简单很多人会不自觉地玩“省略”。最常见的就是不写大括号比如if (age 18) Console.WriteLine(成年); else Console.WriteLine(未成年);这种写法 C# 支持但我不建议。因为一旦后面有人想加一行日志很容易写成这样if (age 18) Console.WriteLine(成年); LogHelper.Write(用户已成年); else Console.WriteLine(未成年);这段代码编译会直接报错因为else前面有两行语句编译器不知道它该跟哪个if配对。哪怕你侥幸没写else只是往if里加一行日志也会出现严重的逻辑错误——日志变成无条件执行了。C# 里else if其实不是一个独立的关键字它的本质是else { if (...) }的简写。所以配对规则遵循“就近匹配”原则。举个容易踩坑的例子if (a) if (b) Console.WriteLine(A and B); else Console.WriteLine(not B);你心里可能想的是else和第一个if配对但编译器会把它和最近的if (b)配对。这个规则很多老手都容易记混我给出的建议很简单无论if、else、else if后面是几条语句一律写大括号哪怕只有一行也写。别嫌麻烦少写的代码不是节省而是给未来的自己和同事埋雷。代码格式化工具如果开了dotnet format的规则检查也会把它标成 warning。1.2 条件表达式的类型陷阱与可空布尔C 和 C 程序员转 C# 的时候有个经典疑惑为什么if (x 1)编译器直接报错因为 C# 的语法规定 if 的条件必须是 bool 类型所以if (x 1)这种把赋值当判断的写法在 C# 里根本编译不过。这是语言设计上的进步但不代表没有类似的坑。真正需要小心的是bool?也就是可空布尔类型的判断。比如你从数据库读出一个字段或者从配置中心拿一个开关bool? isEnabled config.GetValuebool?(FeatureSwitch); if (isEnabled true) { // 打开状态 } else if (isEnabled false) { // 明确关闭 } else { // 没有配置默认处理 }这种三段式判断是bool?的标准打开方式。但反例也很常见有人会图省事写if (isEnabled.HasValue isEnabled.Value)代码能跑但读起来费劲。还有人会写if (isEnabled true)时不加空值判断直接用if (isEnabled)这在 C# 里编译不过因为bool?不能隐式转成bool。我自己的习惯是对于bool?优先用isEnabled true表示“明确为真”用isEnabled is true也可以后者是 C# 9 之后更推荐的模式匹配写法。它读起来更接近自然语言而且不会出现误用的风险。1.3 优先级与括号别让编译器替你猜C# 的运算符优先级里有几个容易记混的位置比如、||与、!的相对优先级。的优先级高于和||所以a b c d会按预期解析成(a b) (c d)。但一旦条件多起来光靠优先级去背就很痛苦比如if (status Status.Running || status Status.Paused !isMaintenance)这行代码实际含义是status Status.Running || (status Status.Paused !isMaintenance)因为优先级高于||。但读代码的人第一眼未必能看出来可能以为它是(status Running || status Paused) !isMaintenance。这种歧义极其危险。我的做法是只要条件里同时出现和||就果断用括号把逻辑组圈起来。不需要知道优先级也不需要让读代码的人去回忆优先级表。括号多一点不丢人逻辑错了才是真丢人。2. 条件表达式怎么写才算“值得读”2.1 正面表达避免双重否定代码评审的时候我经常看到这样的条件if (!string.IsNullOrEmpty(userName) false) { // 用户名不为空的时候做什么 }这个!加 false的组合看得人血压飙升。先不说逻辑对不对这行代码本身就是一种双重否定的经典反例。它的实际含义是“用户名为空”但写出来却绕了两个弯。正确写法无非两种if (string.IsNullOrEmpty(userName)) { // 用户名为空的处理 } // 或者反过来 if (!string.IsNullOrEmpty(userName)) { // 用户名非空的处理 }如果你要处理的是“非空”分支完全可以直接写if (!string.IsNullOrEmpty(userName))没有必要再加一个 false或者 true。对于布尔判断直接使用变量本身if (isSuccess)而不是if (isSuccess true)if (!isSuccess)而不是if (isSuccess false)。不过这里也有一个例外如果变量名本身带有负面含义比如isNotFound那if (!isNotFound)就是一种双重否定。最好的办法是换个正面的变量名比如isFound。变量命名直接影响条件表达式的可读性这一点在 C# 里尤其重要因为 C# 的语义本身还算直白如果变量名绕来绕去阅读成本就全堆在条件判断上了。2.2 卫语句让嵌套少一层我见过太多“箭头形”代码一层 if 套一层 if最里面才是真正的业务逻辑if (user ! null) { if (user.IsActive) { if (user.Role Role.Admin) { // 真正的管理后台逻辑几十行 } else { // 普通用户的逻辑 } } else { LogHelper.Write(用户未激活); } } else { LogHelper.Write(用户不存在); }这种写法在逻辑上没错但可读性很差。阅读者需要一直记着当前处于第几层嵌套才能搞明白每个分支的运行条件。更推荐的做法是先把不满足条件的场景“挡在门外”用卫语句提前返回if (user null) { LogHelper.Write(用户不存在); return; } if (!user.IsActive) { LogHelper.Write(用户未激活); return; } if (user.Role Role.Admin) { // 管理后台逻辑 } else { // 普通用户逻辑 }卫语句的核心思想是先处理异常、边界、不合法的场景让正常流程继续往下走。这样后面“幸存下来”的代码前提条件都非常清晰不需要一层一层去回溯。我在上位机开发里也经常用这个模式比如接收扫码枪的数据时先判断数据长度是否合法再判断是否包含结束符最后才做解析。每一步都提前 return代码结构非常清爽。2.3 边界判断空值、范围、字符串空格一个都不能漏实际项目中if/else 的很多 bug 不是逻辑不对而是边界条件没考虑全。最常见的三个空值、范围、字符串里的可见字符。空值判断的典型反例是只判断了 null没判断空数组或空字符串。比如获取一个订单列表然后判断“有订单就处理”if (orderList ! null) { foreach (var order in orderList) { // 处理 } }这里的问题在于orderList不为 null 但Count 0时foreach根本不会进入循环体逻辑上倒没什么大错但如果后面还有“没有订单时给默认值”的需求这种判断就不够严谨。更稳妥的写法是用orderList is { Count: 0 }这种属性模式或者用orderList?.Count 0。C# 里属性模式很强大能一次性判断 null 和数量if (orderList is { Count: 0 }) { // 有订单 }字符串的边界就更多了。判断“用户输入了有效信息”时如果直接用if (input.Length 0)那输入一个空格也会被当成有效信息。正确做法是用string.IsNullOrWhiteSpace(input)它把 null、空串、全空格都覆盖了。还有一个经常被忽略的条件是字符串前后有空格导致比较失败比如读取配置文件里的开关值 true和true就不相等。所以在关键的比较场景里先Trim()再比较能省去很多查不出来的诡异问题。范围判断方面C# 里比较常见的是“开区间/闭区间”搞混。比如分数在 90 到 100 之间时逻辑不同有人写if (score 90 score 100)如果业务要求是“90 分含以上”那这个条件就错了正确应该是if (score 90 score 100)。这种半开半闭的边界问题在数值判断里几乎是 bug 高发区。我的习惯是写条件时把所有等于号都用文字注释标出来比如“包含 90 和不包含 100”然后再翻译成代码。2.4 把复杂条件拆成命名方法当一个 if 条件写了三四行或者包含多个逻辑运算符时即使加上括号也很难读。这个时候最好的做法不是尝试精简表达式而是把整个条件抽成一个方法给这个“判断动作”取一个清晰的名字。举个例子一个上位机程序里要判断一条指令是否是合法的移动指令条件可能包含设备状态、当前模式、指令类型、超时标记等。如果直接塞进 ifif (deviceStatus DeviceStatus.Ready currentMode Mode.Manual cmdType CmdType.Move !isTimeout) { // 执行移动 }这个条件不复杂但已经需要读几秒了。抽成方法之后if (CanExecuteMoveCmd(deviceStatus, currentMode, cmdType, isTimeout)) { // 执行移动 } private static bool CanExecuteMoveCmd(DeviceStatus status, Mode mode, CmdType cmdType, bool isTimeout) { return status DeviceStatus.Ready mode Mode.Manual cmdType CmdType.Move !isTimeout; }这样读代码的人一眼就能看出意图“能执行移动指令吗”而不用去理解四个条件各自是什么含义。这就是命名带来的价值。抽方法的另一个好处是如果将来判断逻辑变得更复杂比如新增一个“重复运行保护”的条件只需要在方法内部加一行不会污染调用方的阅读体验。3. 不一定非要用 if / else替代方案怎么选3.1 简单赋值用三元运算符但记得适可而止二选一的情况下三元运算符确实比 if/else 更紧凑。典型的场景是根据条件赋一个变量var level score 60 ? Pass : Fail;这行代码把 if/else 写四行才能完成的事情压缩成了一行。我也经常在组装报文、拼接字符串时用它比如var result string.Format({0}:{1}, deviceId, isOnline ? 1 : 0);但三元运算符的底线是只用于这种简单的单一赋值。一旦出现嵌套比如a ? b : (c ? d : e)可读性直线下降我见过有人写出三层三元嵌套那完全是给自己和后人添堵。我的建议是当三元运算符开始要跨行书写或者需要加注释才能看懂的时候就应该老老实实改写 if/else。3.2 switch 表达式与模式匹配C# 8 之后的新选择很多从 C# 6、C# 7 时代过来的人对 switch 的印象还停留在“只能判断整数和字符串常量”的旧语法。其实从 C# 8 开始switch 表达式和模式匹配已经非常能打了很多场景下它比链式 if/else 更直观。举个例子根据指令类型做不同处理var handleResult cmdType switch { CmdType.Move HandleMove(), CmdType.Stop HandleStop(), CmdType.Home HandleHome(), _ HandleUnknown() };这段代码的功能等同于一大堆 if/else 或者旧式 switch但结构清晰得多。C# 9 之后还支持属性模式可以直接判断对象的状态var statusInfo deviceStatus switch { { IsReady: true, IsTimeout: false } 设备就绪, { IsReady: false } 设备未就绪, _ 未知状态 };这种写法在判断同一个对象的多个属性时特别好用。我在处理海康相机或者 HALCON 返回的状态信息时就经常用属性模式把“成功但结果为空”“失败但错误码正常”之类的组合状态一次性表达清楚。但要注意一点switch 表达式更适合“每个分支之间互斥、逻辑独立”的场景。如果分支之间存在复杂的业务依赖或者每个分支内部的代码超过十几行还是老老实实写 if/else或者抽方法不要硬套新语法。3.3 用字典映射和委托消除重复判断在热词搜索里出现了很多“C#上位机”“C# Socket”“扫码枪触发事件”这类场景这些场景里最常见的 if/else 问题就是根据不同指令码分发处理逻辑。很多人会用一长串 if/else 去判断指令码比如if (msg.StartsWith(MOVE:)) { // 处理移动指令 } else if (msg.StartsWith(STOP:)) { // 处理停止指令 } else if (msg.StartsWith(HOME:)) { // 处理回零指令 } // 还有十几个...每来一个新指令就要往这个函数里再加一个 else if。日子久了这个函数能长到几百行改一个分支还会担心影响其他分支。这种情况下用字典映射加委托来处理会优雅得多。private readonly Dictionarystring, Actionstring _commandHandlers new() { [MOVE] HandleMove, [STOP] HandleStop, [HOME] HandleHome, }; private void DispatchCommand(string rawMsg) { var prefix rawMsg[..rawMsg.IndexOf(:)]; if (_commandHandlers.TryGetValue(prefix, out var handler)) { handler(rawMsg); } else { LogHelper.Write($未知指令{prefix}); } }这样每新增一种指令只需要注册一个新的处理方法而不会改动 DispatchCommand 本身符合开闭原则。阅读代码的时候整个“指令路由”逻辑也变成了一张表非常直观。需要注意的坑是字典的键区分大小写如果你要兼容大小写混用的输入可以在构造字典时用StringComparer.OrdinalIgnoreCase。3.4 引入设计模式的时机别为了去掉 if 而过度设计有些同学看到“策略模式”“状态模式”能减少 if/else就像拿到新玩具一样不管三七二十一把所有条件分支都改成设计模式。结果是代码类是变短了但整个项目多了十几个类、几十个接口读代码要到处跳文件。我的观点是设计模式是用来管理复杂度的不是用来消灭 if/else 的。当你只有三五种分支每种分支也就一两行逻辑时if/else 就是最清晰的表达方式硬上策略模式反而制造复杂度。可一旦分支数量开始膨胀而且每个分支的处理逻辑都有独立演进的趋势时才应该考虑用接口抽象、字典映射或者策略模式。判断标准很简单你在频繁改动这个 if/else 链吗你每次改动都要小心翼翼吗如果是那该重构的信号已经出现。4. 真实案例上位机指令处理的 if / else 重构4.1 接手前的代码长什么样去年我接手维护一个工业设备的上位机程序里面有一段处理 TCP 报文的函数核心思路是根据设备发来的 ASCII 指令执行不同动作。原代码大概是这样的private void ProcessMessage(string msg) { if (msg.StartsWith(GET_STATUS)) { // 查询状态拼接返回报文 var status GetDeviceStatus(); SendResponse($STATUS:{status}); } else if (msg.StartsWith(SET_SPEED)) { var speed ExtractParam(msg); if (speed 0 speed 1000) { SetSpeed(speed); SendResponse(OK); } else { SendResponse(ERR:SPEED_RANGE); } } else if (msg.StartsWith(SET_ACC)) { // 类似的逻辑 } // 后面还跟着十几个 else if }这段代码大概有三百多行最明显的两个问题一是指令越来越多这个函数越来越长二是嵌套层级越来越深尤其是一旦涉及参数校验里面又套了一层 if/else。每次新接一台设备要加指令大家都得在这坨代码里找插入点改一次怕一次。4.2 第一步抽方法 卫语句我第一次重构没急着马上改造成什么花哨结构而是先做两件小事把每个分支里的处理逻辑抽成独立方法同时用卫语句把参数校验提前。改造之后主函数的逻辑变成了这样private void ProcessMessage(string msg) { if (string.IsNullOrWhiteSpace(msg)) { return; } if (msg.StartsWith(SET_SPEED)) { HandleSetSpeed(msg); } else if (msg.StartsWith(GET_STATUS)) { HandleGetStatus(); } // ... } private void HandleSetSpeed(string msg) { if (!TryExtractParam(msg, out int speed)) { SendResponse(ERR:PARSE); return; } if (speed 0 || speed 1000) { SendResponse(ERR:SPEED_RANGE); return; } SetSpeed(speed); SendResponse(OK); }这一步做完至少消除了三层嵌套的问题。每个处理方法内部都是“先校验再执行最后返回”的顺序读起来比原来的“there 套 if、else 套 if”清晰太多了。但我还没有解决“指令越来越多路由代码越来越长”的核心矛盾所以继续走第二步。4.3 第二步用字典映射代替 if / else 指令分发第一轮重构后路由函数里依然是一长串else if (msg.StartsWith(...))。为了让新增指令不再改这个主函数我把处理逻辑统一成了委托然后用字典把“指令前缀”和“处理方法”映射起来。这里要处理一个问题有些指令需要带参数有些不需要。所以我定义了一个统一的委托签名private delegate void MessageHandler(string msg); private readonly Dictionarystring, MessageHandler _handlers new() { [GET_STATUS] HandleGetStatus, [SET_SPEED] HandleSetSpeed, [SET_ACC] HandleSetAcc, [HOME] HandleHome, };路由函数瘦身成这个样子private void ProcessMessage(string msg) { if (string.IsNullOrWhiteSpace(msg)) { return; } var prefix msg.Split(:)[0]; if (_handlers.TryGetValue(prefix, out var handler)) { handler(msg); } else { SendResponse(ERR:UNKNOWN_CMD); LogHelper.Write($未知指令{msg}); } }这里有个小细节原来的StartsWith是不带分隔符的模糊匹配可能会有SET_SPEED和SET_SPEED_1这种前缀冲突。我改成Split(:)[0]之后强制约定协议里指令前缀后面必须跟冒号这样路由更精确也避免了很多带有相似前缀的指令互相干扰。4.4 重构之后的收获这轮重构之后最直观的感受是新增一条指令只需要写一个新的处理方法然后在字典里注册一下。主路由函数不再需要改动测试也容易写多了。我可以把ProcessMessage当做一个纯分发器来测单独测试每个 Handler 也只需要关注自己的输入输出不用把整个消息链路的上下文都模拟出来。另一个收获是“未知指令”的处理。原来的 if/else 链如果收到一个不认识的指令最后会静默忽略或者走到一个莫名其妙的默认分支。现在字典的TryGetValue天然就带了一个清晰的找不到分支我顺手加了统计日志排查问题的时候方便多了。这个过程也让我更坚定了一个想法if/else 本身不是罪魁祸首真正的问题是不加节制地使用并且没有及时识别出“这其实是一张指令映射表”的抽象。5. 性能与工程实践if / else 会拖慢程序吗5.1 分支判断的底层成本很多人写代码时会有一种迷思if/else 是不是很慢要不要用位运算或者什么骚操作去避免分支实际上现代 CPU 对分支的处理已经非常成熟普通 if/else 在绝大多数业务代码里性能开销小到可以忽略不计。真正影响性能的是分支预测失败也就是 CPU 事先预测了某条分支会执行结果预测错了需要清空流水线重新执行。但在 C# 这种托管语言里常规的业务逻辑很难精确控制到这一步。如果你处理的场景是几百毫秒级的网络通讯、数据库读写、文件操作那 if/else 那点判断成本根本不值一提。把时间花在优化 if/else 的微性能上不如把代码可读性提上来。性能优化有一条很实际的原则先测量再优化。你连瓶颈在哪都不知道就先别动 if/else 的脑筋。5.2 switch 的跳转表优化真相有些读者可能听过一个说法switch 比 if/else 快因为编译器会把它编译成跳转表。这个说法在 C/C 里有一定道理但在 C# 里要分情况看。JIT 编译器会对密集的整数 switch 生成高效的跳转逻辑对于字符串 switch可能在内部会先算哈希再做比较。如果分支数量不多if/else 和 switch 的差异几乎看不出来。所以我的建议是性能差异不应该是你选择 switch 还是 if/else 的主要理由可读性和可维护性才是。在条件分支固定的场景下switch 表达式的模式匹配能提升代码表达力这就足够成为用它替代 if/else 的理由了。至于跳转表优化只是附带红利不要盯着它做决策。5.3 循环里的 if把不变量提出来如果说 if/else 在性能上真有什么值得注意的点那就是循环体里的条件判断。假设你有一个高频执行的循环循环内部每次都要判断一个配置开关是否打开for (int i 0; i data.Length; i) { if (_config.IsDebugMode) { LogHelper.Write(data[i]); } Process(data[i]); }如果IsDebugMode在循环期间不会变化理论上这个if每次都要重新判断是一种浪费。直接把常量条件提到循环外会更清晰if (_config.IsDebugMode) { for (int i 0; i data.Length; i) { LogHelper.Write(data[i]); Process(data[i]); } } else { for (int i 0; i data.Length; i) { Process(data[i]); } }但这么改有代码重复的问题也不一定值得。实战中JIT 往往能识别这类循环不变量优化把它自动提出去。所以除非你用 profiler 实测出这里确实是热点否则不要为了所谓性能牺牲代码结构。真正值得关注的是不要在循环里做一些可以提前聚类的判断比如“每一帧都判断对象类型”这种情况考虑用多态替代反而更合理。5.4 别用 if / else 控制异常流程工程实践里还有一种 if/else 的误用就是用条件判断去模拟异常处理。比如if (File.Exists(path) false) { ShowError(文件不存在); return; } var content File.ReadAllText(path);这段代码看起来没什么问题但在并发环境下文件可能在File.Exists之后、ReadAllText之前被删掉依然会抛出FileNotFoundException。更合理的做法是直接尝试读文件捕获特定异常再做降级处理try { var content File.ReadAllText(path); return content; } catch (FileNotFoundException) { ShowError(文件不存在或已被删除); }所以判断“是否存在”“是否可读”“是否有权限”这类场景预检查不是完全没用但要注意它只是减少异常概率不能完全替代异常处理。正确姿势是能用 if/else 表达的业务状态就用 if/else凡是可能因为外部环境变化而出现的不确定性错误用 try/catch 兜底。两者不是替代关系而是配合关系。6. 常见问题速查从语法到风格一脸看明白6.1 if、else if、多个 if 的区别这个问题面试里经常被问代码评审中也经常看到有人混淆。直接说结论else if是短路逻辑只要前面的条件为真后面的所有条件都不会再判断而多个独立的if每个条件都会依次判断。看代码最直观int score 85; string level; if (score 90) level A; else if (score 60) level B; else level C; // 如果写成三个独立 if if (score 90) level A; if (score 60) level B; if (score 60) level C;两种写法在特定输入下结果不同。当score 85时第一种得到B第二种先得到A又被第二次 if 覆盖成B最后因为不满足第三个条件结果还是B。如果条件之间有重叠或某个变量会在分支内被修改多个 if 的覆盖效应就会导致诡异 bug。实际编码时如果业务上几个条件是互斥区间优先用else if如果几个判断是互不关联的独立事件才用多个 if。场景使用建议风险点互斥的多分支用 if/else if/else条件顺序影响结果互不相关的判断用多个独立 if注意条件重叠导致的值覆盖映射关系明确用 switch 表达式或字典分支过多时维护成本高6.2 字符串比较 与 Equals 到底怎么选C# 里字符串比较有个经典盲区。对于字符串类型实际调用的是字符串的相等性比较而不是引用比较这一点和 Java 不同。所以在大多数场景下if (cmd STOP) { // 没问题 }是完全可以的。但有几个细节要注意大小写敏感。如果协议里对方可能发stop或STOP你直接会判断不相等。这时用string.Equals(cmd, STOP, StringComparison.OrdinalIgnoreCase)更稳妥。还有一个坑是字符串前后空格用Trim()或者比较时使用StringComparison.OrdinalIgnoreCase的同时先处理空白具体看业务语义。在需要大量比较、且这些比较发生在热路径时可以考虑用switch表达式编译器会对字符串 switch 做哈希或跳转优化但日常业务里已经完全够用不必刻意替换成Equals。6.3 可空值类型的判断与模式匹配C# 8 之后is null和is not null逐渐成为推荐写法。和 null相比is null有几个好处当对象重载了运算符时is null不会受运算符重载影响语义更安全同时它在模式匹配的语境下读起来也更自然。if (obj is null) { // 空值处理 } if (obj is not null) { // 非空处理 }对于可空值类型的判断C# 9 之后bool?也可以配合模式匹配写if (flag is true) { // 明确为 true } else if (flag is false) { // 明确为 false } else { // null }这种写法相比flag true最大的好处是编译器能更好地推断出分支里的类型信息配合属性模式还能做更复杂的解构判断。如果你还在用HasValue加双重判断建议试试新的模式匹配写法会让条件分支清晰不少。6.4 关于缩进、大括号和格式化的统一建议最后聊一个工程层面的小话题代码风格。if/else 本身不是性能杀手但“一种代码一种风格”绝对是维护成本的隐形杀手。见过有的项目里一半人把左大括号放在行尾一半人放在下一行每次合并代码都是格式大战还有的人用 tab有的人用空格。这类问题不该靠人的自觉去约束直接用.editorconfig和dotnet format在 CI 里强制统一就行。缩进方面我的习惯是 4 空格配合 IDE 的自动格式化。大括号只要在配置里设成统一规则全团队保持一致即可。真正的重点是绝不省略大括号、条件表达式尽量加括号、if 和 else 分支如果都有代码尽量让两条路径的代码长度接近避免一行分支、几十行分支的失衡感。这些细节单独看每一条都很小但放在一起就是代码可读性的分水岭。实际写代码的时候我们经常在“快速实现”和“优雅设计”之间摇摆。我个人的经验是if/else 这种基础语法真正体现功力的地方不在于会不会写而在于能不能在各种场景下选择最合适的表达方式以及能不能让读代码的人不费脑子就理解逻辑。哪怕只是把嵌套改平、把条件抽成方法、把简单映射从 if/else 换成字典代码的维护体验都会有质的提升。最后再补充一个我踩过不少次的坑改动 if/else 分支时永远记得检查反向分支。很多 bug 不是因为在某个分支里写错了什么而是因为新增了一个if却没有配套处理它的else场景导致边界输入掉进了老逻辑。写条件语句时多问自己一句“不满足这个条件时程序该走哪条路”很多线上问题都能提前拦截在编码阶段。
返回列表