⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 3sharing.html

📁 C ++ in action
💻 HTML
📖 第 1 页 / 共 2 页
字号:
<HTML>
<HEAD>

<TITLE>Sharing</TITLE>
<link rel="stylesheet" href="../rs.css">
    <meta  name="description" content="Sharing global information">
    <meta name="keywords" content="design, implementation, template, global">
</HEAD>
<BODY background="../images/margin.gif" bgcolor="#ffffe0">

<!-- Main Table -->
<table cellpadding="6">
    <tr>
    <td width="78">&nbsp;</td>
    <td>

<h2>Code Review 3: Sharing</h2>
<p>

</td></tr>
<tr>
<td class=margin valign=top>
<a href="source/calc2.zip">
<img src="../images/brace.gif" width=16 height=16 border=1 alt="Download!"><br>source</a>
</td>
<td>

<P>Have you noticed how we oscillate between global and local perspective in our code reviews? Whenever we talk about sharing, we take the more global stance. Whenever we talk about data or implementation hiding, we take a local stance. It's time to swing the pendulum towards sharing.

<h3>Isolating Global Program Parameters</h3>

<P>From the global perspective some of the constants that we've been so busily burying inside local scopes are actually tunable parameters of our program. What if somebody (say, our client) wants to increase the maximum length of symbolic names? Or what if he wants to be able to add more entries to the symbol table? Or desperately needs to increase the size of the input buffer? We'll have to dig deep into our classes and functions to find the appropriate constants.
<P>Fortunately, there are several ways to make such fine tuning less cumbersome. I'll only show the most basic one-collecting all tunable constants in a single file <span class="file">params.h</span>. Here's the contents of this file

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>const int maxBuf = 100;        // size of input buffer
const int maxSymbols = 40;     // size of symbol table
const int maxSymLen = 80;      // max length of symbol name</pre>
    </td></tr>
</table>
<!-- End Code -->

<P>There will always be some tunable parameters in your program whose change will require recompilation. Their place is in <span class="file">params.h</span>. 
<P>There usually is another set of parameters that should be tunable by the user (or the administrator). These are called user preferences and are stored persistently in some convenient location. Even these parameters have their default values whose place is, you guessed it, in <span class="file">params.h</span>.

<h3>Testing Boundary Conditions</h3>

<p>We know that our program has built-in limitations: see <span class="file">params.h</span>. Obviously we want to make these limitations as unobtrusive as possible. We generously allocate space for symbols, characters in buffers, etc., so that in day-to-day operation the user doesn't hit these limits. Unfortunately, that also means that we, the programmers, are unlikely to hit these limitations in our testing. And rest assured--whatever boundaries we don't hit, our users will!

<p>So let's make a little experiment. Let's edit the file <span class="file">params.h</span> and set all the limits to some relatively low values. We might even keep two sets of parameters--one for regular operation and one for testing. I used conditional compilation directives to switch between the two sets. Changing 0 to 1 in the <var>#if</var> directive will switch me back to the regular set.

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre><b>#if 0</b>
const int maxBuf = 100;     // size of input buffer
const int maxSymbols = 40;  // size of symbol table
const int maxSymLen = 80;   // max length of symbol name
<b>#else</b>
const int maxBuf = 8;     // size of input buffer
const int maxSymbols = 5;  // size of symbol table
const int maxSymLen = 4;   // max length of symbol name
<b>#endif</b></pre>
    </td></tr>
</table>
<!-- End Code -->

<p>After recompiling the whole program, I ran it and immediately hit an assertion in the constructor of the <var>Function::Table</var>. Quick inspection showed that the program was trying to cram 14 symbols for built-in functions into our symbol table, whose maximum capacity was set to 5. That's good! Obviously the value of 5 for the size of the symbol table was too small, but we were able to catch it immediately.
<p>Is an assertion good enough protection from this kind of a problem? In general, yes. This problem might only arise when somebody changes the value of <var>maxSymLen</var> or ads more built-in functions. We are safe in that case, because:

<!-- Definition -->
<p>
<table border=4 cellpadding=10><tr>
    <td bgcolor="#ffffff" class=defTable>
After every code change the program will be re-built in the debugging version (with the assertions turned on) and run at least once before it's released to others.     </td></tr>
</table>
<!-- End Definition -->
<p>By &quot;others&quot; I don't mean customers--I mean other programmers on the same team. Before releasing a program to customers, much stricter testing is required.

<p>So let's now set the size of the symbol table to 14--just enough for built-in functions--and try to enter an expression that introduces a new symbolic variable. Kaboom! We get a general protection fault!

<p>What happened? In <var>Parser::Factor</var> we correctly discovered a problem, even printed out "Error: Too many variables," and then returned a null pointer. Well, what else were we supposed to do?! Unfortunately, the caller of <var>Parser::Factor</var> never expected a null pointer. Back to the drawing board! 

<p>Let's add checks for a null pointer to all the callers of <var>Parser::Factor</var> (as well as the callers of <var>Parser::Term</var>, who can propagate the null obtained from <var>Parser::Factor</var>).

<p>One more test--this time everything works fine; that is until we exit the program. Remember, all this time we are running a debug build of our program under the debugger. A good debug runtime will do a heap check when your program frees memory. A heap check should discover such problems as buffer overflows, double deletions, etc. And indeed it does! The destructor of <var>Store</var> reports an overwritten memory block. A little more sleuthing and the culprit becomes obvious. Look at this code in the constructor of <var>Store</var>:

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>int id = symTab.ForceAdd ("e");
SetValue (id, exp (1));
cerr &lt;&lt; "pi = " &lt;&lt; 2 * acos (0.0) &lt;&lt; endl;
id = symTab.ForceAdd ("pi");
SetValue (id, 2 * acos (0.0));</pre>
    </td></tr>
</table>
<!-- End Code -->
<p>When our symbol table overflows, it returns -1. <var>SymbolTable::SetValue</var> is called with -1 and happily obliges.

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>void SetValue (int id, double val)
{
    assert (id &lt; _size);
    _cell [id] = val;
    _status [id] = stInit;
}</pre>
    </td></tr>
</table>
<!-- End Code -->

We were smart enough to assert that <var>id</var> be less than the size of the table, but not smart enough to guard against a negative value of <var>id</var>.
<p>Okay, let's change that and try again. This time we hit the assertion immediately. Great! One more test with <var>maxSymbols = 16</var> and we're in the open.

<p>Now let's test the restriction on the size of the input buffer. Let's input a large expression, something like one with ten zeros. The program works, but not exactly as the user would expect. It first prints the incorrect result, then exits.

<p>First of all, why does it exit? It turns out that, since it didn't retrieve all the characters from the input on the first try, the next time it calls <var>cin.getline</var> it returns immediately with an empty buffer (I'm not sure why it's empty). But that's not our biggest problem. A program that quietly returns incorrect results is much worse than the one that crashes. Incorrect information is worse than no information at all. We have to fix it immediately.

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>char buf [maxBuf+1];
// ...
cin.getline (buf, maxBuf+1);
if (strlen (buf) == maxBuf)
{
    cerr &lt;&lt; "Error: Input buffer overflow\n";
    status = stError;
    break;
}</pre>
    </td></tr>
</table>
<!-- End Code -->

<p>Notice what I have done. I increased the size of the buffer by one, so that I could grab one character more than the self-imposed limit. Now I can detect if my greedy request was satisfied and, if so, I know that the input was larger than <var>maxBuf</var> and I bail out.

<p>Now for the last test--maximum symbol length. Before we try that, we have to increase <var>maxSymbols</var>, so that we can enter a few test symbol into our program. What we discover is that, although the program works, it quietly lies to the user. Try inputing and expression <var>toolong = 6</var> and then displaying the value of another variable <var>tool</var>. Instead of complaining about the use of unitialized variable, the program quietly treats both names as representing the same variable. The least we can do is to display a warning in <var>Scanner::Accept</var>.

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>if (_lenSymbol &gt; maxSymLen)
{
    cerr &lt;&lt; "Warning: Variable name truncated\n";
    _lenSymbol = maxSymLen;
}</pre>
    </td></tr>
</table>
<!-- End Code -->

<p>Scary, isn't it? Why was this program so buggy? Was this the norm or an exception? The truth is that, no matter how good you are, your programs will contain errors. The compiler will catch most of the simple errors--typos, type mismatches, wrong number of arguments, etc. Many errors will become obvious after some elementary testing. The rest are the &quot;hard bugs&quot;.

<p>There are many techniques to eradicate hard bugs. One is testing--in particular you can make testing easier by instrumenting your program--writing code whose only purpose is to catch bugs. We've done something like that by instrumenting the file <span class="file">params.h</span>. I'll show you more testing and instrumentation techniques later.
<p>There is also the matter of attitude. I wrote this program without much concern for error checking and exceptional conditions. Normally I would be much more careful.
<p>Finally, there is the matter of policy. Dealing with exceptional conditions is not something you do on a whim. You have to develop some rules. We'll talk about it some more.

<h3>Templates</h3>

<P>We have put the class <var>List</var> together with its sequencer in a standalone file with code reuse in mind. Any time in the future we need a linked list of integers we can just include this well tested and solid code. Did I just say &quot;a linked list of integers&quot;? What if we need a list of unsigned longs or doubles or even <var>Star</var>s for that matter? We need a class that is parametrized by a type. Enter templates.

<P>To turn <var>List</var> into a template, you need to preface its class definition with 

<!-- Code -->
<table width="100%" cellspacing=10><tr>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -