类型系统的安全益处是什么?


47

在道格拉斯·克罗克福德(Douglas Crockford)的《JavaScript:The Good Parts》中,他在继承章节中提到:

经典继承的另一个好处是它包括类型系统的规范。这主要使程序员不必编写显式的转换操作,这是一件非常好的事情,因为在转换时,类型系统的安全性丧失了。

那么首先,安全到底是什么?防止数据损坏,黑客或系统故障等?

类型系统的安全益处是什么?是什么让类型系统与众不同,才能提供这些安全优势?


我不确定类型系统是否可以为非编译语言带来任何好处,但是作为编译语言的长期用户,我发现经过仔细类型检查的编译语言可以有效地防止多种类型的歧义,未定义或不完整的代码超越“编译”阶段。我猜您可能会说类型提示和Lint系统对于Web脚本(JavaScript)很有用,如果是的话,我敢肯定我们会看到足够的内容。飞镖有人吗?缺少静态类型系统,像Python这样的动态语言似乎也不差。
沃伦·P

1
今天,我们了解到打字应该是行为的,而不是结构性的。可悲的是,大多数现代编程语言都无法断言类型的行为(有关详细信息,请参见此问题)。这使得类型系统在大多数情况下几乎毫无用处,尤其是因为聪明的linter可以捕获此处提到的简单类型错误,以检查常见问题。
本杰明·格林鲍姆

4
@BenjaminGruenbaum您的描述已经静态存在于OCaml等语言中。这称为结构化类型,实际上它已经很旧了,名义上的类型是新的。
jozefg

2
@BenjaminGruenbaum:...什么!?这显然不是不可判定在静态类型语言中,否则写那些语言的编译器是不可能的。
BlueRaja-Danny Pflughoeft13年

6
@BenjaminGruenbaum:您的评论很有价值,并且该论文很有趣,但是它并不能证明“在Java等静态语言中通常也无法确定”这一说法,因为它证明了它在C#中可以确定的,并没有解决问题。它在Java中是否不可思议。(无论如何,IME,当静态类型语言的编译器无法确定某种类型正确的东西时,它会拒绝它(或无法对其进行编译),因此不可判定性是一种烦恼,而不是类型上的漏洞-安全。)
ruakh

Answers:


82

类型系统防止错误

类型系统消除了非法程序。考虑以下Python代码。

 a = 'foo'
 b = True
 c = a / b

在Python中,该程序失败;它会引发异常。在Java,C#,Haskell之类的语言中,这甚至都不是合法程序。您完全避免了这些错误,因为在输入程序集中根本不可能发生这些错误。

同样,更好的类型系统可以排除更多错误。如果我们跳到超高级类型系统,我们可以这样说:

 Definition divide x (y : {x : integer | x /= 0}) = x / y

现在,类型系统保证没有任何0分频错误。

什么样的错误

这是系统可以防止的错误类型的简要列表

  1. 超出范围的错误
  2. SQL注入
  3. 概括2,许多安全问题(Perl中的污染检查是什么)
  4. 乱序错误(忘记调用init)
  5. 强制使用值的子集(例如,仅大于0的整数)
  6. 邪恶的小猫 (是的,这是个玩笑)
  7. 精度损失错误
  8. 软件事务存储(STM)错误(这需要纯度,也需要类型)
  9. 概括8,控制副作用
  10. 数据结构的不变性(二叉树是否平衡?)
  11. 忘记例外或抛出错误

请记住,这也是在编译时。无需编写100%代码覆盖率的测试来简单地检查类型错误,编译器将为您完成:)

案例研究:键入的lambda演算

好吧,让我们研究一下所有类型最简单的系统,即简单地输入lambda演算

基本上有两种类型

Type = Unit | Type -> Type

并且所有术语都是变量,lambda或应用程序。基于此,我们可以证明任何类型良好的程序都将终止。永远不会出现程序被卡住或永远循环的情况。在正常的lambda演算中这是无法证明的,因为那是不正确的。

想想看,我们可以使用类型系统来保证我们的程序不会永远循环,而是很酷吧?

绕道进入动态类型

动态类型系统可以提供与静态类型系统相同的保证,但是可以在运行时而不是在编译时提供。实际上,由于它是运行时,因此您实际上可以提供更多信息。但是,您失去了某些保证,尤其是关于静态属性(例如终止)的保证。

因此,动态类型并不排除某些程序,而是将格式错误的程序路由到定义明确的操作,例如引发异常。

TLDR

因此,总的来说,长短是类型系统排除某些程序。许多程序以某种方式被破坏,因此,对于类型系统,我们避免了这些破坏的程序。


25
+1相当于编写大量测试。
丹·尼利

3
@DanNeely仅仅是为了说明,在动态语言中,您需要练习代码的所有部分以捕获类型系统免费检查的错误。使用依赖类型的语言,您实际上可以完全用类型替换测试。但是,很多时候您需要证明其他正确性定理
jozefg

3
如果您的类型系统已经证明您的程序必须终止,那么它(可能)是通过证明它正在计算原始递归函数来完成的。我想这很酷,但是复杂度却比真正的图灵机所能解决的复杂度低得多。(这并不意味着中间值不大; Ackermann函数是原始递归的…)
Donal Fellows

5
@DonalFellows尽管Ackermann函数是一个完全可计算的函数,它不是原始的递归函数。
2013年

4
@sacundim确实,像agda之类的语言允许进行可选的总计检查,在极少数情况下,您确实想要任意递归,那么您可以很好地询问,这是一个非常漂亮的系统。
jozefg

17

现实本身就是打字的。您不能在重量上增加长度。尽管您可以将英尺增加到米(均为长度单位),但至少应缩放两者之一。否则,您的火星任务将因此崩溃。

在类型安全系统中,将两个以不同单位表示的长度相加可能是错误,也可能会导致自动转换。


15

类型系统可以帮助您避免简单的编码错误,或者允许编译器为您捕获这些错误。

例如,在JavaScript和Python中,以下问题通常仅在运行时才发现-并且取决于测试条件的质量/稀有性,实际上可能使其进入生产环境:

if (someRareCondition)
     a = 1
else
     a = {1, 2, 3}

// 10 lines below
k = a.length

强类型语言会强制您明确声明它a是一个数组,并且不允许您分配整数。通过这种方式,没有任何机会a不会有length-甚至在少数情况下。


5
在像WebStorm JavaScript这样的IDE中,一个聪明的短毛猫可能会说“可能未定义对数字a的a.length的引用”。拥有明确的类型系统并不能为我们提供这一点。
本杰明·格林鲍姆

4
1.静态上不是很强2. @BenjaminGruenbaum是的,但这是通过在后台追踪分配图来完成的,将其视为一个微型解释器,试图弄清楚事情的发展方向。比类型免费提供时要难得多
jozefg

6
@BenjaminGruenbaum:不要将隐式/显式与强/弱相混淆。例如,Haskell有一个非常强大的类型系统,这使大多数其他语言都感到羞耻,但是由于某些语言设计决策,它也几乎可以完全通用类型推断,从而使其成为支持显式类型的强隐式类型语言。 (您应该使用它,因为类型推断器只能从您写的内容中推断出您的意思,而不是您的意思!)
Phoshi

6
“强类型语言将迫使您明确声明a是数组”。这是错误的。Python是强类型的,不需要这样做。即使是静态和强类型的语言也不需要它们支持类型推断(并且当今大多数主流语言都至少部分支持)。
康拉德·鲁道夫2013年

1
@BenjaminGruenbaum:嗯,很公平。即使这样,在某些情况下,JS静态分析器也无法执行强类型语言将提供的相同类型的类型检查,因此通常情况下,解决该问题需要解决停止问题。为了实现接近100%的类型推断,Haskell必须做出一些设计决策,而C#/ Scala不能推断所有内容。当然,在那种情况下,这并不重要,因为您可以显式指定类型-在Javascript中,这意味着即使最好的静态分析器也无法再检查您的代码。
Phoshi

5

在软件开发周期中,您越早发现错误,则修复成本就越低。考虑一个导致最大客户或所有客户丢失数据的错误。如果仅在真正的客户丢失数据之后才发现这种错误,那么您的公司就可能灭亡!显然,将其转移到生产环境之前,查找和修复该错误的成本更低。

即使对于代价较低的错误而言,如果涉及测试人员,则花费的时间和精力也要比程序员能够找到并修复它的时间和精力更多。如果不将其检入到源代码管理中,其他程序员可以在该代码中构建依赖它的软件,那么它会更便宜。类型安全性可防止某些类型的错误甚至无法编译,从而消除了这些错误的几乎所有潜在成本。

但这还不是全部。就像任何使用动态语言编程的人都会告诉您的那样,有时候您的程序只是编译就很好了,因此您可以尝试其中的一部分而不必花费所有细节。在安全性和便利性之间需要权衡取舍。单元测试可以减轻使用动态语言的某些风险,但是编写和维护良好的单元测试有其自身的成本,其成本可能比使用类型安全的语言要高。

如果您正在试验,如果您的代码仅使用一次(例如一次性报告),或者您处于无论如何都不想编写单元测试的情况下,那么动态语言可能是完美的选择为了你。如果您有一个大型应用程序,并且希望在不破坏其余部分的情况下进行更改,那么输入安全性可以节省生命。安全捕获的错误类型恰恰是重构时人类容易忽略或犯错的错误类型。


这使动态类型卖空了,没有提到它的主要好处(所提到的那些好处相对不重要)。这似乎还暗示着单元测试有些奇怪-是的,它们很难做到而且成本很高,并且也适用于静态类型的语言。这是在说什么?它也没有提到当前类型系统的局限性(通过设计),无论是它们可以表达的内容还是可以捕获的错误。

@MattFenwick您觉得动态打字的主要好处是什么?
GlenPeterson 2013年

典型的静态类型系统通过设计拒绝许多类型良好的程序。(一种选择)(顺便说一句,我的批评只针对第3和第4款。)

4

介绍

类型安全可以使用静态类型(编译的静态类型检查)和/或运行时(评估的动态类型检查)语言来实现。根据Wikipedia的描述,“ ... 强类型系统”被认为是其中不可能发生未经检查的运行时类型错误的系统(ed Luca Cardelli)。换句话说,不存在未经检查的运行时错误称为安全性或类型安全性...'

安全-静态类型检查

通常,在C,C ++和Haskell等语言中,类型安全已与静态类型同义,这些语言旨在在编译时检测类型不匹配。这样做的好处是在执行程序时避免了潜在的未定义或容易出错的情况。在存在指针类型可能不匹配的风险的情况下,例如,如果未检测到可能导致灾难性后果的情况,这将是无价的。在这种意义上,静态类型被认为是内存安全的同义词。

静态类型化并不完全安全,但是可以提高安全性。即使是静态类型的系统也可能造成灾难性的后果。许多专家认为,静态类型可用于编写更健壮和更不易出错(关键任务)的系统。

静态类型的语言可以帮助减少数据丢失或数值工作精度损失的风险,这种风险可能是由于不匹配或截断双精度浮点数或整数和浮点数类型不匹配而引起的。

使用静态类型的语言具有效率和执行速度的优势。运行时受益于不必在执行期间确定类型。

安全-运行时类型检查

例如,Erlang是一种在虚拟机上运行的类型声明式,动态类型检查的语言。Erlang代码可以按字节编译。Erlang被认为是可用的最重要的关键任务,容错语言,据报道,Erlang具有九个9的可靠性(每年99.9999999%或不超过31.5毫秒)。

某些语言(例如Common Lisp)不是静态类型的,但是可以根据需要声明类型,这有助于提高速度和效率。还应注意,在评估循环下,许多更广泛使用的解释语言(例如Python)是以静态类型的语言(例如C或C ++)编写的。上面的定义认为Commom Lisp和Python都是类型安全的。


2
我反对“强力打字”。您的意思是静态类型。强类型几乎没有任何意义,它通常用来说“我喜欢这种类型的系统”
jozefg

@ jozefg好点。我会修改职位。
AsymLabs

3
说解释性语言也没用……关于某种语言实现,但不是语言本身。可以解释或编译任何语言。甚至在编辑后,您仍在使用强类型和弱类型。
Esailija

3
@jozefg:我一直认为强类型表示每个值都有固定的类型(例如,整数,字符串等),而弱类型表示如果认为方便,则可以将一个值强制转换为其他类型的值所以。例如,在Python(强类型)中,1 + "1"引发异常,而在PHP(弱类型)中1 + "1"产生2(字符串"1"自动转换为integer 1)。
乔治

1
@Giorgio具有强定义,例如Java。但在许多情况下,据称是这样。这些话毫无意义。像jozefg所说的那样,“强/弱类型”的定义要准确得多,因为“我喜欢/不喜欢这种语言”。
Esailija

1

类型系统的安全利益就丧失了。

那么首先,安全到底是什么?防止数据损坏,黑客或系统故障等?

类型系统的安全益处是什么?是什么让类型系统与众不同,才能提供这些安全优势?

我觉得类型系统有这样的负面看法。类型系统更多地是要保证而不是证明没有错误。后者是类型系统的结果。一种用于编程语言的类型系统是一种在编译时生成程序符合某种规范的证明的方式。

一个人可以编码为一种类型的规范的种类取决于语言,或更直接地取决于语言的类型系统的强度。

最基本的规范是关于功能的输入/输出行为以及功能主体内部有效性的保证。考虑一个函数头

f : (Int,Int) -> String

一个好的类型系统将确保f仅应用于在求值时会产生一对Int的对象,并保证f会始终产生一个字符串。

某些语言的语句(例如if-then块)没有输入/输出行为。这里的类型系统保证了块中的每个声明或语句都是有效的;将操作应用于正确种类的对象。这些保证是可组合的。

同样,这确实提供了某种内存安全条件。您正在处理的报价是关于铸造的。在某些情况下,强制转换很好,就像将32位Int转换为64位Int一样。但是,通常,它会使类型系统崩溃。

考虑

Foo x = new Foo(3,4,5,6);
f((Int)x,(Int)x);

由于强制转换,x变成了Int,因此从技术上讲,上面的代码确实进行类型检查;但是,它确实无法实现类型检查的目的。

可以构成另一种更好的类型系统的一件事是,禁止将(A)x强制转换为X,其中x在案例为B之前,除非B是A的子类型(或子对象)。子类型理论的思想已在安全性中使用。消除整数上溢/下溢攻击的可能性。

摘要

类型系统是一种证明程序符合某种规格的方法。类型系统可以提供的好处取决于所使用类型系统的强度。


1

类型系统尚未提及的一个优点集中于这样一个事实,即读取许多程序而不是编写更多的程序,并且在许多情况下,类型系统可以允许以简明且容易的方式指定许多信息。被阅读代码的人消化。尽管参数类型不能代替描述性注释,但大多数人会发现它读起来更快:“ int Distance;”。要么Distance As Int32而不是阅读“距离必须是+/- 2147483647的整数”;此外,参数类型可以帮助缩小API的特定实现恰好与调用者有权依赖的范围之间的差距。例如,如果API的特定Javascript实现使用它在这将迫使任何字符串到数字形式的方法参数,可能不清楚呼叫者是否允许依靠这样的行为,或者API的其他实现方式可能会发生故障,如果给定的字符串。拥有其参数被指定为法Double会明确指出任何字符串值在传递之前必须由调用者强制执行;具有一个方法,该方法的重载可以接受,Double而另一个可以接受String 这样会更清楚地表明,允许持有字符串的呼叫者按原样传递它们。


0

那么首先,安全到底是什么?防止数据损坏,黑客入侵或系统故障等?

所有其他答案等等。通常,“类型安全”仅表示编译器成功编译的所有程序都不会包含类型错误。

现在,什么是类型错误?原则上,您可以将任何不希望的属性指定为类型错误,并且某些类型系统将能够静态地确保没有程序出现此类错误。

上面的“属性”是指某种适用于您的程序的逻辑命题,例如,“所有索引都在数组范围内”。其他类型的属性包括“所有引用的指针均有效”,“此程序不执行任何I / O”或“此程序仅对/ dev / null执行I / O”等。可以指定类型并以这种方式检查类型,具体取决于类型系统的表现力。

依赖类型系统是最通用的类​​型系统之一,通过它可以强制执行几乎任何您喜欢的属性。这不一定容易这样做,虽然,因为复杂性都受到不完整的礼貌哥德尔

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.