Reading Notes on Code Style

Variables

  • Minimize variable scope. Keep references to a variable together, make variables local or module-level wherever possible, and avoid global variables.
  • Give each variable exactly one purpose.
  • Avoid global data because better techniques can replace it, rather than simply because it is dangerous.
  • If global data is unavoidable, access it through accessor routines. These provide all the functionality of global variables and can offer additional features.

Basic Data Types

Constants

  • Does the code avoid “magic numbers” (constants?)?
  • Does the program guard against division by “0”?
  • Are type conversions explicit?
  • If an expression contains variables of two different types, is it evaluated as intended?
  • Does the program avoid mixed-type comparisons?
  • Does the program compile without warnings?

Integers

  • Do integer division expressions produce the expected results?
  • Do integer expressions avoid overflow?

Floating-Point Numbers

  • Is addition or subtraction between numbers of vastly different magnitudes avoided?
  • Does the program systematically guard against rounding errors?
  • Does the program avoid equality comparisons between floating-point numbers?

Characters and Strings

  • Does the program avoid literal characters and strings?
  • Are string accesses free of boundary errors?
  • In a C program, are character arrays and string pointers treated differently?
  • In a C program, is the convention of declaring string lengths as CONSTANT+1 followed?
  • Are character arrays used instead of pointers?
  • In a C program, are characters initialized to NULL to avoid infinitely long strings?
  • In C, is strncpy() used instead of strcpy(), and are strncat() and strncpy() used?

Boolean Variables

  • Does the program use additional Boolean variables to explain conditions?
  • Does the program use additional Boolean variables to simplify conditions?

Enumerated Types

  • Does the program use enumerated types instead of named constants to improve readability, reliability, and ease of modification?
  • Are enumerated types used instead of Boolean variables to improve readability and flexibility?
  • Do conditions involving enumerated types check for invalid values?
  • Is the first entry of an enumerated type reserved as invalid?

Named Constants

  • Are named constants used in data declarations?
  • Are named constants declared consistently, rather than alternating between named constants and numeric values?

Arrays

  • Are all indexes within array bounds?
  • Are all array accesses free of out-of-bounds errors?
  • Are the indexes of multidimensional arrays in the correct order?
  • In nested loops, are array indexes used as loop variables correctly? Have any indexes been mixed up?

Pointers

  • Are pointer operations isolated in functions?
  • Are pointer references valid? Are dangling pointers used inadvertently?
  • Does the program check pointers before using them?
  • Is the validity of a variable referenced by a pointer checked before use?
  • Are pointers assigned NULL or NIL after being freed?
  • Does the program use all necessary pointer variables to improve readability?
  • Are pointers in linked lists freed in the correct order?
  • Does the program allocate reserve memory as a safeguard to preserve data and work if memory overflows?
  • Are pointers used only as a last resort?

Complex Data Types

  • Structuring data appropriately can make a program simpler, easier to understand, and easier to maintain.
  • Tables can replace complex logic. When program logic becomes confusing, consider whether a table lookup could simplify it.
  • Abstract data types are a powerful way to reduce complexity. They let you write programs in layers and express the top level in terms of the problem domain rather than programming-language details.

Sequential Statements

  • The best principle for organizing sequential code is to identify its dependencies.
  • Indicate dependencies with suitable routine names, parameter lists, and comments.
  • If code has no obvious dependencies, group related statements, especially those using the same parameters.

Conditional Statements

  • Pay attention to the order of if and else branches. When handling many exceptional cases, make sure the normal flow remains clear.
  • Arrange the cases in if-then-else and case statements for maximum readability.
  • Catch unexpected errors in the default branch of a case statement and the final else of an if-then-else statement.
  • Control structures are not equally useful. Choose the most appropriate one when coding.

Loops

  • Loops are complex; simplifying them improves readability.
  • Ways to simplify loops include avoiding unusual loops, minimizing the number of iterations, making entry and exit points clear, and keeping housekeeping code in one place.
  • Do not misuse loop control variables. Give each a meaningful name and a single purpose.
  • Think through the entire loop to ensure it works correctly under all conditions and termination criteria.

Less Common Control Structures

  • Multiple returns sometimes improve readability and maintainability and prevent deeply nested logic, but there is no need to focus exclusively on using return well.
  • Recursive calls can solve simple problems elegantly. Use recursion with care.

Common Control Issues

  • Keeping Boolean expressions simple and readable greatly benefits code quality.
  • Deep nesting makes programs difficult to understand, but relatively simple methods can avoid it.
  • Structured programming is an approach to simplifying programs. Any program can be written using a combination of sequence, selection, and iteration.
  • This approach to simplifying programs can improve productivity and quality.
  • If your language does not support structured constructs, you can emulate them. You should write a program in a language rather than program with a language.
  • Reducing complexity is the key to writing high-quality code.

Layout and Style

  • The primary purpose of layout is to show a program’s logical structure. Judge it by accuracy, consistency, readability, and maintainability. Appearance is a secondary, weaker criterion. If the other criteria are met and the program also looks good, the layout is generally successful.
  • Structuring code has a purpose. Prefer established layout conventions over inventing new ones so that your code stays consistent with others’. Departing from conventions is likely to affect readability.
  • Many opinions about layout are matters of belief or personal preference. Try to separate objective requirements from subjective preferences. Follow clear rules when choosing your preferred layout.

Documentation

  • Commenting is like legislating: done well, it is worthwhile; done poorly, it wastes time and causes harm. Source code should contain most of the important information about a program. As long as the program remains in use, its comments will not be lost or discarded. Putting important information in the code matters.
  • Good comments operate at the level of intent. They explain “why” rather than “what.”
  • Comments should convey what the code itself cannot. Good code should be self-explanatory. When commenting, ask yourself, “How could I improve this code so that the comment becomes unnecessary?” Improve the code, then add comments to make it clearer.

Reviews

  • In general, reviews are better than testing at finding errors.
  • Reviews focus on finding errors rather than correcting them.
  • Reviews often find more varied errors than testing, which means you should use reviews or debugging to ensure software quality.
  • Use checklists, carefully assigned roles, and a continuous process to maximize error detection. Inspections often find more errors than walkthroughs.
  • Walkthroughs and code reading are alternatives to inspections. Code reading can use everyone’s time effectively.

Unit Testing

  • Developer testing is an important part of a complete testing strategy.
  • Combining testing methods also contributes to software quality. High-quality development methods include systematic testing of requirements and designs. Inspections and walkthroughs are at least as effective as debugging at finding errors and can uncover various types of errors.
  • Basic testing, data-flow analysis, and error guessing can generate many test cases.
  • Errors tend to cluster in a small number of routines. Find those routines, redesign them, and rewrite them.
  • Test data often contains more errors than the code under test. Looking for errors this way wastes time without improving code quality. Errors in test data are often more frustrating than coding errors. Careful testing can avoid this.
  • Ultimately, the best way to improve testing quality is to measure it systematically and use what you learn to improve it.

Debugging

  • Debugging is a challenge in software development. The best approach is to use the other methods in this book to prevent errors in the first place. Still, spend time improving your debugging skills: the ratio between the best and worst debugging efficiency is 10:1 or greater.
  • Pay attention to compiler warnings and promptly correct reported errors. If you ignore obvious errors, subtle ones will be difficult to fix.
  • The key to finding and fixing errors is a systematic approach. Pay attention to your tests so that each step moves you forward.
  • Understand a problem’s nature before trying to fix it. Random guesses and changes will only make your program worse.
  • Debugging is a powerful tool in software development. You should know how to use debugging tools.