Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
148,17 KB
Nội dung
tabrow := 1; FOR company_cur IN company_rec LOOP profit_and_loss (company_cur.company_id); net_profit_table (tabrow) := calculate_net_profit (company_cur.company_id); tabrow := tabrow + 1; END LOOP; Try reading this code out loud: "For every company in my cursor, profit-and-loss the company and then add the calculate-net-profit to the table." It doesn't sound right and it doesn't look right. If you simply name the procedure "profit_and_loss," you do not explain what you are doing with the profit and loss. And if you include a verb in the function name, you have one verb too many; the assignment operator itself already serves as the "action" for that statement. I can make a few, minor adjustments in these module names and end up with much more readable code, as shown in this example: tabrow := 1; FOR company_cur IN company_rec LOOP calculate_p_and_l (company_cur.company_id); net_profit_table (tabrow) := net_profit (company_cur. company_id); tabrow := tabrow + 1; END LOOP; The module names say it all: "For each company retrieved from the cursor, calculate the profit and loss and store the net profit for that company in the net profit PL/SQL table." Additional, formal documentation seems unnecessary. Can I say the same thing about the next version of the loop? tabrow := 1; FOR company_cur IN company_rec LOOP calcpl (company_cur.company_id); net_profit_table (tabrow) := np (company_cur. company_id); tabrow := tabrow + 1; END LOOP; The module names in this code are excessively abbreviated. It is unrealistic to expect someone who didn't actually write the calcpl procedure and the np function to understand what those terms are supposed to mean. You should strike a happy medium between the above obscure identifiers and verbose names like these: Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. calculate_profit_and_loss_per_company net_profit_for_single_company Don't forget that you or someone else will have to type those program names! 22.1.2 Develop Consistent Naming Conventions for Your Formal Parameters A parameter plays a specific role within a program; its name should indicate this difference. I recommend that in order to further distinguish parameters from other PL/SQL identifiers you include the parameter mode right in the name of the formal parameter. A parameter has one of three modes: IN, OUT, or IN OUT. An IN parameter passes a value into the module, but its value cannot be modified. An OUT parameter passes a value out of the module, but its value cannot be referenced in the module. An IN OUT parameter can be both referenced and modified in the module. By incorporating the parameter mode directly into the parameter name, its purpose in the module is self-documenting. Whenever you encounter that parameter in the code, you know that you are looking at a parameter, not a local variable, and you know exactly the ways in which that parameter can be used and/or changed. In order for the parameter mode to stand out in the formal parameter name, you can include it either as a suffix or as a prefix. As a suffix, the mode is appended to the end of the parameter name, as follows: PROCEDURE combine_and_format_names (first_name_inout IN OUT VARCHAR2, last_name_inout IN OUT VARCHAR2, full_name_out OUT VARCHAR2, name_format_in IN VARCHAR2 := 'LAST,FIRST'); The prefix standard would result in a parameter list for the above procedure as follows: PROCEDURE combine_and_format_names (inout_first_name IN OUT VARCHAR2, inout_last_name IN OUT VARCHAR2, out_full_name OUT VARCHAR2, in_name_format IN VARCHAR2 := 'LAST,FIRST') ; You could also opt for a minimum of typing by simply using the first letter of each parameter mode: PROCEDURE combine_and_format_names (io_first_name IN OUT VARCHAR2, Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. io_last_name IN OUT VARCHAR2, o_full_name OUT VARCHAR2, i_name_format IN VARCHAR2 := 'LAST,FIRST') ; In any of these cases, a quick glance over the code identifies which objects represent parameters in the program. You can also more easily catch logic errors, such as the following case of an illegal update of an IN parameter: in_name_format := 'FIRST MIDDLE LAST'; A parameter with an "in" suffix or prefix should never have its value changed. A parameter with an "out" suffix or prefix should never be used on the right-hand side of an assignment because its value is indeterminate in the program (until it is given a value). 22.1.3 Name Packages and Their Elements to Reflect the Packaged Structure Given that a package provides a new layer or context over the normal variables and modules, you should take some special care in naming your packages and the elements within them. The name you use for a standalone module, in particular, will change when you move it inside a package. Consider the following example. If I develop a set of standalone procedures and functions to maintain lists of information, I might name them as follows: PROCEDURE list_create (list_in IN VARCHAR2); PROCEDURE list_get (list_in IN VARCHAR2, position_in IN NUMBER, item_out OUT VARCHAR2); and so on. I need to use the "list_" prefix in front of each module name so that the name clearly identifies the structure for the action. Suppose I now move modules like these inside the scope of a package called "list." The specification for the package looks like this: PACKAGE list IS PROCEDURE list_create (list_in IN VARCHAR2); PROCEDURE list_get (list_in IN VARCHAR2, position_in IN NUMBER, item_out OUT VARCHAR2); END list; At first glance this looks reasonable enough. The real test, however, comes when you try to use the modules. Let's try it. To create a list using the package procedure, I would execute a statement like Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. this: list_pkg.list_create ('company_names'); Now, that looks silly. The "list" is mentioned in both the package name and the module name. It didn't make much sense to carry over the standalone module names to the package names. A much cleaner naming scheme should produce executable statements that remove the redundancies: PACKAGE list_pkg IS PROCEDURE create (list_in IN VARCHAR2); PROCEDURE get (list_in IN VARCHAR2, position_in IN NUMBER, item_out OUT VARCHAR2); END list_pkg; list.create ('company_names'); As you define modules in a package, always think of them within the context of the package. If a package is supposed to maintain lists, then the name of the package should reflect that general purpose. The names of the individual modules ought to focus on their particular purpose -- again, within the broader orientation of the package. You might also consider setting as a standard the use of a _pkg suffix for all package names. With this approach, the list package and calls to the package look like this: PACKAGE list_pkg IS PROCEDURE create (list_in IN VARCHAR2); PROCEDURE get (list_in IN VARCHAR2, position_in IN NUMBER, item_out OUT VARCHAR2); END list_pkg; list_pkg.create ('company_names'); Personally, I have mixed feelings about this naming convention. It is important and useful to include information about the type of data in the name of the data, and I certainly encourage such a convention with cursors, records, tables, etc. A package, however, is a different sort of animal. It contains all those other language constructs. The way in which the package name is used to qualify the object already tells you what it is. It seems to me that inclusion of the _pkg suffix (or other indicator) reduces the readability of the code. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. Previous: VI. Making PL/ SQL Programs Work OraclePL/SQL Programming, 2nd Edition Next: 22.2 Build the Most Functional Functions VI. Making PL/SQL Programs Work Book Index 22.2 Build the Most Functional Functions The Oracle Library Navigation Copyright (c) 2000 O'Reilly & Associates. All rights reserved. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. Previous: 22.1 Select Meaningful Module and Parameter Names Chapter 22 Code Design Tips Next: 22.3 Take Full Advantage of Local Modularization 22.2 Build the Most Functional Functions I like functions. They can replace an awful lot of complex logic in an expression with a simple statement of the result of all that logic. The following tips will help you construct functions that are as useful as possible. 22.2.1 Avoid Side Effects in Functions Your function has one purpose in life: returning a single value via the RETURN statement. If it does anything else, such as update global or database information, then that function has potentially created a side effect. Side effects generally limit functions' usefulness. Let's look at a few examples and discuss how you can avoid side effects. 22.2.1.1 Do not use OUT and IN OUT parameters Although a function returns its value with the RETURN statement, PL/SQL allows you to define OUT and IN OUT parameters for a function. If you do that, the function can actually pass changed data back through the parameter itself into the calling block of code through the parameters. This is generally not a good idea, and is a typical side effect in a function. The format_name function below contains side effect parameters. Let's examine the impact of these parameters. The function takes a first name and a last name and returns a full name with the format LAST, FIRST. Because a requirement of the application happens to be that all names must be in uppercase, the function also converts the first and last names to their uppercase versions. It uses two IN OUT parameters to do this. FUNCTION format_name (first_name_inout IN OUT VARCHAR2, last_name_inout IN OUT VARCHAR2) RETURN VARCHAR2 IS BEGIN first_name_inout := UPPER (first_name_inout); Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. last_name_inout := UPPER (last_name_inout); RETURN last_name_inout || ', ' || first_name_inout; END; If the application requires uppercase names, you may wonder, then, what is wrong with converting the first and last names to uppercase in format_name? First of all, this approach may be trying to meet the requirement a bit too enthusiastically. If the formatted name -- the output, that is, of the function -- must be in uppercase, you can certainly accomplish that without modifying the parameters, as follows: FUNCTION format_name (first_name_in IN VARCHAR2, last_name_in IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN UPPER (last_name_in || ', ' || first_name_in); END; The second reason you would not want to automatically uppercase the name components is that you will make format_name less useful as a basic utility. Your current application may insist on uppercase, but there will be plenty of other applications that maintain entities having first and last names -- and they do not have a similar requirement. If you insist on the IN OUT parameters, the format_name utility could not be used in these other application development efforts. 22.2.1.2 Switch to a procedure with IN OUT parameters If you do want a module that will convert both individual name components and the combined name to uppercase, then you would be better served with a procedure: PROCEDURE combine_and_format_names (first_name_inout IN OUT VARCHAR2, last_name_inout IN OUT VARCHAR2, full_name_out OUT VARCHAR2) IS BEGIN first_name_inout := UPPER (first_name_inout); last_name_inout := UPPER (last_name_inout); full_name_out := last_name_inout || ', ' || first_name_inout; END; By using a procedure, you make it clearer that the module is applying changes to all the parameters. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. You avoid providing a function with side effects (i.e., other actions and impact beyond the return of a value). Let's take a look at the way these different modules would appear in your code. ● Let's first look at the function version. All our attention is focused on depositing the formatted name into the caller_name item: full_name := format_name (first_name, :last_name); ● Now let's look at the procedure version. The use of the plural of "name" in the name of the procedure indicates a change to more than one of the parameters: combine_and_format_names (first_name, last_name, full_name); Generally speaking, when your function has a side effect such as the uppercasing of the parameter values, that function is less broadly useful. What if programmers don't want to have the other values uppercased? They would have to write their own function that does the same thing as format_name, but without the UPPER statements. This code redundancy will create nightmares down the road. If the function is supposed to return a formatted name based on the first and last names, then that is all it should do -- never use OUT and IN OUT parameters with a function. 22.2.1.3 Don't interact with users Such parameters are not the only kinds of side effects you may be tempted to slip into a function. Consider this Oracle Forms function, which retrieves the name of a company from its primary key: FUNCTION company_name (company_id_in IN company.company_id %TYPE) RETURN VARCHAR2 IS CURSOR name_cur IS SELECT name FROM company WHERE company_id = company_id_in; name_rec name_cur%ROWTYPE; BEGIN OPEN name_cur; FETCH name_cur INTO name_rec; IF name_cur%FOUND THEN CLOSE name_cur; RETURN name_rec.name; ELSE BELL; MESSAGE ('Invalid company ID: ' || TO_CHAR (company_id_in), Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ACKNOWLEDGE); RAISE FORM_TRIGGER_FAILURE; END IF; END; When the company ID returns an existing company, the function properly closes the cursor and returns the company's name. When the SELECT statement comes up empty, however, I change my approach completely. Rather than return a value, I go into the equivalent of PL/SQL panic mode: ring the bell, display a message (and force the user to acknowledge that message), and then fail out of the calling trigger. I don't even close the cursor! Rather strong stuff, and very much out of place in this function. My response to invalid data is a side effect in the function, made all the worse by the fact that my side effect completely substitutes for a RETURN. When the company ID does not find a match, I do not execute a RETURN statement at all. The consequences of my error handling in this function are: ● This function can be called only in Oracle Forms. It is, however, a fairly generic task: take a primary key and return a name/description. It could be a function stored in the database and available to any program that needs to perform the lookup. I have now made that impossible. ● Anyone who calls this function gives up control over his or her own program. You cannot decide for yourself what to do if the name is not found. That message is displayed whether or not it is appropriate. That bell sounds even if users don't like to announce to everyone around them that they made a mistake. The best you can do is code an exception handler for FORM_TRIGGER_FAILURE in whatever trigger or program calls company_name. A much better approach to the company_name function is simply to return a NULL value if the company is not found. Since the company name is a NOT NULL column, a NULL return value clearly indicates "no data found." This new approach is shown in the following example: FUNCTION company_name (company_id_in IN company.company_id %TYPE) RETURN VARCHAR2 IS CURSOR name_cur IS SELECT name FROM company WHERE company_id = company_id_in; name_rec name_cur%ROWTYPE; no_company_found EXCEPTION; BEGIN OPEN name_cur; FETCH name_cur INTO name_rec; IF name_cur%FOUND Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. THEN CLOSE name_cur; RETURN name_rec.name; ELSE RAISE no_company_found; END IF; EXCEPTION WHEN no_company_found OR OTHERS THEN CLOSE name_cur; RETURN NULL; END; This is now a nonjudgmental, mind-your-own-business function. It doesn't try to dictate the terms of surrender, or tell programmers who call it how they should handle a lack of data. It simply reports back the problem by returning a NULL value. Users of the function may then decide for themselves on an appropriate consequence, which might well consist of every action formerly embedded in the function itself: new_company := company_name (:company.company_id); IF new_company IS NULL THEN BELL; MESSAGE ('Invalid company ID: ' || TO_CHAR (:company. company_id), ACKNOWLEDGE); RAISE FORM_TRIGGER_FAILURE; END IF; At least now the programmer has freedom of choice. Side effects in functions come in many shapes and flavors. If you remember to keep the focus of the function on computing and returning its value, the resulting module will be more effectively and widely used. 22.2.2 Use a Single RETURN Statement for Successful Termination A function exists to return a single value. The best way to structurally emphasize this single-minded focus in the body of the function is to make sure that the only RETURN statement in the execution section of the function is the last executable statement. Consider the following code-translating function: FUNCTION status_desc (status_cd_in IN VARCHAR2) RETURN VARCHAR2 Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. [...]... assert_condition is similar to the way Oracle Forms handles level 25 errors when it executes a form No matter how high a value you have set for SYSTEM.MESSAGE_LEVEL, Oracle Forms always halts processing in a form when it encounters a level 25 error, which indicates a design error in the application Previous: 22.1 Select Meaningful Module and Parameter Names OraclePL/SQL Programming, 2nd Edition Next:... function The exception section is the way out of a module that has hit an error and raised an exception A programming language like PL/SQL is constructed very carefully: every keyword and element of syntax is chosen with a specific purpose Although you can at times justify using a language construct in an unorthodox way, in most cases such an action will raise a red flag and be closely examined One... better bundled into a local module Previous: 22.2 Build the Most Functional Functions 22.2 Build the Most Functional Functions OraclePL/SQL Programming, 2nd Edition Book Index Next: 22.4 Be Wary of Modules Without Any Parameters 22.4 Be Wary of Modules Without Any Parameters The Oracle Library Navigation Copyright (c) 2000 O'Reilly & Associates All rights reserved Please purchase PDF Split-Merge on www.verypdf.com... the parameter list of the module Previous: 22.3 Take Full Advantage of Local Modularization 22.3 Take Full Advantage of Local Modularization OraclePL/SQL Programming, 2nd Edition Book Index Next: 22.5 Create Independent Modules 22.5 Create Independent Modules The Oracle Library Navigation Copyright (c) 2000 O'Reilly & Associates All rights reserved Please purchase PDF Split-Merge on www.verypdf.com to... use of them to a minimum Previous: 22.4 Be Wary of Modules Without Any Parameters 22.4 Be Wary of Modules Without Any Parameters OraclePL/SQL Programming, 2nd Edition Book Index Next: 22.6 Construct Abstract Data Types (ADTs) 22.6 Construct Abstract Data Types (ADTs) The Oracle Library Navigation Copyright (c) 2000 O'Reilly & Associates All rights reserved Please purchase PDF Split-Merge on www.verypdf.com... Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark /* Raise an exception specific to Oracle Forms */ RAISE FORM_TRIGGER_FAILURE; END IF; END; You can even produce assertion modules that handle very specific kinds of conditions If you do a lot of work with record groups in Oracle Forms, you find yourself checking repeatedly at the beginning of your program to see that the name or... is always only one entry point for a module: the first executable statement is always the one that follows the BEGIN statement PL/SQL does not allow you to enter a block at an arbitrary line of code The same cannot be said, however, for the way in which a module terminates A PL/SQL program unit may complete its execution either through the last executable statement, a RETURN statement, or the exception... module in which it is declared It is invisible to all other modules, and can be called only from within that defining module Few PL/SQL developers are aware of the local module feature, and fewer yet take full advantage of this capability Yet I can think of few other aspects of the language that are more important to constructing clean, elegant, easily maintained programs I strongly encourage you to use... Modularization Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark 22.1 Select Meaningful Module and Parameter Names Book Index 22.3 Take Full Advantage of Local Modularization The Oracle Library Navigation Copyright (c) 2000 O'Reilly & Associates All rights reserved Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Previous: 22.2 Build the Most Functional... (raise an exception) when necessary 22.2.4.2 An application-specific assertion module Of course, you can build less generic versions of assert_condition The next function shows an assertion module for Oracle Forms that displays an optional message to the screen: PROCEDURE assert_condition (condition_in IN BOOLEAN, message_in IN VARCHAR2 := NULL) IS BEGIN IF NOT condition_in THEN /* Use NVL to substitute . Making PL/ SQL Programs Work Oracle PL/SQL Programming, 2nd Edition Next: 22.2 Build the Most Functional Functions VI. Making PL/SQL Programs Work Book Index. module that has hit an error and raised an exception. A programming language like PL/SQL is constructed very carefully: every keyword and element of syntax