Implementing OverloadCheck: The Construction Phase (Continued)
Implementing OverloadCheck: The Construction Phase (Continued)
By Steven Feuerstein
Back to Part 1
Show Me the Beef?
By this point, you may be losing patience with me. Why yet another program? When are you going to see some real code? I understand your reaction. But let me ask you: Has it been difficult to follow the logic (now consisting of more than 100 lines of code) that I've presented so far? Or has it seemed so transparent—so simplistic, in fact—that you're impatient to move on?
If the latter is the case, then consider how many times you've experienced a similar feeling when looking at someone else's code for the very first time (or your own for the second time)? Isn't it much more common to read a person's code and experience bafflement and even resentment at the program's incomprehensibility? So please stick with me. Put aside your frustration at the multiple levels of indirection or encapsulation. Notice the transparency of the application code, and you'll soon enough see some satisfyingly complicated logic.
Checking for Similarity
The check_for_similarity procedure is the last or lowest level of detail that OverloadCheck needs in order to perform the ambiguity analysis. It accepts as its parameters the information it needs to identify two overloadings and a specific subset of the parameter list of each overloading. You can describe this as check overloading 1 called with its first three parameters against overloading 2 called with all of its six parameters. If it finds that the two overloadings are ambiguous, check_for_similarity reports this to cc_report.
How do you know if there's an ambiguity? Let's think through some of the required logic:
* If the number of arguments in overloading 1's invocation is different from that of overloading 2, then they're not ambiguous.
* If neither of the invocations have any arguments, then they're definitely too similar.
* Otherwise, I need to compare the datatype of each argument in overloading 1 with its corresponding (by position) argument in overloading 2. If every comparison yields a too-similar answer, then the overloadings are ambiguous.
That should give me enough detail to begin implementation of the procedure (see Listing 5). I describe the code in Table 4.
That's all I need to write (so it would seem) in the overloadcheck.overloadings procedure to perform the analysis. Can I now run this program to see if it works? Dream on. All I have done is essentially brainstorm (using the top-down design methodology) how to get overloadcheck.overloadings to work.
I have fully specified what I need at the next level of refinement of my logic. In the next article in this series, I'll move down to the next level of detail (in programming circles, this is called stepwise refinement) and implement each of the programs in cc_arguments and cc_smartargs that I identified in OverloadCheck.
Steven Feuerstein ( email@example.com) is one of the world's leading experts on Oracle PL/SQL. He is the author or co-author of nine books on PL/SQL, including Oracle PL/SQL Programming, 3rd Edition and Oracle PL/SQL Best Practices (O'Reilly, oracle.oreilly.com). Steven is a senior technology advisor for Quest Software, has been developing software since 1980, and worked for Oracle from 1987 to 1992. He is also past president of the board of directors of the Crossroads Fund, which makes grants to Chicago-area community groups working for social, racial, and economic justice (www.CrossroadsFund.org).
Roadmap for Building a Code-Analysis Utility
Steven Feuerstein, Oracle PS/SQL language expert, shows each step in the process of creating OverloadCheck—a utility that checks the quality of PL/SQL code. In a series of eight articles, he takes you from the beginning of the process (defining the problem he wants his utility to solve), to researching, designing, and refining this new creation until it truly works. As you watch the quality-assurance utility come to life, you'll also get exposure to some of PL/SQL's newest features and see how using best practices can benefit any coding project.
Article 1: Building a Code-Analysis Utility and Doing It Right the First Time
The first step shows what's involved in defining the goal of this utility, how to perform required analysis, and how to translate the results of that analysis into a useful form for developers.
Article 2. Getting Started, Starting with Testing
Although it would be tempting to start writing code at this stage in the process, the author shows the importance of waiting while he decides how to test his code.
Article 3. Creating a High-Level Design
In this article, see how to conceive a basic but workable architecture, while avoiding over designing.
Article 4. Implementing OverloadCheck: The Construction Phase
While showing how to get started writing code, Feuerstein demonstrates how to ensure code readability and how to minimize the number of bugs.
Article 5. Adding Smarts to the Argument Information
This article tackles some of the most complex logic involved in analyzing potential overloading ambiguities. It also shows how important it is to hide the complexity of such structures as multilevel and string-indexed collections behind procedures and functions.
Article 6. Crafting 'Service Providers': Packages with Focused Functionality
Learn by example how to identify the distinct services that relatively small units offer to higher-level packages, and how to consolidate everything related to those services.
Article 7. Crafting Service Providers: Creating a Versatile Reporting Package
Find out how to streamline code with the dynamic WHERE clause and how to put a user-friendly wrapper around unwieldy-but-useful procedures (such as DBMS_UTILITY.NAME_RESOLVE and DBMS_OUTPUT.PUT_LINE) as you explore the creation of OverloadCheck's reporting package.
Article 8. Getting it Right the Second Time
In the final stage, Feuerstein uses a test engine and his predefined test cases to do some testing. He also shows how to use the process of refactoring to fix problems and improve the inner workings of the program.
Table 1: Thinking process for the definition of elements
2 Call the initialization program. What does it do? I'll return to this later, because it's the next level of detail and not relevant here.
3 I need to loop through all the distinct program names in the package. How do I know what those are? That information is available in ALL_ARGUMENTS, as in:
SELECT DISTINCT object_name FROM all_arguments
WHERE owner = and package_name =
But I have already decided that all such argument information will be handled in cc_arguments or perhaps cc_smartargs. In fact, because cc_arguments is supposed to do little more than reflect the contents of ALL_ARGUMENTS, I would say that the list of distinct programs is a "smart" addition and so it belongs in cc_smartargs. This leads me right away to think in terms of an API or set of programs that gives me the information about this set of distinct names: the first, the next, the last. So, line 4 grabs the first name.
5-6 Start up the loop and indicate how to terminate it (when I've gone through all distinct names, the program name will be NULL).
8 Does this name reference an overloaded program in the package? Only cc_smartargs can tell me, and so I define another function in the API: the Boolean has_overloadings.
10-13 Now I need to look at each overloading for this program. It looks like once again the information I need comes from data derived from ALL_ARGUMENTS, so I will rely on new programs in cc_smartargs to define the boundaries: from the first to the last overloadings for the current program.
14 I need to check each overloading I find against the other overloadings to see if there are any ambiguous or too-similar invocations possible. This seems like logic that belongs in the overloadcheck.overloadings procedure, so I just put a stub in here for a procedure check_against_other_overloadings, local to overloadings.
18 When I'm done with this program, I move on to the next, again relying on a function from cc_smartargs.
21 Time to show the results. I define the header for show_results, the first procedure in the cc_report package. I definitely need to pass it the program name, but I'm not sure at this point what else I may need. I'll cross that bridge when I get to it.
Table 2: Description of the implementation of same_program_types
1 Although you cannot tell from Listing 3 , unlike initialize and compare_with_others, this function is not a local module defined within overloadings. I have chosen to define it at the same level as overloadings within the package. I did this because it seems that the content of compare_with_others is specific to the logical requirements of overloadings. Another high-level check routine in OverloadCheck may well want to know if two overloadings are of the same program type.
8-11 How do I ascertain the type of a particular overloading? You may not be too surprised that I can and should get that information from cc_smartargs. It has the list of overloadings, so it should also keep track of information about each overloading. Whew. It's becoming rapidly apparent that cc_smartargs is going to be one tough package to implement. Hopefully, if I take it one layer at a time, it won't be too bad.
13-17 The core logic of same_program_types is a straightforward Boolean expression. The types are the same if they are both functions, or they are both "not functions" (procedures).
Table 3: The logic behind compare_all_invocations
2-4 The parameters. I have one overloading (check_this_in) that I check against another overloading (against_this_in). An overloading is identified simply by an integer, the Nth overloading for the specified program name.
9-16 The outer loop of my comparison logic. I use a label to give a name to this (and the inner) loop, to improve readability. Once again, I rely on cc_smartargs to retrieve the first and last invocations for the "check this" overloading. How are these invocations defined? I don't know, and for now, I don't care. I simply know that there are N valid invocations and that cc_smartargs will guide me through them.
8-25 The inner loop, which takes us through the "against this" invocations.
26-32 Finally, the "action"—the body of the nested loops. And here I discover that I simply call another procedure—check_for_similarity. I pass to this program all the information necessary for it to identify the two invocations for comparison.
Table 4: Description of the check_for_similarity implementation.
1-4 The header and parameter list. The overload1_in and overload2_in arguments refer to a specific overloading of program_in. perm1_in and perm2_in are the specific invocations of the parameters lists for each of the corresponding overloadings.
7-13 In order to compare each pair of arguments in the two overloadings, I need to get the list of "top level" parameter information (those rows in ALL_ARGUMENTS that actually appear in the program's parameter list) from cc_smartargs. But what I will retrieve is a list of parameters as defined by cc_arguments. So, I declare two local variables that are actually collections, or lists of parameters, defined in cc_arguments. But I call a function in cc_smartargs called parameter_list that returns just the information I need—namely, the 1st. through Nth argument as dictated by this invocation.
15-16 Here I grab the number of rows in each of these parameter lists and assign them to local variables.
21-27 The simple part of this procedure: I look at the numbers of arguments in each parameter list and decide if I am done (clearly dissimilar or clearly similar—no arguments).
29-39 Time to compare the corresponding parameters in each of the lists. I use a simple loop to step through each position in the list. I'll stop when I run out of parameters in the first overloading or when I discover that there's sufficient difference between the overloadings to stop checking. In the body of the loop, I call a function in cc_arguments to find out if the datatypes of these two arguments are in the "same family." Line 38 moves me along to the next parameter in the list.
42-49 If the two overloadings are too similar, it's time to use cc_report to keep track of this fact. I'll need a procedure to report on an ambiguous overloading—and I'll need to pass it all the information required to identify these two particular invocations: the name of the program, the overloading sequence, and the ending argument position.