延迟进度。本文以经典邮箱类型、货币类型、密码类型为例。善用类型系统,可以很好地改进编码方式,同时为技术人找到“打好基础”的乐趣。2.字符串类型转化为邮箱类型。作者厌倦了使用原始类型,并尝试使用原始类型对域进行建模。字符串值(String)类型不仅用于保存用户的邮箱地址或国籍信息,还有更丰富的用途。我需要一个EmailAddress的类型,并且定义它不能为空,希望有一个单独的入口来创建这个类型的对象。在返回值之前,需要对其进行验证和规范化。同时也希望这个数据类型有一些方法,比如.Domain()或者.NonAliasValue()。当输入foo+bar@gmail.com时,会分别调用这两个方法,返回gmail.com和foo@gmail。com。在类型设计中应该考虑这个有用的特性,引入它有助于防止错误并提高类型的可维护性。3.设计良好的功能类型例如,EmailAddress可以提供两种方法来检查是否相等。lEquals方法用于判断两个(标准化的)邮件地址是否相同,如果相同则返回true。lEqualsInPrinciple该方法对foo@gmail.com和foo+bar@gmail.com的输入判断相同,所以也返回true。(这里假设两个邮箱都是同一个人注册的,所以same需要判断两个邮箱是“相等的”)具体类型的方法在不同的使用场景下会起到不同的作用。如果用户jane@gmail.com注册,但随后以Jane@gmail.com身份登录,则用户登录应该不会失败(只是首字母大写不同)。同样,如果用户使用电子邮件地址(foo@gmail.com)和另一个注册帐户(foo+svc@gmail.com)联系客户支持,则这两个电子邮件地址需要有效匹配。这些是典型的应用场景。如果没有分散在代码库中的业务逻辑,一个简单的字符串是无法满足的。注意:根据OfficeRFC的描述,邮箱地址中@符号之前的部分可以区分大小写,但各大邮箱主机都将其视为不区分大小写,所以域名类型也考虑了这方面。4.好的类型可以防止错误。按照上面的邮箱类型的例子,如果我们想更进一步,如果我们想要一个电子邮件地址被验证或不被验证。通过向个人收件箱发送唯一代码来验证电子邮件地址是一种常见的做法。这些“商业”交互也可以通过类型系统来表达。例如,创建名为VerifiedEmailAddress的第二个类型。该类型可以继承自EmailAddress。并确保代码中只有一个地方可以生成VerifiedEmailAddress的实例,即负责验证用户地址的服务。这样,应用程序的其他部分就可以依赖这个新类型来防止错误。任何发送电子邮件的功能都可以依赖此类来验证电子邮件地址的安全性。想象一下,如果电子邮件地址表示为简单的字符串。因此,要找到关联的用户帐户,请检查一些晦涩的标志,例如HasVerifiedEmail或IsActive,以确保这些标志设置正确并且没有在默认构造函数中错误地初始化为true。由于使用原始字符串,导致错误的空间太大,以至于某些检查没有到位,并且这种原始类型表达式的使用被认为是懒惰且缺乏想象力的编程。5.丰富的类型不受错误影响另一个很好的例子是货币!我已经记不清有多少应用程序使用小数来表示货币价值。也无法统计有多少应用程序使用decimal类型来表示货币值。为什么?这种类型有很多问题,甚至很难理解。每个处理金钱的领域都应该有一个专门的货币类型。货币类型应包括货币和运算符重载(或其他安全功能)以防止愚蠢的错误,例如将100美元乘以20英镑。此外,并非每种货币的小数点后只有两位数。一些货币,如巴林或科威特第纳尔有三位数。如果您正在处理投资或银行贷款,那么您最好确保您出示的UnidaddeFomento带有4个小数点。这些问题非常重要,足以保证专门的Moneytype,但还远远不够。除非所有功能都在系统内完成,否则您将不得不与第三方系统打交道。例如,大多数支付网关以整数值形式请求和响应资金。由于整数值不能覆盖像浮点数(double类型)这样的舍入操作,所以它们优先于浮点数。唯一需要注意的是,值必须以小单位(例如美分、便士、迪拉姆、格罗兹、科比等)传输,这意味着如果您的程序处理小数点值,在与外部API通信时,您将不得不不断地来回转换它们。如前所述,并非每种货币都使用两位小数,因此不是每次都简单地乘以/除以100。事情很快就会变得困难,如果将这些业务规则封装成一个简洁的单一类型,事情就会大大简化。varx=Money.FromMinorUnit(100,"GBP"):£1vary=Money.FromUnit(100.50,"GBP"):£1.50Console.WriteLine(x.AsUnit()):1.5Console.WriteLine(x.AsMinorUnit()):150如果这还不够复杂,各国也有不同的货币格式来表示货币。在英国,“一万英镑五十便士”会表示为10,000.50,但在德国“一万欧元五十美分”会显示为10.000,50。试想一下,如果这些规则没有放入统一的货币类型中,那么整个代码库中会有多少与货币相关的代码是零散的。此外,专门的货币类型可以包含许多功能,这些功能将使处理货币值变得轻而易举。vargbp=Currency.Parse("GBP");varloc=Locale.Parse("Europe/London");varmoney=Money.FromMinorUnit(1000050,gbp);money.Format(loc)//==>£10,000.50money.FormatVerbose(loc)//==>GBP10,000.50money.FormatShort(loc)//==>10k英镑,那么代码库的其余部分可以带来更高的安全性,并防止大部分错误,否则这些错误会随着时间的推移而蔓延。尽管像Money.FromUnit(decimalv,Currencyc)或Money.FromMinorUnit(intv,Currencyc)这样的小函数可能看起来并不多,但它使参与持续开发的程序员能够意识到用户输入或外部是否值API接收到的信息被包括在内,这首先防止了错误的产生。6.智能类型设计减少副作用丰富类型的好处在于它们可以以任何方式塑造。这是另一个示例,说明丰富的类型如何使团队免于巨大的运营开销,甚至可以防止安全漏洞。相信在很多系统的代码库中都有类似于字符串secretKey或者字符串password的东西,作为函数的参数。那么什么情况下会出问题呢?以下(伪)代码:try{varuserLogin=newUserLogin{Username=usernamePassword=password}varsuccess=_loginService.TryAuthenticate(userLogin);if(success)RedirectToHomeScreen(userLogin)。返回未授权()。}catch(Exceptionex){Logger.LogError(ex,"Userloginfailedfor{login}",userLogin);}这里的问题是,如果在认证过程中抛出异常,应用程序会将明文密码写入日志。当然,这个代码一开始就不应该存在,而且随着时间的推移会发生这种情况。大多数这些错误会随着时间逐渐发生。最初,UserLogin类可能有一组不同的属性,在最初的代码审查中,这段代码可能没问题。多年后,可能有人修改了UserLogin类以包含明文密码。此功能甚至不会出现在代码提交的差异中,因此可以逃过代码审查。因此引入了安全漏洞。但是,如果引入丰富的类型(专有类型),就可以避免类似的错误。在C#中(以该语言为例),当一个对象写入日志时,会自动调用ToString()方法。有了这些知识,我们就可以设计这样的密码类型。publicreadonlyrecordstructPassword(){publicoverridestringToString(){返回“****”。}publicstringCleartext(){返回_cleartext。}}虽然改动很小,但不可能在系统的任何地方不小心输出明文密码。这不好吗?当然,在实际的认证过程中,可能还是需要明文取值,所以需要实现非常明确的命名方法Cleartext(),所以这个操作的敏感性没有歧义,自动引导开发者有意和使用此方法节制。同样的原则也适用于处理用户的PII(例如国民保险号、税号等)。使用专门的类型对这些信息建模。覆盖默认函数,如.ToString()。ToString()的默认函数,并通过相应命名的函数暴露敏感数据。您永远不会将PII泄漏到日志和其他地方,并且以后可能需要大量操作才能将其再次清除。一个小技巧大有帮助!7.养成习惯每当开发人员处理特殊规则、行为或敏感数据时,请考虑如何通过创建显式类型来帮助自己。让我们再举一个密码类型的例子来更进一步!密码在存储到数据库之前经过哈希处理,但此哈希不是简单的字符串。在某些时候,我们将不得不在登录过程中将先前存储的哈希值与新计算的哈希值进行比较。但并非每个开发人员都是安全专家,比较两个哈希字符串可能会使代码暴露在攻击之下。检查两个密码哈希是否相等的推荐方法是以非优化方式进行。[MethodImpl(MethodImplOptions.NoInlining|MethodImplOptions.NoOptimization)]privatestaticboolByteArraysEqual(byte[]a,byte[]b){if(a==null&&b==null){returntrue;}if(a==null||b==null||a.Length!=b.Length){returnfalse;}varareSame=true;for(vari=0;i
