如何重构触发Sonar告警的getBodyFields TypeScript函数
Sonar告警原因
原函数触发Sonar重构规则的核心问题是违反了DRY(Don't Repeat Yourself)原则:
- 17个可选字段全部重复编写「值存在且不是空字符串」的判断逻辑,代码冗余度超过70%
- 后续新增、删除需要透传的字段时,需要重复编写结构完全一致的代码,容易出现拼写错误、漏判问题
- 现有判空逻辑存在缺陷:当字段值为
0、false这类业务上合法的假值时,会被错误过滤,不会被带到请求体中
可选重构方案
方案1:字段统一遍历(首推,平衡可维护性和安全性)
将所有需要过滤空值的字段统一维护在常量列表中,通过循环统一做判空处理,既消除重复代码,又能精确控制需要透传的字段,不会把request上多余的属性带到请求体:
function getBodyFields(request: UpdateCustomerRequest, tmsGuid: string) { // 所有需要透传、且需要过滤空值的字段统一在这里维护 const validFields = [ 'organizationName', 'firstName', 'middleInitial', 'lastName', 'email', 'communicationEmail', 'areaCode', 'phoneNumber', 'phoneType', 'alternatePhoneAreaCode', 'alternatePhoneNumber', 'alternatePhoneType', 'address1', 'address2', 'city', 'state', 'zip' ] as const; const body: Record<string, unknown> = { tmsGuid }; for (const field of validFields) { const fieldValue = request[field]; // 仅过滤null、undefined、空字符串,保留0、false等合法值 if (fieldValue != null && fieldValue !== "") { body[field] = fieldValue; } } return body as { tmsGuid: string } & Partial<Pick<UpdateCustomerRequest, typeof validFields[number]>>; }
这个方案的优势:
- 重复代码完全消除,后续调整透传字段只需要修改
validFields数组即可 - 判空逻辑更严谨,修复原代码误过滤合法假值的问题
- TypeScript类型安全,字段拼写错误会被TS直接提示
方案2:通用工具函数抽离(适合多场景复用)
如果项目中多处需要做对象空值过滤,可以把过滤逻辑抽成通用工具函数,减少全项目的重复代码:
/** * 过滤对象中值为null、undefined、空字符串的属性 * 注意:会遍历对象所有自有属性,仅适用于对象属性和接口字段完全对应的场景 */ function filterEmptyProps<T extends Record<string, any>>(source: T): Partial<T> { return Object.fromEntries( Object.entries(source).filter(([_, val]) => val != null && val !== "") ) as Partial<T>; } function getBodyFields(request: UpdateCustomerRequest, tmsGuid: string) { return { tmsGuid, ...filterEmptyProps(request) }; }
注意:如果
UpdateCustomerRequest类型中存在不需要透传给接口的字段,不要使用这个方案,避免多余字段被带到请求体中引发接口报错。
方案3:最小改动方案(不调整原有代码结构)
如果不想调整原有代码的展开写法,只需要把重复的判空逻辑抽成公共判断函数,即可消除Sonar的重复代码告警:
function getBodyFields(request: UpdateCustomerRequest, tmsGuid: string) { // 抽离重复的非空判断逻辑 const isNonEmpty = <T>(val: T): val is NonNullable<T> => val != null && val !== ""; return { tmsGuid, ...(isNonEmpty(request.organizationName) && { organizationName: request.organizationName }), ...(isNonEmpty(request.firstName) && { firstName: request.firstName }), ...(isNonEmpty(request.middleInitial) && { middleInitial: request.middleInitial }), ...(isNonEmpty(request.lastName) && { lastName: request.lastName }), ...(isNonEmpty(request.email) && { email: request.email }), ...(isNonEmpty(request.communicationEmail) && { communicationEmail: request.communicationEmail }), ...(isNonEmpty(request.areaCode) && { areaCode: request.areaCode }), ...(isNonEmpty(request.phoneNumber) && { phoneNumber: request.phoneNumber }), ...(isNonEmpty(request.phoneType) && { phoneType: request.phoneType }), ...(isNonEmpty(request.alternatePhoneAreaCode) && { alternatePhoneAreaCode: request.alternatePhoneAreaCode }), ...(isNonEmpty(request.alternatePhoneNumber) && { alternatePhoneNumber: request.alternatePhoneNumber }), ...(isNonEmpty(request.alternatePhoneType) && { alternatePhoneType: request.alternatePhoneType }), ...(isNonEmpty(request.address1) && { address1: request.address1 }), ...(isNonEmpty(request.address2) && { address2: request.address2 }), ...(isNonEmpty(request.city) && { city: request.city }), ...(isNonEmpty(request.state) && { state: request.state }), ...(isNonEmpty(request.zip) && { zip: request.zip }), } }
内容的提问来源于stack exchange,提问作者sagar
相关产品推荐
相关产品推荐

