- Welcome to Software Construction
- Software development or construction includes:
- Problem definition
- Requirements development
- Construction planning
- Software architecture or high-level design
- Detailed design
- Coding and debugging
- Unit testing
- Integration testing
- Integration
- System Testing
- Corrective Maintenance
- Construction focuses on coding and debugging, but also includes detailed design, unit testing, integration testing, and other activities
- Areas not included in construction:
- Management
- Requirements development
- Architecture
- UI design
- System testing
- Maintenance
- Reasons Construction is Important
- Construction is a large part of software development (30-80%)
- Construction is the central activity of software development
- During construction productivity can vary by factor of 10-20, important to get more programmers up to speed
- Source code is often the most accurate description since documentation can go out of date
- Only activity that’s guaranteed to be done
- Metaphors for a Richer Understanding of Software Development
- Metaphors are heuristics, not algorithms. They can be sloppy
- Metaphors help you understand the software-development process by relating it to other activities you already know about.
- Writing doesn’t capture collaborative and persistent, constantly changing nature of projects
- Oyster Farm: It is incremental. Pearl gradually grows.
- Treating software construction similar to building construction suggests that careful preparation is needed and illustrates the difference between large and small projects
- Choosing the right tool for each problem is one key to being an effective programmer
- Use the combination of metaphors that works best
- Measure Twice, Cut Once: Upstream Prerequisites
- Risk Reduction
- Overarching goal of preparing for construction
- Be sure your preparation activities are not increasing risks
- Incomplete Preparation
- Many times developers asked to do the upstream work has no experience
- Other times ignore because want to get into coding
- Managers also may not want time spent on prerequisite planning
- Attention to Quality
- Must be part of software-development process
- Attention to quality at beginning has bigger impact
- Educating colleagues
- Programmers job to educate bosses and coworkers
- Includes importance of adequate preparation before programming begins
- Important to know what you are building
- Research shows puring erros by the beginning of construction allows rework to be done 10-100x less expensively vs. after its done
- Project Type Matters
- Kind of projects affects prerequisites
- Some should be highly iterative while others are better sequential
- Even in iterative process, prereq planning can save money
- Problem Definition
- Problem definition simply states what you are trying to do
- If you don’t have a good problem definition specified, you may be solving the wrong problem
- Requirements
- If good requirement work hasn’t been done, might have missed important details of the problem
- Requirements changes cost 20 to 100 times as much in the stages following construction
- Explicit requirements keeps the programmer from guessing what the user wants
- Requirements frequently change because customers better understand their needs as you progress
- Ways to handle changes
- Keep a checklist to assess quality and redo if not good
- Make sure everyone understands the cost of requirement changes
- Set up a change-control procedure
- Use an evolutionary delivery approach
- Cancel the project if not workable
- Keep eye on business case and consider incremental business value
- Architecture
- Can be solving the right problem in the wrong way
- Cost increases as more code is written
- Organization: define minimized building blocks with specific functions. communication rules should be well-defined
- Major Classes: responsibilties, interactions, hierarchies, state transitions, object persistence. Specify 20 percent that account for 80 percent of system.
- Data Design: Describe table design and why decisions were used.
- Business Rules: Identify how they impact the system design
- User Interface: Should be modularized so new interface won’t affect business rules and outputs.
- Resource Management: Scarce resources such as database connectiosn, threads, and handles. Estimate nominal and extreme cases.
- Security: Design and code-level security. Coding guidelines should be developed for handling buffers, untrusted data, encryption, protection, etc.
- Performance: priorities between speed, memory, and cost. plans for how it will be achieved.
- Scalability: How it will address growth
- Interoperability: If it will share data or resources with other systems, describe how it will be accomplished.
- Internationalization/Localization: Considered character sets used.
- Input/Output: Specify reading scheme and level at which I/O errors are detected: field, record, stream or file
- Error Processing: Because of implications, better to treat at architecterual level.
- Fault Tolerance: How to increase reliability by detecting errors.
- Overengineering: Clearly indicate whether programmers should err on side of overengineering or on side of doing the simplest thing that works
- Buy-vs-Build: Should explain if not using off-the-shelf components
- Reuse Decisions: Explain how preexising software will be made to work
- Change Strategy: describe strategy for handling changes and show possible enhancements that have been considered and easiest ones
- General Architetural Quality: It should fit nicely into the problem and changes should fit cleanly with the overall concept. Should describe motivatiosn for all major decisions.
- Understand prerequisites
- Generally well-run projects devote about 10-20% of effort and 20-30% of schedule to requirements, architecture and up-front planning (detailed design is part of construction)
- If requirements are unstable, treat requirements work as its town project
- Unreasonable to ask for a bid before you know what you’re building
- Choose your construction approach accordingly
- Key Construction Decisions
- Language
- Be aware of each languages’ strengths and weaknesses
- Major impact on productivity
- Familiar languages and high level languages generally result in higher complexity
- Ada: general, high-level based on Pascal. Good for real-time and embedded systems.
- Assembly Language: low-level corresponds to a machine instruction.
- C: general-purpose, mid-level. has structured data, structured control flow, machine independence, and a rich set of operators.
- C++: OO language. Provides classes, polymorphism, exception handling, templates and type checking.
- C#: General purpose OO. Provides extensive tools that aid development on Microsoft platforms.
- Cobol: English-like used primarily for business applications. Still one of most widely used next to Visual Basic.
- Fortran: high-level language introduced variables and high-level loops. Used mainly in scientific and engineering applications.
- Java: OO langauge designed to run on any platform by converting Java source code to byte code. Then runs within virtual machine.
- JavaScript: Interpreted scripting language. Used primarily for client-side programming such as adding simple functions and online applications to web pages.
- Perl: string-handling language. Often used for system admin tasks and report generation and processing.
- PHP: Runs on all major OS and executes server side interactions. Can be embedded in web pages to access and present database information.
- Python: Interpreted, interactive, OO language that runs in numerous environments. Commonly used for writing scripts and small Web apps.
- SQL: Standard for querying, updating, managing relational databases. Declarative, defines result of some operations.
- Visual Basic: high-level OO visual programming version of BASIC. Can support customization of desktop applications such as MS Office and create web programs. In 2000’s, most professional developers were working in VB.
- Convention
- Establish before you program because impossible to change later
- Guidelines should cover variable names, class names, routine names, formatting conventions, and commenting conventions
- Variations can tax the brain and divert valuable mental resources
- Construction Practices
- More exist than you can use in any single project
- Consciously choose the one best suited for your project
- Program Into the Language
- Ask whether the programming practices you are using are a response to the programming language or controlled by it
- A program may encourage a practice that may not be best for your purposes
- Technology Wave and Timing
- Identify where you are and adjust plans accordingly
- Mature technologies benefit from a rich software development infrastructure and tools
- In early wave environments, the situation is the opposite
- If you are in the early parts of a wave, more likely spending time trying to figure out the language and undocumented features, debugging, revising code, etc.
- In mature technologies, more time writing new functionality.
- Design in Construction
- Introduction
- the programmer usually designs part of the program, officially or otherwise
- Software design means the conception, invention of a scheme for turning a specification for computer software into operational software
- Involves trade-offs, restrictions, nondeterministic, heuristic process, emergent (evolve and improve)
- Complexity
- Software’s Primary Technical Imperative is managing complexity
- Greatly aided by a design focus on simplicity
- Essential difficulties lies in interfacing with the complex, disorderly real world and designing exact solutions
- Projects often fail because of poor requirements, poor planning, or poor management
- When they do fail for technical reasons, usually due to uncontrolled complexity
- Goal should be to be able to focus on one part of the program at a time
- Working in project domain instead of lowlevel implementation helps reduce load on brain
- Simplicity
- Minimize the amount of essential complexity that anyone’s brain has to deal with at one time
- Keep accidental complexity from proliferating needlessly
- Good Design
- Minimial Complexity: Focus on “simple” and “easy-to-understand”. If you can’t safetly ignore other parts of the program, the design isn’t doing its job.
- Ease of Maintenance: Think about the maintenance programmer and design the system to be self-explanatory
- Loose Coupling: Hold connections to different parts of a program to a minimum. Use principles of good abstraction in class interfaces, encapsulation, and information hiding.
- Extensibility: Can enhance without causing violence to underlying structure. Can change a piece without affecting other pieces.
- Reusability
- High fan-in: High number of classes that use a given class
- Low-to-medium fan-out: a given class uses low-to-medium number of other classes. Using a large number may mean its overly complex.
- Portability: Easily move to another environment
- Leanness: No extra parts.
- Stratification: keep level of decomposition stratified so you can view the system at any single level. e.g. adding an interface layer for bad code.
- Standard Techniques: Using exotic pieces will make it harder for someone trying to understand it.
- Levels of Design
- Software System: entire system, don’t jump into classes, beneficial to think through higher level combination of classes such as subsystems and packages
- Subsystems/Packages:
- main product of design is identification of all major subsystems.
- DB, UI, business rules, command interpreter, report engine, etc.
- Restrict communication otherwise entrophy can make too interconnected.
- connect on a need-to-know basis
- Business Rules – laws, regulations, policies, and procedures.
- User Interface – Isolated from rest of system
- Database Access – hide implementation details. Focus on how used at the business-problem level.
- System Dependencies – Package operating-system dependencies into a subsystem. If you later move, only need to change interface subsystem
- Division into classes within packages: Specify class interactions. Classes and objects more or less interchangeable terms here.
- Division into data and routine within classes: fully defining routines often results in better understanding of the class’s interface.
- Internal routine design: Typically left to individual programmer. Consists of pseudocode, looking up algorithms, deciding how to organize the pargraphs of code in aroutine, and writing programming-language code
- Design is Heuristic
- Dogmatic adherence to any single methodology hurts creativity and hurts your programs
- Because design is nondeterministic, skillful application of heuristics is the core activity in good design
- Real World Objects
- Identify the objects and attributes
- Determine what can be done to each object
- Determine what each object is allowed to do to other objects
- Determine what parts will public/private
- Define each object’s public interface
- Form Consistent Abstractions
- Safely ignore some of its details
- a good clas interface is an abstraction that allows you to focus on the interface without needing to worry about the internal workings of the class
- Good programmeres create abstractions at the routine-interface level, class-interface level, and package-interface level
- Encapsulate Implementation Details
- Don’t let you look at an object at any other level of detail
- When Inheritance Simplifies Design
- Some objects may have only some factors different e.g. part-time employee and full-time employee
- Allows dealing with the object at different levels of abstraction
- Polymorphism allows using same methods on different instances
- Hide Secrets (Information Hiding)
- Assists in modularity by hiding complexity
- Interface to a class should reveal as little as possible about its inner workings
- Hide complexity so brain doesn’t have to deal with it
- Also, hide source sof change so effects are localized
- Excessive information, circular dependencies, and perceived performance penalities serve as barriers
- Identify Areas Likely to Change
- Identify Areas likely to change
- Separate items that are likely to change
- Isolate items that seem likely to change
- Areas Likely to Change: Business Rules, Hardware Dependencies, I/O, Nonstandard language features, Difficult design and construciton areas, Status variables, and Data-size constraints
- Design system so that the effect or scope of change is proportional to the chance that the change will occur.
- Identify the minimal subset of the program that might be of use first
- Define increments to the system. These improvements constitute potential changes
- Loose Coupling
- Good coupling is loose enough that one module can easily be used by other modules
- Try to make modules that depend little on other modules
- Semantic coupling occurs when a module knows how another module works
- Dangerous because changes in code can cause breakage
- Design Patterns
- Can allow designers to think and discuss at a larger level of granularity
- Formalize Class Contracts
- Assign Responsibilities
- Design for Test: tends to result in more formalized class interfaces, which is generally beneficial
- Avoid Failure:
- People tend to focus on why something worked than why it might not
- Black box has simple interface and well-defined functionality that for any specific input you can accurately predict the output
- Sometimes thinking about how to assemble a system from a set of black boxes provides insights that information hiding and encapsulation don’t
- Iterative
- Good design is iterative
- The more design possibilities you try, the better your final design will be
- Top Down: Start with general classes and decompose into more specialized classes. Keep going until, it seems easier to code than to decompose further
- Bottom Up:
- Ask what the system needs to do.
- Identify concrete objects and responsibilities from that question.
- Identify common objects and group them using subsystem organization, packages, composition within objects or inheritance, whichever is appropriate
- Continue with the next level up, or go back to the top and try to work down
- Experimental Prototyping
- Sometimes don’t know whether a design will work until you better understand some implementation detail
- Experimental prototyping mean writing the absolute minimum amount of throwaway code to answer a specific design question
- Doesn’t work well if developers aren’t disciplined about writing the absolute minimum of code
- Also works poorly when design question is not specific enough
- Maybe use another language or tool for the prototyping so people don’t get attached
- generally less mission-critical or shorter projects require lower detail and documentation formality
- Methods: Save design charts, Take pictures, CRC (Class, Responsibility, Collaborator) cards, UML diagrams
- Working Classes
- Abstract Data Types (ADTs)
- Without understanding ADTs, programmers create “classes” that are little more than convenient carrying cases for loosely related collections of data and routines
- ADTs allow for classes that are easier to implement and easier to modify over time
- e.g. Instead of inserting a node, you can insert a cell into a spreadsheet
- ad hoc programming limits how object can be used
- Benefits of using ADTs
- Hide Implementation Details: You can change in one place without affecting the whole program
- More Informative Interface: can tell you move about what it’s doing e.g. changing pixels or points
- Easier to improve performance: Focus on few well-defined routines rather than going through entire program
- More Obviously Correct: only need to check that the correct function is being called and that it is working correctly
- Self-documenting: currentFont.SetBoldOn() is more informative than currentFont.attribute or 0x02
- Don’t have to pass data all over your program: data is only accessed by routines that are part of the ADT
- Work with real-world entities instead of low-level implementations: provides a better level of abstraction for the rest of your program to work with
- Using ADTs
- Build or use low-level data types as ADTs not low level data types e.g. call a stack employees
- Treat common objects like files as ADTS: You can use pushing and popping, but can create another level on top that works at the level of the real-world problem
- Treat even simple items as ADTS: Even something simple like turning a light on and off. more self-documenting
- Refer to an ADT independently of the medium its stored on: how its stored may change making name incorrect
- Interfaces
- Class interfaces should provide a consistent abstraction
- Many problems arise from violating this single principle
- A class that presents poor abstraction would be one that contained a collection of misc functions
- Present a consistent level of abstraction: e.g. shouldn’t address employees and the list container. Mixed levels of abstraction make a program harder to understand.
- Be sure to understand what abstraction the class is implementing: Understand what you want to show and not show
- Provide services in pairs with their opposites: if you turn something off, you should probably be able to turn it off. don’t add gratuitously but check if you need it.
- More unrelated Info to another class: If half is only working on one part and the other half is working on another, break into two
- Make Interfaces programmatic:
- Programmatic part consists of the data types and other attribtues of the interface that can be enforced by the compiler. Semantic cannot be enforced by the compiler (e.g. RoutineA must be called before RoutineB).
- Look for ways to convert semantic to programmatic.
- Try to keep interfaces minimally dependent.
- Erosion of interface’s abstraction under modification:
- watch that unrelated or low-level abstraction doesn’t work its way in
- Don’t add public members that are inconsistent with the interface abstraction
- Consider abstraction and cohesion together: Focusing on abstraction tends to provide more insight into class design than focusing on cohesion
- Good Encapsulation / Hidden Interfaces
- Should hide system interface, design decision, or implementation detail
- Encapsulation enforces abstraction w/o it, abstraction ends to break down
- Minimize Accessibility of Classes and Members
- Favor strictest level of privacy that’s workable
- What best preserves the integrity of the interface abstraction
- Don’t expose member data in public: client code is free to monkey around with it
- Avoid putting private implementation details into a class’s interface
- You can separate the class interface from the class implementation
- Include a pointer to the class’s implementation
- only the class with the pointer will have access to implementation details
- Don’t make assumptions about the class’s users
- Shoun’t make assumptions other than what’s documented in the interface
- Avoid Friend Classes: Generally violates encapsulation and expand the amount of code you have to think about at any one time
- Don’t put a routine into the public interface just because it only uses public routines
- Instead ask if it would be consistent with the abstraction presented by teh interface
- Favor read-time convenience to write-time convenience
- Code is read more than it is written
- Sometimes may be tempting to add a routine to an interface that would be convenient for the particular client of a class, but can lead to slippery slope
- Be very, very wary of semantic violations of encapsulation
- Ways to break encapsulation semantically include Not calling Class A’s InitializeOperations() because you know Class A’s PerformFirstOperation() calls it automatically or Not calling db.connect, because you know employee.retrieve() will connect if there isn’t a connection.
- These make the client code dependent not on the class’s public interface, but on its private implementation
- Look at the interface, not the implementation
- Right response is to contact the author of the class and say you can’t figure out how to use the class.
- The correct resposne from the author is to update the documentation and see if you can understand
- Watch for coupling that’s too tight
- minimize accessiblity of classes and members
- Avoid friend classes (can access private and protected info)
- Make data private (only avail to class) rather than protected (avail to subclasses)
- avoid exposing member data in a class’s public interface
- Be wary of semantic violations of encapsulation
- Observe the “Law of Demeter”
- Containment (“has a” Relationships)
- Simple idea that a class contains a primitive data element or object
- Containment is usually preferable to inheritance unless you’re modeling an “is a” relationship
- Implement “has a” through containment: e.g. an employee has a name, phone number, tax id
- Implement “has a” through private inheritance as a last resort
- privately inheriting from the contained object would allow access to protected member functions and data, but can result in overly cozy relationship
- Be critical of classes that contain more than about seven data members
- People can remember 5-9 items, if more harder to reason about
- Lean toward high end of range if simple data and lower end if complex objects
- Inheritance (“is a” relationships)
- Useful tool, but adds complexity
- Creates simpler code by defining a base class with common elements
- Helps avoid repeat code and data in multiple locations
- Implement “is a” through public inheritance
- if the derived class isn’t going to adhere completely to the same interface, inheritance might not be right
- Design and document for inheritance or prohibit it because adds complexity
- Adhere to Liskov Substitution Principle (LSP)
- subclasses must be usable through the base class “interface without the need for the user to know the difference” (Hunt and Thomas 2000)
- all the routines in the base class should mean the same thing
- Powerful if it allows progammers to focus on the generic attributes, but if they have to think about semantic differences, adds complexity
- Inherit only what you want to inherit
- Beware of inheriting implementation just because you’re inheriting an interface
- Beware of inheriting an interface just because you want to inherit an implementation
- If you want to use a class’s implementation, but not interface, use containment
- Don’t “override” a non-overridable member function
- if a function is private in the base class, a derived class can create the same name
- Don’t reuse names of non-overridable base-class routines in derived classes
- Move common interfaces, data, and behavior as high as possible in the inheritance tree
- Use abstraction as your guide
- Be suspicious of classes of which there is only one instance
- Consider whether you could just create an object instead of a new class
- The Singleton pattern is one notable exception
- Be suspicious of base classes of which there is only one derived class
- Best way to prepare for the future is to make current work as clear, straightforward, and simple as possible
- Be suspicious of classes that override a routine and do nothing inside the derived routine
- e.g. ScratchlessCat can result in ScratchlessTaillessCat
- fix in the original cat class
- root of problem is the assumption that all cats scratch
- create a claws class and contain that within the cats class
- Avoid Deep Inheritance Trees
- Arthur riel suggests limiting hierarchies to a max of six levels
- Many people have trouble with 2-3 levels
- Significantly associated with increased fault rates
- Prefer polymorphism to extensive type checking
- Frequently repeated case statement sometimes sugest inheritance might be better, but not always true
- Make all data private, not protected
- if access is really needed provide protected accessor functions instead
- Multiple Inheritance
- usually best to avoid
- maybe for mixins
- When to Use Inheritance
- If multiple classes share common data, but not behavior, create a common object that those classes can contain
- If multiple classes share common behavior but not data, derive a common base class that defines common routines
- If multiple classes shares common data and behavior
- Inherit when you want the base class to control your interface; contain when you want to control your interface
- Member Functions and Data
- Keep the number of routines in a class as small as possible, but deep inheritance trees, large number of routines called within a class and strong coupling were more significant
- Disallow Implicitly generated member functions and operators you don’t want
- you can disallow uses by declaring them private
- Minimize the number of different routines called by a class
- Minimize indirect routine calls to other classes
- indirect connections such as chained method calls can be hazardous
- If object A instantiates an Object B, it can call any of Object B’s routines, but should avoid calling routines on objects provided by Object B
- Minimize the extent to which a class collaborates with other classes
- Constructors
- Initialize all member data in all constructors, if possible
- Enforce the singleton property by using a private constructor
- Prefer deep copies to shallow copies until proven otherwise
- rarely causes performance issues
- Reasons to Create a Class
- Model real-world objects
- Model abstract objects
- Reduce Complexity:
- After you write you should be able to forget details and use without knowing inner workings
- Minimize code size, improve maintainability, and improve correctness
- Isolate Complexity: easier to fix if able to localize within a class
- Hide Implementaion Details
- Limit effects of change: make sure areas most likely to change are easiest to change
- Hide global data: You can change structure of data, monitor access, and encourages you to think about whether the data is really global
- Streamline parameter passing: passing lots of data around suggests that a different class orgnaization might work better
- Make central points of control: centralized control is similar to information hiding
- Facilitate reusable code: even if a section of code is called from only one place, it makes sense to put into its own class if that piece of code might be used in another program
- Plan for a family of program:
- can be a powerful heuristic for anticipating entire categories of changes
- Can also save significant time in the future
- Package related operations: Classes are one means of combining related operations e.g. trig functions, stats, string-manipulation, bit-manipulation, graphic-routines, etc
- Accomplish a specific refactoring:
- Classes to Avoid
- Avoid god classes:
- class that retrieves data from other classes using get or set
- ask whether better to be orgnaized into those other classes
- Eliminate irrelevant classes:
- If only contains data, consider demoting it so its member data just becomes attributes of one or more other classes
- Avoid classes named after verbs:
- consider turning into a routine on some other class
- Primary Tool
- Classes are your primary tool for managing complexity
- Give their design as much attention as needed to accomplish that objective
- High-Quality Routines
- Intro
- Routine is an individual method or procedure invocable for a single purpose
- Purpose
- The most important reason for creating a routine is to improve the intellectual manageability of a program
- Saving space is a minor reason
- Improved readability, reliability, and modifiability are better
- Deep nesting may mean you should break into own routine
- New abstraction: A good name is self-documenting
- Avoid duplicate code: saves space and makes modification and checking easier
- Support subclassing: need less new code to override a short, well-factored routine than a long, poorly factored routine
- Hide sequences: hide the order in which events happen to be processed
- Hide pointer operations: Pointer operations are hard to read and error prone. Isolate them and concentrate on intent of operation rather than mechanics.
- Improve portability: Isolate nonportable capabilities
- Simplify complicated boolean tests: understanding in detail rarely necessary for understanding program flow. Put details out of the way.
- Improve performance: can optimize in one place and more practical to recode
- Simplicity
- Sometimes the operations the most benefits from being put into a routine of its own is a simple one
- Having small steps all over the place can add up
- turning into a routine can approach self-documentation
- Small functions also tend to turn into large operations
- Design at the Routine Level
- Cohesion refers to how closely the operations in a routine are related
- Goal is to have each routine do one thing well and not do anything else
- function cohesion is the strongest and best kind (when a routine performs only one operation)
- Considered not ideal
- Sequential cohesion exists when operations must be performed in a specific order.
- Communicational cohesion occurs when operations in a routine make use of the same data, but are not related themselves
- Temporal cohesion ocurs when operations are combined because they are done at the same time
- Unacceptable
- Procedural cohesion: Done in a specified order, but are unrelated
- Logical cohesion: control flow or logic is the only thing that ties operations together. Usually alright if code onsists only of if or case statements to other routines without doing any processing.
- Coincidental cohesion: no discernible relationship.
- Naming
- Name of routine indicates its quality
- Describes everything
- Describe all outputs and side effects
- Program so you cause things to happen directly rather than with side effects
- Avoid meaningless verbs
- Handle, perform, output, process, deal with
- can also be vague because the purpose is weak and vague
- Don’t differentiate solely by number
- Make names of routines as long as necessary
- Use a description of the return value to name a function
- To name a procedure, use a strong verb followed by an object
- should imply verb-plus-object name
- In OO languages, might not need to include since object name is included in the call
- Use opposites precisely:
- add/remove, begin/end, create/destroy, get/put, etc
- Establish conventions for common operations:
- e.g. how do you get id employee.id.get(), dependent.getid(), suvervisor(), candidate.id()
- How Long?
- Most routines will likely be short
- Some complex ones can grow to 100-200 lines
- Routine’s cohesion, depth of nesting, number of variables, number of decision points, number of comments needed, and other complexity-related consideration are more important factors than strictly number of lines
- Need to becareful if longer than 200 lines though
- Routine Parameters
- 39% of all errors were due to errors in communication between routines (Basili and Perricone 1984).
- Put parameters in input-modify-output order: ordering imples sequence of operations
- Create your own in and out keywords: Becareful though
- If several routines use similar parameters, put the similar parameters in a consistent order
- Use all parameters: If you aren’t using something, remove it
- Put status or error variables last: they are incidental to the main purpose
- Don’t use routine parameters as working variables:
- Use local variables instead
- Document interface assumptions about parameters
- Limit the number of a routine’s parameters to about seven
- Can pass composite data as a “chunk”
- Consider an input, modify, and output naming convention for parameters
- could prefix them with i_, m_, and o_
- or input_, modify_, output_
- Pass variables or objects that routine needs to maintain its interface abstraction
- e.g. should you pass an object or just the necessary variables
- depends on the abstraction, do you just need the 3 parts
- or might the level need the object
- if you are creating an object just to extract the variables, probably should just pass the variables
- if you are constantly changing the parmaeter list, probably should pass object
- Use named parameters:
- very useful with longer than average lists
- usually best practices esp. for mission-critical programs
- Make sure actual parameters match formal parameters:
- Functions
- function returns a value
- procedure does not return a value
- Should only be used when the primary purpose of the function is to return the specific value described by the function’s name
- Make sure each path returns a value
- Make sure not returning references or pointers to local data
- Macros
- Careful programmers use macro routines with care and only as a last resort
- Fully parenthesize macro expressions
- Surround multiple-statement macros with curly braces
- Name macros that expand to code like routines so that they can be replaced by routines if necessary
- use inline routines sparingly
- Defensive Programming
- Production
- Production code should handle errors in more sophisticated way than “garbage in, garbage out”
- Check the values of all data from external sources
- check to make sure the data falls within the allowable range
- make sure input is valid for intended purposes
- Check the values of all routine input parameters
- similar to above except data coming from another routine
- Decide how to handle bad inputs
- best form is not inserting errors in the first place
- iterative design, writing pseudocode, writing test cases, having low-level design inspections are all activities that will help prevent inserting defects and should be given higher priority
- Benefits
- Makes errors easier to find, easier to fix, and less damaging to production code
- Assertions
- code that allows a program to check itself as it runs
- Help detect errors early, esp. in large systems, high-reliability systems, and fast-changing code bases
- Usually don’t want users to see assertion messages in production code
- normally compiled in during development and out of code for production
- Use error-handling for conditions you expect to occur; use assertions for conditions that should never occur
- Avoid putting executable code into assertions:
- put execuable statement on own line and assign result to status variable
- test status variable
- Use assertions to document and verify preconditions and postconditions:
- preconditions are properties that the client code of a routine or class promises will be true before it calls the routine or instantiates the object
- postconditions are properties of the routine or class promises will be true when it concludes executing
- For highly robust code, assert and then handle the error anyway
- development spaced out by geography, time, and people
- In complex projects, every conceivable error won’t be detected so error handling is useful
- Error-Handling Technique
- How to handle is a key error-handling decision and a key high-level design decision
- Return a neutral value
- 0, “”, None, default value
- Substitute the next piece of valid data
- Exceptions
- Exceptions provide a means of handling errors that operate in a different dimension from the normal flow of code
- They are a valuable addition to the programmer’s intellectual toolbox when used with care, and should be weighted against other error-processing techniques
- Substitute the next piece of valid data
- Return the same answer as the previous time
- Substitute the closest legal value
- Log a warning message to a file
- Return an error code
- Call an error-processing routine/object
- centralize error handling in a global error-handling routine or object
- trade off is whole program will be coupled to it
- Display an error message wherever the error is encountered
- potentially spread UI messages through the entire system
- Also worry about telling attackers too much
- Handle the error in whatever way works best locally
- provides individual developers with greater flexibility
- downside is overall performance of system might not satisfy requirements for correctness or robustness
- Shut Down
- Might be good for safety-critical applications e.g. radiation machine
- Robustness vs. Correctness
- Style depends on the kind of software the error occurs in
- Safety-critical applications tend to favor correctness to robustness. No result is better than wrong result
- Consumer applications tend to favor robustness. Seamless experience is more favorable.
- High Level Design Implications of Error Processing
- With so many options, good to handle in a consistent way
- Deciding on general approach to bad parameters is an architectural or high-level design decision
- Unless you’ve set an architectural guideline of not checking system calls for erros, check for error codes after each call
- Exceptions
- Exceptions are a specific means by which code can pass along errors or exceptional events to the code that called it
- Routine uses throw to throw an exception object
- Code in some other routine up the calling hierarchy will catch the exception with a try-catch block
- Used imprudently, they can make code almost impossible to follow
- Use exceptions to notify other parts of the program about errors that should not be ignored
- Eliminate the possibility that an error condition can propagate through a code base undetected
- Throw an exception only for conditions that are truly exceptional
- specifically for conditions that cannot be addressed by other coding practices
- events that should never occur
- trade-off between handling unexpected conditions and increased complexity on the other
- weakens encapsulation by requiring code the calls a routine to know which exceptions might be thrown
- Don’t use an exception to pass the buck, handle locally if you can
- Avoid throwing exceptions in constructors and destructors unless you catch them in the same place
- Throw exceptions at the right level of abstraction
- e.g. passing a lower-level EOFException exposes detail about implementation
- throws EmployeeDataNotAvailable() preserves the interface abstraction
- Include in the exception message all information that led to the exceptional
- exceptions happen under specific circumstances
- the information is valuable to the person reading the message
- Avoid empty catch blocks
- don’t group a bunch of code under a try clause
- determine root cause and fix the try block or the catch block
- if an exception at the lower level really doesn’t represent an exception at the level of abstraction of the calling routine, document the case with comments by logging the message
- Know the exceptions your library code throws
- If the library code doesn’t document the exceptions it throws, create prototyping code to exercise the libraries and flush out the exceptions
- Consider building a centralized exception reporter
- Standardize your project’s use of exceptions
- for compatibility with other languages, consider throwing only objects derived from the Exception base class
- Consider creating your own project-specific exception class
- Define the specific circumstances under which code is allowed to use throw-catch syntax to perform error processing locally
- Define the specific circumstances under which code is allowed to throw an exception that won’t be handled locally
- Determine whether a centralized execution reporter will be used
- Define whether exceptions are allowed in constructors and destructors
- Consider alternatives to exceptions
- little conventional wisdom on how to use them safely
- consider alternatives: handling the error locally, propagating the error by using an error code, loggin debug information to a file, shutting down the system, or using some other approach
- Using exceptions just because your language provides exception handling is a classic example of programming in a language
- Barricade Your Program
- One way is to design certain interfaces as boundaries to “safe” areas
- Check data crossing the boundaries of a safe area for validity, and respond sensibly if the data isn’t valid
- Can also use at the class level, assume the data is unsafe
- private methods can assume data is safe
- Convert input data to the proper type at input type
- Routines outside the barricade should use error handling because it isn’t safe to make assumptions about the data
- Routines inside the barricade should use assertions because data is supposed to be sanitized
- If routine inside barricade detects bad data, there is an error in the programming
- Debugging Aids
- Production vs Development
- Be willing to trade speed and resources during development in exchange for built-in tools that can make development go more smoothly
- Offensive Programming
- Exceptional cases should be handled in a way that makes them obvious during development and recoverable when production code is running
- Some ways you can program offensively:
- Make assert abort the program
- Completely fill any memory allocated so you can detect memory allocation erros
- completely fill any files or streams allocated to flush out any file-format errors
- Be sure the code in each case statement’s default or else clause fails hard
- Fill an object with junk data just before its deleted
- Set up the program to email error log files to yourself so you can see the kinds of errors that are occuring
- Plan to remove debugging aids
- Performance penalty in size and speed can be prohibitive
- Use version-control tools and build tools like and and make
- Use a built-in preprocessor. Certain languages you can exclude debug code
- Write your own preprocessor that responds to keywords
- Use debugging stubs: in production replace checking routine with one that just returns control back quickly
- Determine how much defensive programming to have in production code
- In development you want errors to be loud, but opposite in production
- Leave in code that checks for important errors
- Remove code that checks for trivial errors – not physically, but with a precompiler or version control
- Remove code that results in hard crashes
- Users want a chance to be able to save their work
- Leave in code that helps the program crash gracefully
- Log errors for your technical support personnel
- Make sure that the error messages you leave in are friendly to the user
- Defensive about Defensive Programming
- too much creates its own problems
- Can make program fat and slow
- Adds complexity and defensive code can be wrong too
- Think about where you need to be dfensive and set priorities accordingly
- The Pseudocode Programming Process
- Iterative
- Constructing classes and routines tend to be an iterative process
- Insights gained while constructing specific routines tend to ripple back through the class’s design
- Building a Class
- Create a general design for the class and what level of abstraction interfaces will capture
- Construct each routine within the class
- typically unearths the need for additional routines
- issues arising ripple back to the overall class design
- Review and test the class as a whole
- Steps in Building a Routine
- Design, check design, code the routine, review and test
- Repeat
- Good Pseudocode
- Writing good pseudocode calls for using understandable english, avoiding features specific to a single programming language
- Write at the level of intent (describe what the design does)
- Generating code from it should be nearly automatic
- If level is too high can gloss over problematic details in the code
- Refine in more detail until it seems like it would be easier to write the code
- Benefits of Pseudocode
- Makes reviews easier
- Supports the idea of iterative refinement
- easier to change blueprint than the house
- minimizes commenting effort
- Easier to maintain than other forms of design documentation
- Pseudocode Programming Process (PPP)
- Useful tool for detailed design and makes coding easy
- translates directly into comments, ensuring comments are accurate and useful
- Design the Routines
- Check the Prerequisites
- see if the job is well defined and fits cleanly into the overall design
- check that routine is actually called for
- Define the problem the routine will solve. High level design should indicate:
- information the routine will hide
- inputs to the routine
- outputs from the routine
- preconditions
- postconditions
- Name the routine
- Decide how to test the routine
- Research functionality available in the standard libraries
- Good chance that what you are writing has already been done
- Think about error handling
- What could go wrong?
- Bad inputs?
- Choose consciously how to handle errors
- Think about efficiency
- Most cases efficiency isn’t critical, with good encapsulation, can focus on those parts later
- When it is critical, design your routines so it will meet its resource and speed goals
- usually waste of effort to work on efficiency at the level of individual routines
- big optimizations come from refining high-level design
- Research the algorithms and data types
- Write pseudocode
- start with the general and work toward something specific
- most general is the header comment describing what routine is supposed to do
- If you have difficulty, you might need to understand the routine’s role better
- After general comment, fill in high-level pseudo code or the routine
- Think about the data
- if data manipulation is a prominent part of the routine, worthwhile to think about the major pieces of data before you think about the routine’s logic
- Check the pseudocode
- Think about how you would explain it to someone else
- people more willing to review pseudocode
- Try a few ideas in pseudocode, and keep the best (iterate)
- try as many ideas as you can
- Once you start, you get emotionally involved and it gets harder to start over
- if pseudocode is too high level, may have to decompose further
- Code the Routine
- Write the routine declaration
- could be a good time to make notes about interface assumptions
- Turn pseudocode into high-level comments
- Fill in the code below each comment
- all variables have been declared and defined close to the point they’re first used
- comments should be 2-10 lines
- Check whether code should be further factored
- if you find one line of pseudocode expanding into more code, factor the code into its own routine
- apply the PPP recursively
- Check the Code
- Any errors you miss at this stage won’t be found until later testing
- More expensive to find and correct then so should try to find at this stage
- May have more insights on structure of solution after you have the implementation
- Mentally check the routine for errors
- Understand each line of code
- most likely error is due to your code
- If you don’t know why it works, it probably doesn’t
- Compile the routine
- If you compile before it works, can get you in a mindset of trying to hack, compile, and fix
- need to know routine is right first
- Step through the code in the debugger
- make sure each line works as you expect
- Test the code
- Remove errors from the routine
- Clean up Leftovers and check your work
- Check your work at each step and encourage others to check it too
- Can catch mistakes at the least expensive level, when you’ve invested the least amount of effort
- Check interface, make sure all input and output are accounted for
- Check for general design quality, make sure routine does one thing
- check variables, naming, unused objects, undeclared variables, improperly initialized objects
- check statements and logic
- check layout, make sure you’ve used whitespace to clarify the logical structure
- check routine’s documentation, make sure comments are still accurate
- remove redundant comments
- Alternatives to PPP
- Test first development
- Refactoring
- Design by contract
- Hacking?
- Multiple Approaches
- Don’t settle for the first design you think of
- Iterate through multiple approaches in pseudocode and pick the best approach before you begin writing code
- General Issues in Using Variables
- Data Initialization
- Use explicit declarations when you can
- Create naming conventions for suffixes e.g. num, no
- Initialize each variable as its declared
- Initialize each variable close to where it’s first used
- Ensures they are actually used
- Principle of proximity: keep related actions together
- Ideally, declare and define each variable close to where it’s first used
- Use final or const when possible to prevent change
- Pay special attention to counters and accumulators: don’t forget to reset
- Initialize a class’s member data in its constructor
- Check the need for reinitialization: if repeated, make sure inside part of code that’s repeated
- Initialize named constants once; initialize variables with executable code
- Use the compiler setting that automatically initializes all variables
- Take advantage of your compiler’s warning message
- Check input parameters for validity
- Use a memory-access checker to check for bad pointers
- Initialize working memory at the beginning of your program
- Minimize Scope
- refers to extent to which your variables are known and can be referenced
- localize references keeping close together and keep live time as short as possible (code lines)
- minimize scope of each variable
- Initialize variables used in a loop immediately before the loop rather than back at the beginning of the routine containing the loop
- Don’t assign a value to a variable until just befor the value is used
- Group related statements
- Break groups of related statements into separate routines
- Begin with most restricted visibility, and expand the variable’s scope only if necessary
- global data favors write ease vs reading ease
- Binding
- Early binding tends to limit flexibility but minimize complexity
- Late binding increases flexibility but increases complexity
- e.g. Hardcoding, constant variable, using a method to return value
- Build as much flexiblity as needed but not more than is required
- Data and Control
- Sequential data translates to sequential statements in a program
- Selective data translates to if and case statements in a program
- Iterative data translates to for, repeat, and while looping structures
- Purpose
- Use each variable for one purpose only
- Avoid variables with hidden meanings e.g. returning -1 if error
- Make sure all declared variable are used
- The Power of Variable Names
- Good Names
- Good variable names are a key element of program readability
- Specific kinds of variables such as loop indexes and status variables require specific considerations
- Specificity
- Names should be as specific as possible
- Names that are vague or general enough to be used for more than one purpose are usually bad names
- good name fully and accurately describes the entity
- speaks to problem, focuses on the what than how
- ok length: numTeamMembers, numSeatsInStadium
- Scopes
- conventions should distinguish among local, class, and global data
- They distinguish among type names, named constants, enumerated types, and variables
- longer name good for rarely used
- shorter better for local or loop variables
- Computed-Value Qualifiers in Variable Names
- Put modifiers like Total, Sum, Average, Max at end of name
- put most significant part of name at front
- Also more similarities in related variable names
- Try not to use nums, instead count or total
- Common Opposites in Variable Names
- use opposites precisely
- begin/end, first/last, locked/unlocked, source/target, etc.
- Naming Specific Types of Data
- Loop: if loop is long than a few lines, use something more descriptive than i
- Status: Use better name than flag for status variables e.g. dataReady, characterType, recordType, recalcNeeded
- if you are trying to “figure out” a section of code, consider renaming variables
- Temp: “temp” doesn’t tell you anything
- Bool: done, error, found, success or ok (names that imply True or False). Use positive, not “notFound”
- Enumerated: color_red, color_green, color_blue; planet_earth, planet_mars, etc
- Constants: CAPS and what it represents not the actual number
- Conventions
- should adopt a variable naming convention regardless of project
- Kind of convention depends on size of project and number of people
- Benefits
- take more for granted and focus on more important characteristics of the code
- help transfer knowledge across projects
- learn code more quickly on a new project
- reduce name proliferation (avoid duplicates for same thing)
- compensate for language weakness
- emphasize relationships among related items
- Informal Conventions
- Differentiate between variable names and routine names
- Differentiate between class and objects
- Identify global variables (g_ prefix)
- identify member variables (m_ prefix)
- identify type definitions
- identify named constants
- identify elements of enumerated types
- identify input-only parameters in languages that don’t enforce them
- format names to enhance readability
- Abbreviations
- Rarely needed with modern programming languages
- If you do use, keep track of abbreviations in project dictionary
- Names to Avoid
- misleading names
- names with similar meaning
- variables with different meaning but similar names e.g. one letter difference
- names that sound similar e.g. rap and wrap
- numerals in names
- avoid misspelled words in names
- avoid words commonly misspelled in English
- don’t differentiated solely by capitalization
- avoid multiple natural languages
- avoid names of standard types, variables and routines
- don’t use names that are totally unrelated to what the variable represents
- avoid names containing hard to read characters
- Write vs Read
- Code is read far more times than it is written
- Favor read-time convenience over write-time convenience
- Fundamental Data Types
- Rules
- Working with specific data types means remembering many individual rules for each type
- Make sure you’ve considered common problems
- Numbers
- Avoid magic numbers i.e. hard coded values
- Use hard-coded 0’s and 1’s if you need to e.g. incrementing
- Anticipate divide-by-zero errors
- Make type conversions obvious
- Avoid mixed-type comparisons
- Heed your compiler’s warnings
- Integers
- Check for integer division i.e. results in 0 not fraction
- Check for integer overflow i.e. large numbers
- check for overflow in intermediate results
- Floating-Point Numbers
- Avoid additions and subtractions on numbers that have greatly different magnitudes bc significant digits may lose low terms
- Avoid equality comparisons, maybe use an acceptable range
- Anticipate rounding errors
- Check language and library support for specific data types
- Character and Strings
- Avoid magic characters and strings
- Watch for off-by-one errors
- Know how your language and environment support Unicode
- Decide on an internationalization/localization strategy early in the lifetime of a program
- If you know you only need to support a single alphabetic language, consider using an ISO 8859 character set
- If you need to support multiple languages, use Unicode
- Decide on a consistent conversion strategy among string types
- Boolean Variables
- Use boolean variables to document your program i.e. make clear what you are checking for
- Use boolean variables to simplify complicated tests e.g. if (completed)
- Create your own boolean type, if necessary
- Enumerated Types
- Can know all possibilities
- Use enumerated types for readability
- use enumerated types for reliability
- use enumerated types for modifiability
- use as an alternative to boolean variables i.e. sometimes T/F isn’t enough information
- Check for invalid values
- Define the first and last entries of an enumeration for use as loop limits
- Reserve the first entry in the enumerated type as invalid
- Define precisely how First and Last elements are to be used in the project coding standard and use them consistently
- Beware of pitfalls of assigning explicit values to elements of an enumeration
- Named Constants
- Use named constants in data declarations
- Avoid literals, even safe ones e.g. months_in_year
- Simulate named constants with appropriately scoped variables or classes
- Used named constants consistently
- Arrays
- Make sure that all array indexes are within the bounds of the array
- Consider using containers instead of arrays, or think of arrays as sequential structures as random accesses are error prone
- Check the end points of arrays
- If an array is multidimensional, make sure its subscripts are used in the correct order; consider using more meaningful names than A[i][j]
- Watch out for index cross-talk
- Own Type
- Creating your own type makes your programs easier to modify and more self-documenting
- Create types with functionally orientated names
- associated with real world data
- don’t refer to computer data
- Avoid predefined types
- Don’t redefine a predefined type
- Define substitute types for portability
- Consider creating a class rather than using a typedef
- Simple Type
- When you createa simple type using typedef, consider whether you should be creating a new class instead
- Unusual Data Types
- Structures
- Can help make programs less complicated, easier to understand and easier to maintain
- Reasons to Use
- clarify data relationships
- Use structures to simplify operations on blocks of data
- Use structures to simplify parameter lists
- Use structures to reduce maintenance
- Classes
- Consider whether a class would work better
- Pointers
- Pointer consists of two parts: location in memory and knowledge of how to interpret the contents in that location (pointer’s base type)
- memory just hold bits, up to pointer to interpret meaningfully
- pointers are error-prone
- Protect yourself by using access routines or classes and defensive-programming practices
- General Pointer Tips
- Isolate pointer operations in routines or classes
- Declare and define pointers at the same time
- Delete pointers at the same scoping level as they were allocated
- Check pointers before using them
- Check the variables referenced by the pointer before using it
- Use dog-tag fields to check for corrupted memory
- Add explicit redundancies
- Use extra pointer variables for clarity
- Simplify complicate pointer expressions
- Draw a picture
- Delete pointers in a linked lists in the right order
- Allocate a reserve parachute in memory
- Shred your garbage
- Set pointers to null after deleting or freeing them
- Check for bad pointers before deleting a variable
- Keep track of pointer allocations
- Write cover routines to centralize your strategy to avoiding pointer problems
- Use a non-pointer technique
- Global Variables
- Avoid, not just because dangerous, but because you can replace them with something better
- Problems with Global Data
- Inadvertent changes to global data (side effects)
- Bizarre and exciting aliasing problems
- Re-entrant code problems with global data
- Code reuse hindered by global data
- Uncertain initialization-order issues
- Modularity and intellectual manageability damaged by global data
- Reasons to Use Global Data
- preserve global values e.g. state of program interactive vs command-line mode
- Emulation of named constants
- Emulation of enumerated types
- Streamlining use of extremely common data
- Eliminating tramp data
- when data is passed so it can be called in another routine
- If you can’t avoid Global variables
- work with them through access routines
- access routines give you everything that global variables give you, and more
- Begin by making each variable local and make variables global only as you need to
- Distinguish between global and class variables
- don’t access class variables directly from outside, create access routines
- Advantages of Access Routines
- Centralized control over data
- Ensure all references to the variable are barricaded
- Benefits of information hiding automatically
- Easy to convert to an abstract data type
- Using Access Routines
- require code to access routines rather than the direct data
- Don’t just throw all your global data into the same barrel
- Use locking to control access to global variables
- Build a level of abstraction into your access routines
- node = node.next vs account = NextAccount(account)
- Keep all access to the data at the same level of abstraction
- InitStack(), PushStack(), array[stack.top] (inconsistent)
- Reduce Risks of Using Global Data
- Develop naming convention that makes global variables obvious
- Create a well-annotated list of all your global variables
- Don’t use global variables to contain intermediate results
- Don’t pretend you’re not using global data by pulling all your data into a monster object and passing it everywhere
- Pure overhead and produces none of the benefits of true encapsulation
- Organizing Straight-Line Code
- Statements that must be in a specific order
- take steps to make sure the dependencies are clear
- don’t secretly initialize data
- Name routines so that dependencies are obvious
- Use routine parameters to make dependencies obvious
- passing data sets up clues that order is important
- Document unclear dependencies with comments
- Check for dependencies with assertions or error-handling code
- Order Doesn’t Matter
- Make code read from top to bottom
- Group related statements
- Using Conditionals
- If-Then Statements
- Write the nominal path through the code first; then write unusual cases
- Make sure that you branch correctly on equality
- Put the normal case after the if rather than after the else
- Follow the if clause with a meaningful statement
- Consider the else clause
- Test the else clause for correctness
- Check for reversal of the if and else clauses
- If-then-else
- Simplify complicated tests with boolean function calls e.g. elif is_digit()
- Put the most common case first
- Make sure that all cases are covered
- code a final else with an error or assertion
- Replace if-then-else with other constructs if your language supports them
- case statement are easier to read
- Case Statements
- Order cases alphabetically or numerically
- Put the normal case first if one normal case and many exceptions
- Order cases by frequency
- Keep actions in each case simple
- Don’t make up phony variables to be able to use the case statement
- Use the default clause only to detect legitimate defaults
- Use the default clause the detect errors
- In C++ and Java, avoid dropping through the end of a case statement
- Controlling Loops
- While Loops
- flexible, good if you don’t know ahead of time how many times to do something
- Loop With Exit
- Exit condition appears in middle
- available explicitly in VB
- if you use, put all exit conditions in one place
- use comments for clarification
- Not use much in practice
- For Loop
- good for executing specific number of times
- simple incrementing or decrementing
- For Each Loop
- Controlling the Loop
- Errors: loop initialization, omitted accumulators, improper nesting, incorrect termination, incorrect incrementing
- minimize number of factors that affect loop
- simplify, simplify, simplify
- Explicitly state the conditions under which the body of the loop is to be executed
- Entering the Loop
- Enter the loop from one location only
- Put initialization code directly before the loop
- Use while(true) for infinite loops
- Prefer for loops when they’re appropriate
- common error is changing loop control in while part, but not fixing body
- Don’t use a for loop when a while loop is more appropriate
- don’t hide a while loop in a for loop
- Processing the Middle of the Loop
- Use { and } to enclose the statement in a loop
- Avoid empty loops
- Keep loop-housekeeping chores at either the beginning or end of the loop
- Make each loop perform only one function
- Exiting the Loop
- Assure yourself that the loop ends
- Make loop-termination conditions obvious
- don’t use break to get out of loop
- Don’t monkey with the loop index of a for loop to make the loop terminate
- Avoid code that depends on the loop index’s final value
- don’t use index value later
- Consider using safety counters
- Exiting Loops Early
- Consider using break statements rather than boolean flags in a while loop
- Be wary of a loop with a lot of breaks scattered through it
- code may not be thought out clearly
- might be better to have separate loops
- Use continue for tests at the top of a loop
- Use the labeled break structure if your language supports it
- specify which loop to break out of
- Use break and continue only with caution
- using break eliminates the possibility of treating a loop as a black box
- Checking Endpoints
- mentally run through the first case, a middle case, and the last case
- willingness to perform this kind of check is the difference between efficient and inefficient programmers
- bad programmers randomly change certain variables
- mental exercise helps you understand how the code is working better
- Using Loop variables
- Use ordinal or enumerated types for limits on both arrays and loops
- Use meaningful variable names to make nested loops more readable
- Use meaningful names to avoid loop-index cross-talk
- Limit the scope of loop-index variables to the loop itself
- How Long should a loop be?
- Make your loops short enough to view all at once
- Limit nesting to three levels
- Move loop innards of long loops into routines
- Make long loops especially clear
- avoid break, continue, multiple exits or complicated termination conditions
- Creating Loops Easily–From the Inside Out
- Start with the inner function and write with literals
- add a loop structure around it and update variables
- Correspondence Between Loops and Arrays
- Some arrays may have built-in functions so you don’t need to use a loop
- Unusual Control Structures
- Multiple Returns from a Routine
- Use a return when it enhances readability
- if you want to return once you know that answer
- Use guard clauses (early returns or exits) to simplify complex error processing
- four if statements the do checking better than nested version
- Minimize the number of returns in each routine
- Recursion
- usually hard to understand so use selectively
- iteration is more understandable
- Make sure the recursion stops
- Use safety counters to prevent infinite recursion
- Limit recursion to one routine
- Keep an eye on the stack
- Don’t use recursion for factorials or Fibonacci numbers
- Consider using stacks and iteration
- Goto
- Arguments against goto
- hard to format
- defeats compiler optimizations
- Disrupts logical flow
- Arguments to use
- can reduce duplicate code
- useful in resource allocation
- Error Processing and gotos
- rewrite with nested if statements
- rewrite with a status variable
- rewrite with try-finally
- Goto Guidelines
- in modern languages easy to replace
- Use gotos to emulate structured control constructs in languages that don’t support them directly
- Don’t use the goto when an equivalent built-in construct is available
- Measure the performance of any goto used to improve efficiency
- Limit yourself to one goto label per routine
- Limit yourself to gotos that go forward
- Make sure all goto labels are used
- Make sure a goto doesn’t create unreachable code
- Table-Driven Methods
- Table-driven methods are schemes that allow you to look up information in a table rather than using logic statements (i.e. case, if).
- In simple cases, it’s quicker and easier to use logic statements, but as the logic chain becomes more complex, table-driven code is simpler than complicated logic, easier to modify and more efficient.
- Tables provide an alternative to complicated logic and inheritance structures
- If you find that you’re confused by a program’s logic or inheritance tree, think about simplifying with a lookup table
- One key consideration in using a table is deciding how to access the table
- Direct access
- indexed access
- stair-step access
- Another key consideration in using a table is deciding what exactly to put into the table
- General Control Issues
- Boolean Expressions
- make boolean expressions simple and readable
- Compare boolean values to true and false implicitly
- while (not done) vs while (done = false)
- Break complicated tests into partial tests with new boolean variables
- Move complicated expressions into boolean functions
- e.g. If (DocumentIsValid())
- instead of complex test
- Use decision tables to replace complicated conditions
- In if statements, convert negatives to positives and flip-flop the code in the if and else clauses
- change if (!statusOK) to if (statusOK)
- Apply DeMorgan’s Theorems to simplify boolean tests with negatives
- (!displayOK || !printerOK) to (!(displayOK || printerOK))
- Use parentheses to clarify boolean expressions
- Counting technique to balance parentheses
- start at 0, increase by 1 for each left and decrease by one for each right. should end at 0
- Fully parenthesize logical expressions
- Know how boolean operations are evaluated (shortcut)
- order number expressions in number-line order
- MIN_ELEM <= i and i <= MAX_ELEM
- compare logical variables implicitly e.g. while (!done)
- compare numbers to 0 e.g. while (balance != 0)
- compare pointers to NULL
- Compound Statements
- compound statement or a block is a collection of statements that are treated as a single statement for purposes of controlling the flow of a program
- Write pairs of braces together
- Use braces to clarify conditionals
- Null Statements
- Call attention to null statements
- Create a preprocessor DoNothing() macro or inline function for null statements
- makes it clear nothing is happening
- Consider whether code would be clearer with a non-null loop body
- Taming Dangerously Deep Nesting
- They are hard to understand, easy to avoid
- Simplify a nested if by retesting part of the condition
- Simplify nested if by using a break block
- uncommon and only use if team is familiar w/ practice
- Convert a nested if to a set of if-then-elses
- Convert a nested if to a case statement
- Factor deeply nested code into its own routine
- Use a more object-orientated approach
- Redesign deeply nested code
- complex code is a sign you don’t understand your program well enough to make it simple
- Structured Programming
- Core of structured programming is that a program should use only one-in, one-out control constructs
- Any program can be built out of a combination of sequences, selections and iterations
- Sequence: set of statements executed in order
- Selection: Causes statements to be executed selectively (if-then)
- Iteration: causes group of statements to be executed multiple times
- Almost any programs can be written with these three and others should be evaluated very carefully
- Control Structures and Complexity
- Complexity can be measured in decision points
- Moving part of a routine around can change number of decision points you deal with at one time
- The Software-Quality Landscape
- Characteristics of Software Quality
- External: Correctness, Usability, Efficiency, Reliability, Integrity (safety), Adaptability, Accuracy (how well it does job), Robustness
- Internal: Maintainability, Flexibility, Portability, Reusability, Readability, Testability, Understandability
- Some of these factors work together and some work against each other

- Techniques for Improving Software Quality
- Software-quality objectives
- without explicit goals, programmers might work to maximize characteristics different from the ones you expect them to maximize
- Explicit quality-assurance activity
- In some organizations, quick and dirty programming is the rule
- Testing Strategy
- Software-engineering guidelines
- Informal technical reviews
- Formal technical reviews
- try to catch problems at the lowest-value stage
- quality gates are periodic tests that determine whether the quality of the product at one stage is sufficient to support moving on to the next
- External Audits
- Development Process
- Change-control procedures for requirement changes
- Measurement of results to tell if quality plan is working
- Prototyping can lead to better designs, better matches with user needs, and improved maintainability
- Setting Objectives
- Explicitly set quality objectives
- Programmers will optimize what you specify
- Relative Effectiveness of Quality Techniques
- Project developers striving for a higher defect-detection rate need to use a combination of techniques
- Most studies have found that inspections are cheaper than testing
- Recommendations
- Formal inspections of all requirements, all architecture, and designs for critical parts of a system
- Modeling or prototyping
- Code reading or inspections
- Execution testing
- When to do Quality Assurance
- earlier error is inserted into software, the more entangled it becomes
- Errors in requirements or architecture tend to be more sweeping than construction errors
- Emphasis should be put on early stages and throughout the rest
- It should be planned into the project as work begins; It should be part of the technical fiber of the project as work continues, and it should punctuate the end of the project
- General Principle of Software Quality
- Improving quality reduces development costs
- The best way to improve productivity and quality is to reduce the time spent reworking code
- Avg. industry-productivity is about 10-50 lines of delivered coder per person per day
- Biggest time spent is debugging and correcting code
- Upstream activities have more leverage on product quality than downstream activities
- Collaborative Construction
- Overview of Collaborative Development Practices
- Pair programming, formal inspections, informal technical reviews, and document reading
- Developers are blind to some of the trouble spots of their work that others aren’t
- Pair programming achieves quality similar to formal inspections (10-25% more cost, but ~45% reduction in development time)
- collaborative practices more effective than testing and catches different kinds of errors as well
- Collaborative practices can be applied to other parts of the development cycle (estimates, plans, requirements, architecture, testing, maintenance work)
- Mentoring
- Important mechanism for giving programmers feedback
- Provides important venue to discuss technical issues
- Collective Ownership
- Better code from multiple eyes
- Impact of someone leaving is lessened
- Defect-correction cycles are shorter
- Pair Programming
- Support pair programming with coding standards
- Don’t let pair programming turn into watching
- Don’t force pair programming on easy stuff
- One group found it more expedient to do detailed design at the white board then program solo
- Rotate pairs and work assignments regularly
- different benefits from learning different parts of system
- Encourage pairs to match each other’s pace
- Make sure both partners can see the monitor
- Don’t force people who don’t like each other to pair
- Avoid pairing all newbies
- Assign a team leader
- Benefits of Pair Programming
- Holds up better under stress than solo development
- Improves code quality
- Shortens schedules, less time spent correcting defects
- Disseminates corporate culture, mentors juniors, fosters collective ownership
- Formal Inspections
- Checklists focus the reviewers’ attention on areas that have been problems in the past
- Focus on defect detection not correction
- Reviewers prepare before hand and arrive with a list of problems
- Distinct roles assigned to all participants
- Moderator isn’t the author of the work product
- Moderator has received specific training in moderating inspections
- Meeting only held if all participants have adequately prepared
- Data is collected and fed into future inspections to improve them
- General management doesn’t attend unless inspecting a project plan or other management materials
- Results from Inspections?
- Catches about 60% of defects (higher than other techniques)
- answers question of whether technical work is being done and if it is being done well
- Roles During an inspection
- Moderator – make sure moving at fast enough pace, must understand technical aspect, distributes design or code, sets up meeting, etc.
- Author – explain parts that are unclear. code should mostly speak for itself.
- Reviewer – direct interest in code, review before and try to find more during process
- Scribe – records errors and the assignments of action items
- Management – not a good idea to include, people feel they are evaluated, shouldn’t be used for performance appraisals
- Limit to about 6 people, can’t be less than 3
- General Procedure for an Inspection
- Planning – author gives code to moderator who decides who will be reviewers
- Overview – Tends to be dangerous because can lead to glossing over unclear points
- Preparation – might be more effective when reviewer assigned a specific perspective or scenarios
- Inspection Meeting – Don’t discuss solutions. Less than 2 hours.
- Inspection Report – within a day, moderator should produce a report. Data collection is important because any new methodology needs to justify its existence.
- Rework – moderator assigns defects to someone, usually author.
- Follow-Up – moderator is responsible for seeing that all rework assigned is carried out. Maybe review entire product, only fixes, or author just fixes without follow-up.
- Third-Hour Meeting – Maybe set time to discuss solutions
- Fine Tuning the Inspection
- “Instrument” inspection to know if changes are beneficial
- Companies have found that removing or combing stages costs more than is saved
- Don’t change without measuring
- If certain errors are common, create a checklist (1 page ore less) that calls attention to those errors
- If some stop happening, remove them
- Egos in Inspection
- experience should be positive for the author
- point is not to explore alternatives
- Author has ultimate responsibility for deciding what to do about a defect
- Summary
- Inspection checklists encourage focused concentration
- Self-optimizing because it uses a formal feedback loop
- Other Kinds of Collaborative Practices
- Walk-Throughs
- usually hosted or moderated by author
- focuses on technical issues
- all participants prepare by reading design or code looking for errors
- chance for senior programmers to pass on experience to juniors
- usually 30-60 minutes
- emphasis on error detection
- can produce similar results as an inspection
- generally expensive though (meeting) if devoting time, might want to just structure it
- Code Reading
- more individual review
- benefit is less time is spent in meetings
- and most errors typically found during review anyway
- more time efficient
- Dog and Pony Shows
- Usually for showing customers
- generally don’t use for improving quality
- Comparison
- Pair programming and formal review similar benefits, ultimately may depend on personal style
- Developer Tesitng
- Testing probably should take 8-25% of project time
- During Construction
- Make sure requirements have been implemented
- Test for each relevant design concern to make sure the design has been implemented
- Use “basis testing” to add detailed test cases to those that test the requirements and the design
- Use a checklist of the kinds of errors you’ve made on the project to date or have made on previous projects
- First or Last?
- Writing tests before minimizes time for error detection
- Writing first doesn’t take any more effort than writing after
- Forces you to think about the requirements and design before hand
- exposes requirement problems sooner
- Limitations of Developer Testing
- Developers tend to be “clean” coders
- check ways it works vs ways it breaks
- Developer testing tends to have an optimistic view of test coverage
- Developer testing tends to skip more sophisticated kinds of test coverage
- Bag of Testing Tricks
- Incomplete Testing: test things most likely causing erros
- Structured Basis Testing: Test each statement in a program at least once e.g. if-then-else
- Data-Flow Testing: data is defined, used, or killed. they are also entered or exited. certain patterns should raise suspicions.
- Equivalence Partitioning: if two test cases flush out exactly same error, you only need one
- Error Guessing: Add tests where you think there are errors
- Boundary Analysis: Exercise boundary conditions, test off by 1 cases
- Classes of Bad Data
- Too little data
- Too much data
- Wrong data
- wrong size of data
- uninitialized data
- Classes of Good Data
- Nominal cases – middle-of-the-road, expected values
- min normal config
- max normal config
- compatibility with old data
- Use Test Cases That Make Hand-Checks Convenient
- Typical Errors
- Scope of errors is fairly limited
- Many errors are outside the domain of construction
- Most construction errors are the programmers’ fault
- Clerical errors (typos) are a surprisingly common source of problems
- Misunderstanding the design is a recurring theme in studies of programmer errors
- Most errors are easy to fix
- It’s a good idea to measure your own organization’s experience with errors
- Errors in Test
- Check your work
- Plan test cases as you develop your software
- Keep your test cases
- Plug unit tests into a test framework
- Improving Your Testing
- Plan to test from the beginning
- Regression testing is making sure a change didn’t break anything
- Automate testing
- Keep test Records
- Description of defect
- full description of the problem
- steps taken to repeat the problem
- suggested workaround
- related defects
- severity of the problem
- origin of defect: requirements, design, coding, testing
- sub-classification of coding defect: off-by-one, bad assignment, bad array index, bad routine call, and so on
- classes and routines changed by the fix
- Number of lines of code affected by the defect
- Hours to find the defect
- Hours to fix the defect
- Understand Test records
- Number of defects in each class
- number of defects in each routine
- average number of testing hours per defect found
- average number of defects per test case
- Average number of hours per defect fixed
- percentage of code covered by test cases
- Number of outstanding defects in each severity classification
- Control Structures and Complexity
- Minimizing complexity is a key to writing high-quality code
- Debugging
- Variations in Debugging Performance
- Difference between experienced programmers and inexperienced programmers can be 20-to-1
- Defects as Opportunities
- If you program through trial and error, defects are guaranteed
- Learn about the program you’re working on
- Learn about the kinds of mistakes you make
- Learn about the quality of your code from the point of view of someone who has to read it
- Learn about how you solve problems
- Learn how to fix defects
- An Ineffective Approach
- Find the defect by guessing
- Don’t waste time trying to understand the problem
- Fix the error with the most obvious fix
- Finding a Defect
- Finding and understanding a defect is usually 90% of the work
- Stabilize the error (make it occur reliability)
- Locate the source of the error
- Gather the data the produces the defect
- Analyze the data that has been gathered, and form a hypothesis about the defect
- Determine how to prove or disprove the hypothesis, either by testing the program or examining the code
- Prove or disprove the hypothesis by using procedure identified above
- Fix the defect
- Test the fix
- Look for similar errors
- Tips for Finding Defects
- Use all the data available to make your hypothesis
- Refine the test cases that produce the error
- Exercise the code in your unit test suite
- Defects easier to find in small fragments of code
- Use available tools
- Reproduce the error several different ways
- Generate more data to generate more hypotheses
- Use the results of negative tests to narrow focus
- Brainstorm for possible hypotheses
- Keep a notepad by your desk, and make a list of things to try
- Narrow the suspicious region of the code
- Be suspicious of classes and routine that have had defects before
- Check code that’s changed recently
- Expand the suspicious region of the code
- Integrate incrementally – add pieces to a system one at a time
- Check for common defects
- Talk to someone else about the problem
- Take a break from the problem
- Brute-Force Debugging
- Perform a full design and/or code review on the broken code
- Throw away the section of code and redesign/recode it from scratch
- Throw away the whole program and redesign/recode it form scratch
- Compile code with full debugging information
- Compile code at pickiest warning level and fix all warnings
- Strap on a unit test harness and test the new code in isolation
- Create an automated test suite
- Step through a big loop in a debugger manually until you get the error condition
- Instrument the code with print, display, or other logging statements
- Compile the code with a different compiler
- Compile and run program in a different environment
- Link or run the code against special libraries that produce warnings when code is used incorrectly
- Replicate the end-user’s full machine configuration
- Integrate new code in small pieces, fully testing each piece as it’s integrated
- Set a maximum time for quick and dirty debugging
- Make a list of brute-force techniques
- Syntax Errors
- Don’t trust line numbers in compiler messages
- Don’t trust compiler messages
- Don’t trust the compiler’s second message
- Divide and conquer
- Find misplaced comments and quotation marks
- Fixing a Defect
- Understand the problem before you fix it
- Understand the program, not just the problem
- Confirm the defect diagnosis
- Relax, don’t hurry, saves time in long-run
- Save the original source code
- Fix the problem, not the symptom (makes the code worse)
- Change the code only for good reason (you should know what the change will do)
- Make one change at a time
- Check your fix
- Add a unit test that exposes the defect
- Look for similar defects – tend to occur in groups
- Psychological Considerations in Debugging
- mind is trained to see things to make sense many times
- problems can occur with similar variable names
- Debugging tools
- Source-Code Comparators (diffs)
- Compiler Warning Messages
- Set your compiler’s warning level to the highest, pickiest possible, and fix the error it reports
- Treat warnings as errors
- Initiate project-wide standards for compile-time settings
- Extended Syntax and Logic Checking
- Execution Profilers
- Test Frameworks/Scaffolding
- Debuggers
- Refactoring
- Software Evolution
- Code will change, use the knowledge to your advantage
- Strive to improve code so future changes are easier
- Reasons to Refactor
- Code is duplicated
- A Routine is too long
- A loop is too long or too deeply nested
- A class has poor cohesion – break into multiple
- A class interface does not provide a consistent level of abstraction
- A parameter list has too many parameters
- Changes within a class tend to be compartmentalized
- If you find yourself only making changes to one part of a class
- Change require parallel modifications to multiple classes
- Inheritance hierarchies have to be modified in parallel
- case statements have to be modified in parallel – inheritance might be better
- Related data items that are used together are not organized into classes
- A routine uses more features of another class than its own class
- A primitive data type is overloaded – consider making own type
- A class doesn’t do very much – reassign and remove
- A chain of routines passes tramp data
- A middleman object isn’t doing anything e.g. a class just calls routines in other classes
- One class is overly intimate with another – err on side of strong encapsulation
- A routine has a poor name
- Data members are public – blur interface and implementation
- A subclass uses only a small percentage of its parents’ routines
- Comments are used to explain difficult code
- Global variables are used
- A routine uses setup code before a routine call or takedown code after a routine call
- good routine doesn’t require setup or takedown code
- if you’re passing multiple attributes of an object, think if you should just pass the object
- A program contains code that seems like it might be needed someday
- Data-Level Refactorings
- Replace a magic number with a named constant
- Rename a variable with a clearer or more informative name
- Move an expression inline
- Replace an expression with a routine
- Introduce an intermediate variable
- Convert a multi-use variable to multiple single-use variables
- Use a local variable for local purposes rather than a parameter
- Convert a data primitive to a class
- Convert a set of type codes to a class or enumeration
- Convert a set of type codes to a class with subclasses
- Change an array to an object
- Encapsulate a collection
- Replace a traditional record with a data class
- Statement-Level Refactorings
- Decompose a boolean expression
- simplify by introducing a well-named intermediate variable that help documents the meaning
- Move a complex boolean expression into a well-named boolean function
- Consolidate fragments that are duplicated within different parts of a conditional
- if you call the same thing at the end of an if and else, move to be after
- Use break or return instead of a loop control variable
- Return as soon as you know the answer instead of assigning a return value within the nested if-then-else statement
- Replace conditionals (especially repeated case statements) with polymorphism
- Create and use null objects instead of testing for null values
- consider moving the responsibility for handling null values out of the client code and into the class
- Routine-Level Refactorings
- Extract routine/extract method
- Move a routine’s code inline
- Convert a long routine to a class
- Substitute a simple algorithm for a complex algorithm
- Add a parameter
- Remove a parameter
- Separate query operations from modification operations
- Combine similar routines by parameterizing them
- Separate routine whose behavior depends on parameters passed in
- Pass a whole object rather than specific fields
- Pass specific fields rather than a whole object
- Encapsulate downcasting – A routine should return the most specific type of object it knows about
- Class Implementation Refactorings
- Change value objects to reference objects
- If you find yourself creating and maintaining numerous copies of large complex objects, change your usage so one master copy exists and the rest of the code uses references
- Change reference objects to value objects
- Replace virtual routines with data initialization
- Change member routine or data placement
- Pull a routine up into its superclass
- Pull a field up into its superclass
- Pull a constructor body up into its superclass
- Push a routine down into its derived classes
- Push a constructor body down into its derived class
- Extract specialized code into a subclass
- Combine similar code into a superclass
- Class Interface Refactorings
- Move a routine to another class
- Convert one class to two
- Eliminate a class
- Hide a delegate
- Remove a middleman
- Replace inheritance with delegation
- if class uses another class but wants more control over interface, make superclass a field of the former subclass
- Replace delegation with inheritance
- If a class exposes every public routine of a delegate class, inherit from the delegate class instead of just using the class
- Introduce a foreign routine – create a new routine within the client class that provides that functionality
- Introduce an extension class
- Encapsulate an exposed member variable
- Remove Set() routines for fields that cannot be changed
- Hide routines that are not intended to be used outside the class
- Encapsulate unused routines
- Collapse a superclass and a subclass if their implementations are very similar
- System-Level Refactorings
- Create a definitive reference source for data you can’t control
- Change unidirectional class association to bidirectional class association
- Change bidirectional class association to unidirectional class association
- Provide a factory method rather than a simple constructor
- Replace error codes with exceptions or vice versa
- Refactoring Safely
- Save the code you start with
- Keep refactorings small
- Do refactorings one at a time
- Make a list of steps you intend to take
- Make a parking lot – keep track of what you’d like to change later or don’t need to do now
- Make frequent checkpoints
- Use your compiler warnings
- Retest
- Add test cases – remove any that have been made obsolete
- Review changes – treat simple changes as if they were complicated
- Adjust your approach depending on the risk level of refactoring – err on the side of caution
- Bad Times to refactor
- Don’t use refactoring as a cover for code and fix
- Refactoring is change in working code
- Avoid refactoring instead of rewriting code
- Refactoring Strategies
- Spend time on 20 percent of refactorings that provide 80% of benefit
- Refactor when you add a routine
- Refactor when you add a class
- Refactor when you fix a defect
- Target error-prone modules
- Target high-complexity modules
- In a maintenance environment, improve the parts you touch
- Define an interface between clean code and ugly code, and then move code across the interface
- Coding-Tuning Strategies
- Quality Characteristics and Performance
- Users are more focused on tangible program characteristics than they are in code quality
- Before focusing on improving speed or size at the code level, consider alternatives
- Program requirements
- make sure you’re solving a problem that needs to be solved
- Program design
- Setting individual resource goals make the system’s ultimate performance predictable
- Making goals explicit improves likelihood that they’ll be achieved
- Can set goals that don’t achieve efficiency directly but do so in the long-run
- Class and routine design
- choose data structure and algorithms at this level
- Operating-system interactions
- Code compilation
- Hardware
- Code tuning
- modify correct code to run more efficiently
- usually changing a few lines of code
- not large-scale design changes
- Introduction to Code Tuning
- Usually not the best way to achieve efficiency
- appealing because mastering the art of writing efficient code is a rite of passage to becoming a serious programmer
- The Pareto Principle
- Should measure the code to find the hot spots
- Old Wives’ Tales
- Reducing the lines of code in a high-level language improves the speed or size of the resulting machine code
- Certain operations are probably faster or smaller than others
- You should optimize as you go
- premature optimization lacks perspective
- A fast program is just as important as a correct one
- When to Tune
- Don’t optimize until you need to
- Usually only a small part is resulting in the bottle neck
- Compiler Optimizations
- Some compilers optimize better than others
- You’ll have to check your own compiler to measure the effect
- Common Sources of Inefficiency
- Unnecessary I/O operations – use in memory if you can
- Paging
- swapping pages of memory is slower than working on only one page
- System calls
- Write your own services
- Avoid going to the system
- Work with the system vendor the make the call faster
- Interpreted Languages
- Errors
- Measurement
- Must measure to find hot spots
- You can’t tell if optimizations are working w/o measuring
- Measurements should be precise
- Profiling tools are useful or use your system’s clock
- Try to factor out measurement overhead and program-startup
- Iteration
- Can get good results if combining multiple strategies
- Code-Tuning Techniques
- Logic
- Stop Testing When You Know the Answer
- Order Tests by Frequency
- Compare Performance of Similar Logic Structures
- In some languages case is faster and in others if-then-else
- no reliable way to know except measuring
- Substitute Table Lookups for Complicated Expressions
- Use Lazy Evaluation
- Doesn’t calc until it is needed
- Loops
- Unswitching
- if decision doesn’t need to happen everytime, move it outside the loop
- Jamming
- Unrolling
- writing out the code separately instead of looping
- doesn’t necessarily result in time savings
- Minimizing the Work Inside Loops
- Sentinel Values
- value at end of last search term, which will indicate if loop should end
- Putting the Busiest Loop on the Inside
- Strength Reduction
- replace expensive operation with cheaper operation
- Data Transformations
- Use Integers Rather than Floating-Point Numbers
- Use the Fewest Array Dimensions Possible
- use one dimension if possible
- Minimize Array References
- don’t access through array if you’re checking the same location each time
- Use Supplementary Indexes
- Use Caching
- save values so you can use later
- more it costs to generate a new element and the more times the same information is requested, the more valuable a cache is
- the cheaper it is to access cached element and save new elements, the more valuable a cache is
- caches add complexity and tend to be error prone
- Expressions
- Exploit Algebraic Identities
- not a and not b == not (a or b)
- but save one not operation
- Use Strength Reduction
- replace multiplication with addition
- replace exponentiation with multiplication
- replace trigonometric routines with identities
- replace longlong ints with longs or ints
- replace floating point numbers with fixed point numbers or ints
- replace double-precision floating point with single-precision numbers
- replace integer multiplication-by-two with shift operations
- Initialize at Compile Time
- Be Wary of System Routines
- usually results in unnecessary amount of accuracy
- Use the Correct Type of Constants
- Pre-compute Results
- Eliminate Common Subexpressions and replace with a variable
- Routines
- Rewrite Routines Inline generally not useful anymore
- Recoding in a Low-Level Language
- The More Things Change, the More They Stay the Same
- Computers are very powerful these days many of the optimizations discussed have become irrelevant
- People writing desktop applications may not need to know as well, but might still be relevant for people writing software for embedded systems, real-time systems, and other systems with strict speed or space restrictions
- If optimization isn’t important enough to haul out the profiler, it isn’t important enough to degrade readability, maintainability, and other code characteristics
- How Program Size Affects Construction
- Communication and Size
- as number of people on project increases, the number of communication paths increase
- increases proportionate to the square of the number of people on the team
- Effect of Project size
- As project size increases, errors usually come more from requirements and design
- More development and architectural designs are required on large projects increasing opportunities for errors in those areas
- density (error per line of code) of problems increase w/ project size as well
- Effect of Project Size on Productivity
- At small sizes, biggest influence on productivity is skill of the individual programmer
- As size increases, team size and organization have greater influence
- Effects start happening when a team increases from 2 to 3 people
- productivity on smaller projects is 2-3x higher than large projects
- Effects of Project Size on Development Activities
- Activity Proportions and Size
- need for formal communication increases
- construction becomes less predominant as project size increases
- Activities that grow more than linearly with project size include: communication, planning, management, requirements development, system functional design, interface design and specification, architecture, integration, defect removal, system testing, document production
- a few techniques will always be valuable: disciplined coding practices, design and code inspections by other developers, good tool support, and use of high-level languages
- Programs, Products, Systems, and System Products
- Simplest kind of software is a program
- A sophisticated program is a product
- a group of programs is a software system
- a system product cost about nine times as much as simple programs
- A failure to appreciate the differences in polish and complexity among these is a common cause of estimation errors
- Can’t linearly extrapolate small project to large/complex project time
- Methodology and Size
- The point of most methodologies is to reduce communication problems
- Methodology can be explicit or unstated, but one exists
- More complex projects require greater conscious attention to methodology
- More formal documentation is need to coordinate people
- Forces you to think carefully about your plans
- Better to start small and scale up for large projects
- Managing Construction
- Encourage Good Coding
- mandating a strict set of technical standards usually isn’t a good idea
- If they are set, make sure they are set by someone respected and get buy-in
- Assign two people to every part of the project
- Review every line of code
- usually the programmer and two reviewers
- increases quality because programmers know their code will be reviewed
- Require code sign-offs
- Route good code examples for review
- Emphasize that coding listings are public assets
- Reward good code
- One easy standard
- Configuration Management or Change control
- Configuration management is the practice of identifying project artifacts and handling changes systematically so that a system can maintain its integrity over time
- If you don’t control changes to requirements, can end up writing code for parts that are eventually eliminated
- Can waste serious time without good configuration management
- Problem is over control
- Requirements and Design Changes
- Follow a systematic change-control procedure
- Handle change requests in groups – make sure best changes are made
- Estimate the cost of each change
- Be wary of high change volumes – warning sign that requirements, architecture, or top-level design isn’t good
- Establish a change-control board or its equivalent in a way that makes sense for your project
- Watch for bureaucracy, but don’t let the fear of bureaucracy preclude effective change control
- lack of disciplined change control is one of the biggest management problems facing the industry today
- undermines status visibility, long-range predictability, project planning, risk management, and project management
- look for ways to streamline
- Best Practices
- Use version control software
- Have a backup plan and make sure you can recover
- Estimating a Construction Schedule
- Estimation Approaches
- Establish objectives
- Allow time for the estimate, and plan it
- Spell out software requirements
- Estimate at a low level of detail
- Use several different estimation techniques, and compare the results
- Reestimate periodically
- Estimating the Amount of Construction
- Keep records of your organization’s experience on projects, and use them to estimate the time future projects will take
- Influences on Schedules
- largest influence is size of the program
- What to Do If You’re Behind
- Hope that you’ll catch up – projects generally fall further behind later on
- Expand the team – adding people to a late software project makes it later
- Reduce the scope of the project – plan “must haves”, “nice to haves”, and “optionals”
- Measurement
- For any project attribute, it’s possible to measure that attribute in a way that’s superior to not measuring it at all
- Be aware of measurement side effects people tend to focus on work that’s measured and ignore work that isn’t
- To argue against measurements is to argue that it’s better not to know what’s really happening on your project
- don’t start by collecting data on all possible measurements
- Treating Programmers as People
- A lot of variation between best and worst programmers
- No relationship between a programmer’s amount of experience and code quality or productivity
- Teams also exhibit sizable differences in quality and productivity
- Religious issues: language, indention style, placing of braces, IDE, commenting style, efficiency vs readability trade-offs, choice of methodology (Scrum, Extreme, evolutionary delivery), programming utilities, naming conventions, use of gotos, use of global variables, measurements
- Be aware that you’re dealing with a sensitive area
- Use “suggestions” or “guidelines” with respect to the area
- Finesse the issues you can by sidestepping explicit mandates
- Have programmer develop their own standards
- programmer do better with larger, quieter spaces
- Managing Your Manager
- Most managers won’t be current with technical topics
- Plant ideas for what you want to do
- Educate your manager about the right way to do things
- Focus on your manager’s interests, don’t distract with unnecessary implementation details
- Refuse to do what your manager tells you and insist on doing your job the right way
- Find another job
- Integration
- Importance of the Integration Approach
- Usually thought of as a testing activity
- Complex enough that it should be viewed as an independent activity
- Benefits of careful integration:
- Easier defect diagnosis
- Fewer defects
- Less scaffolding
- Shorter time to first working product
- Shorter overall development schedules
- Better customer relations
- Improved morale
- Improved chance of project completion
- More reliable schedule estimates
- More accurate status reporting
- Improved code quality
- Less documentation
- Integration Frequency–Phased or Incremental?
- Phased Integration
- Step 1: Design, code, test, and debug each class (unit development)
- Step 2: Combine the classes into one whopping-big system (system integration)
- Step 3: Test and debug the whole system (system dis-integration)
- Problem is when classes are put together for the first time, new problems inevitably surface
- Usually a lot of classes and all problems present themselves at once
- Can’t begin until late in the project, usually only good for tiny programs
- Incremental Integration
- Step 1: Develop a small part and thoroughly test
- Step 2: Design, code, test, and debug a class
- Step 3: Integrate new class with skeleton, test and debug the combination, repeat
- Combine larger components after they have been throughly tested
- Benefits of Incremental Integration
- Errors are easy to locate
- The system succeeds early in the project
- You get improved progress monitoring
- You’ll improve customer relations – signs of progress
- The units of the system are tested more fully
- You can build the system with a shorter development schedule – some work can be done in parallel
- Incremental Integration Strategies
- Top-Down
- Interfaces between classes must be carefully specified
- Control logic of he system is tested early
- Can result in a partially working system early
- Pure top-down usually not good because leaves the tricky parts last which may bubble up and change high-level design
- Requires a lot of stubs, likely to contain errors
- Vertical Slicing approach may be better, focusing on a group at a time
- Bottom-Up
- Add low-level classes one at a time
- Restricts possible sources of errors
- Problem is it leaves integration of the major high-level system interfaces until last
- If system has conceptual design problems, won’t be discovered until all the detailed work has been done
- Can do bottom up in sections
- Sandwich
- First integrate the high-level business-object classes
- Then integrate the device-interface classes and widely used utility classes at the bottom
- Integrates often-troublesome classes first and has the potential to minimize the amount of scaffolding
- Risk-Orientated (hard part first integration)
- Identify the level of risk associated with each class
- Implement the difficult parts first
- Feature-Orientated
- Integrate one feature at a time
- Do small features within each feature
- Eliminates scaffolding
- Each newly integrated feature bring about an incremental addition in functionality
- Works well with object-orientated design
- T-Shaped Integration
- One specific vertical slice is selected for development first
- These are mostly heuristics, you’ll have to modify to suit your needs
- Daily Build and Smoke Test
- every file is compiled, linked, and combined into an executable program every day
- Do a relatively simple check to see whether the product “smokes” when it runs
- don’t allow it to deteriorate to the point where time-consuming quality problems occur
- supports easier defect diagnosis
- Improves morale to see something working
- Doesn’t really slow process, but amortize work more steadily throughout the project
- Using Daily Builds
- Build Daily
- prevents code from getting out of sync
- some organizations build every week, but if build is broken one week, might go several weeks before next good build, losing benefit of frequent builds
- Check for broken builds
- A good build should:
- Compile all files, libraries, and other components successfully
- Link all files, libraries, and other components successfully
- Not contain any showstopper bugs that prevent the program from running or that make it hazardous to operate
- Smoke test daily
- Keep the smoke test current
- Automate the daily build and smoke test
- Establish a build group
- Add revisions to the build only when it makes sense to do so
- …But don’t wait too long to add a set of revisions
- make sure developer doesn’t go more than a couple days
- sometimes forces you to break the construction of a single feature into multiple episodes
- overhead is an acceptable price to pay for the redued integration risk
- Require developers to smoke test their code before adding it to the system
- Create a holding area for code that’s to be added to the build
- Create a penalty for breaking the build
- Release the builds in the morning
- Smoke testing and releasing build in the morning has several advantages:
- Testers can test with a fresh build that day
- You have more reliable access to developers when there are problems with the build
- Calling people in the middle of the night is harder on the team and you lose more than you gain
- Build and smoke test even under pressure
- under stress, developers lose some of their discipline
- Daily builds enforce discipline
- What kind of projects can use the daily build process?
- Microsoft Windows 2000 consisted of about 50 million lines of code
- A complete build could take as many as 19 hours
- The windows development team still managed to build every day and attributed much of its success to their daily builds
- Continuous Integration
- Most published references use “continuous” to mean “at least daily”
- Probably not good to do every few hours
- Programming Tools
- Design Tools
- for creating design diagrams
- UML, architecture, hierarchy charts, entity relationships diagrams, or class diagrams
- Editing
- Integrated Development Environments (IDEs)
- compilation and error detection
- integration with source-code, build, test, and debugging tools
- compressed or outline views of programs
- jump to definitions of classes, routines, and variables
- jump to all places where a class, routine or variable is used
- language-specific formatting
- interactive help for the language being edited
- brace (begin-end) matching
- templates for common language constructs
- smart indenting
- automated code transformations or refactorings
- macros programmable in a familiar programming language
- listing of search of common strings
- regular expressions in search-and-replace
- search-and-replace across a group of files
- editing multiple files simultaneously
- side-by-side diff comparisons
- multilevel undo
- Multiple-File String Searching and Replacing
- useful for search for all occurrences of a class or routine name
- Diff Tools – see what changed
- Merge Tools
- Source-Code Beautifiers
- Interface Documentation Tools – extracts detailed programmer-interface documentation from source-code
- Templates – streamline keyboarding tasks that you do often
- Cross-Reference Tool lists variables and routines and all places in which they’re used
- Class Hierarchy Generators
- Analyzing Code Quality
- Picky Syntax and Semantics Checkers
- Metrics Reporters
- Refactoring Source Code
- Refactorers
- Restructurers – organizess gotos
- Code Translators – to different languages
- Version Control
- source-code control
- dependency control
- project documentation versioning
- Relating project artifacts like requirements, code, and test cases so that when a requirement changes, you can find the code and tests that are affected
- Data Dictionaries
- describes all significant data in a project
- focuses primarily on database schemas
- useful for avoiding naming clashes on large projects
- different names can also be used to mean the same thing
- Code Creation
- Compilers
- Linkers – links one or more object files
- Build Tools
- Code Libraries
- Code-Generation Wizards
- common for db applications, user interfaces and compilers
- tend to generate code that’s unreadable
- Setup and Installation
- Preprocessors
- Useful for debugging because make it easy to switch between development code and production code
- Debugging
- Compiler warning messages
- Test scaffolding
- Diff tools
- Execution profilers
- Trace monitors
- Interactive debuggers
- Testing
- Automated test frameworks like JUnit, NUnit CppUnit
- Automated test generators
- Test-case record and playback utilities
- Coverage monitors
- Symbolic debuggers
- System pertubers (memory fillers, memory shakers, selective memory failers, memory-access checkers)
- Diff tools (for comparing data files, captured output, and screen images)
- Scaffolding
- Defect-injection tools
- Defect-tracking software
- Code Tuning
- Execution profilers – tells you how many times each statement is executed
- Assembler Listings and Disassemblers – compare high level language with assembly language
- Tool-Orientated Environments
- UNIX environment is famous for its collection of small tools that work well together: grep, diff, sort, make, crypt, tar, lint, ctags, sed, awk, vi
- Build Your Own Programming Tools
- Writing a tool might be the better option in a lot of cases
- Project-Specific Tools
- Scripts
- in some systems are called batch files or macros
- if you find yourself typing something longer than about five characters more than a few times a day, could be a good candidate
- Tool Fantasyland
- No tool will eliminate programming because at its essence programming is fundamentally hard
- programmers will have to wrestle with the messy real world
- people have to rigorously think about sequences, dependencies, exceptions and users who can’t make up their mind
- Need people who can tell computers what to do
- Layout and Style
- Layout Fundamentals
- Fundamental Theorem of Formatting: good visual layout shows the logical structure of a program
- Good programmers write code that humans can understand
- The details of the specific method are less important than the fact that the program is structured consistently
- Objectives of Good Layout
- Accurately represents the logical structure of the code
- Consistently represent the logical structure of the code
- Improve readability
- Withstand modifications
- Layout Techniques
- White Space: Grouping, Blank lines, Indentation,
- Parentheses: Use more than you think you need
- Layout Styles
- Pure Blocks – begin and end is explicit
- Emulating Pure Blocks – can use braces in place of explicit begin and end
- Using begin-end Pairs (Braces) to Designate Block Boundaries – keep braces on the inner portion
- Endline Layout – start is out side of indent, and the end is part of the indent block – don’t use this
- Pick a style and be consistent
- Laying Out Control Structures
- Avoid unindented begin-end pairs
- Avoid double indentation with begin and end – look more complex
- Use blank lines between paragraphs
- Format single-statement blocks consistently
- For complicated expressions, put separate conditions on separate lines
- Avoid gotos
- No endline exception for case statements
- Laying Out Individual Statements
- Statement Length – keep less than 80 chars
- Spaces for Clarity
- Use spaces to make logical expressions readable
- Separate identifiers from other identifiers
- Use spaces to make array references readable
- Use spaces to make routine arguments readable
- Formatting Continuation Lines
- Make the incompleteness of a statement obvi.
- Keep closely related elements together
- Indent routine-call continuation lines the standard amount
- Make it easy to find the end of a continuation line
- Indent control-statement continuation lines the standard amount
- Do not align right sides of assignment statements
- Indent assignment-statement continuation lines the standard amount
- Using Only One Statement Per Line
- One statement lines provides accurate view of complexity
- Reads from top to bottom, not left to right
- Easy to find syntax errors
- Easy to edit individual statements
- Laying Out Data Declarations
- Use only one data declaration per line
- Declare variables close to where they’re first used
- Order declarations sensibly
- In C++, put the asterisk next to the variable name in pointer declarations or declare pointer types
- Laying Out Comments
- Indent a comment with its corresponding code
- Set off each comment with at least one blank line
- Laying out Routines
- Use blank lines to separate parts of a routine
- Use standard indentation for routine arguments
- Laying Out Classes
- Laying Out Class Interfaces
- 1) Header comment that describes the class and provides any notes about the overall usage of the class
- 2) Constructors and destructors
- 3) public routines
- 4) protected routines
- 5) private routines and member data
- Laying Out Class Implementations
- 1) Header comment
- 2) Class data
- 3) Public routines
- 4) Protected routines
- 5) Private routines
- If you have more than one class in a file, identify each class clearly
- Laying Out Files and Programs
- Put one class in one file
- Give the file a name related to the class name
- Separate routines within a file clearly
- Sequence routines alphabetically
- Self-Documenting Code
- External Documentation
- Unit development folders – informal document used by developers during construction to provide a trail of design decisions
- Detailed-design document – low-level design document, formal describes class level or routine-level design decisions
- Programming Style as Documentation
- good structure and variable names can be self-documenting
- To Comment or Not to Comment
- Easy to do comments in a bad way
- comments shouldn’t say what the code is doing, but describe intent
- comments should explain at a higher level of abstraction
- Kinds of Comments
- Repeat of the Code
- Explanation of the Code – usually better to improve code
- Marker in the Code – standardize so easier to find e.g. TODO
- Summary of the code – valuable bc reader can scan
- Description of the Code’s intent – level of problem
- Information That Cannot possible be expressed by the code itself – copyright, version numbers, etc
- Commenting Efficiently
- Use styles that don’t break down or discourage modification
- Use the Pseudocode Programming Process to reduce commenting time
- Integrate commenting into your development style – leaving it to the end, makes it a another task and harder to remember later
- Performance is not a good reason to avoid commenting
- Optimum Number of Comments
- Roughly one comment for every 10 statements
- Commenting Techniques
- Commenting Individual Lines
- Avoid self-indulgent comments
- Endline Comments and their problems
- can be hard to format and maintain
- Avoid endline comments on single lines
- hard to write meaningful comment
- Avoid enline comments for multiple lines of code
- Use endline comments to annotate data declarations
- Avoid using endline comments for maintenance notes
- Use endline comments to mark ends of blocks
- Commenting Paragraphs of Code
- Write comments at the level of the code’s intent
- Focus your documentation efforts on the code itself
- Focus paragraph comments on the why rather than the how
- Use comments to prepare reader for what is to follow
- Make every comment count
- Document surprises
- Avoid abbreviations
- Differentiate between major and minor comments
- Comment anything that gets around an error or an undocumented feature in a language or an environment
- Justify violations of good programming style
- Don’t comment tricky code; rewrite it
- Commenting Data Declarations
- Comment the units of numeric data
- Comment the range of allowable numeric values
- Comment coded meanings
- Comment limitations on input data
- Document flags to the bit level
- Stamp comments related to a variable with the variable’s name
- Document global data
- Commenting Control Structures
- space before control structure natural plae to put comment
- Put a comment before each if, case, loop, or block of statements
- Comment the end of each control structure – indicate which control structure is ending (e.g. //if, //while, //for)
- Treat end-of-loop comments as a warning indicating complicated code
- Commenting Routines
- Keep comments close to the code they describe
- Describe each routine in one or two sentence at the top of the routine
- Document parameters where they are declared
- Take advantage of code documentation utilities such as Javadoc
- Differentiate between input and output data
- Document interface assumptions
- Comment on the routine’s limitations
- Document the routine’s global effects
- Document the source of algorithms that are used
- Use comments to mark parts of your program
- Classes
- Describe the design approach to the class
- Describe limitations, usage assumptions, and so on
- input and output data
- error-handling
- global effects
- source of algorithms
- Comment the class interface
- Don’t document implementation details in the class interface
- File Documentation
- Describe the purpose and contents of each file
- Put your name, email, address, and phone number in the block comment
- Include a version-control tag
- Include legal notices in the block comment
- Give the file a name related to its contents
- Program Documentation
- No proof one specific technique is good, but combined effectiveness is strong
- Book Paradigm: think of code as a special kind of book
- Preface: introductory comments
- Table of contents:top-level files, classes, routines
- Sections: divisions within routines
- Cross-references: maps of code
- IEEE Standards
- for more detailed standards
- Personal Character
- Isn’t Personal Character Off the Topic?
- unsupervisable
- your are responsible for making yourself great
- Intelligence and Humility
- programming is an attempt to compensate for the strictly limited size of our skulls
- Best programmers realize how small our brains are
- Decompose a system to make it simpler to understand
- Conducting reviews, inspections, and tests is a way of compensating for anticipated human fallibilities
- Keeping routines short reduces the load on your brain
- Writing in terms of problem domain reduces mental workload
- Using conventions frees brain from mundane tasks
- Curiosity
- Must always be up to date
- Must be curious about how to do your job better
- Build your awareness of the development process
- Experiment – build a small program to learn how something works
- Read about problem solving
- Analyze and plan before you act – most people lean towards action and don’t need to worry as much about analysis-paralysis
- Learn about successful projects – find code written by superior programmers and read it
- Read – look through documentation
- Read other books and periodicals
- Affiliate with other professionals
- Make a commitment to professional development
- Level 1: beginner capable of using the basics of one language
- Level 2: intermediate can use basics of multiple languages and comfortable in at least one language
- Level 3: competent programmer has expertise in a language. Might have referenced manual memorized.
- Level 4: leader has expertise of level 3 and recognizes that programming is only 15 percent communicating with the computer and 85 percent communicating with people.
- Intellectual Honesty
- Refusing to pretend that you’re an expert when you’re not
- Readily admitting your mistakes
- Trying to understand a compiler warning rather than suppressing the message
- Clearly understanding your program-not compiling it to see if it works
- Providing realistic status reports
- Providing realistic schedule estimates and holding your ground when management asks you to adjust them
- Communication and Cooperation
- excellent programmers learn how to work and play with others
- keep the person who has to read your code in mind
- Creativity and Discipline
- Without standards and conventions, completing large projects would be impossible
- Form is liberating
- programming a masterpiece requires much discipline
- Laziness
- Deferring an unpleasant task
- Doing an unpleasant task quickly to get it out of the way
- Writing a tool to do the unpleasant task so you never have to do the task again
- Third kind can be good
- Easy to confuse motion with progress
- The important work in effective programming is thinking
- Characteristics That Don’t Matter As Much As You Might Think
- Persistence
- most times is pigheadedness
- sometime better to take time off and try to attack from another angle
- Experience
- In software, even basic knowledge changes rapidly
- “experience” has little relationship to performance
- Need to prove yourself year after year
- If you work for 10 years, do you get 10 years of experience of 1 year of experience 10 times?
- Need to make learning a continuous commitment
- Gonzo Programming
- working at unsustainable pace is not good
- excitement is no substitute for competency
- Habits
- good habits matter because most of what you do as a programmer you do without consciously thinking about it
- respond to criticism in a friendly way
- always looking to make code readable or fast
- when you’re first do it, you’re actively thinking about it so learn it the right way
- Themes in Software Craftsmanship
- Conquer Complexity
- Divide a system into subsystems
- Carefully design class interfaces
- Preserving the abstraction represented by the class interface
- Avoid global data
- Avoid deep inheritance
- Avoid deep nesting
- Avoid gotos
- Carefully design approach to error handling
- Be systematic about the use of built-in exception mechanisms
- Don’t allow classes to grow into monster classes
- Keep routines short
- Use clear, self-explanatory variable names
- Minimizing the number of parameters passed to a routine
- Using conventions to spare your brain from remembering arbitrary difference between different sections of code
- Attacking accidental difficulties wherever possible
- Pick Your Process
- How you build your software is important
- Process is more important on bigger projects
- Requirements should be stable, because you need to know what you’re building
- Earlier errors are more costly than later errors
- Premature optimization wastes time
- Write Programs for People First, Computers Second
- Aids in comprehensibility, review-ability, error rate, debugging, modif-ability, development time, external quality
- Not optional or convenience
- Professional programmers write readable code
- Program into Your Language, Not in it
- Best programmers think about what they want to do
- if you want to use enumeration or assert, make your own
- may have to balance between approach with potential accidental difficulties of your new tools
- Focus Your Attention with the Help of Conventions
- Help manage complexity
- Many conventions are arbitrary
- Too many conventions can be too burdensome
- provide structure where structure is needed
- Program in terms of the Problem Domain
- Work at the highest possible level of abstraction
- Shouldn’t be filled with details on implementation
- Separating a Program into Levels of Abstraction
- Level 0: Operating-System Operations and Machine Instructions
- Don’t have to worry if high level language
- Level 1: Programming-Language Structures and Tools
- primitive data types and control structures
- many programmers don’t work above this level
- Level 2: Low-Level Implementation Structures
- Operations and Data Types
- Stacks, queues, linked-list, trees, indexed files, sequential files, sort algorithms, search algorithms, etc
- Level 3: Low-Level Problem-Domain Terms
- glue between CS structures and the high-level problem-domain
- In many applications, will be the business objects layer or services layer
- Level 4: High-Level Problem-Domain Terms
- Work with a problem with its own terms
- code should be somewhat readable by someone who’s not a computer-science whiz
- Shouldn’t be dependent on specific features of your programming language
- Embody the user’s view of the world in the program at this level
- Changes in the problem domain should affect this layer a lot
- Low-Level Techniques for Working in the Problem Domain
- Use classes to implement structures that are meaningful in problem-domain terms
- Hid information about the low-level data types and their implementation details
- Use named constants to document the meanings of strings and numeric literals
- Assign intermediate variables to document the results of intermediate calculations
- Use boolean functions to clarify complex boolean tests
- Watch for Falling Rocks
- Takes plenty of individual judgment
- Must learn to be sensitive to warning signs
- “This is tricky code”
- Classes that have had more errors than average
- Lots of debugging
- difficulty writing comments
- making repetitive changes
- lots of decision points in one method
- A good process wouldn’t allow error-prone code to be developed
- Iterate, Repeatedly, Again and Again
- Many parts of the process are iterative
- Throwing something away in earlier stage much cheaper than throwing it away later
- Thou Shalt Rend Software and Religion Asunder
- Software Oracles
- Difference between technology transfer and someone trying to advance a one-size-fits-all solution
- Eclecticism
- Blind faith in one method precludes the selectivity you need to find the most effective solution
- Software development is a heuristic process which means rigid processes are inappropriate
- Sometimes the tool doesn’t matter, but in others, it matters a lot
- Use the tool that is most appropriate
- Experimentation
- You must be willing to change your beliefs based on the results of the experiment
- Inflexibility arises out of fear of making mistakes many times
- Must set up tests to learn whether an approach fails or not
- Where to Find More Information
- Information About Software Construction
- Pragmatic Programmer
- Programming Pearls
- Extreme Programming Explained: Embrace Change
- Beyond Construction
- Facts and Fallacies of Software Engineering
- Professional Software Development
- Swebok: Guide to the Software Engineering Body of Knowledge
- The Psychology of Computer Programming
- The Mythical Man Month
- Software Creativity
- Software-Engineering Overviews
- Software Engineering: A Practitioner’s Approach
- Software Engineering (Sommerville 2000)
- A Software Developer’s Reading Plan
- Intro
- Conceptual Blockbusting
- Programming Pearls
- Facts and Fallacies of Software Engineering
- Software Project Survival Guide
- Code Complete
- Practitioner Level
- Software Configuration Management Patterns
- UML Distilled
- Software Creativity
- Testing Computer Software
- Applying UML and Patterns
- Rapid Development
- Software Requirements
- Manager’s Handbook for Software Development
- Professional Level (leadership)
- Software Architecture in Practice
- Refactoring: Improving the Design of Existing Code
- Design Patterns
- Principles of Software Engineering Management
- Writing Solid Code
- Object-Orientated Software Construction
- Software Measurement Guidebook
Like this:
Like Loading...