它山之玉可以重构:身份证号(第四天)

本文分享了一个关于SocialID类重构的过程,包括改进测试覆盖、优化构造器逻辑、提升代码质量和可维护性等方面的内容。

可以说,到了今天,我才真正能开始做我想要的重构/改进。之前,只是补充测试,调整了一下结构。
是的,非常的缓慢,这居然被称为“敏捷”!? 你说奇怪不奇怪?
还好,这种节奏适合我这种大龄青年,合用就好,关它是风花还是雪月。

==》 测试覆盖

上一次漏掉了最重要的异步,测试覆盖:

4_1

本以为会秀一个漂亮的100%覆盖率的测试出来,人算不如天算,居然有一个方法是75%!

(本文版权属于© 2012 - 2013 予沁安

4_2

恩,无效的生日没有测试。

很简单,就增加一个测试而已,就不在这罗嗦了。直接贴覆盖率,显摆一下。

4_3

再显摆一下代码质量参数:

4_4

复杂度 最大的就是构造器了。可维护指标还是不错的 76分。

==>优化改进:属性,静态设值和其他

零零碎碎的改进,你可以如前面一样,基于一个一个测试纵向改,也可以全部改完在一起测试,没有太大关系,前者是严格的测试驱动。但是,我觉得不需太学术化,关键是,你的任务足够小,能在今天完成,那就是合适。

1。 把所有的信息块改成属性方式,因为,一个是Java与C#的区别,第二,把原代码的缓冲生日的逻辑做到极致(极限编程?呵),一开始就缓冲(构造器中)

 public string CardNumber { get;private set; }
 public string AddressCode { get; private set; }
 public DateTime BirthDate { get; private set; }
 public Gender Gender { get; private set; }

2。数据解析放在构造器中,并且独立成方法,只是在构造器中调用

void extract()
        {
            AddressCode = CardNumber.Substring(0, 6);
            Gender = ((int) CardNumber[CARD_NUMBER_LENGTH - 2])%2 == 0 ? Gender.Female : Gender.Male;
            BirthDate = extract_birth_date();
        }

日期足够复杂,所以又独立出方法

public DateTime extract_birth_date()
        {
            try
            {
                return DateTime.ParseExact(CardNumber.Substring(6, 8), BIRTH_DATE_FORMAT, null);
            }
            catch (Exception e)
            {
                throw new ApplicationException("身份证的出生日期无效");
            }
        }

3。从之前的代码分析参数,看到构造器复杂度太高,主要是几个验证。做一个改进,一个提出验证方法,二个去掉null, empty的验证,因为正则表达式已经包含了。

 private void validate(string cardNumber)
        {
            if (!SOCIAL_NUMBER_PATTERN.IsMatch(cardNumber))
                throw new ApplicationException("Card Number has wrong charactor(s).");

            if (cardNumber[CARD_NUMBER_LENGTH - 1] != verifier.verify(cardNumber))
                throw new ApplicationException("Card Number verified code is not match.");
        }
public SocialID(String cardNumber)
        {
            validate(cardNumber);
            CardNumber= cardNumber;
            extract();
        }


==》OK,现在可以站起来,来杯咖啡,欣赏一下我们的成果

可维护性提高到82,复杂度最高是validate() 3,

4_5

完全代码,是不是很清晰了?

using System;
using System.Text.RegularExpressions;

namespace Skight.eLiteWeb.Domain
{
    public enum Gender
    {
        Female,
        Male
    }
    public class SocialID
    {
        private static Verifier verifier = new Verifier();
        private static String BIRTH_DATE_FORMAT = "yyyyMMdd";
        private static int CARD_NUMBER_LENGTH = 18;
        private static Regex SOCIAL_NUMBER_PATTERN = new Regex(@"^[0-9]{17}[0-9X]$");

        public SocialID(String cardNumber)
        {
            validate(cardNumber);
            CardNumber= cardNumber;
            extract();
        }

        private void validate(string cardNumber)
        {
            if (!SOCIAL_NUMBER_PATTERN.IsMatch(cardNumber))
                throw new ApplicationException("Card Number has wrong charactor(s).");

            if (cardNumber[CARD_NUMBER_LENGTH - 1] != verifier.verify(cardNumber))
                throw new ApplicationException("Card Number verified code is not match.");
        }
        void extract()
        {
            AddressCode = CardNumber.Substring(0, 6);
            Gender = ((int) CardNumber[CARD_NUMBER_LENGTH - 2])%2 == 0 ? Gender.Female : Gender.Male;
            BirthDate = extract_birth_date();
        }
        public DateTime extract_birth_date()
        {
            try
            {
                return DateTime.ParseExact(CardNumber.Substring(6, 8), BIRTH_DATE_FORMAT, null);
            }
            catch (Exception e)
            {
                throw new ApplicationException("身份证的出生日期无效");
            }
        }

        public string CardNumber { get;private set; }
        public string AddressCode { get; private set; }
        public DateTime BirthDate { get; private set; }
        public Gender Gender { get; private set; }
    }
}

(本文版权属于© 2012 - 2013 予沁安 | 转载请注明作者和出处


如果你把第四步去掉,即: > **去掉这行代码:** ```java String requestStr = FormatUtil.getLogExcludeAndLengthLimitedStr(true, api, request, !responseSuccess); String responseStr = FormatUtil.getLogExcludeAndLengthLimitedStr(false, api, response, !responseSuccess); ``` 并直接使用原始对象转 JSON 或完全不处理,会带来以下几个**严重的影响和风险**: --- ### ❌ 影响一:失去敏感数据脱敏能力(安全风险) - 原逻辑中通过 `inRequestExcludeConfig(api)` 判断是否屏蔽某些接口(如登录、支付、用户信息查询)的请求/响应体。 - 如果去掉该步骤: - 所有请求和响应内容都会被完整打印到日志中; - 可能导致 **密码、身份证号、银行卡、Token 等敏感信息泄露**; - 违反公司安全规范或合规要求(如 GDPR、等保)。 📌 示例: ```json { "username": "admin", "password": "123456" } ``` → 如果不脱敏,日志里就会明文出现 `"password": "123456"`。 --- ### ❌ 影响二:日志可能过大,影响系统稳定性 - 没有调用 `getLogExcludeAndLengthLimitedStr` 就意味着失去了 **长度截断功能**。 - 某些接口返回的数据非常大(比如分页查 1000 条记录、文件下载接口等),JSON 字符串可能长达几万甚至几十万字符。 - 直接写入日志会导致: - 日志文件急剧膨胀; - 写日志线程阻塞(I/O 压力); - 占用大量内存(字符串对象巨大); - ELK/Kafka 日志收集系统崩溃或超时; - 数据库存储字段溢出(如果写入 DB)。 --- ### ❌ 影响三:无法实现“失败才打印全文”的智能策略 原设计有一个关键优化逻辑: ```java !responseSuccess // 失败时 keepResponse = true → 显示完整报文 ``` 也就是说: - 成功时:可选择隐藏 + 截断(节省资源) - 失败时:强制保留全文(便于排查问题) ✅ 这是一种典型的 **“故障优先可见性”** 设计。 🚫 如果你去掉这个处理,就只能做到: - 要么永远不打(看不到错误细节), - 要么永远全打(浪费资源、增加风险)。 没有中间平衡点。 --- ### ❌ 影响四:违反统一日志格式规范 大多数企业级系统会对日志中的字段做标准化控制。例如规定: > 所有 `request` 和 `response` 字段最大不超过 2KB。 如果不经过 `getLogExcludeAndLengthLimitedStr` 的统一处理,不同地方的日志输出将变得不可控,给后续的: - 日志检索(Kibana/Grafana) - 自动告警解析 - 审计系统分析 带来极大困难。 --- ### ✅ 正确做法建议(替代而非删除) 如果你觉得这段逻辑太重或想“简化”,应该 **重构而不是删除**,例如: #### ✔️ 方案1:封装成更通用的方法 ```java LogMaskingUtil.maskAndTruncate(request, api, isRequest, includeOnSuccess); ``` #### ✔️ 方案2:使用注解标记敏感字段自动脱敏 ```java public class LoginRequest { private String username; @Sensitive(type = SensitiveType.PASSWORD) private String password; } ``` 然后在序列化时自动替换为 `***`。 #### ✔️ 方案3:只对特定 API 开启全文记录(白名单机制) 避免一刀切。 --- ### 总结:去掉第四步的后果 | 问题类型 | 后果 | |--------|------| | 🔒 安全性 | 敏感信息明文打印,存在数据泄露风险 | | 💥 稳定性 | 日志过大可能导致服务卡顿、OOM、日志系统崩溃 | | 🛠 可维护性 | 故障排查困难,日志杂乱无章,难以分析 | | 📉 监控准确性 | 无法区分正常与异常流量的日志级别 | 👉 因此:**不能简单地去掉第四步**,这是保障系统可观测性和安全性的关键环节。 --- ###
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值