COMP 3430 – Operating Systems
COMP 3430 – Operating Systems
Copyright By PowCoder代写 加微信 powcoder
2008/2010, 2017, 2021
Whitespace
Exit Points
breaks and continues
switch Statements
return Statements
Exceptions to the rules
Functional Decomposition
Commenting
The most important part of being a good programmer isn’t getting the code to work, that just takes hard work and practice. There is, however, an art to making code readable by you, your peers, and your instructor. As your programs grow in complexity and size this becomes more and more important. The following are practices that we’ve picked up over time. They don’t make your programs work but they do make them easier to read, which helps make it easier for you to make your programs work.
The goal of this document is to instill best practices as early as possible. Bad habits are easy to pick up and the hardest to overcome. Taking these practices to heart now will help you become a better programmer and reduce the stress and strain of having to change when you enter the workforce.
People tend to think that giving a variable a shorter name makes it easier to code; there’s less to type so you can code faster. Here are two examples of implementing the multiplication of two fractions:
class Fraction
int numerator;
int denominator;
Fraction fraction1;
Fraction fraction2;
Fraction result;
result.numerator = fraction1.numerator *
fraction2.numerator;
result.denominator = fraction1.denominator *
fraction2.denominator;
F f1, f2, r;
r.a = f1.a * f2.a;
r.b = f1.b * f2.b;
If this were a long program with many functions manipulating the fraction data it would become very easy to get confused as to what a and b represent.
Note that it is better to declare one variable per line. This makes it easier to tell what variables you have declared without having to read across a single line. It also makes it easier and more logical to initialize variables as part of the declaration.
Finally, always declare all of the variables used within a function at the beginning of the function (often called the variable dictionary). It is bad practice to declare variables midstream for a number of reasons:
When looking at the code later it’s hard to determine all variables and their types without scanning the entire function.
You have the potential for scoping problems since a variable may mask or shadow another variable with the same name (declared earlier) resulting in potentially erroneous accesses to variables.
is free so don’t be afraid to use it. When memory was a scarce commodity it made sense to make your programs smaller by not using spaces. Now a few extra bytes don’t matter and make your code a lot easier to read.
Spacing portions of a statement makes the code easier to read if done wisely. Logical blocks of code within a function should be separated with a blank line to indicate that a group of instructions work together to get something done. This also holds true for the variable dictionary; place a blank line between your variable declarations and the rest of your code. These practices are illustrated in the first example below.
If there is only one statement following a control statement (such as an if or case) do not put both statements on a single line. As well as making the code hard to follow (when scanning it looks like you forgot the statement), doing so makes it hard to add statements at a later date. This practice is illustrated in the second example below.
The indenting of your code is fundamental to making it readable. Any code within a set of braces or to be executed as part of a control statement must be indented. This is done to show that the indented statements belong to a specific block and are executed as a group (e.g. based on the results of the control statement’s execution). The level of indentation should be 2 or 4 spaces, less makes it hard to tell that there is any indentation and more makes the code too offset from the containing block. This practice is illustrated in the third example below.
As an initial example, consider the fraction code above. Note the use of spaces around the assignment and multiplication operators and how the operands are aligned after the assignment operator. While not always necessary, it is often useful to break a statement across multiple lines so that it is easily viewable. This is often done for complex if statements where each logical expression is on an individual line and aligned to indicate that the belong to the same statement (with the logical operator at the end of each line).
Here are some more examples:
fraction sum;
fraction right;
fraction left;
long divisor;
// give them common denominators
left.numerator *= right.denominator;
right.numerator *= left.denominator;
sum.denominator = right.denominator * left.denominator;
// add them
sum.numerator = left.numerator + right.numerator;
// reduce the result
divisor = gcd( sum.numerator, sum.denominator );
sum.numerator /= divisor;
sum.denominator /= divisor;
fraction sum;
fraction right;
fraction left;
long divisor;
left.numerator*=right.denominator;
right.numerator*=left.denominator;
sum.denominator=right.denominator*left.denominator;
sum.numerator=left.numerator+right.numerator;
divisor=gcd(sum.numerator,sum.denominator);
sum.numerator/=divisor;
sum.denominator/=divisor;
if ( value == 0 )
result = true;
if (denominator == 0 )
denominator = 1;
if ( value == 0 ) result = true;
if (denominator == 0 ) denominator = 1;
done = false;
x = getNextValue();
while ( !done )
processX( x );
x = getNextValue();
if ( x >= sentinal )
done = true;
done = false;
x = getNextValue();
while ( !done )
processX( x );
x = getNextValue();
if ( x >= sentinal )
done = true;
TIP: Use a code formatting too, like clang-format to automatically format your code!
Exit Points
It’s imperative that any block of code only have a single exit point. For something like a for or while loop this done through the conditional test at the top of the loop. For a method (which is just another block of code) this is done through the return statement. There are a number of methods that can be used to short circuit this standard flow (each of which is equivalent to the use of a goto). The following two sections discuss these issues.
breaks and continues
If you’ve never seen these statements I’ll simply say – don’t use them! To skip some amount of code and go to the next iteration of a loop or to jump out of the loop is simply adding “advanced” goto statements. Constructs such as for and while were defined so we wouldn’t have to use gotos. I don’t normally say things like this, but if you use either of these in your assignments (obviously excluding the switch statement), labs or the exam you will lose marks.
As an example, consider the following code with a break and a goto. Is there any difference?
for ( i=0 ; i < LIMIT ; i++ ) if ( found ) for ( i=0 ; i < LIMIT ; i++ ) if ( found ) goto exit_for; How should this code be written? For starters, you should be using a while that terminates on found being set to true. switch Statements switch statements are the one exception to the rule about using break statements. If several cases have identical behaviours, then fall-through is permitted, but cases that have similar behaviours with partially shared code relying on fall-through is not. Always terminate each group of statements with a break, even the last one. For example, the following are two similar (though not identical) situations: switch (command) switch (command) printf("adding 1\n"); printf("subtracting 1\n"); The “bad” example should be broken up into four separate cases, each terminating with a break. If there is a substantial amount of code shared between cases, either move it to a routine, or rearrange the logic. return Statements A common error is to use return statements anywhere in a function; if you’re done what needs to be done then exit the function. The problem with this is that you interrupt the logical flow of the function and jump to the end of the function, which is equivalent to a goto statement. The reading of complex functions becomes quite difficult if they can exit at any point in their code (i.e. they no longer have a logical flow). Instead, reorganize your code such that there is only one return statement and it is the last statement of the function. Instead of exiting when a condition is reached, set a variable that can be checked and used as the return value. Here is an example: boolean isNegative( int value ) boolean isNegative = false; if ( value < 0 ) isNegative = true; return isNegative; boolean isNegative( int value ) if ( value < 0 ) return true; return false; Obviously, this is a simple example but in functions that are tens or hundreds of lines long it becomes a real problem. It’s also worth noting that the function with a single return statement will execute more efficiently. Exceptions to the rules Note that recursion is a special case. While it is possible to rearrange a recursive function to only have one exit point it tends to make the code unwieldy. Error handling In circumstances where code is significantly simpler to read in terms of bailing out due to error states (e.g., early exit when open fails) is acceptable: int main(void) int fd = open("This file doesn't exist", O_RDONLY ); if ( fd == -1 ) printf("Couldn't open file.\n"); exit(EXIT_FAILURE); // proceed with the understanding that `open` returned // a valid file handle. read( fd, &buffer, 10 ); return EXIT_SUCCESS; There’s nothing that can make a programmer’s blood boil like a discussion on where to put your opening braces. I find it funny that a generation brought up with Pascal and the placement of the begin statement on the line following a control statement is almost 100% against the practice when it comes to braces. While I will not say that it is the only way, placing an opening brace on a line by itself (i.e., not at the end of an if, while, for statement) aids readability. What this lets you do is align the opening and closing braces at the same level of indentation. This in turn makes it easy to match the braces and verify your logical blocks. Placing the brace on the same line as a control statement requires more effort in identifying your blocks of code (which becomes more difficult as your code increases in complexity) and makes it more likely that you will forgot to include the closing brace (I’ve seen this error in textbook code examples!). Some people argue that you should put the opening brace after the control statement as a reminder that you shouldn’t put a semi-colon there. The fact is, this is a fundamental rule within the language’s syntax and people need to understand the difference between statements and control structures. All of the examples in this document make use of this practice and can be viewed as examples of how this should be done. An example of really bad brace placement is the following (again, the problem becomes more apparent with more complex code): if ( found ) { processData() getMoreData() Functional Decomposition This is simple and straightforward, when done properly. Every method you write should have a specific purpose and shouldn’t deviate from that purpose. For example, if you have a method that computes the circumference of a circle then that’s all it should do. The method has no “right” to do anything else (e.g. changing the diameter before computing the circumference, or printing the circumference of the circle). This concept extends to the development of complex applications. If you are asked to implement a program that must perform a number of tasks, start by making the tasks methods. This is the first step toward good software design. It prepares you for the design and implementation of complex classes (and Abstract Data Types) used by other programs. Commenting Well-written code should tell the story of what it’s doing. Choosing appropriate control structures, using the simplest possible logic, and following the previously discussed practices can help. But when code isn’t completely self-explanatory, comments can fill in the rest of the story. One principle to remember for writing good comments is that your comments should primarily answer the question why, rather than how. The answer to “how?” should already be answered by your code; for example, rather than writing /* this is a quicksort */, move the sort code to a function called quicksort(). Use comments to justify non-obvious decisions, explain your intent, or in rare cases, to overtly describe your method (if it is a novel or highly-optimized solution to a problem, e.g., Duff’s device or Fast InvSqrt()). Some variable declarations require comments. One example of a vital fact about a variable that may not be mentioned in its name is its unit (e.g. is the value in a variable timer measuring milliseconds or hours?). You can also comment about ranges of values or encoded meanings; for example: int comparison; // negative is less than, zero is equal, //positive is greater than Also, externally-visible functions generally should have a descriptive prologue comment at the top. The public interface of a module needs to be documented so that it can be used without reading the underlying code. Work on the assumption that the code may be someday released as a (binary) library, and the only documentation available will be headers and your comments. Some of the semantics of each function should already be represented in the function and parameter names. To keep a consistent style, this information may have to be duplicated in the comments. But the really important information is what’s not obvious: ranges or encoded meanings of input or output values, side effects, and any other conditions not inherent in the purpose of the function. For the purposes of your assignments in this course, write these kinds of prologue comments for all your routines, because the marker will need them. 程序代写 CS代考 加微信: powcoder QQ: 1823890830 Email: powcoder@163.com