例外是OOP概念吗?


37

昨天看了一篇帖子,我意识到我对异常的起源并不了解。它仅是一个与OOP相关的概念吗?我倾向于认为是这样,但是同样有数据库异常。


我曾经读过一个“ Goodenuf先生”(或类似的人)发明了例外,以回应“没关系,是Goodenuf ,,!” 风格欺凌。我现在似乎找不到参考资料-也许有人可以找到。知道首先添加哪种语言会很有趣。
2013年

7
Haskell有例外,完全没有面向对象
jozefg

1
尽管异常本身并不是严格地面向对象的,但将异常定义为对象并抛出并捕获这些对象的实例的常见做法是非常面向对象的。
Dougvj 2013年

异常和GOTO之间的区别是一个有趣的问题。
奥斯汀·亨利

2
@Austin-怎么样?“尽管异常抛出违反了结构化编程的一些最严格的门徒拥护绝对的单一出口点原则,但它们不允许以这种方式进行不受约束的意大利面条控制流goto。特别是,抛出是由上下文决定的,基于块结构的嵌套。因此,异常甚至取决于结构化编程的稍微宽松一些的形式,在这种形式下,将单出口原则作为准则,而不是绝对准则。
Steve314 2013年

Answers:


5

异常不是OOP概念。

但是它们在一点点上也不是完全无关的。

正如其他答案所表明的那样:异常的概念已经用几种非OOP语言实现了。该概念中的任何内容都不需要 OOP。

但是认真对待 OOP的所有OOP语言(如果不是全部)都需要例外,因为其他错误处理方法在某个特定点失败:构造函数。

OOP的要点之一是对象应完全一致地封装和管理其内部状态。这也意味着在纯OOP中,您需要一个概念来创建一个具有“原子”一致状态的新对象-从内存分配(如果需要)到初始化到有意义的状态(即简单地将内存清零是不够的)都必须用一个表达式完成。因此,需要一个构造函数

Foo myFoo = Foo("foo", "bar", 42);

但这意味着构造函数也可能由于某些错误而失败。如何在没有异常的情况下从构造函数中传播错误信息?

  • 返回值?失败,因为在某些语言中new只能返回null但不能返回任何有意义的信息。在其他语言中(例如C ++)myFoo不是指针。您无法对照进行检查null。您也不能问myFoo该错误-它没有初始化,因此在OOP思维中“不存在”。

  • 全局错误标志?关于封装状态,然后封装一些全局变量,那么多了吗?去h ... ;-)

  • 混合物?绝对没有更好的选择。

因此,例外是比OOP更基本的概念,但是OOP是自然地在它们之上建立的。


3
据我所知,这个“答案”中唯一能解决实际问题的单词是“否”。其余的似乎与OOP中的例外有关-出于对我的所有应有的尊重,这读作介于模糊相关完全不相关之间 -再次,在提出的问题中
t

@gnat:TO也说他不知道例外的起源。因此,对于OO领域中到处都是例外的情况,我有点背景。YMMV
AH

1
@AH我必须同意,除了开头部分,它真的没有解决这个问题。您对gnat的回答是“说他不知道异常的起源”,但实际上您没有给出异常的起源,只是在对象实例化过程中随机使用了一些异常。

伙计们,认真吗?-1?多数其他答案也不是100%正确。向我+1以补偿。在类设计破烂的世界中,此答案提供了很好的背景建议。(修订:应避免提及多步构造函数)
Jo So

44

它仅与OOP相关吗?

否。异常与OOP无关。

异常处理是一种处理错误的机制。通过将当前执行状态保存在预定义的位置并将执行切换到称为异常处理程序的特定子例程来处理异常。

比较C(不是真正的OOP语言可以以某种方式模拟C中的异常)和C ++(OOP,支持异常),没有什么可以阻止C的标准委员会向C添加异常处理,它仍然不会使C成为OOP语言。


2
您甚至可能会争辩说,通常的OS中已经支持某种例外。让您的程序崩溃(未捕获的“异常”),并使用核心转储和调试器,您甚至可以获得堆栈跟踪。
bhaak

12
甚至80年代初期的MS BASIC都有异常处理:ON ERROR GOTO xxxx
jwernerny

1
@bhaak他还可以谈论内存转储在Windows
JohnL

11
@jwernerny错误处理?当然。但是没有人会称这种异常处理。实际上,它通常(结构化)异常处理形成对比
康拉德·鲁道夫2013年

1
@jwernerny不确定我是否遵循;据我了解,异常处理是一种非常具体的错误处理方式。当我听到异常时,我总是会想到这种try catch构造。
安迪

12

简而言之,例外情况是需要引起注意的例外情况,并且通常会改变程序执行流程。通过该定义,异常和异常处理不限于面向对象,并且简单的程序错误可以视为异常的一种形式。

面向对象的语言通常具有本机异常类,根据上下文,单词“ exception”可能确实是指该本机类而不是一般概念。与大多数面向对象一样,面向对象的异常处理是语法糖,可以很容易地在决定性的非面向对象语言中进行仿真。这是C编程Wikibook中的C示例:

#include <stdio.h>
#include <setjmp.h>

jmp_buf test1;

void tryjump()
{
    longjmp(test1, 3);
}

int main (void)
{
    if (setjmp(test1)==0) {
        printf ("setjmp() returned 0.");
        tryjump();
    } else {
        printf ("setjmp returned from a longjmp function call.");
    }
}

6
它不仅是语法糖。使用setjmp很难重新创建完整的堆栈展开和基于类型的捕获处理程序。此外,异常的特殊编译会产生setjmp无法模仿的优势。
edA-qa mort-ora-y

3
我讨厌描述例外是例外情况。我宁愿说当在当前上下文中无法解决错误情况时,应该生成(引发)异常,因为当前上下文中没有足够的信息来正确修复错误。
马丁·约克


9

答案很简单。

ADA是非OO语言的一个很好的例子。


4
嗯,为什么ADA不是OO语言?当然,ADA83缺乏多态性,但仍可以视为基于对象的。同样从ADA95开始,该语言完全面向对象。
yannis 2013年

据我所知,异常处理早于ADA83,因此ADA本身是具有异常处理的非OO。
Uwe Plonus

2
@YannisRizos:Ada83具有软件包和通用软件包,但没有对象。它们是随Ada95一起引入的。
mouviciel 2013年

2
@Yannis-没有多态性的对象就像没有嵌套块的结构化编程。多态性是OOP的定义特征之一。即使在Ada95中,支持运行时绑定的类型也称为“标记类型”而不是“类”,尽管这只是拼写。Ada 83具有变体记录和各种其他类型,但这些类型均未提供特定于OOP的功能。Ada 83是模块化和结构化的,但它不是面向对象的。
Steve314

3
@Yannis-基本上,Ada社区中的某些人(例如大多数语言的拥护者)不能接受某个功能可以是好的,但是不能以他们喜欢的语言实现的功能,并且会构成各种借口,使人们难以相信。但是,似乎一门好的语言并不需要具备所有可能的好语言特性(尽管很容易相信Ada设计师是这么认为的)。我不是真正的极简主义语言设计者,但是极简主义的语言也不是完美的。
Steve314

7

这里已经有一些非常好的答案。非OOP编程语言的其他示例也有例外:

  • Oracle PL / SQL

  • 经典的Visual Basic(V6及以下版本,“ On Error Goto”是IMHO异常处理的一种形式)

(更确切地说,您在两种语言中都找到了一些OO元素,但是异常处理机制没有使用它们,我想是因为该概念是在将OO元素添加到这些语言之前的几年才引入的)。


至少在DOS上更高的QuickBASIC版本(早于Visual Basic;根据Wikipedia,VB 1.0 1991,QB 4.5是1988)具有使用ON ERROR GOTO语法的错误处理。甚至QuickBASIC也有一些类似于OO的概念(我认为QB 4.5甚至支持某种类),但很难将大多数传统的BASIC称为正确的面向对象语言。[Wikipedia ]
CVn

5

异常背后的基本思想是清理程序流,以便程序员可以更轻松地遵循“正常”执行路径。考虑一个在C中打开文件的简单情况。尝试打开文件后,程序员需要立即检查fopen()调用的响应并确定调用是否成功。如果调用未成功,则程序员必须做出适当的响应。处理错误或失败条件后,将出现“正常”执行路径中的下一个调用,可能是对fread()或fwrite()的调用。可能在下一个屏幕上。

使用提供异常的语言,等效的fopen()调用可以紧随其后的是fread()或fwrite()。没有错误处理隐藏了“正常”执行路径的“下一步”。程序员可以在一个屏幕上看到更多的正常路径,因此可以更轻松地跟随执行。错误处理将移至程序的另一部分。

异常本身不是OOP的概念,但是通常使用OOP的概念来实现,这使它们更加方便和强大。例如,可以使用继承层次结构定义异常。使用我们打开和读取或写入文件的概念性示例,这些调用中的每一个都可能生成各种异常-FileClosedException,DeviceFullException,NoSuchFileException,InsufficientFilePermissionsException等。每个异常都可以继承自FileException,而FileException可以继承自IOException。从GenericException继承。

如果程序员正在执行一种快速而肮脏的实现来测试一个概念,那么他可能会大多忽略异常处理,而只为GenericException实现一个处理程序。该处理程序将处理GenericException以及从GenericException继承的任何异常。如果他想以相同的方式处理任何与文件相关的异常,则可以为FileException编写处理程序。这将被FileExceptions以及从FileException继承的任何异常调用。如果他想编写一个对各种错误情况做出不同响应的程序,则可以为每个特定的异常编写一个特定的处理程序。


3

其他人正确地回答了语言示例。我认为我可以通过添加一个示例来扩展,该示例涉及如何在不涉及OOP的情况下向语言添加例外。

OZ的DSKL(声明性顺序内核语言)的情况下,我会这样做,这是一种非常适合此类学术界语言的语言。可以在此处(随机搜索结果),“语句和值”部分看到DSKL(或DKL)。确切的定义并不重要,除了这是一种非常简单的语言,没有可修改的变量(它们被声明并稍后绑定),并且没有内置的OOP。

OOP甚至不能作为该语言的语言抽象添加。通过将唯一的名称添加到内核语言(NewName)并使用本地作用域,可以实现封装。或者通过将可变状态添加到内核语言(NewCell)并使用本地作用域,可以实现带有封装的适当OOP。但是,仅靠指定的内核语言无法实现。

如果随后将异常添加到内核语言中,我们将使用不支持OOP的语言,但会包含异常。让我展示如何:

通过定义具有堆栈和存储空间的抽象机,我们可以定义语言中的每个语句应执行的操作(语句的语义)。例如skip,堆栈中什么都不做,A = 3堆栈中应该将A绑定到(/ with)3。

我们首先添加应如何定义异常的语法。为此,我们<statement>在DKL中添加了另外两个子句。

<statement>  ::== ... (old stuff)
                 | try <statement> catch <id> then <statement> end
                 | raise <id> end

这是已知的try / catch,以及引发/引发异常的方法。

我们通过它们在抽象机器上的工作方式来定义它们的语义

尝试
语义语句是(try <statement1> catch <id> then <statement2> end)

  1. 将语义语句压入堆栈 (catch <id> then <statement2> end)
  2. 将语义语句压入堆栈 (<statement1>)

请注意,语句1将位于堆栈的顶部,并首先尝试执行。

提高
语义声明是(raise <id> end)

  1. 如果堆栈上没有更多内容,请停止并报告未捕获的异常。
  2. 否则,从堆栈中弹出第一个语义语句。如果不是catch语句,请转到步骤1。
  3. 我们有一个陷阱,就是“ (catch <id> then <statement> end)
    (<statement>)入堆栈”表单。

捕获
如果在正常执行过程中看到捕获状态,则表示内部执行的任何内容都不会引发异常直至此级别。因此,我们只是弹出catch堆栈,什么也不做。

QED,我们有一种例外的语言,没有OOP的可能性。

我已经从抽象机中删除了环境部分,以使其更简单。


1

没有。

IIRC,例外出现在第一种面向对象的语言之前。AFAIK,早期的LISP实施首先支持例外。早期的结构化语言(例如ALGOL)和早期的OO语言(例如SIMULA)不支持例外。


当然,ALGON 68也有例外(“事件”),但也有其他所有情况。PL / I也有它们(“接通条件”),并且从1969年开始有文献描述它们的使用。
罗斯·帕特森
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.