.NET Core中降低JsonPatchDocument验证代码认知复杂度的方案
在.NET Core开发中,需在将请求中的JsonPatchDocument应用到对象前验证其操作:如replace操作的path/value为空时需返回400 Bad Request,copy、move等操作也需对应校验。最初使用switch-case写法,后改为if-else,但两种实现的认知复杂度均超过CodeClimate设定的20阈值(当前为28),导致CI流水线失败。
原switch-case实现
foreach (var operation in patchDocument.Operations) { var op = operation.op; var path = operation.path; var value = operation.value; var from = operation.from; switch (op) { case "add": case "replace": case "test": if (string.IsNullOrEmpty(path) || value == null) { return this.BadRequest(this.ModelState); } break; case "copy": case "move": if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(from)) { return this.BadRequest(this.ModelState); } break; case "remove": if (string.IsNullOrEmpty(path)) { return this.BadRequest(this.ModelState); } break; default: return this.BadRequest(this.ModelState); } }
修改后的if-else实现
private static bool ValidatePatchRequest(JsonPatchDocument<UserPatch> patchDocument) { var validationResult = false; foreach (var item in patchDocument.Operations) { if (string.IsNullOrEmpty(item.op)) { validationResult = false; break; } if (item.op == "add" || item.op == "test" || item.op == "replace") { if (string.IsNullOrEmpty(item.path) || item.value == null) { validationResult = false; break; } } if (item.op == "copy" || item.op == "move") { if (string.IsNullOrEmpty(item.path) || item.from == null) { validationResult = false; break; } } if (item.op == "remove") { if (string.IsNullOrEmpty(item.path)) { validationResult = false; break; } } validationResult = true; } return validationResult; }
CodeClimate错误提示
[{"engine_name":"structure","fingerprint":"7558ff4c7ee81fda7b1f****","categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# 认知复杂度\n认知复杂度用于衡量一段代码的直观理解难度。与圈复杂度(衡量代码测试难度)不同,认知复杂度告诉你代码的阅读和理解难度。\n\n### 方法的认知复杂度基于以下简单规则:\n* 使用语言提供的简写将多个语句合并为一个时,代码不会被认为更复杂\n* 每出现一次“代码线性流程中断”,代码复杂度会增加\n* 当“流程中断结构嵌套”时,代码复杂度会增加\n\n### 扩展阅读\n* 认知复杂度文档\n* 认知复杂度:衡量代码易懂性的新方式\n"},"description":"方法
ValidatePatchRequest的认知复杂度为28(超过允许的20)。考虑重构。","location":{"path":"****Controller.cs","lines":{"begin":233,"end":276}},"other_locations":[],"remediation_points":950000,"severity":"minor","type":"issue"},
{"name":"csharp.parse.succeeded","type":"measurement","value":15,"engine_name":"structure"},
{"name":"csharp.parse.succeeded","type":"measurement","value":15,"engine_name":"duplication"}]
降低认知复杂度的验证方案
方案一:字典映射操作与验证逻辑
将每种操作对应的验证逻辑封装为委托,通过字典建立映射,主方法保持线性流程,无嵌套判断:
private delegate bool PatchOperationValidator(JsonPatchOperation operation); private static readonly Dictionary<string, PatchOperationValidator> _operationValidators = new Dictionary<string, PatchOperationValidator> { ["add"] = op => !string.IsNullOrEmpty(op.path) && op.value != null, ["replace"] = op => !string.IsNullOrEmpty(op.path) && op.value != null, ["test"] = op => !string.IsNullOrEmpty(op.path) && op.value != null, ["copy"] = op => !string.IsNullOrEmpty(op.path) && !string.IsNullOrEmpty(op.from), ["move"] = op => !string.IsNullOrEmpty(op.path) && !string.IsNullOrEmpty(op.from), ["remove"] = op => !string.IsNullOrEmpty(op.path) }; private static bool ValidatePatchRequest(JsonPatchDocument<UserPatch> patchDocument) { if (patchDocument?.Operations == null) return false; foreach (var operation in patchDocument.Operations) { if (string.IsNullOrEmpty(operation.op)) return false; if (!_operationValidators.TryGetValue(operation.op, out var validator)) return false; if (!validator(operation)) return false; } return true; }
方案二:提取验证方法+模式匹配
将不同操作组的验证逻辑拆分为独立方法,主方法使用C# 8.0+的模式匹配switch表达式,简化流程:
private static bool ValidatePatchRequest(JsonPatchDocument<UserPatch> patchDocument) { if (patchDocument?.Operations == null) return false; foreach (var operation in patchDocument.Operations) { if (string.IsNullOrEmpty(operation.op)) return false; var isValid = operation.op switch { "add" or "replace" or "test" => ValidateAddReplaceTest(operation), "copy" or "move" => ValidateCopyMove(operation), "remove" => ValidateRemove(operation), _ => false }; if (!isValid) return false; } return true; } private static bool ValidateAddReplaceTest(JsonPatchOperation operation) { return !string.IsNullOrEmpty(operation.path) && operation.value != null; } private static bool ValidateCopyMove(JsonPatchOperation operation) { return !string.IsNullOrEmpty(operation.path) && !string.IsNullOrEmpty(operation.from); } private static bool ValidateRemove(JsonPatchOperation operation) { return !string.IsNullOrEmpty(operation.path); }
这两种方案都能大幅降低认知复杂度,符合CodeClimate的阈值要求,同时保持代码的可读性与可维护性。
内容的提问来源于stack exchange,提问作者Amir Suhel

