• 它山之玉可以重构:身份证号码解析、验证工具(第三天)


    前两天的进度似乎有些慢,今天加快了一点, 不把每一步说的那么详细了.

    ==》地区信息的提取

    继性别和生日之后,最后一个信息块,只是列出测试如下.

    ==》有效性

    这是一个比较大的问题. 前面,我临时性的把不同地方的验证去掉了. 代码原作者也过来, 畅叙了他关于验证的看法. 他是对的, 这种完全验证的方式,根本上说是 DDD的设计思想。不过,想我所说,我知识临时性的去掉,保证测试的单元性。验证的功能,由验证的测试来驱动。而第二点考虑,我的验证打算放在构造器中,也就是说,如果,有任何错误的输入,连第一道门都进不来。

    这里,测试和实现都很简单,看起来很多,只是一些罗列,不同的错误场景而已。

     1 [Subject("身份证,有效性")]
     2     public class when_create_social_id_with_valid_format {
     3         private Because of = () => subject = new SocialID("430103123456780020");
     4 
     5         private It should_create_social_properly =
     6             () => subject.getCardNumber().ShouldEqual("430103123456780020");
     7         private static SocialID subject;
     8     }
     9     [Subject("身份证,有效性")]
    10     public class when_create_social_id_with_null_string {
    11         private Because of = () =>exception= Catch.Exception(()=>new SocialID(null));
    12 
    13         private It should_not_allow_to_create =
    14             () =>exception.ShouldNotBeNull();
    15         private static SocialID subject;
    16         private static Exception exception;
    17     }
    18 
    19     [Subject("身份证,有效性")]
    20     public class when_create_social_id_with_empty_string {
    21         private Because of = () => exception = Catch.Exception(() => new SocialID(string.Empty));
    22 
    23         private It should_not_allow_to_create =
    24             () => exception.ShouldNotBeNull();
    25         private static SocialID subject;
    26         private static Exception exception;
    27     }
    28 
    29     [Subject("身份证,有效性")]
    30     public class when_create_social_id_with_2_length_string {
    31         private Because of = () => exception = Catch.Exception(() => new SocialID("12"));
    32 
    33         private It should_not_allow_to_create =
    34             () => exception.ShouldNotBeNull();
    35         private static SocialID subject;
    36         private static Exception exception;
    37     }
    38     [Subject("身份证,有效性")]
    39     public class when_create_social_id_with_20_length_string {
    40         private Because of = () => exception = Catch.Exception(() => new SocialID("12345678901234567890"));
    41 
    42         private It should_not_allow_to_create =
    43             () => exception.ShouldNotBeNull();
    44         private static SocialID subject;
    45         private static Exception exception;
    46     }
    47     [Subject("身份证,有效性")]
    48     public class when_create_social_id_alphet_length_string {
    49         private Because of = () => exception = Catch.Exception(() => new SocialID("A23456789012345678"));
    50 
    51         private It should_not_allow_to_create =
    52             () => exception.ShouldNotBeNull();
    53         private static SocialID subject;
    54         private static Exception exception;
    55     }

    实现

    1 public SocialID(String cardNumber)
    2         {
    3             if (string.IsNullOrEmpty(cardNumber))
    4                 throw new ApplicationException("Card Number is empty");
    5             if (cardNumber.Length != CARD_NUMBER_LENGTH)
    6                 throw new ApplicationException("Card Number Length is wrong.");
    7             if (!SOCIAL_NUMBER_PATTERN.IsMatch(cardNumber))
    8                 throw new ApplicationException("Card Number has wrong charactor(s).");          
    9         }

    ==》验证码

    验证码是个特殊的有效性检查,较为复杂,我这里,把这部分逻辑代码提炼出来成为一个验证器。

    测试极其简单,和实现几乎原封不动。

    测试:

    1 public class when_verify_soical_number:Specification<Verifier>
    2     {
    3         Because of = () => { code = subject.verify("43010319791211453"); };
    4 
    5         private It verify_code_should_match =
    6             () => code.ShouldEqual('4');
    7         private static char code;
    8     }

    实现

     1 namespace Skight.eLiteWeb.Domain.Specs.Properties
     2 {
     3     public class Verifier
     4     {
     5         private static char[] VERIFY_CODE =
     6             {
     7                 '1', '0', 'X', '9', '8', '7',
     8                 '6', '5', '4', '3', '2'
     9             };
    10 
    11         /**
    12          * 18位身份证中,各个数字的生成校验码时的权值
    13          */
    14 
    15         private static int[] VERIFY_CODE_WEIGHT =
    16             {
    17                 7, 9, 10, 5, 8, 4, 2, 1,
    18                 6, 3, 7, 9, 10, 5, 8, 4, 2
    19             };
    20         private static int CARD_NUMBER_LENGTH = 18;
    21 
    22         public char verify(string source)
    23         {
    24             /**
    25              * <li>校验码(第十八位数):<br/>
    26              * <ul>
    27              * <li>十七位数字本体码加权求和公式 S = Sum(Ai * Wi), i = 0...16 ,先对前17位数字的权求和;
    28              * Ai:表示第i位置上的身份证号码数字值 Wi:表示第i位置上的加权因子 Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4
    29              * 2;</li>
    30              * <li>计算模 Y = mod(S, 11)</li>
    31              * <li>通过模得到对应的校验码 Y: 0 1 2 3 4 5 6 7 8 9 10 校验码: 1 0 X 9 8 7 6 5 4 3 2</li>
    32              * </ul>
    33              * 
    34              * @param cardNumber
    35              * @return
    36              */
    37 
    38             int sum = 0;
    39             for (int i = 0; i < CARD_NUMBER_LENGTH - 1; i++)
    40             {
    41                 char ch = source[i];
    42                 sum += ((int) (ch - '0'))*VERIFY_CODE_WEIGHT[i];
    43             }
    44             return VERIFY_CODE[sum%11];
    45         }
    46 
    47     }
    48 }

    这时候,身份证构造器的完整实现就变成了

     1 public SocialID(String cardNumber)
     2         {
     3             if (string.IsNullOrEmpty(cardNumber))
     4                 throw new ApplicationException("Card Number is empty");
     5             if (cardNumber.Length != CARD_NUMBER_LENGTH)
     6                 throw new ApplicationException("Card Number Length is wrong.");
     7             if (!SOCIAL_NUMBER_PATTERN.IsMatch(cardNumber))
     8                 throw new ApplicationException("Card Number has wrong charactor(s).");
     9 
    10             if (cardNumber[CARD_NUMBER_LENGTH - 1] != verifier.verify(cardNumber))
    11                 throw new ApplicationException("Card Number verified code is not match.");
    12             this.cardNumber = cardNumber;
    13         }

    至此,代码已经很干净了。 是的,还有进一步的改进,如,3个元素(地区,生日,性别)的提炼应该移到构造器中,各个提取的功能就变成了,简单的数据读取。Social 的类型,不是class而是struct,因为这是典型的 Value Object。 另外,我把15转18位的部分也去掉了,这可以看作一个Utilit,可以在外部做,不是核心功能。

    你,是否能继续了?

    最后,欣赏一下测试结果:


    完整代码:

    SocialID.cs

    Verifier.cs


    从优秀到卓越
    皓月碧空,漫野如洗,行往卓越的路上

  • 相关阅读:
    日志配置
    Mybaties核心配置文件
    配置3
    写了两个数据获得方式----费劲周折
    applicationContext
    配置2
    Django-缓存的配置
    RabbitMQ的工作模式
    centos下保留python2安装python3
    python位运算
  • 原文地址:https://www.cnblogs.com/Wonner/p/2845306.html
Copyright © 2020-2023  润新知