對於軟體開發這檔子事,長期以來一直有兩種截然不同的看法。有人認為軟體開發是一種工程,因此可以採用工程的方法來控管與執行軟體開發的各項活動,希望能藉此開發出高品質的軟體產品。因此,程式設計師被要求紀錄工作時數、每日撰寫程式碼的數量(包含增加、修改與刪除的程式碼行數)、軟體缺陷 (defects)產生的數量與原因、修復缺陷所花費的時間等等。對於身為程式設計師的你而言,最直接的結果就是有一大堆填不完的報表等著你。近幾年政府與學界在台灣推行的軟體流程改善方法,例如 PSP (不是 Sony 的掌上型電玩,而是 Personal Software Process 的簡寫) 與 CMMI 就是兩個典型的例子。
也有人認為軟體開發比較接近於藝術創作,因為軟體開發屬於是思考性與創作性的活動,這一點和大多數的藝術創作很類似。難道我們會要求畢卡索每天記錄工作時間、畫了幾幅畫、浪費了多少顏料與畫布?
將軟體開發視為藝術創作最大的問題可能是生產力難以預估。大多數的人可以接受創作一件藝術品是需要時間的蘊釀,但我們很難告訴顧客說:「目前本公司的 chief architect正在尋找下一個創作的靈感,因此你們產品交貨的時間將被無限期延後」。
不管我們偉大的阿扁總統告訴我們:「沒有所謂的中間路線,或是新中間路線」,我相信通常對於一個問題有兩種截然不同的看法時,真正比較可行的作法總是介於這兩者之間 。近年來廣為流行的 agile methods 就是一種試圖平衡軟體開發的工程面向與藝術面向的一種方法 。
關於軟體開發倒底是什麼東東,我覺得最好的兩個比喻是 Kent Beck 在 Extreme Programming Explained 這本書中所提出的「開車」(driving),以及 Andrew Hunt 與 David Thomas 在 The Pragmatic Programmer 這本書中所提出的「園藝」(gardening)。 Beck 認為軟體開發好比開車,並非永遠朝直線前進,而是要打起精神,隨時準備向這邊修正一點、向那邊修正一點。由於改變是軟體開發中唯一不變的事情,因此程式設計師永遠要作好修正的準備。有時候我們甚至要朝向一個完全相反的方向前進 (開錯路 ?!),但這就是程式設計師的宿命。
Hunt 與 Thomas 認為軟體開發是一種園藝,原始碼就好比花園,身為程式設計的我們是就是園丁。因此我們需要「時時勤拂拭,不使惹塵埃」,我們的軟體才會長的頭好壯壯。想像一下你所看過的所有美麗的花園,如果一個月不去整理那會是個怎樣的情況! 千萬別忽視「雜草」的生命力,一周不除草就可能毀掉你心愛的草坪。因此,Hunt 與 Thomas 進一步提出:「All Programming is maintenance programming 」,因為我們鮮少寫真正新的程式碼,大部分的情況是,我們維護已經存在的程式碼 (無論是寫新的程式碼或是修改、刪除原有的程式碼),使它變的更好。
在大多數的程式碼還是需要依靠人來撰寫的情況下,我們還是需要抱持著「時時勤拂拭,不使惹塵埃」的心態來照顧我們所開發的軟體。也許有一天 MDA (Modern-Driven Architecture) 與程式碼自動產生的技術成熟到可以取代程式設計師的角色,我們方可昇華到「本來無一物,何處惹塵埃」的境界;只是,我們可能需要擔心工作不保了。
l
2008年1月29日 星期二
2008年1月10日 星期四
例外處理 (3):Exceptions and exception handling mechanisms
Exception is an abstraction of errors and failures. As such, an exception carries information about the errors or failures it represents. Exception handling is the programmed activities conducted among multiple collaborating components in a program in response to an exception. These activities are conducted by code units called exception handlers or handlers. A handler can be attached to a statement, a block, a method, an object, a class, a module, or an exception. The plan about how to choose or devise an exception to represent errors and failures and how to respond to an exception is called exception handling design. An exception handling mechanism (EHM) is a programming language’s fundamental supports for dealing with exceptions, including representation, definition, signaling, propagation, resolution, and continuation of exceptions.
An important objective in the design of EHM is to keep separated the code for delivering normal functionalities and the code for dealing with exceptional conditions. In a language that lacks adequate EHM support, programs explicitly check each function call on the return code, data fields, flags, or global variables for finding out about the execution status. Since the checks and handling of anomalies are mixed with the logic for normal functionalities, code becomes cluttered and the program becomes incomprehensible. EHMs in modern programming languages provide better supports for separation of concerns in this regard. With exceptions, it is no longer needed to check every statement for error state. Once the execution of a statement gives rise to an exception, the runtime environment automatically alters the program execution from a normal path to an exceptional path. This allows the programmer to structure the logic for exception handling in appropriate exception handling constructs without cluttering the program.
Exception representation is the way that a programming language expresses an exception internally. It defines an exception context which contains information explicitly passed by the exception creator or implicitly passed by the language runtime. An exception can be represented as a symbol, a data object, or a full object [1]. Exceptions defined as symbols are strings or numbers. Data-object exceptions are primarily used to hold error and failure information only. Full-object exceptions directly encapsulate signaling, propagation, and continuation behaviors of exceptions in the class definition.
Programmers use exception definition to define exceptions in a program. If exceptions are represented as symbols, new exceptions are defined as strings or numbers. If exceptions are data objects and full objects, a class is used to define an exception. Some languages allow any class to be exceptions while others require that only particular types of classes can be exceptions. For example, in C++ any class can be exceptions but in Java only classes which directly or indirectly inherit from Throwable class can be exceptions.
An exception occurrence is an instance of an exception. The instruction to explicitly transmit an exception occurrence to the exception receiver is called throwing, signaling, raising, or triggering an exception. The sender of the exception is called signaler; the receiver of an exception is called the exception target, or target. An explicit throw instruction creates a synchronous exception, which is a direct result of performing the instruction. Conversely, an asynchronous exception can occur at any time in spite of the program statements under execution. Asynchronous exceptions can be produced by the runtime environment upon encountering an internal error or by stopping or suspending a thread.
If an exception is signaled and not coped with locally, the exception can be propagated to the caller of the signaling method. Exception propagation can be explicit or implicit (or automatic). In the former, a receiver must explicitly re-throw an unhandled received exception for further propagation; in the latter, an unhandled exception is automatically propagated. An exception is internal if it is not propagated; otherwise it is external. Some languages require propagated exceptions to be declared in signatures while others allow exceptions to be propagated without declaration.
Exception resolution or handler binding is a process of finding a suitable handler in the target, which is resolved by static scoping at compiler-time, dynamic invocation chain at runtime, or both. There are two methods to dynamically find a handler: stack unwinding and stack cutting. Stack unwinding pops the stack frames to search for the matching exception handler while stack cutting maintains a list of registered exception handlers and looks up the list for a suitable exception handler. While stack unwinding incurs no overhead in the normal execution, a significant overhead is involved in the exceptional execution. In contrast, stack cutting requires some housekeeping overhead to register and deregister exception handlers in the normal execution but incurs only a relatively minor overhead in the exceptional execution.
An exception continuation or exception model specifies the execution flow after an exception handler returns its control. In the termination model or the nonresumptive model, the execution of the component is terminated. A variation of the termination model is the retry model where the original execution is terminated and then the component is engaged for execution again. In the resumption model, the execution continues from the location or the next location where the exception was raised. Although program languages can support more than one model, mainstream programming languages such as C++, Java, and C# favor the termination model for its simplicity.
Reference
An important objective in the design of EHM is to keep separated the code for delivering normal functionalities and the code for dealing with exceptional conditions. In a language that lacks adequate EHM support, programs explicitly check each function call on the return code, data fields, flags, or global variables for finding out about the execution status. Since the checks and handling of anomalies are mixed with the logic for normal functionalities, code becomes cluttered and the program becomes incomprehensible. EHMs in modern programming languages provide better supports for separation of concerns in this regard. With exceptions, it is no longer needed to check every statement for error state. Once the execution of a statement gives rise to an exception, the runtime environment automatically alters the program execution from a normal path to an exceptional path. This allows the programmer to structure the logic for exception handling in appropriate exception handling constructs without cluttering the program.
Exception representation is the way that a programming language expresses an exception internally. It defines an exception context which contains information explicitly passed by the exception creator or implicitly passed by the language runtime. An exception can be represented as a symbol, a data object, or a full object [1]. Exceptions defined as symbols are strings or numbers. Data-object exceptions are primarily used to hold error and failure information only. Full-object exceptions directly encapsulate signaling, propagation, and continuation behaviors of exceptions in the class definition.
Programmers use exception definition to define exceptions in a program. If exceptions are represented as symbols, new exceptions are defined as strings or numbers. If exceptions are data objects and full objects, a class is used to define an exception. Some languages allow any class to be exceptions while others require that only particular types of classes can be exceptions. For example, in C++ any class can be exceptions but in Java only classes which directly or indirectly inherit from Throwable class can be exceptions.
An exception occurrence is an instance of an exception. The instruction to explicitly transmit an exception occurrence to the exception receiver is called throwing, signaling, raising, or triggering an exception. The sender of the exception is called signaler; the receiver of an exception is called the exception target, or target. An explicit throw instruction creates a synchronous exception, which is a direct result of performing the instruction. Conversely, an asynchronous exception can occur at any time in spite of the program statements under execution. Asynchronous exceptions can be produced by the runtime environment upon encountering an internal error or by stopping or suspending a thread.
If an exception is signaled and not coped with locally, the exception can be propagated to the caller of the signaling method. Exception propagation can be explicit or implicit (or automatic). In the former, a receiver must explicitly re-throw an unhandled received exception for further propagation; in the latter, an unhandled exception is automatically propagated. An exception is internal if it is not propagated; otherwise it is external. Some languages require propagated exceptions to be declared in signatures while others allow exceptions to be propagated without declaration.
Exception resolution or handler binding is a process of finding a suitable handler in the target, which is resolved by static scoping at compiler-time, dynamic invocation chain at runtime, or both. There are two methods to dynamically find a handler: stack unwinding and stack cutting. Stack unwinding pops the stack frames to search for the matching exception handler while stack cutting maintains a list of registered exception handlers and looks up the list for a suitable exception handler. While stack unwinding incurs no overhead in the normal execution, a significant overhead is involved in the exceptional execution. In contrast, stack cutting requires some housekeeping overhead to register and deregister exception handlers in the normal execution but incurs only a relatively minor overhead in the exceptional execution.
An exception continuation or exception model specifies the execution flow after an exception handler returns its control. In the termination model or the nonresumptive model, the execution of the component is terminated. A variation of the termination model is the retry model where the original execution is terminated and then the component is engaged for execution again. In the resumption model, the execution continues from the location or the next location where the exception was raised. Although program languages can support more than one model, mainstream programming languages such as C++, Java, and C# favor the termination model for its simplicity.
Reference
- A. F. Garcia, C. M. F. Rubira, A. Romanovsky, and J. Xu, A comparative study of exception handling mechanisms for building dependable object-oriented software, Journal of Systems and Software, vol. 59, no. 2: 197-222, 2001.
訂閱:
文章 (Atom)