以下是「第3章 类型安全」的读书笔记。
使用类型来使程序更加安全。
避免基本类型偏执来防止错误解释
数值1000可以表示1000美元,或者1000英里。两个不同的开发人员可能将这个值解释为不同的单位
我们能够依赖类型系统来让假定变得明确,即通过定义类型来描述值,此类型检查器能够检测到不兼容的情况,并在不兼容的类型导致问题之前就给出警告。
基本类型偏执反模式
设计模式描述了高度可靠、有效的、可重用的软件设计。反模式指的是在存在更好的替代方案的情况下使用的常见的、低效的设计。
例如使用number表示邮编,使用string表示电话号码等就是一种常见的反模式:基本类型偏执。
如果要表示的是简单的值,例如物理测量值和邮编,可以考虑把它们定义为新类型,即使新类型只是简单地封装了一个数字或字符串。这可以为类型系统提供更多的信息,帮助其分析我们的代码,消除由于不兼容的假定而导致的众多问题,使代码的可读性变得更好。
添加类型信息
类型转换将一个表达式的类型转换为另一个类型。每个编程语言都制定了自己的规则,决定哪些转换是合法的,哪些是不合法的,哪些能够由编译器自动完成,哪些必须使用额外的代码来完成,
类型转换
显式类型转换允许我们告诉编译器将某个值视为特定的类型处理。在TypeScript中,通过在值的前面添加<NewType>或者在值的后面添加as NewType来将其转换为NewType。
如果滥用,这种技术可能很危险:如果绕过类型检查器,那么在试图把一个值当作其他类型使用时,会导致运行时错误。
例如,用Bike代表自行车,用ride()代表骑自行车的动作,用SportsCar代表跑车,用drive()代表开车的动作,那么尽管可以把Bike强制转换为SportsCar,却仍然不能对Bike调用drive()
我们必须先把myBike转换为unknown类型,然后再转换为SportsCar,因为TypeScript编译器认识到Bike和SportsCar类型并不重叠(一个类型的值不会是另一个类型的有效值)。
因此,简单地调用<SportsCar>myBike仍然会导致错误。相反,我们首先指定<unknown>myBike,告诉编译器忘记myBike的类型。然后,就可以说:“相信我们,这是一个SportsCar。”不过,可以看到,这仍然会导致一个运行时错误。在其他语言中,这可能会导致崩溃。一般来说,这种情况是不合法的。那么,这种技术在什么时候有用呢?
常见类型转换
隐式和显式类型转换
隐式类型转换是编译器自动执行的一种类型转换,并不需要编写任何代码。这种转换通常是安全的。与之相对地,显式类型转换指的是需要我们编写代码进行指定的类型转换。这种类型转换实际上会绕过类型系统的规则,所以应该谨慎使用
向上转换和向下转换
将子类型得对象解释为父类型是一种常见得类型转换。即将派生类解释为基类得做法称为向上转换,也称为显示转换
从父类转换到派生类称为向下转换,大多数强类型语言不会自动完成向下转换。
拓宽转换和缩窄转换
另外一种常见的隐式转换是从固定位数的整数类型(例如一个8位无符号整数)转换为另外一个更多位数的整数类型(例如一个16位无符号整数)。之所以可以隐式地完成这种转换,是因为16位无符号整数可以表示所有8位无符号整数值。这种类型的转换称为拓宽转换。
将一个带符号整数转换为无符号整数是危险的操作,因为无符号整数无法表示负数。将位数更多的整数转换为位数更少的整数,例如将16位无符号整数转换为8位无符号整数,只能用于小类型可以表示的值。这种类型的转换称为缩窄转换。
因为缩窄转换具有危险性,所以编译器要求必须显式指定这种转换。这种要求的好处是,显式指定缩窄转换能够清晰地表明你不是不小心进行了缩窄转换。有的编译器允许缩窄转换,但是会给出警告。当新类型无法容纳值时,运行时的行为与第2章介绍的整数溢出相似:取决于具体的语言,我们可能会得到一个错误,或者该值可能被截断,使其能够被容纳到新类型中,如图所示。