CS 370 Statements D R. M I C H A E L J. R E A L E F A L L 2 0 1 5
Overview In these slides, we ll talk about good practices related to programming statements: Organizing straight-line code Using conditionals Controlling loops Unusual control structures Table-driven methods
Organizing Straight-Line Code
Order Dependencies Often a series of statements must be executed in a certain order statements have dependencies Usually data being passed from one statement to the next When statements have dependencies make it clear as possible!
Making Dependencies Obvious Make routines so dependencies are obvious E.g., computesum() modifies sum (may be class variable) Use routine parameters (and returned data) to make dependencies obvious Examples: initstatistics(statistics); insertsamples(samples, statistics); computesum(statistics); computemean(statistics); computestandarddev(statistics); statistics = initstatistics(statistics); statistics = insertsamples(samples); statistics = computesum(statistics); statistics = computemean(statistics); statistics = computestandarddev(statistics);
Making Dependencies Obvious If a dependency is unclear, add a comment NOT ideal, but may not be able to help it if you can t modify the code for some reason (e.g., maintaining other code) Example: // Must compute mean before computing standard deviation computemean(statistics); computestandarddev(statistics); Check for dependencies inside routines themselves Example: computemean() sets a variable meancomputed Any function that needs the mean will check if meancomputed is true before proceeding (e.g., computestandarddev()) Problems: Creates new code/variables that could introduce new errors Potentially inefficient
When Order Doesn t Matter When order does not matter: Group related statements together so code reads from top to bottom Do this: SalesData salesdata; salesdata.computeannual(); salesdata.print(); TravelData traveldata; traveldata.computeannual(); traveldata.print(); NOT this: SalesData salesdata; TravelData traveldata; salesdata.computeannual(); traveldata.computeannual(); salesdata.print(); traveldata.print();
When Order Doesn t Matter Quick check: draw imaginary boxes around related statements If they overlap, you have a problem Do this: SalesData salesdata; salesdata.computeannual(); salesdata.print(); TravelData traveldata; traveldata.computeannual(); traveldata.print(); NOT this: SalesData salesdata; TravelData traveldata; salesdata.computeannual(); traveldata.computeannual(); salesdata.print(); traveldata.print();
Using Conditionals
If Statements Write nominal path through code first then write unusual cases Should be a straight run for the expected case Check whether you want to use: < vs. <= > vs. >=
If Statements Put normal case after if instead of after the else Makes it easier to find expected path through code Particularly bad if you mix them Mixing error and nominal cases: Error case Nominal case Nominal case Error case openfile(inputfile, status); if(status == ERROR) { displayerror( Cannot open file! ); else { readfile(inputfile, filedata, status); if(status == SUCCESS) { printfiledata(filedata); else { displayerror( Cannot read file! );
If Statements Nominal case Nominal case Error case Error case Better layout: openfile(inputfile, status); if(status == SUCCESS) { readfile(inputfile, filedata, status); if(status == SUCCESS) { printfiledata(filedata); else { displayerror( Cannot read file! ); else { displayerror( Cannot open file! );
If Statements Follow the if clause with a meaningful clause DON T do this: if( condition ) { // Do nothing else { // Do something Do this: if(!condition ) { // Do something
Else Clause Consider the else clause Think about whether you need one GM study: 50%-80% of if statements should have an else clause Arguably, even if you don t need one, coding an empty else clause (with a comment explaining what it means) at least means you considered it Test the else clause Easy to forget to test in favor of the original if Make sure you haven t swapped the if and else clauses Common mistake
Chains of If-Else If you don t have case statements (or if you can t use them with the situation you have) have to resort to chain of if-else clauses E.g., testing for a control character, letter, or number When using these: Simplify complicated boolean tests routines Put most common cases first Means reader can find common cases quickly Make sure all cases are covered Put final else statement with error message so you can catch missing cases
Deep Nesting Deep nesting Excessive levels of indentation Estimated that one shouldn t nest deeper than 3 levels Hard to avoid, but can employ different strategies to reduce it Possible strategies to reduce deep nesting: Retest part of a condition Use a break block Convert a nested if to set a of if-then-elses Convert a nested if to a case statement Factor deeply nested code into its own routine Use objects and polymorphic dispatch Redesign entirely http://d28xhcgddm1buq.cloudfront.net/product-images/bird-nests-10-natural-honeysuckle-4.jpg
Deep Nesting: Retesting Retest part of a condition Not always the best idea Tradeoff: nesting levels for more complicated tests Nested version: if(image.data!= null) { // Piles of code if(featuretracker!= null) { // Buckets of code if(featuretracker.allocatedest()) { // Acres of code if(featuretracker.processimage(image)) { // Oodles of code
Deep Nesting: Retesting Retest part of a condition Less nested version: if(image.data!= null) { // Piles of code if(featuretracker!= null) { // Buckets of code if( (image.data!= null) && (featuretracker!= null) && featuretracker.allocatedest()) { // Acres of code if(featuretracker.processimage(image)) { // Oodles of code
Deep Nesting: Retesting Less nested version with better knowledge of code: if(image.data!= null) { // Piles of code bool featuretrackerallocatedsuccess = false; if(featuretracker!= null) { // Buckets of code featuretrackerallocatedsuccess = featuretracker.allocatedest(); if(featuretrackerallocatedsuccess) { // Acres of code if( (image.data!= null) && featuretrackerallocatedsuccess) { if(featuretracker.processimage(image)) { // Oodles of code
Deep Nesting: Break Block Use a break block Method: Have a bunch of code that must be executed in sequence Put it inside a do-while loop (with while(false)) If a condition that prevents execution occurs, break Kind of uncommon; should only be used if your team is familiar with the idea do { if(image.data == null) { break; // Piles of code if(featuretracker == null) { break; // Buckets of code if(!featuretracker.allocatedest()) { // Cleanup code? break; // Acres of code if(!featuretracker.processimage(image)) { break; // Oodles of code while(false);
Deep Nesting: If to If-Else Chain Convert a nested if to set a of if-then-elses Overgrown Decision Tree: if( pixel > 10) { if(pixel > 100) { if(pixel > 200) { thresholdedpixel = CLASS_D; else { thresholdpixel = CLASS_C; else { thresholdpixel = CLASS_B; else { thresholdpixel = CLASS_A;
Deep Nesting: If to If-Else Chain Convert a nested if to set a of if-then-elses Note: example below works particularly well because numbers increasing neatly Converted to if-elses: if(pixel > 200) { thresholdedpixel = CLASS_D; else if(pixel > 100) { thresholdpixel = CLASS_C; else if(pixel > 10) { thresholdpixel = CLASS_B; else { thresholdpixel = CLASS_A;
Deep Nesting: If to If-Else Chain Convert a nested if to set a of if-then-elses Alternative: allows more complicated number ranges (and allows different order of else-if statements Alternative converted to if-elses: if(pixel > 200) { thresholdedpixel = CLASS_D; else if((pixel > 100) && (pixel <= 200)) { thresholdpixel = CLASS_C; else if((pixel > 10) && (pixel <= 100)) { thresholdpixel = CLASS_B; else if (pixel <= 10) { thresholdpixel = CLASS_A;
Deep Nesting: OOP Approach Use objects and polymorphic dispatch Analogous to breaking code off into routines Have multiple subclasses same function implemented differently FeatureExtractor *extractor = null; switch(featuretype) { case LBP: extractor = LBPFeatureExtractor(); break; case GABOR: extractor = GaborFeatureExtractor(); break; default: DisplayError( UNKNOWN FEATURE ); return; if(extractor!= null) { extractor->extractfeatures(images);
Case Statements: Ordering Ordering of case statements: If equal importance alphabetical or numerical Put normal case first (and then the weird ones after that) Order cases by frequency Can find most common cases easily
Case Statements: Tips Keep case actions simple If you need to do something complicated, move it to a routine Don t try to force-fit using a case statement Example: C++ only supports ordinal types for case statements Checking a command string Use the first letter of the command TERRIBLY IDEA If you can t use a case statement, then use an if-else chain
Case Statements: Tips Don t use the default case for regular values only use it for detecting invalid values Otherwise, more difficult to understand and modify Be VERY careful with falling through in case statements I.e., not putting a break statement at the end of a case Good idea to avoid it If you must, comment the end of the case clearly where the fall-through occurs
Controlling Loops
Loop-with-Exit Loop Loop-with-Exit loop Loop that has an exit condition in the middle of the loop rather than (just) at the beginning or end Visual Basic explicit C++/Java while loop with break Can be used if you find yourself repeating part of the loop structure before the loop executes Not in common use Regular while with repeated code: getnextcameraimage(image); while(image.data!= null) { blurimage(image); displayimage(image); getnextcameraimage(image); Loop-with-exit loop alternative: while(true) { getnextcameraimage(image); if(image.data == null) { break; blurimage(image); displayimage(image);
For Loops for loops simple activities that don t require external loop controls Consequently, avoid changing loop index value inside loop if you have to do this, consider a while loop instead
Loops in General Think of body of loop as black box Should be able to understand loop conditions without having to look inside Use while(true) for infinite loops Don t fake it with a for loop that goes up to 9999999 Don t abuse flexible for loop structures If you need a while loop, just use it
Loop Processing Use { for loops all the time Good defensive strategy Improves readability Avoid empty loops Usually caused by doing actual processing in the loop condition Move the processing to the loop body while( ( inputchar = datafile.getchar() )!= CHAR_EOF) { ; do { inputchar = datafile.getchar(); while ( inputchar!= CHAR_EOF);
Loop Processing Keep loop-housekeeping statements at either beginning or end of loop Example: Put things like j++ at end of loop Make each loop perform only one function Similar to routines
Loop Termination Make sure loop actually ends Make loop termination conditions obvious PLEASE don t set loop index to a value inside the loop to force an exit! Avoid code that depends on loop index s final value
Unusual Control Structures
Multiple Returns There s a little debate on whether you should: Use only ONE return statement in your routine Allow for multiple return statements Cases where multiple returns can be useful: Use an early return IFF: Know answer already before rest of code in routine executes No cleanup code needed Prevents MORE code from being written Guard clauses Check for conditions that prevent further processing can just return However, multiple returns Makes code potentially more confusing May forget return is possible
Recursion Recursion Can be useful for certain classes of problems Otherwise: Can make code more difficult to understand Can be less efficient When using recursion: Make sure it STOPS Use safety counters to prevent infinite recursion E.g., pass counter in as parameter and increment it on each call Limit recursion to ONE routine Don t have A call B call C call A Consider whether you really have to You can do anything with stacks and iteration that you can do with recursion
Table-Driven Methods
Table-Driven Methods Table-drive methods Using look-up table for logic Alternative to things like if and case statements Can make logic statements easier and more direct Example: checking type of character have table chartypetable that stores character type for each ASCII char chartype = chartypetable[ inputchar ];
Issues Two issues one must consider when dealing with tabledriven methods: How will you look up entries in the table? Examples: Days in month easy index is 1-12 Social security number harder (can t just use number directly) Possible access methods: Direct access Index access Stair-step access What will you store in the table? Data? Single value? Object? An action?
Table Access: Direct Access Direct Access Table Put in index(es) and jump straight to value you want Easy example: days in month More complex example: Insurance rate table Need age, marital status, and smoking status 3 dimensions lots of possible values Age has specified ranges: 0-17, 18-65, 66+ (we ll talk about this in a second ) Can read table off file or database can change values without changing code!
Fudging Table-Lookup Keys In the previous example, age actually had three ranges we cared about How can we handle this with our table? Duplicate information to make key work directly In other words, data for ages 0-17 are repeats Advantages: straightforward Disadvantages: Redundant and space-consuming Prone to errors if you forget/miss a column when changing data Transform the key to make it work directly Change any age between 0-17 to one key (maybe 17) If you do this: Isolate the key transformation in its own routine Can also use built-in transformation functions (e.g., Java s HashMap)
Table Access: Indexed Access Indexed Access Tables Use primary data to look up key in index table use key to access value from lookup table Example: Part numbers 0 through 9999 Only 100 items actually in stock
Table Access: Indexed Access Advantages: If each entry in main lookup table is large less space to create index array Can create multiple index mappings One by name, one by age, etc.
Table Access: Stair-Step Access Stair-Step Access Tables Entries are for valid ranges of data rather than distinct data points Example: grade ranges char grade = { F, D, C, B, A float limits = { 60.0, 70.0, 80.0, 90.0, 101.0 Loop through limits can compare with value If value < limit[i] grade[i] Advantages: Works with floating-point indices Allows for irregularly spaced data Wastes less space than other approaches Disadvantages: Can be slower for a large number of ranges cost of search
Table Access: Stair-Step Access Things to look out for when using stair-step access: Watch the endpoints Check whether you want < or <= Consider a binary rather than sequential search Put stair-step table lookup in its own routine